下面的内容为 [中文直播]第39期 | 虎跳龙拿--新一代增强输入框架EnhancedInput | Epic 大钊 的学习笔记,大量的内容来自视频中的。
增强输入系统(Enhanced Input System)是对默认输入系统做了一个扩展,通过模块化的方式,解耦了从输入的按键配置到事件处理的逻辑处理过程,提供了更灵活、便利的输入配置和处理功能,同时又能向后兼容虚幻引擎4(UE4)中的默认输入系统。
问题由来
当前的系统输入的流程
旧系统问题
- 旧系统实现基础的功能比较简单,但在想构建更复杂机制上就得需要在User层做更多的工作。例如角色在不同情景下的输入变化。(近战/远程/载具)
- 过于简陋,原来的输入系统只是告诉你事件,需自己实现众多行为。例如按住/双击。
新系统目标
- 重新梳理简化。由原来的 Axis/Action 简化为 Action
- 运行时重映射输入场景。UInputMappingContext
- 对初级用户易配置。大量默认行为实现,Tap/Hold...
- 对高级用户易扩展,可继承子类扩展。
- 修改器:修改输入值
- 触发器:决定触发条件
- 优先级:配置输入场景优先级
- 模块化,不再只依赖ini配置,以资源asset方式配置,堆栈式分隔逻辑。
- 提高性能,不需要检查所有的输入,只需关心当前的场景和绑定。
- UE5 正式替换掉旧有输入系统
基础用法
使用前的配置
在 Plugins 中,开启 Enhanced Input 插件,并重启编辑器。
在 Project Settings -> Input 分类中,替换默认类型(Default Classes)。原来的值可能为 UInputComponent,需要修改为 UEnhancedInputComponent。UEnhancedInputComponent 是 UInputComponent 的子类。
项目中添加 "EnhancedInput" 模块。可以在 PublicDependencyModuleNames 中添加,也可以在 PrivateDependencyModuleNames 中添加:
public class LeveForLight : ModuleRules
{
public LeveForLight(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[] { "EnhancedInput" });
}
}
使用的大致流程
下面为使用的大致流程,具体的设置会在下面详细介绍。
创建 InputAction
创建 InputMapingContext
InputMapingContext 中记录了按键和 Action 的绑定关系。一个按键和 action 是多对多的关系,即:一个按键可以和多个 Action 进行绑定,不同的按键可以绑定同一个 Action。
绑定 Action 委托
绑定 Action 的委托。
蓝图方式如下:
代码方式如下:首先在类中定义了可以处理的 Action,然后在 SetupPlayerInputComponent 中进行绑定 Action 的回调方法。
UPROPERTY(EditDefaultsOnly, Category = "Input|Action")
TObjectPtr<UInputAction> IA_LookUp;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* JumpAction;
// Called to bind functionality to input
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
if (IA_LookUp)
{
EnhancedInputComponent->BindAction(IA_LookUp, ETriggerEvent::Triggered, this, &AMyCharacter::LookUp);
}
}
}
void AMyCharacter::LookUp(const FInputActionValue& InputValue)
{
}
应用 InputMappingContext
下面为在 Character 中,先获取 PlayerController,再获取 UEnhancedInputLocalPlayerSubsystem,最后应用 InputMappingContent 的过程。其中:
- Priority:优先级,数字约大,优先级越高。
蓝图方式如下:
代码方式如下:现在类中定义需要处理 InputMappingContext,然后在 SetupPlayerInputComponent 进行绑定
UPROPERTY(EditDefaultsOnly, Category = Input)
TObjectPtr<UInputMappingContext> InputMappingContext;
#include "MyCharacter.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
// Called to bind functionality to input
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (APlayerController* PC = CastChecked<APlayerController>(GetController()))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
{
Subsystem->AddMappingContext(InputMappingContext, 100);
}
}
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
if (IA_LookUp)
{
EnhancedInputComponent->BindAction(IA_LookUp, ETriggerEvent::Triggered, this, &AMyCharacter::LookUp);
}
}
}
void AMyCharacter::LookUp(const FInputActionValue& InputValue)
{
}
UEnhancedInputComponent 是 UInputComponent 的子类。
另外:AddMappingContext 和 BindAction 的执行先后顺序没有限制,它是两个独立的过程
调试命令:ShowDebug enhancedinput,下面有更多的 Debug 命令。
核心概念
EnhancedPlayerlnput
存储按键映射:Key->InputAction
InputModifier
修改器和触发器可以在 Maping 和 InputAciton 中同时设置:
- Mapping.Modifiers / Triggers 针对当前 IMC 场景,和按键强相关的
- InputAction.Modifiers / Triggers 针对全局,不需要关心按键,主要关心值怎么动,处理逻辑相关的,
两个地方都可以配置 Triggers 和 Modifiers,上面两个是链式处理的。先经过 Maping 再经过 Action。
修改器有:
- DeadZone: 限定值的范围
- Scalar:缩放一个标量
- Negate:取反
- Smooth:多帧之间平滑
- CurveExponential:指数曲线,XYZ
- CurveUser:自定义指数曲线,CurveFloat
- FOVScaling: FOV缩放
- ToWorldSpace:输入设备坐标系向世界坐标系转换(调换XYZ顺序)
- SwizzleAxis:互换轴值
- Collection: 嵌套子修改器集合
比如:摇杆中的事件,为了防止误触,可以将较小的值约束为 0。
InputTrigger
- ETriggerEvent:ETriggerState发生转变时触发的事件,BindXXX的时候关注某个事件
- Down:值大于阈值(默认0.5)就触发
- Pressed:不激活到激活
- Released:激活到不激活
- Hold:按住大于某个时间
- HoldAndRelease:按住大于某个时间后松开
- Tap: 按下后快速抬起(默认0.2)
- Chorded: 根据别的Action联动触发
InputAction
回调参数可以为:
- FlnputActionValue:Action的值,XYZ,0/1。
- FlnputActionlnstance:Action的运行时状态。
在 C++ 中,回调方法可以为:
- void()
- vold(const FinputActionValue&)
- void(const FinputActioninstance&)
在蓝图(BP)中:
-
void(FlnputActionValue ActionValue, float Elapsed Time, float TriggeredTime)
Consume Input是否把事件吞噬掉,如果不吞噬的话,一个按键还是传送到之后的事件处理流程。
InputMappingContext
InputMappingContext 相当于一套当前的 Key->InputAction 的映射集合。多个IMC同时作用,高优先级的会先处理,如果没有则触发到低优先级的。高优先级的 Key 绑定会屏蔽低优先级的绑定。
EnhancedInput 处理流程
- 兼容 Input 处理
- 遍历 EnhancedActionMappings
- 通过 KeyStateMap 查询获取激活的 Action
- 应用 Mapping.Modifiers 修改值
- 应用 Mapping.Triggers 决定触发状态
- 应用 Action.Modifiers 修改值
- 应用 Action.Triggers 决定触发状态
- 遍历 InputStack 获取 EnhancedInputComponent 中的 Binding 回调
- 触发所有Delegates
注意:是先经过 Maping 修改,再经过 Action 修改。
AddMappingContext 流程
- InputMappingContext 会根据优先级排序
- IMC:Mappings 也会根据动作联动而优先级排序
- IMC:Mappings 会复制添加到 Playerlnput 的 EnhancedActionMappings里
EnhancedInputSubsystem
最佳实践
IMC BindAction
- 初始情况在应该在哪里开始应用 IMC
- 后续运行时在蓝图中应该如何切换 IMC
- 何时 Remove IMC?
- 在哪里绑定 Action 和 Axis 在蓝图中如何 BindAction
PlayerController IMC+BindAction
- C++ 中 SetupInputComponent 依然是最合适的位置
- BP 中可在 BeginPlay 后 Add(IMC)
- BP中可直接Add Action Event
Pawn IMC+BindAction
- C++ SetuplnputComponent依然合适
- BP中应在Possessed事件内Add(IMC)
- BP中可直接Add Action Event
事件绑定的位置是卸载 Controller 中?还是 Pawn 上?
- 和 Pawn 关联的 action 写在对应的 Pawn 上。
- 基础的操作可以写在 controller 上
controller 控制的 Pawn 切换之后,会调用 Pawn 对应的 OnPossess 方法,继而调用 SetupInputComponent 方法。
/**
* Called when this Pawn is possessed. Only called on the server (or in standalone).
* @param NewController The controller possessing this pawn
*/
virtual void PossessedBy(AController* NewController);
Pawn Remove(IMC)
- C++ UnPossessed 事件可移除自身 IMC
- BP 可在 Unpossessed 事件来移除自身 IMC
另外:PlayerController 的 IMC 不需要移除,因为PC一直在
IMC BindAction最佳实践
- 分层的 IMC 设计:基本输入(移动),武器/载具,行为/Buff
- Add(IMC,Priority),规划好 Priority 的值,IMC 的 Priorit 体现的是 Key 和 InputAction 之间的映射关系,但是找到 InputAction 之后,还是依然按照 InputStack 的顺序来处理。
- Pawn上可携带多个 IMC,但只 Apply 一个
- IMC 不一定跟 Pawn 绑定关联,可根据运行时逻辑灵活Add
- IMC 代表输入的逻辑处理环境,BindAction 代表输入事件该由谁来处理的职责
Debug
ConsoleCommands:
- Input.+action ActionName Value:强制添加某个Action的输入
- Input.-action ActionName:移除某Action的输入
- Input.+key Key Value:添加某Key的输入
- Input.-key keyName:移除某 Key 的输入
- ShowDebug Enhancedlnput:显示调试界面
Playerlnput::InputKey/Axis{}
UEnhancedPlayerlnput:::InjectlnputForAction{}
扩展
- 继承扩展 InputModifier 和 InputTrigger,实现自己的输入值链式修改逻辑,自己的触发逻辑
- 规划好灵活的IMC应用策略,可继承添加更多的信息
- 利用调试注入Key/Action可方便安排自己的测试输入序列
- EnhancedInput还在开发中,更多Todo改进