Skip to content

GameSystemUI 移动控件 ​

GameSystemUI 提供一套平台自适应的移动输入控件:手机端显示虚拟摇杆,PC 端默认使用 WASD。它有两种使用模式:

  • 单位控制模式:绑定 Player.LocalPlayer.MainUnit 或指定 Unit,控件自动发出单位移动指令。
  • 输入事件模式:不绑定单位,只订阅移动输入事件,用于纯 GameGraph / SceneGraph、相机、载具、光标或其它自定义控制目标。

定义位置:GameSystemUI.MoveSystem

常用数据入口:gamesystemui.ScopeData.GameDataControl.MoveControl

快速开始 ​

game_entry 模板默认引用 GameSystemUI.dll,并把 gamesystemui.ScopeData 配置为 GSUI 全局别名。通常直接创建平台自适应根控件即可:

csharp
#if CLIENT
using GameSystemUI.MoveSystem;

var moveControlRoot = GSUI.GameDataControl.MoveControl.Data!.CreateControl();
moveControlRoot.AddToVisualTree();
#endif

MoveControl 是 GameDataControlPlatformAdaptivePanel,运行时会根据平台选择实际子控件:

平台实际控件行为
手机 / 移动调试MoveJoyStick虚拟摇杆
Windows / PCMoveKeyBoardWASD

默认模式:控制主控单位 ​

默认情况下 UseMainUnit = true。控件会绑定本地玩家主控单位,并把输入转换为 VectorMove / VectorMoveAdjust / VectorMoveStop 指令。

服务端需要先设置主控单位:

csharp
#if SERVER
player.MainUnit = hero;
#endif

客户端添加移动控件:

csharp
#if CLIENT
using GameSystemUI.MoveSystem;

var moveControlRoot = GSUI.GameDataControl.MoveControl.Data!.CreateControl();
moveControlRoot.AddToVisualTree();
#endif

适合常规 2.5D / 3D 单位游戏。相机角度变化时,控件会重新按当前相机方向修正移动指令。

指定单位模式 ​

如果不想使用本地玩家 MainUnit,可以手动绑定单位:

csharp
#if CLIENT
using GameSystemUI.MoveSystem;

var moveControlRoot = GSUI.GameDataControl.MoveControl.Data!.CreateControl();
moveControlRoot.AddToVisualTree();

moveControlRoot.SetUseMainUnit(false);
moveControlRoot.BindMoveUnit(myUnit);
#endif

也可以先取得实际 MoveControlBase:

csharp
var moveControl = moveControlRoot.GetMoveControl();
if (moveControl != null)
{
    moveControl.BindUnit = myUnit; // 手动设置 BindUnit 会自动关闭 UseMainUnit
}

输入事件模式:不绑定单位 ​

纯 GameGraph / SceneGraph 游戏没有 Unit 或 MainUnit 的概念。此时可以只使用 GameSystemUI 的跨平台输入 UI,不让它发单位移动指令:

csharp
#if CLIENT
using GameUI.Control;
using GameSystemUI.MoveSystem;
using System.Numerics;

private Control? moveControlRoot;
private MoveControlBase? moveControl;
private Vector2 moveDirection;

private void CreateMoveInput()
{
    moveControlRoot = GSUI.GameDataControl.MoveControl.Data!.CreateControl();
    moveControlRoot.AddToVisualTree();

    moveControl = moveControlRoot.GetMoveControl();
    if (moveControl == null)
    {
        Game.Logger.LogWarning("MoveControl was created, but no MoveControlBase child was found.");
        return;
    }

    moveControl.UseMainUnit = false;
    moveControl.BindUnit = null;
    moveControl.OnMoveInput += OnMoveInput;
}

private void OnMoveInput(object? sender, MoveInputEventArgs e)
{
    if (e.State == MoveInputState.Stopped)
    {
        moveDirection = Vector2.Zero;
        return;
    }

    // 常规 SCE 世界平面控制使用镜头补偿方向;固定 XY 正交相机见下文。
    moveDirection = e.WorldDirection;
}

private void DisposeMoveInput()
{
    if (moveControl != null)
    {
        moveControl.OnMoveInput -= OnMoveInput;
        moveControl = null;
    }

    moveControlRoot?.RemoveFromVisualTree();
    moveControlRoot?.Dispose();
    moveControlRoot = null;
}
#endif

在 Think 或其它更新循环中,把 moveDirection 应用到自定义目标即可:

csharp
node.Position += new Vector3(moveDirection.X, moveDirection.Y, 0f) * speed * dt;

项目应根据目标坐标系选择原始 Direction 或带镜头补偿的 WorldDirection,然后进行必要的轴映射。下节说明固定二维正交相机的转换方式。

输入坐标与镜头补偿 ​

Direction 保留键盘/摇杆的屏幕输入约定:X 向右、Y 向下。W 与摇杆上推对应 (0, -1),S 与下推对应 (0, 1);A 向左、D 向右。WorldDirection 则按默认镜头偏移旋转该向量。

