This commit is contained in:
WS-3917 2025-05-03 22:31:20 +08:00
parent e86575e80b
commit ee29207d71
4 changed files with 192 additions and 156 deletions

35
Objects/UI/Joystick.tscn Normal file
View File

@ -0,0 +1,35 @@
[gd_scene load_steps=3 format=3 uid="uid://joystick"]
[ext_resource type="Texture2D" path="res://Sprites/Menus/Mobile/JoystickBase.png" id=1]
[ext_resource type="Texture2D" path="res://Sprites/Menus/Mobile/JoystickHandle.png" id=2]
[node name="Joystick" type="Control" script="res://Scripts/UI/Joystick.cs"]
anchor_right = 0
anchor_bottom = 0
offset_right = 200
offset_bottom = 200
mouse_filter = 2 # Ignore mouse input
[node name="BaseCircle" type="TextureRect" parent="."]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -100
offset_top = -100
offset_right = 100
offset_bottom = 100
texture = ExtResource(1)
stretch_mode = 6 # Keep aspect centered
[node name="HandleCircle" type="TextureRect" parent="."]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -40
offset_top = -40
offset_right = 40
offset_bottom = 40
texture = ExtResource(2)
stretch_mode = 6 # Keep aspect centered

View File

@ -6,28 +6,6 @@
[node name="TouchControls" type="CanvasLayer"]
script = ExtResource("1_42104")
[node name="Joystick" type="Control" parent="."]
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("2_fufkg")
[node name="OuterCircle" type="TextureRect" parent="Joystick"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="DeadzoneCircle" type="TextureRect" parent="Joystick"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="HandleCircle" type="TextureRect" parent="Joystick"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="Buttons" type="Control" parent="."]
layout_mode = 3
anchors_preset = 0
@ -48,3 +26,26 @@ offset_bottom = 40.0
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="Joystick" type="Control" parent="."]
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("2_fufkg")
[node name="OuterCircle" type="TextureRect" parent="Joystick"]
layout_mode = 0
offset_top = -1.0
offset_right = 40.0
offset_bottom = 39.0
[node name="DeadzoneCircle" type="TextureRect" parent="Joystick"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="HandleCircle" type="TextureRect" parent="Joystick"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0

View File

@ -3,57 +3,57 @@ using System;
public partial class Joystick : Control
{
private Vector2 touchStartPos = Vector2.Zero;
private Vector2 currentPos = Vector2.Zero;
private int fingerId = -1;
private Vector2 _startTouchPos = Vector2.Zero;
private Vector2 _currentTouchPos = Vector2.Zero;
private bool _touching = false;
[Export] public float DeadzoneRadius = 40f;
[Export] public float MaxRadius = 120f;
[Export] public float DeadzoneRadius = 30f;
[Export] public float MaxRadius = 90f;
public override void _Input(InputEvent @event)
public override void _GuiInput(InputEvent @event)
{
if (@event is InputEventScreenTouch touch)
{
if (touch.Pressed && fingerId == -1 && GetGlobalRect().HasPoint(touch.Position))
if (touch.Pressed)
{
fingerId = touch.Index;
touchStartPos = touch.Position;
currentPos = touch.Position;
_touching = true;
_startTouchPos = touch.Position;
_currentTouchPos = touch.Position;
}
else if (!touch.Pressed && touch.Index == fingerId)
else
{
fingerId = -1;
currentPos = touchStartPos;
_touching = false;
}
}
else if (@event is InputEventScreenDrag drag && drag.Index == fingerId)
else if (@event is InputEventScreenDrag drag && _touching)
{
currentPos = drag.Position;
_currentTouchPos = drag.Position;
}
}
public override void _Process(double delta)
{
Vector2 localOffset = currentPos - touchStartPos;
if (localOffset.Length() > MaxRadius)
localOffset = localOffset.Normalized() * MaxRadius;
// 更新手柄图形位置
var handle = GetNode<TextureRect>("HandleCircle");
handle.Position = localOffset;
QueueRedraw();
}
public Vector2 GetDirection()
{
if (fingerId == -1)
if (!_touching) return Vector2.Zero;
Vector2 offset = _currentTouchPos - _startTouchPos;
if (offset.Length() < DeadzoneRadius)
return Vector2.Zero;
Vector2 delta = currentPos - touchStartPos;
if (delta.Length() < DeadzoneRadius)
return Vector2.Zero;
return offset.Normalized() * Mathf.Min(offset.Length(), MaxRadius);
}
return delta.Normalized();
public override void _Draw()
{
Vector2 center = new Vector2(0, 0);
Vector2 handleOffset = _touching ? (_currentTouchPos - _startTouchPos).Normalized() * Mathf.Min((_currentTouchPos - _startTouchPos).Length(), MaxRadius) : Vector2.Zero;
DrawCircle(center, MaxRadius, new Color(1, 1, 1, 1), false);
DrawCircle(center, DeadzoneRadius, new Color(1, 1, 0, 1), false);
DrawCircle(center + handleOffset, DeadzoneRadius, new Color(1, 1, 1, 0.3f), false); // 当前触点
}
public override void _Process(double delta)
{
QueueRedraw();
}
}

View File

@ -1,110 +1,110 @@
using Godot;
using System;
using Godot;
using System;
public partial class TouchControls : CanvasLayer
{
public override void _Ready()
public partial class TouchControls : CanvasLayer
{
// 仅在 Android 平台启用
//if (OS.GetName() != "Android")
//{
//QueueFree();
//return;
//}
SetButtonTextures();
SetupButtonBindings();
UpdateButtonPositions(); // 设置初始位置
}
public override void _Process(double delta)
{
Vector2 dir = GetNode<Joystick>("Joystick").GetDirection();
UpdateDirectionInput(dir);
}
private void SetButtonTextures()
{
string basePath = "res://Sprites/Menus/Mobile/";
GetNode<TextureButton>("Buttons/ButtonZ").TextureNormal = GD.Load<Texture2D>(basePath + "ButtonZ.png");
GetNode<TextureButton>("Buttons/ButtonX").TextureNormal = GD.Load<Texture2D>(basePath + "ButtonX.png");
GetNode<TextureButton>("Buttons/ButtonC").TextureNormal = GD.Load<Texture2D>(basePath + "ButtonC.png");
}
private void SetupButtonBindings()
{
BindButton("Buttons/ButtonZ", "Confirm");
BindButton("Buttons/ButtonX", "Cancel");
BindButton("Buttons/ButtonC", "Menu");
}
private void BindButton(string path, string actionName)
{
var button = GetNode<TextureButton>(path);
button.Modulate = new Color(1, 1, 1, 0.5f); // 初始透明度
button.Pressed += () =>
public override void _Ready()
{
Input.ActionPress(actionName);
button.Modulate = new Color(1, 1, 1, 1); // 按下全不透明
};
// 仅在 Android 平台启用
if (OS.GetName() != "Android")
{
QueueFree();
return;
}
SetButtonTextures();
SetupButtonBindings();
UpdateButtonPositions(); // 设置初始位置
}
button.ButtonUp += () =>
public override void _Process(double delta)
{
Input.ActionRelease(actionName);
button.Modulate = new Color(1, 1, 1, 0.5f); // 松开恢复
};
}
Vector2 dir = GetNode<Joystick>("Joystick").GetDirection();
UpdateDirectionInput(dir);
}
private void UpdateButtonPositions()
{
Vector2 viewportSize = GetViewport().GetVisibleRect().Size;
SetButtonPosition("Buttons/ButtonZ", viewportSize, new Vector2(0.35f, 0.20f));
SetButtonPosition("Buttons/ButtonX", viewportSize, new Vector2(0.25f, 0.35f));
SetButtonPosition("Buttons/ButtonC", viewportSize, new Vector2(0.15f, 0.50f));
var joystick = GetNode<Joystick>("Joystick");
Vector2 joyPos = new Vector2(
viewportSize.X * 0.05f, // 距左5%
viewportSize.Y * 0.85f // 距底15%
);
joystick.AnchorLeft = 0;
joystick.AnchorTop = 0;
joystick.AnchorRight = 0;
joystick.AnchorBottom = 0;
joystick.Position = joyPos;
GD.Print(joystick.Position);
}
private void SetButtonPosition(string path, Vector2 viewportSize, Vector2 offsetPercent)
{
var button = GetNode<TextureButton>(path);
Vector2 finalPosition = new Vector2(
viewportSize.X * (1 - offsetPercent.X),
viewportSize.Y * (1 - offsetPercent.Y)
);
button.AnchorRight = 1;
button.AnchorBottom = 1;
button.AnchorLeft = 1;
button.AnchorTop = 1;
button.Position = finalPosition; // 从右下角反向偏移
}
private void UpdateDirectionInput(Vector2 dir)
{
string[] actions = { "Left", "Right", "Up", "Down" };
Vector2[] vectors = { Vector2.Left, Vector2.Right, Vector2.Up, Vector2.Down };
const float thresholdAngle = 57f;
for (int i = 0; i < actions.Length; i++)
private void SetButtonTextures()
{
float angle = Mathf.RadToDeg(Mathf.Abs(dir.AngleTo(vectors[i])));
if (dir.Length() > 0.1f && angle < thresholdAngle)
Input.ActionPress(actions[i]);
else
Input.ActionRelease(actions[i]);
string basePath = "res://Sprites/Menus/Mobile/";
GetNode<TextureButton>("Buttons/ButtonZ").TextureNormal = GD.Load<Texture2D>(basePath + "ButtonZ.png");
GetNode<TextureButton>("Buttons/ButtonX").TextureNormal = GD.Load<Texture2D>(basePath + "ButtonX.png");
GetNode<TextureButton>("Buttons/ButtonC").TextureNormal = GD.Load<Texture2D>(basePath + "ButtonC.png");
}
private void SetupButtonBindings()
{
BindButton("Buttons/ButtonZ", "Confirm");
BindButton("Buttons/ButtonX", "Cancel");
BindButton("Buttons/ButtonC", "Menu");
}
private void BindButton(string path, string actionName)
{
var button = GetNode<TextureButton>(path);
button.Modulate = new Color(1, 1, 1, 0.5f); // 初始透明度
button.Pressed += () =>
{
Input.ActionPress(actionName);
button.Modulate = new Color(1, 1, 1, 1); // 按下全不透明
};
button.ButtonUp += () =>
{
Input.ActionRelease(actionName);
button.Modulate = new Color(1, 1, 1, 0.5f); // 松开恢复
};
}
private void UpdateButtonPositions()
{
Vector2 viewportSize = GetViewport().GetVisibleRect().Size;
SetButtonPosition("Buttons/ButtonZ", viewportSize, new Vector2(0.35f, 0.20f));
SetButtonPosition("Buttons/ButtonX", viewportSize, new Vector2(0.25f, 0.35f));
SetButtonPosition("Buttons/ButtonC", viewportSize, new Vector2(0.15f, 0.50f));
var joystick = GetNode<Joystick>("Joystick");
Vector2 joyPos = new Vector2(
viewportSize.X * 0.20f,
viewportSize.Y * 0.75f
);
joystick.AnchorLeft = 0;
joystick.AnchorTop = 0;
joystick.AnchorRight = 0;
joystick.AnchorBottom = 0;
joystick.Position = joyPos;
GD.Print(joystick.Position);
}
private void SetButtonPosition(string path, Vector2 viewportSize, Vector2 offsetPercent)
{
var button = GetNode<TextureButton>(path);
Vector2 finalPosition = new Vector2(
viewportSize.X * (1 - offsetPercent.X),
viewportSize.Y * (1 - offsetPercent.Y)
);
button.AnchorRight = 1;
button.AnchorBottom = 1;
button.AnchorLeft = 1;
button.AnchorTop = 1;
button.Position = finalPosition; // 从右下角反向偏移
}
private void UpdateDirectionInput(Vector2 dir)
{
string[] actions = { "Left", "Right", "Up", "Down" };
Vector2[] vectors = { Vector2.Left, Vector2.Right, Vector2.Up, Vector2.Down };
const float thresholdAngle = 57f;
for (int i = 0; i < actions.Length; i++)
{
float angle = Mathf.RadToDeg(Mathf.Abs(dir.AngleTo(vectors[i])));
if (dir.Length() > 0.1f && angle < thresholdAngle)
Input.ActionPress(actions[i]);
else
Input.ActionRelease(actions[i]);
}
}
}
}