【UEGamePlay】- 3C篇(一) : Input
本文在博客园原创,转载请标注出处
前言
Q1.为什么首先了解输入
A1.在UE初识阶段,经常会遇到输入和角色冲突的情况,例如输入穿透,或者角色完全获取不到输入。而在中后期我们又能遇到特殊需求,例如获取玩家输入缓冲,想要制作出招表/连招表,局内获取玩家输入等等一些需求。
虽然引擎为我们提供了一套成熟的输入解决方案,而在Runtime期间我们很难知道输入事件真实的流程,且输入流程经过多个模块,非常难以直观Debug。于是我把输入放在第一篇。
架构解析
由于输入具有平台性,因此以下分析只限定于Windows中。使用UE5.3版本
基础框架
传统3C由“控制”,“角色”,“相机”,三部分组成,在UE提供的核心框架是由UInputComponent,UCharacterMovementComponent,UCameraComponent三大组件所构成。
而在UE中的输入架构核心围绕UInputComponent,UPlayerInput,APlayerController三者共同完成
当前UE引擎的输入框架图

Input框架
Tips:原有的Input框架已在UE5.1版本以后被EnhancedInput框架所替换,但是主体逻辑保持不变,文章会在经过改动后的组件后标注*
InputComponent*

具有行为与输入的绑定功能(ActionName与函数绑定),保存了输入相关数据,维护各种绑定数组。
通常情况下由具有输入功能的类创建(例如PlayerController、Actor、UserWidget),实例在PlayerController中被纳入栈中整合,最终交由PlayerInput处理。
FInputActionBinding: 对应BindAction
FInputAxisBinding: 对应BindAxis
PlayerController

PushInputComponent/PopInputComponent :管理并维护一个UInputComponent栈。
BuildInputStack : 通过BuildInputStack构建完整输入栈序InputStack,将InputStack转发至UPlayerInput,由UPlayerInput进行遍历UInputComponent委托。
完整的输入栈InputStack由BuildInputStack函数构建,随后转发进PlayerInput :
APlayerController::TickPlayerInput
APlayerController::ProcessPlayerInput
TArray<UInputComponent*> InputStack;
BuildInputStack(InputStack);
PlayerInput->ProcessInputStack(InputStack, DeltaTime, bGamePaused);
栈的顺序以及构成 :

栈的顺序从栈底到栈顶依次为:ControlledPawn → Level → PlayerController → CurrentInputStack
实际接收输入的顺序 为 Actor/UserWidget → Controller → Level → Pawn
PlayerInput*

组合 InputSetting 和 EngineInputSetting 里的 <行为名,按键>,与 InputComponent 里的<行为名,委托>结合,完成最终调用(Ini中的InputSettings(强依赖配置文件))
ActionMappings/AxisMappings :类型为FInputActionKeyMapping和FInputAxisKeyMapping,表明是行为名称,和按键的对应关系。添加方式目前只有一种,就是读取Ini中的InputSettings(强依赖配置文件)
ActionKeyMap / AxisKeyMap :类型为 TMap<FName,FActionKeyDetails>,表明一个行为名称可以对应多个绑定情况
KeyStateMap :类型为TMap<FKey,FKeyState>,主要记录了 Key 的 Value,bDown,bConsumed等属性。用于记录输入状态。
EvaluateKeyMapState(按键状态求解):根据当前 KeyStateMap、轴死区、累加等规则,生成“本帧有事件的键/轴”列表
EvaluateInputDelegates(触发绑定委托) :得到的“有事件的键/轴”按 InputComponent 栈去触发对应绑定
ProcessInputStack() :主要功能利用前期获取的信息,构建完整的当前按键-事件映射,且逐栈调用对应需要触发事件回调
Actor