UI 屏幕位置通常从左上角开始,但这里的 Direction 不是屏幕位置:摇杆先计算相对于自身中心的偏移,再转换为方向;(0,0) 表示居中或停止。不能用地图高度或 UI 原点对移动方向做平移。

TMX 文件中的像素坐标同样是左上原点、Y 向下。GameGraph 加载正交地图后,提供左下原点、Y 向上的地图局部坐标;瓦片行列索引仍保留左上开始的行序。直接把原始输入加到加载后的位置会造成 W/S 反向,原因是 Y 正方向不同。完整对照、位置转换与方向转换见 TMX 文件、地图与输入坐标。

默认镜头补偿按以下顺序取方位:

  1. 优先读取主渲染视口相机的正前方向在 XY 平面的投影,求方位角后加 90°。
  2. 投影不能确定方位时,回退到 SCE 游戏镜头的 Yaw + 90°。
  3. 无法读取镜头或发生异常时,使用 0° 偏移。

设置 UseMainUnit = false 和 BindUnit = null 只阻止发送单位移动指令;输入事件仍然提供带镜头补偿的 WorldDirection。移动期间镜头方位变化,也会触发 Changed 事件。

使用场景方向选择
常规 SCE 单位或按镜头方位操控的世界平面目标WorldDirection,再按项目坐标约定映射
沿 Z 轴观察 XY 平面,画面右方为 +X、上方为 +Y 的固定正交相机Direction,映射为 (X, -Y)
使用 Y 向下屏幕坐标的 Canvas/UI 对象Direction,保持屏幕坐标约定
旋转或采用其它平面的二维相机根据实际相机的屏幕右/上方向转换原始输入

TmxMapSample 的固定相机沿 Z 轴观察,正前方向的 XY 投影为零。默认补偿会回退到 SCE 游戏镜头,不能据此保证该二维视图的上下左右正确。样例使用原始输入并翻转 Y:

这里的“镜头补偿”是输入方向计算,不是切换渲染相机。TMX 样例使用通用 CameraComponent 的正交投影显示二维地图;正交/透视与是否能从正前方向的 XY 投影求方位是两个独立条件。相机关系见TMX 地图、场景与相机。

csharp
private void OnMoveInput(object? sender, MoveInputEventArgs e)
{
    moveDirection = e.State == MoveInputState.Stopped
        ? Vector2.Zero
        : new Vector2(e.Direction.X, -e.Direction.Y);
}

这一转换针对上述固定相机与世界坐标约定,不是所有二维游戏的通用规则。它只转换输入;TileMapObject2D.Position 等地图坐标已由加载器转换,不能再次翻转 Y。完整样例见 code_sample/src/TmxMapSample/。

MoveInputEventArgs ​

OnMoveInput 在有无绑定单位时都会触发。参数如下:

属性类型说明
StateMoveInputStateStarted、Changed、Stopped
AngleAngle?原始输入角度;停止时为 null
WorldAngleAngle?应用相机角度偏移后的角度;停止时为 null
DirectionVector2未应用镜头补偿的屏幕输入方向,X 向右、Y 向下;停止时为 Vector2.Zero
WorldDirectionVector2应用相机角度偏移后的方向;停止时为 Vector2.Zero

Started 通常对应一次移动输入开始,Changed 对应方向变化或相机角度变化,Stopped 对应键盘/摇杆释放。

扩展方法 ​

MoveControlExtensions 可从平台自适应根控件递归找到实际移动控件:

csharp
MoveControlBase? GetMoveControl(this Control control);
bool TryGetMoveControl(this Control control, out MoveControlBase? moveControl);
bool SetUseMainUnit(this Control control, bool useMainUnit);
bool BindMoveUnit(this Control control, Unit? unit);

这些扩展适合 GSUI.GameDataControl.MoveControl.Data!.CreateControl() 返回的是 PlatformAdaptivePanel 时使用。

数据模板字段 ​

DefaultMoveJoyStick 和 DefaultMoveKeyBoard 都支持:

字段说明
UseMainUnit是否自动绑定本地玩家主控单位
StopMoveOnUnitChange绑定单位变化时是否停止旧单位移动

MoveJoyStick 还支持 DeadBand,用于设置摇杆死区像素范围。

常见问题 ​

现象原因处理
移动 UI 出现但单位不动服务端没有设置 player.MainUnit,或单位不可行走设置主控单位并确认单位有移动能力
纯 GameGraph 项目没有 Unit使用了默认单位控制模式设置 UseMainUnit = false,只订阅 OnMoveInput
PC 可输入,手机无摇杆或反之直接创建了单个平台控件优先创建 GSUI.GameDataControl.MoveControl 平台自适应控件
方向与游戏期望不一致项目相机或坐标约定不同按上文选择 Direction / WorldDirection,并在项目代码中映射轴
固定 XY 正交视图中 W/S 反向把屏幕 Y 向下直接当成世界 Y 向上使用 Direction 并转换为 (X, -Y);不要翻转已转换的 TMX 地图坐标
销毁界面后仍收到输入未取消事件订阅或未移除控件在 Dispose 中取消 OnMoveInput 并移除/释放控件