◆ 持续开发中 · 当前为非稳定版本,如需抢先体验请通过 GitHub Issues 联系作者

输入与热键

两条平行的输入通路,共用同一个 InputActionAsset:事件层(HotkeyManager,按动作名注册回调)+ 轮询层(输入组件化,按 map 生成 ECS 组件)。

命名空间 HYC.Framework.Input。

该用哪条

通路入口适用
热键(事件)HotkeyManager + RegisterHotkey菜单 / 确认 / 取消 / 长按 / 带按键提示 UI
组件化(轮询)<Map>InputComponent + InputComponentDriverSystem摇杆 / 向量 / 轴 / 每帧读的玩法输入

一、热键 HotkeyManager

HotkeyManager 是静态注册表,把 InputSystem 的 Action 事件分发给多个回调,并按窗口焦点自动静默。框架不依赖游戏生成的枚举,一律用字符串动作名。

// 引导期(宿主做一次,必须先做)
HotkeyManager.BindAsset(myInputActionAsset);
private HotkeyHandle mHotkey;

public override void OnViewOpen(params object[] args)
{
    // 最简单:动作名 + 回调
    mHotkey = RegisterHotkey(HotkeyActionNames.UI_UGUI_Cancel, ctx => UIManager.Close<BagWindow>());

    // 带长按(< 0.1 会被钳到 0.1,NaN 也变 0.1)
    RegisterHotkey("Game/UseItem", OnUse, holdTime: 0.5f);

    // 带 UI 提示元素(会在 parent 下生成按键提示 UI)
    RegisterHotkey("Game/UseItem", someHotkeyElement, OnUse, priority: 10);
}

public override void OnViewClose()
{
    UnregisterHotkey(mHotkey);
    base.OnViewClose();     // 必调
}

完整签名:

public static HotkeyHandle RegisterHotkey(
    string actionName,
    Action<HotkeyInputContext> callback,
    float holdTime = 0,                              // > 0 → Hold 模式
    Transform parent = null,                         // 提示元素挂载点
    string text = null,                              // 提示文本
    HotkeyElementStyle style = HotkeyElementStyle.UI,
    int priority = 0)

核心类型:HotkeyHandle(轻量 struct 句柄,只装一个 int ID)、HotkeyInputContext(统一回调载荷)、HotkeyInputPhase(Disabled/Waiting/Started/Performed/Canceled/End)、HotkeyMode(Press/Hold)、InputDevice、HotkeyActionNames、IHotkeyElement / BaseHotkeyElement。

热键的坑

  • Press 模式一次按下回调多次,务必用 ctx.started/ctx.performed 过滤。
  • priority 不生效——源码里 _sortedIDs 从未被使用,分发顺序就是注册顺序。
  • BindAsset 重复调用会重复订阅 InputSystem.onActionChange,图标刷新会跑多次。
  • InputAsset == null 时所有注册都失败(返回 ID=0 + LogError)——引导期必须先 BindAsset。
  • UpdateAllHotkeyIcons() 目前实际是空转;DisableTime 只存不消费(给宿主 UI 做置灰倒计时)。

二、输入组件化

把 Input System 的 action 按 action map 动态生成为 ECS 的 IComponentData(富状态 struct),由驱动系统每帧把当前输入写进玩家实体;宿主在游戏逻辑里轮询式地「捞出来看」。

// 引导 / 玩家加入时(游戏层)
var player = em.CreateEntity();
UIInputComponent.AddTo(em, player);     // 便捷挂载(生成器产出)
SenseInputComponent.AddTo(em, player);
InputPlayerRegistry.Register(player, myAssetInstance);
// 在某 System 里(例如挂在 UpdateGroup_B9 表现组)
if (SystemAPI.HasComponent<UIInputComponent>(player))
{
    var ui = SystemAPI.GetComponent<UIInputComponent>(player);
    if (ui.Submit.pressed)        Confirm();
    if (ui.NavMove.active)        MoveCursor(ui.NavMove.value);
    if (ui.Submit.justPressed)    PlayClick();      // 本帧刚按下
}

也可注册 lambda 回调(A0 写完组件后即时触发):

InputPlayerRegistry.RegisterHandler<UIInputComponent>(player, ui =>
{
    if (ui.Submit.justPressed) Confirm();
    if (ui.Cancel.justPressed) CloseSelf();
    if (ui.NavMove.active)     DriveCursor(ui.NavMove.value);
});

菜单:HYC Framework/Input/Generate Input Components(扫描工程里所有 InputComponentMapping 资产并生成);Mapping 资产右键 → Generate This Mapping (Input Component)。

组件化的坑

  • 每名玩家要独立 InputActionAsset 实例——共享实例会让边沿与启用状态跨玩家串味。
  • 生成代码设备无关:生成器只认 action 名与 expectedControlType,不碰任何具体设备;设备配对(InputUser)永远在游戏层。
  • 组件字段按 expectedControlType 定:action 没设控制类型会兜底成 InputButton。要拿向量就得设成 Vector2。
  • 读取必须晚于 A0 写入。
◈
可运行的完整示例见 示例 · 输入组件化。