377 lines
13 KiB
Markdown
377 lines
13 KiB
Markdown
# Virtual HUD Input Design
|
|
|
|
## 目标
|
|
|
|
设计一套可在大厅和小游戏中复用的 HUD 输入组件:
|
|
|
|
- 通用摇杆组件:复用当前大厅摇杆视觉和交互体验,输出纯方向输入事件。
|
|
- 通用按键组件:按 MOBA 手游右下角操作区设计,提供 1 个主按键 + 4 个次按键,支持完整技能键手势和状态反馈。
|
|
|
|
组件层只输出输入事件和输入状态,不直接发送网络消息,不绑定大厅移动、小游戏技能、角色职业或服务端协议。大厅和小游戏各自订阅事件,并把事件映射到自己的业务逻辑。
|
|
|
|
## 范围
|
|
|
|
本设计覆盖:
|
|
|
|
- 通用 `VirtualJoystick` 运行时组件。
|
|
- 通用 `VirtualActionButton` 单按钮组件。
|
|
- 通用 `VirtualActionPad` 组合组件。
|
|
- `UI_VirtualJoystick` 和 `UI_VirtualActionPad` 预设描述,沿用 `Doc/UIPrefabCreater/*.json` 到 prefab 的现有管线。
|
|
- 大厅摇杆从 `LobbyWorldController` 私有采样逻辑迁移到通用组件。
|
|
- 小游戏按需加载通用摇杆或右下角按键组,并消费纯输入事件。
|
|
|
|
不覆盖:
|
|
|
|
- 小游戏协议设计。
|
|
- 具体技能系统、伤害、冷却规则、服务器校验。
|
|
- 专用 MOBA 圆形技能切图生产。第一版复用现有 Common UI 资源,后续可替换美术资源而不改变输入 API。
|
|
- Unity 新 Input System 迁移。第一版继续使用当前项目已有的 UGUI/EventSystem/StandaloneInputModule 路线。
|
|
|
|
## 方案选择
|
|
|
|
采用“通用 VirtualInput 组件层”方案。
|
|
|
|
备选方案一是只重构现有大厅摇杆并单独新增技能键。这种方式改动少,但大厅和小游戏输入会快速分叉。
|
|
|
|
备选方案二是新增完整输入服务和动作映射系统。这更接近正式 Input System,但当前项目还没有动作注册、上下文切换、优先级和协议映射基础,第一版过重。
|
|
|
|
推荐方案在中间:新增独立组件层,组件只负责输入采样和视觉反馈,业务层自己映射响应。它能复用现有 UI 管线,也不会把小游戏协议提前写死到客户端框架里。
|
|
|
|
## 架构
|
|
|
|
新增目录:
|
|
|
|
```text
|
|
Client/Assets/Script/xmain/Input/
|
|
```
|
|
|
|
核心组件:
|
|
|
|
```csharp
|
|
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` 负责鼠标/触摸捕获、死区、归一化方向、摇杆头视觉偏移。它不负责移动、相机、网络同步。
|
|
|
|
```csharp
|
|
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; }
|
|
}
|
|
}
|
|
```
|
|
|
|
```csharp
|
|
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` 是单个技能键状态机。它负责点击、长按、拖拽瞄准、取消、冷却遮罩、次数、禁用视觉。它不关心按钮对应普攻、技能还是道具。
|
|
|
|
```csharp
|
|
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
|
|
|
|
新增:
|
|
|
|
```text
|
|
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_base`、`lobby_joystick_ring`、`lobby_joystick_knob`。
|
|
|
|
旧 `UI_LobbyJoystick` 可以先保留,避免已有资源引用断裂。大厅运行时改用 `UI_VirtualJoystick` 后,旧预设可作为兼容资源,后续再清理。
|
|
|
|
### UI_VirtualActionPad
|
|
|
|
新增:
|
|
|
|
```text
|
|
Doc/UIPrefabCreater/UI_VirtualActionPad.json
|
|
Client/Assets/Game/Art/UI/Prefab/UI_VirtualActionPad.prefab
|
|
```
|
|
|
|
默认布局:
|
|
|
|
- 根节点锚到右下角。
|
|
- `Btn_Primary`:主按键,最大,位于右下核心位置。
|
|
- `Btn_Skill1`、`Btn_Skill2`、`Btn_Skill3`:围绕主按键左侧和上方弧形排布。
|
|
- `Btn_Skill4`:更靠上或靠左的小技能键,用于特殊动作、召唤师技能或道具。
|
|
- `CancelArea`:默认在按钮组上方偏中区域,仅拖拽技能时显示。
|
|
|
|
每个按钮标准子节点:
|
|
|
|
- `Icon`
|
|
- `CooldownMask`
|
|
- `CooldownText`
|
|
- `ChargeText`
|
|
- `AimIndicator` 或 `DragArrow`
|
|
|
|
第一版视觉复用现有 Common 资源:
|
|
|
|
- 主按键底图优先使用 `btn_primary_*`。
|
|
- 次按键底图优先使用 `btn_small_*` 或 `btn_warning_*`。
|
|
- 图标先使用 `ico_sword`、`ico_shield`、`ico_star`、`ico_heart`、`ico_item` 等临时示意图标。
|
|
|
|
如果后续补充圆形技能键切图,只需替换 JSON 资源和 prefab,不改变 `VirtualActionButton`/`VirtualActionPad` API。
|
|
|
|
## 摇杆交互
|
|
|
|
`VirtualJoystick` 行为:
|
|
|
|
1. 捕获鼠标左键或触摸。
|
|
2. 按 pointer ID 锁定一次操作,直到松开或取消。
|
|
3. 支持开始捕获区域放大倍率,默认视觉半径 `1.6x` 内都可开始控制。
|
|
4. 计算屏幕指针相对摇杆中心的向量,并归一化到 `[-1, 1]`。
|
|
5. 小于死区时输出 `Value=Vector2.zero` 且 `IsActive=false`。
|
|
6. 大于死区时输出归一化方向且 `IsActive=true`。
|
|
7. `Knob` 最大视觉移动距离由容器半径和 knob 半径计算,避免不同尺寸下越界。
|
|
8. 松开时重置方向、复位 knob、触发 `Ended`。
|
|
|
|
事件:
|
|
|
|
- `Started`:从未激活进入捕获。
|
|
- `Changed(value, active)`:方向或激活状态变化时触发。
|
|
- `Ended`:释放当前 pointer 后触发。
|
|
|
|
`IsScreenPositionInAcquireArea` 提供给相机、点击移动等外部输入排除逻辑复用,避免各业务重复计算摇杆区域。
|
|
|
|
## 按键交互
|
|
|
|
`VirtualActionButton` 行为:
|
|
|
|
- `PointerDown`:记录按下时间、pointer ID,发 `Down`。
|
|
- `PointerDrag`:计算从按钮中心到当前指针的 `LocalVector`、`Direction`、`Distance01`,发 `Drag`。
|
|
- 进入取消区时切换取消悬停视觉,并在事件中设置 `IsCanceled=true`。
|
|
- 按住超过阈值后发一次 `HoldStart`。
|
|
- 按住期间按固定间隔发 `HoldTick`。
|
|
- 松开时:
|
|
- 如果在取消区,发 `Cancel`。
|
|
- 如果拖拽距离超过释放阈值,发 `Release`,并携带方向。
|
|
- 如果未拖拽且未达到长按阈值,发 `Tap`。
|
|
- 如果已经长按,先补 `HoldEnd`,再按最终状态发 `Release` 或 `Cancel`。
|
|
- 冷却中、禁用、次数为 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`
|
|
- 或两者都加载
|
|
|
|
小游戏客户端订阅事件并自行映射:
|
|
|
|
```text
|
|
Primary Tap -> 普攻 opcode
|
|
Skill1 Release -> 带方向释放技能 opcode
|
|
Skill2 Cancel -> 不发送技能,播放本地取消反馈
|
|
Joystick Changed -> 本地移动或发送移动输入
|
|
```
|
|
|
|
`GameClientCtx` 第一版不扩展输入服务,继续保持资源加载和事件订阅模式。这样不会改变小游戏框架合同,也不会影响已有 RPS 小游戏。
|
|
|
|
## 错误处理
|
|
|
|
- 摇杆 prefab 缺少 `Knob`:记录错误日志,禁用 `VirtualJoystick`。
|
|
- 按钮缺少 `Icon`:仍可输出事件,但不显示图标。
|
|
- 按钮缺少 `CooldownMask`、`CooldownText` 或 `ChargeText`:跳过对应视觉刷新。
|
|
- 缺少 `CancelArea`:拖拽取消功能关闭,按钮仍支持点击和拖拽释放。
|
|
- 多点触控按 pointer ID 锁定,同一个按钮未释放前忽略其他 pointer。
|
|
- 不同按钮可以各自捕获不同 pointer。
|
|
- 冷却、禁用、次数为 0 时只发 `Blocked`,不发 `Tap`、`Release` 或 `Cancel`。
|
|
- 组件销毁或禁用时,如果正在按住,需要复位内部状态和视觉,避免残留按下态。
|
|
|
|
## 测试策略
|
|
|
|
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. 为 `VirtualJoystick` 和 `VirtualActionButton` 写核心状态测试。
|
|
2. 实现 `VirtualJoystick`。
|
|
3. 实现 `VirtualActionButton` 和 `VirtualActionPad`。
|
|
4. 新增 `UI_VirtualJoystick.json` 和 `UI_VirtualActionPad.json`。
|
|
5. 生成并检查 prefab。
|
|
6. 迁移 `LobbyWorldController` 使用 `VirtualJoystick`。
|
|
7. 加测试和手动烟测。
|
|
|
|
## 验收标准
|
|
|
|
- 大厅能继续使用左下角摇杆移动角色。
|
|
- 摇杆输入来自 `VirtualJoystick` 事件,而不是 `LobbyWorldController` 私有采样逻辑。
|
|
- 小游戏能加载右下角 `VirtualActionPad` 并收到 5 个按钮的纯输入事件。
|
|
- 技能键支持点击、长按、拖拽释放、拖拽取消、冷却、次数、禁用。
|
|
- 冷却中或次数为 0 时不会误发释放事件。
|
|
- 缺少可选视觉节点不会导致运行时异常。
|
|
- 现有小游戏框架合同不需要变更。
|