Files
AIC-Project/docs/superpowers/specs/2026-07-30-virtual-hud-input-design.md

13 KiB

Virtual HUD Input Design

目标

设计一套可在大厅和小游戏中复用的 HUD 输入组件:

  • 通用摇杆组件:复用当前大厅摇杆视觉和交互体验,输出纯方向输入事件。
  • 通用按键组件:按 MOBA 手游右下角操作区设计,提供 1 个主按键 + 4 个次按键,支持完整技能键手势和状态反馈。

组件层只输出输入事件和输入状态,不直接发送网络消息,不绑定大厅移动、小游戏技能、角色职业或服务端协议。大厅和小游戏各自订阅事件,并把事件映射到自己的业务逻辑。

范围

本设计覆盖:

  • 通用 VirtualJoystick 运行时组件。
  • 通用 VirtualActionButton 单按钮组件。
  • 通用 VirtualActionPad 组合组件。
  • UI_VirtualJoystickUI_VirtualActionPad 预设描述,沿用 Doc/UIPrefabCreater/*.json 到 prefab 的现有管线。
  • 大厅摇杆从 LobbyWorldController 私有采样逻辑迁移到通用组件。
  • 小游戏按需加载通用摇杆或右下角按键组,并消费纯输入事件。

不覆盖:

  • 小游戏协议设计。
  • 具体技能系统、伤害、冷却规则、服务器校验。
  • 专用 MOBA 圆形技能切图生产。第一版复用现有 Common UI 资源,后续可替换美术资源而不改变输入 API。
  • Unity 新 Input System 迁移。第一版继续使用当前项目已有的 UGUI/EventSystem/StandaloneInputModule 路线。

方案选择

采用“通用 VirtualInput 组件层”方案。

备选方案一是只重构现有大厅摇杆并单独新增技能键。这种方式改动少,但大厅和小游戏输入会快速分叉。

备选方案二是新增完整输入服务和动作映射系统。这更接近正式 Input System,但当前项目还没有动作注册、上下文切换、优先级和协议映射基础,第一版过重。

推荐方案在中间:新增独立组件层,组件只负责输入采样和视觉反馈,业务层自己映射响应。它能复用现有 UI 管线,也不会把小游戏协议提前写死到客户端框架里。

架构

新增目录:

Client/Assets/Script/xmain/Input/

核心组件:

namespace XGame.Input
{
    public sealed class VirtualJoystick : MonoBehaviour
    {
        public Vector2 Value { get; }
        public bool IsActive { get; }

        public event Action Started;
        public event Action<Vector2, bool> Changed;
        public event Action Ended;

        public bool IsScreenPositionInAcquireArea(Vector2 screenPosition);
    }
}

VirtualJoystick 负责鼠标/触摸捕获、死区、归一化方向、摇杆头视觉偏移。它不负责移动、相机、网络同步。

namespace XGame.Input
{
    public enum ActionButtonId
    {
        Primary,
        Skill1,
        Skill2,
        Skill3,
        Skill4
    }

    public enum ActionButtonPhase
    {
        Down,
        Drag,
        HoldStart,
        HoldTick,
        HoldEnd,
        Tap,
        Release,
        Cancel,
        Blocked
    }

    public readonly struct ActionButtonEvent
    {
        public ActionButtonId ButtonId { get; }
        public ActionButtonPhase Phase { get; }
        public Vector2 ScreenPosition { get; }
        public Vector2 LocalVector { get; }
        public Vector2 Direction { get; }
        public float Distance01 { get; }
        public float HeldSeconds { get; }
        public bool IsCanceled { get; }
        public int PointerId { get; }
    }
}
namespace XGame.Input
{
    public sealed class VirtualActionButton : MonoBehaviour
    {
        public ActionButtonId ButtonId;
        public bool IsInteractable { get; }

        public event Action<ActionButtonEvent> ButtonEvent;

        public void SetCooldown(float remainingSeconds, float durationSeconds);
        public void SetCharges(int count);
        public void SetButtonEnabled(bool enabled);
        public void SetCancelArea(RectTransform cancelArea);
        public void SetIcon(Sprite icon);
    }
}

VirtualActionButton 是单个技能键状态机。它负责点击、长按、拖拽瞄准、取消、冷却遮罩、次数、禁用视觉。它不关心按钮对应普攻、技能还是道具。

namespace XGame.Input
{
    public sealed class VirtualActionPad : MonoBehaviour
    {
        public event Action<ActionButtonEvent> ButtonEvent;

        public VirtualActionButton GetButton(ActionButtonId id);
        public void SetCooldown(ActionButtonId id, float remainingSeconds, float durationSeconds);
        public void SetCharges(ActionButtonId id, int count);
        public void SetButtonEnabled(ActionButtonId id, bool enabled);
        public void SetCancelAreaVisible(bool visible);
    }
}

VirtualActionPad 管理右下角 5 个按钮,按稳定 ID 转发事件,并提供统一状态刷新 API。

预设

UI_VirtualJoystick

新增:

Doc/UIPrefabCreater/UI_VirtualJoystick.json
Client/Assets/Game/Art/UI/Prefab/UI_VirtualJoystick.prefab

它从现有 UI_LobbyJoystick 演进而来,保留:

  • Base
  • Ring
  • Knob

默认布局:

  • 根节点锚到左下角。
  • 参考分辨率 2048x1024
  • 默认中心位置约 (260, 220)
  • 默认尺寸 148x148
  • 根节点挂 VirtualJoystick
  • 继续复用 lobby_joystick_baselobby_joystick_ringlobby_joystick_knob

UI_LobbyJoystick 可以先保留,避免已有资源引用断裂。大厅运行时改用 UI_VirtualJoystick 后,旧预设可作为兼容资源,后续再清理。

UI_VirtualActionPad

新增:

Doc/UIPrefabCreater/UI_VirtualActionPad.json
Client/Assets/Game/Art/UI/Prefab/UI_VirtualActionPad.prefab

默认布局:

  • 根节点锚到右下角。
  • Btn_Primary:主按键,最大,位于右下核心位置。
  • Btn_Skill1Btn_Skill2Btn_Skill3:围绕主按键左侧和上方弧形排布。
  • Btn_Skill4:更靠上或靠左的小技能键,用于特殊动作、召唤师技能或道具。
  • CancelArea:默认在按钮组上方偏中区域,仅拖拽技能时显示。

每个按钮标准子节点:

  • Icon
  • CooldownMask
  • CooldownText
  • ChargeText
  • AimIndicatorDragArrow

第一版视觉复用现有 Common 资源:

  • 主按键底图优先使用 btn_primary_*
  • 次按键底图优先使用 btn_small_*btn_warning_*
  • 图标先使用 ico_swordico_shieldico_starico_heartico_item 等临时示意图标。

如果后续补充圆形技能键切图,只需替换 JSON 资源和 prefab,不改变 VirtualActionButton/VirtualActionPad API。

摇杆交互

VirtualJoystick 行为:

  1. 捕获鼠标左键或触摸。
  2. 按 pointer ID 锁定一次操作,直到松开或取消。
  3. 支持开始捕获区域放大倍率,默认视觉半径 1.6x 内都可开始控制。
  4. 计算屏幕指针相对摇杆中心的向量,并归一化到 [-1, 1]
  5. 小于死区时输出 Value=Vector2.zeroIsActive=false
  6. 大于死区时输出归一化方向且 IsActive=true
  7. Knob 最大视觉移动距离由容器半径和 knob 半径计算,避免不同尺寸下越界。
  8. 松开时重置方向、复位 knob、触发 Ended

事件:

  • Started:从未激活进入捕获。
  • Changed(value, active):方向或激活状态变化时触发。
  • Ended:释放当前 pointer 后触发。

IsScreenPositionInAcquireArea 提供给相机、点击移动等外部输入排除逻辑复用,避免各业务重复计算摇杆区域。

按键交互

VirtualActionButton 行为:

  • PointerDown:记录按下时间、pointer ID,发 Down
  • PointerDrag:计算从按钮中心到当前指针的 LocalVectorDirectionDistance01,发 Drag
  • 进入取消区时切换取消悬停视觉,并在事件中设置 IsCanceled=true
  • 按住超过阈值后发一次 HoldStart
  • 按住期间按固定间隔发 HoldTick
  • 松开时:
    • 如果在取消区,发 Cancel
    • 如果拖拽距离超过释放阈值,发 Release,并携带方向。
    • 如果未拖拽且未达到长按阈值,发 Tap
    • 如果已经长按,先补 HoldEnd,再按最终状态发 ReleaseCancel
  • 冷却中、禁用、次数为 0 时不进入按下流程,只发 Blocked

默认阈值:

  • 长按阈值:0.35s
  • HoldTick 间隔:0.1s
  • 拖拽释放阈值:按钮半径的 0.25
  • 最大拖拽归一化距离:按钮半径的 1.5

这些阈值作为序列化字段暴露,允许 prefab 或业务按需调节。

状态与视觉

按钮状态:

  • Normal:可点击。
  • Pressed:按住。
  • Dragging:拖拽瞄准。
  • CancelHover:拖入取消区。
  • Cooldown:冷却中。
  • Disabled:禁用。
  • EmptyCharges:次数为 0。

视觉刷新:

  • CooldownMask 使用 Filled Image 或代码调整 fillAmount
  • CooldownText 显示向上取整的剩余秒数。
  • ChargeText 在次数大于 0 时显示次数,次数为 0 时按钮进入不可用视觉。
  • AimIndicator/DragArrow 在拖拽时显示方向,松手后隐藏。
  • CancelArea 默认隐藏,进入拖拽瞄准时显示;取消或释放后隐藏。

缺少可选节点时跳过对应视觉,不影响事件输出。缺少关键节点时记录日志并禁用该组件。

大厅接入

LobbyWorldController 保留大厅业务职责:

  • 本地角色移动。
  • 远端角色同步。
  • 相机旋转和缩放。
  • 大厅网络移动同步。

改动方向:

  • 移除 LobbyWorldController 内部摇杆采样字段和方法。
  • 加载 UI_VirtualJoystick.prefab 后获取 VirtualJoystick
  • 订阅 Changed/Ended,把 Vector2 缓存为大厅移动输入。
  • GetMoveDirection 继续把键盘 WASD 和摇杆方向合成世界方向。
  • 相机输入排除摇杆区域时调用 VirtualJoystick.IsScreenPositionInAcquireArea
  • 大厅进入小游戏隔离时,将通用摇杆 root 或 VirtualJoystick 列入 MiniGameStageIsolation 管理,避免大厅输入穿透。

小游戏接入

小游戏不强制显示这套 HUD。需要时在 IGameClient.OnEnter 中加载:

  • UI_VirtualJoystick
  • UI_VirtualActionPad
  • 或两者都加载

小游戏客户端订阅事件并自行映射:

Primary Tap       -> 普攻 opcode
Skill1 Release    -> 带方向释放技能 opcode
Skill2 Cancel     -> 不发送技能,播放本地取消反馈
Joystick Changed  -> 本地移动或发送移动输入

GameClientCtx 第一版不扩展输入服务,继续保持资源加载和事件订阅模式。这样不会改变小游戏框架合同,也不会影响已有 RPS 小游戏。

错误处理

  • 摇杆 prefab 缺少 Knob:记录错误日志,禁用 VirtualJoystick
  • 按钮缺少 Icon:仍可输出事件,但不显示图标。
  • 按钮缺少 CooldownMaskCooldownTextChargeText:跳过对应视觉刷新。
  • 缺少 CancelArea:拖拽取消功能关闭,按钮仍支持点击和拖拽释放。
  • 多点触控按 pointer ID 锁定,同一个按钮未释放前忽略其他 pointer。
  • 不同按钮可以各自捕获不同 pointer。
  • 冷却、禁用、次数为 0 时只发 Blocked,不发 TapReleaseCancel
  • 组件销毁或禁用时,如果正在按住,需要复位内部状态和视觉,避免残留按下态。

测试策略

EditMode 测试:

  1. VirtualJoystick 死区:小于死区输出 inactive,松开后归零。
  2. VirtualJoystick 归一化:超过半径时 clamp 到单位圆。
  3. VirtualJoystick knob 行程:按容器和 knob 尺寸计算,不越界。
  4. VirtualActionButton 点击:Down -> Tap。
  5. VirtualActionButton 拖拽释放:Down -> Drag -> Release,并携带方向。
  6. VirtualActionButton 拖拽取消:Down -> Drag -> Cancel。
  7. VirtualActionButton 长按:Down -> HoldStart -> HoldTick -> HoldEnd。
  8. VirtualActionButton Blocked:冷却中、禁用、次数为 0 时不发释放类事件。
  9. VirtualActionPad 能按 ActionButtonId 找到 5 个按钮并转发事件。
  10. 标准节点绑定成功,缺少可选节点不抛异常。
  11. 状态 API 能刷新冷却遮罩、冷却文字、次数文字和可用状态。

大厅接入测试:

  • LobbyWorldController 消费 VirtualJoystick 输出值合成移动方向。
  • 相机输入排除摇杆区域时使用通用组件判断。
  • 小游戏隔离时大厅摇杆不可见或不可交互。

手动 Unity 烟测:

  • PC 鼠标拖动摇杆。
  • 触摸或模拟触摸拖动摇杆。
  • 主按键点击释放。
  • 技能键拖拽瞄准释放。
  • 技能键拖拽到取消区取消。
  • 冷却遮罩和倒计时显示。
  • 次数耗尽后按钮进入不可用态。
  • 退出小游戏后大厅输入恢复。

实施顺序建议

  1. VirtualJoystickVirtualActionButton 写核心状态测试。
  2. 实现 VirtualJoystick
  3. 实现 VirtualActionButtonVirtualActionPad
  4. 新增 UI_VirtualJoystick.jsonUI_VirtualActionPad.json
  5. 生成并检查 prefab。
  6. 迁移 LobbyWorldController 使用 VirtualJoystick
  7. 加测试和手动烟测。

验收标准

  • 大厅能继续使用左下角摇杆移动角色。
  • 摇杆输入来自 VirtualJoystick 事件,而不是 LobbyWorldController 私有采样逻辑。
  • 小游戏能加载右下角 VirtualActionPad 并收到 5 个按钮的纯输入事件。
  • 技能键支持点击、长按、拖拽释放、拖拽取消、冷却、次数、禁用。
  • 冷却中或次数为 0 时不会误发释放事件。
  • 缺少可选视觉节点不会导致运行时异常。
  • 现有小游戏框架合同不需要变更。