增强输入

Enhanced Input System 是对默认输入系统做了一个扩展,它以模块化的方式解耦了从输入的按键配置到事件处理的逻辑处理过程,提供了更灵活和更便利的输入配置和处理功能。同时又能向后兼容虚幻引擎 4 (UE4)中的默认输入系统。以插件的形式供我们使用。

重要的类:

  • UInputAction 输入操作,简称IA
    • 是交互角色可能做出的任何动作,独立于原始输入,可以设置多种类型的输入值
  • UInputMappingContext 输入映射上下文, 简称IMC
    • 将用户的输入映射到操作,并可以动态地为每个用户添加、移除或安排优先次序。通过**本地玩家子系统UEnhancedInputLocalPlayerSubsystem将一个或多个映射应用到本地玩家,并安排它们的优先次序,避免多个操作由于尝试使用同一输入而发生冲突
  • UInputModifier 输入修饰符。简称IM
    • 调整来自用户设备的原始输入的值,如使用 Negate 可以将输入值取负值。
  • UInputTrigeer 输入触发器。简称 IT
    • 根据特定的条件来确定是否应该激活输入操作。

增强输入系统优点

  • 统一案件绑定: Axis/Action —> Action(统一)

  • 运行时重新映射输入场景: UInputMappingContext

  • 对初级用户易配置。大量默认行为实现,Tap/Hold…

  • 对高级用户易扩展,可继承子类扩展

  • 修改器:修改输入值

  • 触发器:决定触发条件

  • 优先级:配置输入场景优先级

  • 模块化,不再只依赖 Ini 配置,以资源 asset 方式配置,堆栈式分隔逻辑

  • 提高性能,不需要检查所有

蓝图与 C++实现

可以参考引擎的模板 Character

创建 InputAction

Pasted image 20231204221456

创建 InputMappingContext

Pasted image 20231204221509 Pasted image 20231204221529

添加输入映射上下文

PlayerController 添加创建的输入映射上下文,可以添加多个上下文 !Pasted image 20231204221559

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class  ATestPlayerController : public APlayerController  
{
protected:
virtual void BeginPlay() override;

private:
UPROPERTY(EditDefaultsOnly, Category = "Maga_Input")
TObjectPtr<UInputMappingContext> InputMappingContext;
}

void ATestPlayerController::BeginPlay()
{
Super::BeginPlay();

//添加InputMappingContext
if (InputMappingContext)
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
{
Subsystem->AddMappingContext(InputMappingContext, 0);
}
}
}

使用 InputAction 事件

在蓝图中调用事件很简单:Pasted image 20231204221614|400

在 C++实现如下,需要将需要绑定的 InputAction 一一声明,然后在角色蓝图中设置调用:
Pasted image 20231204222600|550

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected:
//回调函数
/** Called for movement input */
void Move(const FInputActionValue& Value);
/** Called for looking input */
void Look(const FInputActionValue& Value);
private:
// 重载该函数
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

