输入路由:Begin 被消费后,Move 和 End 不能重新判定

自动关联目录:输入路由:Begin 被消费后,Move 和 End 不能重新判定

核心判断:输入消费是一次手势的所有权协议,不是每帧重新执行的 if-else。Begin 由谁接管,后续 Move、End 或 Cancel 就必须到达同一消费者。

一、业务冲突:编辑工具、UI、脚本和角色都想拿同一个指针

一个点击可能落在场景操纵柄、运行时按钮或可交互物上;文本框获得焦点后,键盘不应继续驱动角色;应用失焦、切换模式或销毁 World 时,又必须结束所有已开始的输入。

若每个事件重新按当前命中判定,按下按钮后移出边界会把 End 发给场景,按钮永久保持 Pressed;若失焦时只是清空按键集合,消费者永远收不到释放,角色会持续移动。

二、实际实现:Begin 锁存所有权,终止阶段先 Cancel 再拆监听

工程中的路由器为每个 Source 与 PointerId 保存活动状态。Begin 先经过 GUI 阻挡、场景工具、UI 捕获和脚本动作;一旦某层消费,结果锁存到该指针,Move 与 End 不再重新仲裁。路由器用单调序号、时间校验和重入保护拒绝倒序事件,Sink 替换与 Reset 延迟到当前分发结束。

UI 自身也记录捕获对象:Begin 命中的控件继续接收 Move/End。焦点进入文本输入时会合成已按键的 End;应用失焦、Play 结束或输入管理器注销时,先向活动手势发 Cancel,再清空按键、触摸兼容状态和监听器。

stateDiagram-v2
    [*] --> Idle
    Idle --> Captured: Begin + winner
    Captured --> Captured: Move -> same owner
    Captured --> Idle: End
    Captured --> Cancelling: focus lost / teardown
    Cancelling --> Idle: Cancel delivered
    Idle --> [*]: listener removed

三、值得保留:终止事件是资源释放协议

Begin 锁存消除了“手指移动后所有权漂移”;Cancel 先于注销则让工具、UI 和脚本都有机会释放拖拽事务、按压视觉和临时资源。把失焦视为终止输入,而非简单暂停,是避免粘键最关键的设计。

文本焦点第一次接管键盘时主动 Flush 已按键,也比持续查询 Slate 焦点更可靠:消费者得到一个完整的 End 边沿,此后再由 Context 阻断新 Begin。

四、结构性缺陷:PlayerController 看门狗是在修补所有权缺失

嵌入式编辑环境会反复设置 GameAndUI、鼠标光标和点击事件,并在 Tick 中通过代理字段检测漂移后重新应用;由于 UE 的 InputMode 缺少完整 Getter,这种做法能恢复功能,却无法回答“谁有权覆盖输入模式”。全局允许应用非激活时接收 Slate 输入,也可能把后台快捷键送入游戏。

硬编码拦截 Escape、全屏和标签快捷键再转发宿主,同样把平台外壳规则塞进全局预处理器。共享纹理模式还要专门绕开回弹环路,说明输入所有权实际跨越宿主、Slate、Viewport 和玩法,却没有统一契约。

五、目标设计:用可撤销的 Input Lease 合成最终策略

编辑器、模态 UI、文本输入、相机和角色控制各申请 InputLease,声明设备范围、Action 集、优先级、消费方式、光标模式和失焦策略。聚合器生成唯一有效的 PlayerController/Enhanced Input/Slate 配置;令牌释放后重算,不由 Tick 抢写状态。

路由状态必须拥有明确终止屏障:切 World、换 Context 或替换 Sink 时先阻止新 Begin,再向旧 Owner 发送 Cancel,等待本地清理完成,最后提交新配置。宿主快捷键作为最高层 Context,也走同一套可观测决策。

void TransitionInputPolicy(InputPolicy next)
{
    gate.BlockNewBegins();
    for (Gesture& g : activeGestures)
        g.owner->OnCancel(g.id, CancelReason::PolicyChanged);
    activeGestures.Reset();
    ApplyToSlateAndPlayerController(next);
    gate.Open();
}

六、决策与排查:查所有权链,不查最后一个布尔值

现象首查证据不应采用的修补
按钮卡在按下态Begin Owner 是否收到 End/Cancel超时强制改视觉
角色失焦后继续移动Flush 是否生成键盘 End每帧轮询按键
UI 与场景同时响应路由 Trace 的首个 Consumer两边各加阻挡布尔值
输入模式反复跳变当前 Lease 集与合成结果Tick 里重复 SetInputMode
后台触发命令应用激活策略与宿主 Context全局吞掉所有键
切模式偶发崩溃Cancel、解绑与对象销毁顺序回调里立即替换 Sink

输入系统的完成条件不是“按键能触发”,而是任意生命周期出口都能证明:没有活动手势、没有悬挂按键、没有旧 Context 仍持有控制权。