EnableInput : Actor 在 EnableInput 时创建 InputComponent,同时调用 PlayerController::PushInputComponent将实例推入PlayerController::CurrentInputStack 。
DisableInput : 将其从 PlayerController 的 CurrentInputStack 中移除;
Tips:例如Pawn, PlayerController, LevelScriptActor会重载EnableInput,并不会主动加入,这些特殊的Actor会在PlayerController::BuildInputStack输入栈构建中会被单独进行压栈处理
EnhancedInput框架
UE5正式替换掉旧有输入系统。新EnhancedInput框架并不会改动原有的输入处理流程,它以模块化的方式解耦了从输入的按键配置到事件处理的逻辑处理过程,通过提供输入动作(UInputAction)、输入修改器(UInputModifier)、输入触发器(UInputTrigger)和输入映射环境(UInputMappingContext)这些可组合功能,在新的增强玩家输入(UEnhancedPlayerInput)和增强输入组件(UEnhancedInputComponent)的配合下提供了更灵活和更便利的输入配置和处理功能。
-
重新梳理简化: Axis/Action —> Action(统一)
-
运行时重新映射输入场景: UInputMappingContext
-
对初级用户易配置。大量默认行为实现,Tap/Hold...
-
对高级用户易扩展,可继承子类扩展
-
修改器:修改输入值
-
触发器:决定触发条件
-
优先级:配置输入场景优先级
-
模块化,不再只依赖Ini配置,以资源asset方式配置,堆栈式分隔逻辑
-
提高性能,不需要检查所有输入,只需要关心当前的场景和绑定
EnhancedInputComponent

- 继承InputComponent。
- 提供新的InputAction结构,能够保存更多信息(修改器,触发器)。
- 同时兼容旧框架流程和提供更符合现代CPP的函数绑定
InputAction/InputActionInstance + InputModifiers + InputTriggers


UInputAction : 输入行为的资产类,继承至UDataAsset,同时作为FInputActionInstance的模板
FInputActionlnstance : Runtime下UInputAction的运行时状态
UInputModifiers :
- 链式处理
- Mapping.Modifiers/Triggers针对当前IMC场景
- InputAction.Modifiers/Triggers针对全局
- 模式:
- DeadZone:限定值的范围
- Scalar:缩放一个标量
- Negate: 取反
- Smooth:多帧之间平滑
- CurveExponential:指数曲线,XYZ
- CurveUser:自定义指数曲线,CurveFloat
- FOVScaling: FOV缩放
- ToWorldSpace:输入设备坐标系向世界坐标系转换(调换XYZ顺序)
- SwizzleAxis:互换轴值
- Collection:嵌套子修改器集合
UInputTriggers:
- 模式:
- ETriggerEvent:ETriggerState发生转变时触发的事件
- Down:值大于阈值(默认0.5)就触发
- Pressed:不激活到激活
- Released:激活到不激活
- Hold:按住大于某个时间
- HoldAndRelease:按住大于某个时间后松开
- Tap:按下后快速抬起(默认0.2)
- Chorded:根据别的Action联动触发
InputMappingContexts

- 一套当前的Key->InputAction的映射集合,承担按键-行为的映射功能,与按键强相关
- 多个IMC同时作用,高优先级的会先处理,如果没有则触发到低优先级的
- 高优先级的Key绑定会屏蔽低优先级的绑定
EnhancedPlayerInput

继承PlayerInput,为了支持 InputAction / MappingContext / Modifiers / Triggers 等概念,在 PlayerInput 的关键步骤上做了重写或替换(原框架的结构逻辑不做改变)。
- AppliedInputContexts为当前所应用的输入上下文,通常由UEnhancedInputLocalPlayerSubsystem::AddMappingContext添加
- “按键→直接触发绑定”替换为“按键→映射上下文→InputAction→触发器/修饰器→事件”
- 旧框架使用 FInputActionKeyMapping/FInputAxisKeyMapping 映射名到绑定;
- EnhancedInput 用 UInputMappingContext和UInputAction进行映射,再通过 Modifiers(对原始值做变换,如 deadzone、scale、invert、radial 等)和 Triggers(决定 Started/Triggered/Completed 等事件时机)来决定何时以何值触发该 InputAction。
- 由原本的”键状态求解 + 委托触发“改为“按映射上下文/动作/触发器”来求值并触发。
- 重写/替代 EvaluateKeyMapState 与 EvaluateInputDelegates
- 重写 EvaluateKeyMapState。用于把 KeyStateMap 转换为当前帧激活的 InputAction,并维护诸如 ActionsWithEventsThisTick 等集合以便后续处理。
- 重写 EvaluateInputDelegates 触发 EnhancedInputComponent 中的 BindAction 产生的绑定回调。
- 支持运行时的 Mapping Context 管理
- UEnhancedPlayerInput 会跟踪被移除或变更的 InputActions,并在必要时从内部集合移除或重建状态
EnhancedInputLocalPlayerSubsystem

串联整个输入体系的关键,可以将一个或多个上下文应用到本地玩家身上,并调整它们的优先级。也可以进行移除
Input-Action流程
整体流程分为两大部分:
输入信息流程(InputKey)