//声明InputAction
/** Jump Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* JumpAction;

/** Move Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* MoveAction;

/** Look Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* LookAction;

SetupPlayerInputComponent() 中绑定委托回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ATestPlayerController::SetupPlayerInputComponent()
{
Super::SetupInputComponent();

// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent)) {

//Jumping
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);

//Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AProjectGASRPGCharacter::Move);

//Looking
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AProjectGASRPGCharacter::Look);
}
}

单独对 Looking 的委托绑定进行解析,我们可以从中看出 C++和蓝图实现的关联 :

Pasted image 20231204221614

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Looking
EnhancedInputComponent->BindAction (LookAction, ETriggerEvent:: Triggered, this, &AProjectGASRPGCharacter::Look);

//回调
void AProjectGASRPGCharacter::Look(const FInputActionValue& Value)
{
// input is a Vector2D
FVector2D LookAxisVector = Value.Get<FVector2D>();

if (Controller != nullptr)
{
// add yaw and pitch input to controller
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}

BindAction 参数解析:

  1. LookAction:要绑定的 InputAction
  2. ETriggerEvent:: Triggered 对应蓝图节点的 Triggered
  3. 最后一个参数:触发 Triggered 时的回调函数。

回调函数解析:

  1. 通过 Value. Get<T> 输入值,类型是由 IA 中的 ValueType 指定
  2. 然后就可以根据输入值执行游戏逻辑了

增强输入用法:控制角色跳跃与移动

Input Action

定义一个 InputAction,等同于一个自定义事件,声明输出值类型、触发时机,Modifier 可用于调整输出值

新建 IA_JumpIA_Move

IA_Jump 定义 bool 输出值:

控制跳跃只需要一个 bool 值或浮点数(一个轴的值)即可
Pasted image 20231204223713
IA_Move 定义 2D 向量(两个轴的值)输出值,通过获取 x 轴与 y 轴值来分别控制左右移动与前后移动
Pasted image 20231204223755
也可以通过定义两个浮点数的 InputAction 来分别控制左右移动与前后移动

Input Mapping Context (IMC)

IMC 定义 IC 与按键的绑定关系,实现按键触发动作事件
在 IMC 中也可以添加约束:触发器、修改器,和 IC 中的触发器、修改器会叠加生效,下面会详细说明

  • 负责将用户的输入映射成输入操作,与按键强相关。
  • 可以根据用户需求动态增减或调整优先级,要用分层、堆栈的思想看待 IMC。
  • 应用场景:某个按钮既可以用来打开场景中的门,又可以用来拾取背包中的道具。
  • 通过 InputMappingContexts,当角色靠近门时,你可以添加 “开门” 这个上下文。假如角色打开背包,你可以添加 “选择道具” 这个输入映射上下文,并且让它的优先级高于 “开门”。
  • 可以通过增强输入本地玩家子系统(Enhanced Input Local Player Subsystem)将一个或多个上下文应用到本地玩家身上,并调整它们的优先级
  • 两种方式屏蔽某个动作:1. 优先级 2. 配 key 不给 Action

新建 IMC_Default

空格控制跳跃,AD 控制左右移动,WS 控制前后移动
Pasted image 20231204224015

Pasted image 20231204224109
Pasted image 20231204224125

角色蓝图

角色蓝图添加动作映射:

通过 Enhanced Input Local Player Subsystem,将玩家控制器与 IMC 绑定,绑定后 IMC 能够获取到玩家控制器的输入。IMC 再根据输入按键匹配 IA ,触发回调。
蓝图可以添加多个 IMC,priority 高的起作用
Pasted image 20231204224529
蓝图添加按键监听:
事件名即 IA 文件名,在 IMC 中一一对应
Pasted image 20231204224542
Pasted image 20231204224556

IA_Move 的输出值是一个二维向量,可以拆分成 x 轴与 y 轴: Action ValueXAction Value Y

按键 D 声明获取默认值,输出值为 x=1, y=0 的二维向量

按键 A 声明获取相反值,输出值为 x=-1, y=0 的二维向量

按键 W 声明交换输入轴的值,默认的 XYZ,将交换后输出 YXZ,事件回调输出值为 x=0, y=1 的二维向量

按键 S 声明交换输入轴的相反值,默认的 XYZ,将交换后输出 YXZ,事件回调输出值为 x=0, y=-1 的二维向量

对于 EnhancedInputAction IA_move 事件而言,它负责监听按键,当按键输入与 IMC 表上声明的按键匹配,事件触发,在回调中返回输出值,输出 IC 中定义好的值。

优化角色移动:
上下旋转镜头后,角色移动会变慢,因为角色正前方的向量被分解了
所以只获取正前方的向量值

bb20d7c7b1e3d6be4fd1d9f2662b0264_MD5

旋转镜头角色不跟着旋转:

ea15f9b3d47278684df4d423280fdef5_MD5

use controller rotation yaw 要取消勾选

cbd1edd0b46cd2e696ba290f0d2635b8_MD5

补充:镜头旋转

IA_Look:
Pasted image 20231204225029
通过 2D 轴值输出 XY 轴向数值

IMC_Default:

Y 轴值取反,使镜头上下旋转的方向与鼠标方向一直
Pasted image 20231204225054
角色蓝图,X 轴控制左右旋转,Y 轴控制上下旋转
Pasted image 20231204225106

设置机械臂:
允许鼠标进行旋转控制,否则上下旋转不生效
Pasted image 20231204225219

解析

新建 IA_test 用于测试

IMC:

45070408f63daefe72f01a86b4055b56_MD5

角色蓝图:

查看在事件开始、结束与触发过程中的输出值

InputAction

Pasted image 20231204225604
Cosume Input:这个操作应该吞下绑定到它的任何输入,还是允许它们通过来影响低优先级绑定操作?
Trigger When Paused:是否可以在游戏暂停时触发
Reserve All Mappings:此操作的映射不会被高优先级上下文映射自动重载。用户必须首先显式移除映射。注意∶这由映射代码作者负责强制执行!

Value Type

Value Type对应事件的返回值类型

Value Type / Time Triggered Started Ongoing Canceled Completed
Digital (bool) true true false
Axis1D (float) 1 1 0
Axis2D (vector2D) (1,0) (1,0) (0,0)
Axis3D (Vector) (1,0,0) (1,0,0) (0,0,0)

InputModifier

调整来自用户设备的原始输入的值

Pasted image 20231204231757

  • Negate:轴值取反
  • SwizzleInputAxisValues:互换轴值,当输入值为两个轴以上的时候,可以将输出值顺序从 XYZ 调整为其他顺序 (比如 XYZ 调整为 YXZ,那么输出的 Vector 值从(x: 1,y: 2,z:3)变为 (x: 2, y: 1, z:3))),

    输出值若为一个轴或 bool 值,将无法获得输出值

  • Scalar:缩放

注意:此处对修改器的顺序有要求,对调轴值后,输出值在 Y 轴,此时对 Y 轴进行缩放才有效,否则无效 341f7a0a2b7e8fb20092c3522e0495c8_MD5

其他的修改器有(多与摇杆有关,具体可向 GPT 提问):

  • DeadZone: 限定触发值的范围
  • FOVScaling: FOV 缩放
  • CurveExponential:指数曲线,XYZ
  • CurveUser:自定义指数曲线,CurveFloat
  • Smooth:多帧之间平滑
  • ToWorldSpace:输入设备坐标系向世界坐标系转换

当将修改器设置为 ToWorldSpace 时,输入设备的输入值将被转换为世界空间(World Space)中的向量,而不是相对于游戏角色的本地空间(Local Space)中的向量。这意味着,无论游戏角色朝向何方,输入设备的输入值都将产生相同的效果,从而减少了玩家在控制角色时需要考虑本地坐标系的复杂性。

举例来说,如果你将一个摇杆的输入值(X,Y)转换为世界空间中的向量,那么这个向量的方向将与摇杆的偏移方向相同,但是大小将根据摇杆的偏移量来确定。这个向量的大小可以通过修改器的 Scale 属性进行缩放,从而控制游戏角色的移动速度。

需要注意的是,将修改器设置为 ToWorldSpace 可能会对游戏的响应速度产生一定的影响,因为它需要对输入值进行矩阵变换以将其从本地空间转换到世界空间。同时,ToWorldSpace 的效果也可能会因不同的游戏和输入设备而有所不同。

IMC 与 IA 可同时设置 IM、IT

修改器和触发器可以在 Maping 和 InputAciton 中同时设置:

  • Mapping. Modifiers / Triggers :针对当前 IMC 场景,和按键强相关的

  • InputAction. Modifiers / Triggers: 针对全局,不需要关心按键,主要关心值怎么动,处理逻辑相关的,

  • 执行顺序:先 IMC 后 IC

InputTrigger

Pasted image 20231204232648
默认使用的是 Down 类型

  1. 按下:触发 Started
  2. 保持:不断触发 Triggered
  3. 松开:触发 Completed
  • Down:可用于机枪的连续射击,Started 开始射击,Triggered 检查子弹数量,Completed 停止射击,

    1. 按下:触发 Started
    2. 保持:不断触发 Triggered
    3. 松开:触发 Completed
  • Pressed

    1. 按下:依次触发 Started、Triggered 与 Completed
    2. 保持:无
    3. 松开:无
  • Hold

    • Hold Time Threshold:保持多久触发 Triggered 事件
    • Affected by Time DilationHold Time Threshold 是否受全局时间膨胀影响
    • IsOneShot:只触发一次,还是每帧触发?
      1. IsOneShot 没有勾选,每帧触发:可用于按下保持一段时间,松开才会触发的动作。如吃鸡里手雷投掷。-
        • 按下:触发 Started
        • 保持:不断触发 Ongoing,超过 Hold Time Threshold 后不断触发 Triggered
        • 松开:如果保持的时间达到或超过 HoldTimeThreshold 设置的时间则触发 Completed,否则触发 Canceled。
      2. IsOneShot 勾选:用于按下保持一段时间后自动触发的动作,如 CS 里拆包
      • 按下:触发 Started
      • 保持:不断触发 Ongoing
      • 没有达到 HoldTimeThreshold 提前松开:触发 Canceled。
      • 达到 HoldTimeThreshold :自动依次触发 Triggered 与 Completed。
  • Release

    1. 按下时触发 Started 事件
    2. 保持按下触发 Ongoing 事件
    3. 松开时依次触发 Triggered 与 Completed
  • HoldAndRelease

    1. 按下:触发 Started
    2. 保持:不断触发 Ongoing
    3. 松开:时如果保持的时间达到 HoldTimeThreshold 设置的时间会依次触发 Triggered 与 Completed,否则触发 Canceled。
  • Pulse:用于保持按下时每隔一段时间触发一次的动作。如按下吃药键,每隔一段时间执行一次吃药的动作

    1. 按下:触发 Started 事件
    2. 如果勾选 TriggerOnStart 则立即触发一次 Triggered 事件,不勾选则间隔一段时间后触发 Triggered。
    3. 保持:触发 Ongoing 事件。
    4. 距上一次 Trigger 的时间间隔达到 Interval 设置的时间触发一次 Triggered 事件,随后判断 Trigger 触发的数量是否达到 TriggerLimit 的限制 (TriggerLimit<=0 表示不限制次数),未达到则继续上一步,否则触发 Completed。
  • Tap 按下后快速抬起(默认 0.2):

    1. 按下按键触发 Started 事件
    2. 保持按下触发 Ongoing 事件
    3. 如果保持的时间超过 TapReleaseTimeThreshold 设置的时间触发 Canceled 事件。
    4. 松开按键时保持的时间小于 TapReleaseTimeThreshold 设置的时间依次触发 Triggered 与 Completed 事件。
  • Chorded Action:用于多个 Action 的串联形成组合键Pasted image 20231205001042按键 1 的 IA 绑定了两个铉操作(按键设置分别为2、3),我们只讨论该 IA 的触发逻辑(绑定的两个铉操作也会执行自己的触发逻辑,这里忽略)
    1. 只按 1:无
    2. 按 1+2 或 1+3:
    - 按下:触发 started
    - 保持:触发 Ongoing
    - 松开:触发 Canceled
    3. 按 1+2+3:
    - 按下:触发 started
    - 保持:触发 Triggered
    - 松开 1:触发 completed
    - 松开 2 或 3 其中之一:触发 Ongoing
    - 松开 2+3,触发Cancel

  • Combo:用于类似上上下下左右左右 BABA 在短时间内完成的组合键。

    1. 按下第一个键触发 Started
    2. 过程当中不断触发 Ongoing
    3. 如果某个键按下的时间没有达到 TimeToPressKey 或者按错键触发 Canceled
    4. 当所有的键完成后依次触发 Triggered 与 Completed

IT 设置 Combo 后,不需要按键触发,是通过 IC 组合来触发 示例:按下 AABBCC 触发: 696599e5b2744eed75358f0502fb2cd6_MD5

Debug

命令行输入

showdebug enhancedinput

最佳实践

[中文直播]第39期 | 虎跳龙拿–新一代增强输入框架EnhancedInput | Epic 大钊_哔哩哔哩_bilibili

IMC BindAction

  • 初始情况应该在哪里开始应用 IMC
  • 后续运行时在蓝图中如何切换 IMC
  • 何时 Remove IMC
  • 在哪里绑定 Action 和 Axis
  • 在蓝图中如何 BindAction

5.1 初始情况应该在哪里开始应用 IMC

a93e70713bed461536d8637dfab3cc72_MD5 8d7093faf0d99c274fe21061608f806e_MD5

5.2 何时 Remove IMC

2ceac629710a76ed056109a05fdafff3_MD5

5.3 IMC BindAction 最佳实践

9a07fc2e0aa7f362e823dd4dbc51521c_MD5

5.4 Debug

fdb5047e04e6bdb5906ca5db58b6d52c_MD5

默认输入系统

轴映射与动作映射

轴映射每帧执行,动作映射一次性执行 Pasted image 20230904135119 Pasted image 20230904204143 c89952d2bf5eee78e9a758b738322967_MD5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)  
{
Super::SetupPlayerInputComponent(PlayerInputComponent);

PlayerInputComponent->BindAction("DropItem", EInputEvent::IE_Pressed, this, &AMyCharacter::DropItem);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMyCharacter::Jump);
PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);
PlayerInputComponent->BindAxis("PitchCamera", this, &AMyCharacter::PitchCamera);
PlayerInputComponent->BindAxis("YawCamera", this, &AMyCharacter::YawCamera);
}

void AMyCharacter::MoveForward(float AxisValue)
{
MovementInput.X = FMath::Clamp<float>(AxisValue, -1.f, 1.f);
}

void AMyCharacter::MoveRight(float AxisValue)
{
MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.f, 1.f);
}

void AMyCharacter::PitchCamera(float AxisValue)
{
CameraInput.Y = AxisValue;
}

void AMyCharacter::YawCamera(float AxisValue)
{
CameraInput.X = AxisValue;
}
  • 设置按键再游戏暂停可以继续响应 bExecuteWhenPaused
1
InputComponent->BindAction("ESCEvent", IE_Pressed, this, &ASLAiPlayerController::ESCEvent).bExecuteWhenPaused=true;//游戏暂停可以执行

从C++中添加轴和动作映射

1
2
3
4
5
6
7
8
9
10
11
12
13
//添加、绑定ActionKeyMapping轴映射 方法一  
FInputActionKeyMapping onFire("OnFire", EKeys::LeftMouseButton, 0, 0, 0, 0);
UPlayerInput::AddEngineDefinedActionMapping(onFire);
PlayerInputComponent->BindAction("OnFire", IE_Pressed, this, &AMyCharacter::OnFire);

//添加、绑定ActionKeyMapping轴映射 方法二
UPlayerInput::AddEngineDefinedActionMapping(FInputActionKeyMapping("Sprint",EKeys::LeftShift));
PlayerInputComponent->BindAction("Sprint", IE_Pressed,this,&AMyCharacter::StartSprint);
PlayerInputComponent->BindAction("Sprint", IE_Released,this,&AMyCharacter::StopSprint);

//添加、绑定AxisMapping轴映射
UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("Turn", EKeys::MouseX, 1.0f));
PlayerInputComponent->BindAxis("Turn", this, &AMyCharacter::OnTurn);