-
操作系统 → 平台层:
Windows将原始信息投递到窗口,FWindowsApplication::ProcessMessage 接受消息并做初步解析以及过滤。
Tips:早期/特殊需求可通过 IWindowsMessageHandler / RawInput 插件拦截/定制原始消息 -
平台层 → Slate/FSlateApplication
平台层把输入事件传递给FSlateApplication。由Slate做事件路由(决定下方由谁接受信息SWidget?Viewport?)。
UMG/Slate在此处理或者阻断事件 -
Slate → GameViewportClient / SViewport → PlayerController
事件进入Runtime路径,通过PlayerController时会让游戏层优先处理,有机会在此处消费,拦截事件
PlayerController::InputKey 常用于在正式记录之前执行一些控制器级别的逻辑 -
如果事件继续由引擎处理(前几个流程未被消费),会调用 PlayerInput::InputKey(),引擎会把按键/按钮的状态记录到 TMap<FKey,FKeyState> KeyStateMap
Tips:
- APlayerController::bool InputKey(const FInputKeyParams& Params) 是处理键事件的入口,会在 IE_Pressed / IE_Released / IE_Repeat 等消息时被调用。目的是更新 KeyStateMap
- 操作系统的键重复(repeat):在按住一段时间后,OS 可能会开始发送 repeat 键事件(在 UE 中表现为 IE_Repeat),这些会再走一次 InputKey,如果打印InputKey日志,会看到间歇性的触发。这些 repeat 的频率由系统设置(键重复延迟和间隔)决定。某些平台或输入源(手柄轴、虚拟按键、模拟事件注入)会每帧产生事件或值变化;那种情况 InputKey 事件 看起来更连续,但键盘标准行为通常如上所述。
- 按住不等于每帧产生 InputKey :按住后通常只会看到一次 Pressed(随后可能有若干 OS Repeat),而不会每帧都有 InputKey 。但是 FKeyState.bDown 会在按下后一直为 true,直到 Released 把它置为 false。OS 的 Repeat 并不会把 bDown 从 true 变为 false —— 它只是再次发事件(通常 IE_Repeat),因此`InputKey 会被调用但 FKeyState.bDown 仍然是 true。
- bConsumed 事件累积等字段会影响事件是否继续被上层消费/转发
- FSlateApplication::ProcessMouseMoveEvent中有可能提前消费鼠标的移动事件
回调流程(InputAction)

- 输入信息被保留在`UPlayerInput::TMap<FKey,FKeyState> KeyStateMap
- 在 APlayerController::TickPlayerInput中每帧调用APlayerController::ProcessPlayerInput构建当前帧的接受输入栈,并将完整输入栈送入UPlayerInput中。
- UPlayerInput::ProcessInputStack中
- 根据当前帧数据(KeyStateMap,InputStack)构建当前帧对应激活的键-事件的映射,并且完成回调流程
Tips:
- UEnhancedPlayerInput 在每个采样周期(通常每帧的输入更新)读取这些 KeyStateMap 来评估 Action Trigger,并据此持续分发 Triggered 回调。
总结:
InputKey:基于事件(Pressed/Released/Repeat),只有当事件来自 OS(或系统产生 repeat)时才调用。
InputAction:基于采样 + Trigger 逻辑。
补充:

EnhancedInput框架并不会改动原有的Input处理流程,通过重写的关键ProcessInputStack中的EvaluateKeyMapState和EvaluateInputDelegates流程,,其中替换/扩展了“映射/处理/绑定”阶段,将静态 Action/Axis映射为了数据驱动层(Input Action、Input Mapping Context、Triggers、Modifiers 等)
工程要点
在上述框架解析后,可以得出几个在工程/项目中可用到的关键点
IWindowsMessageHandler - 最初可以拦截/定制原始消息,中途可以修改经过UI的一些事件消费。
APlayerController::ProcessPlayerInput中构建完整的输入栈,在这里可以进行高度自定义,修改原有输入栈逻辑,调整Runtime下输入消费的顺序.
UInputTriggers - 可以制定特殊的输入方式,例如按键多久(蓄力),连续短按(波动)等
UPlayerInput::InputKey() - 可以做到模拟输入,即使不通过平台也能够键入输入信息.例如一些QA/Debug场景,可由客户端提供输入脚本进行反复测试。还可以应用在一些主机游戏的移植中,有些主机游戏一开始就没有进行键鼠的适配,可以直接绕过原有UE的流程直接调用InputKey,使其键鼠直接适配原本手柄的输入

浙公网安备 33010602011771号