跳至主要內容

c4.2Subsystem|EnhancedInput

Mr.Si大约 9 分钟u++

导读

头像

食用本文建议先了解什么是子系统!直通车

EnhancedInput|增强输入系统

头像
增强输入系统也是一个子系统,这里我们只负责记录一些子系统实践。

1.插件

头像
启用插件,UE5.1后已经内置

2.项目设置

头像
编辑(Edit)——>项目设置(Project Settings)——>按下图设置

3.输入动作(Input Actions)

头像
输入动作是系统和你的项目代码之间的连接。
头像
你这解释有点官腔啊!
头像
没办法官方文档就是这么写的,具体C++部分再来细讲吧,这里你理解成一些行为和动作,比如跑步、走路、开火等。
头像
这样,我们就可以在蓝图中找到对应的动作节点了!

4. 输入映射上下文(Input Mapping Contexts)

头像
有问题!你这里只是一些函数事件啊!按键呢按键去哪了?
头像
问的好,这些确实只是一些函数。我们需要使用Input Mapping Contexts将这些函数绑定到对应的触发按键。
头像
打开后配置对应的动作和按键就行了!

历史问题

头像
这和传统的绑定方案有什么区别啊?怎么感觉更复杂了?
头像
别急,我们康康传统绑定方法有哪些弱项:
  • 复杂的输入机制无法满足。
  • 过于简陋,例如按住、双击、联合输入等都需要用户自己实现。
  • 性能不足,需要自己写切换逻辑,不同情况需要自己判断优先级。

5. 触发状态(Trigger State)

头像
可是你这节点确实多出来好几个!能解释一下具体作用吗?
头像
触发状态(Trigger State) 表示动作的当前状态,我们还是拿源码解释一下吧
原版
enum class ETriggerEvent : uint8
{
	// No significant trigger state changes occurred and there are no active device inputs
	None		= (0x0)		UMETA(Hidden),

	// Triggering occurred after one or more processing ticks
	Triggered	= (1 << 0),	// ETriggerState (None -> Triggered, Ongoing -> Triggered, Triggered -> Triggered)
	
	// An event has occurred that has begun Trigger evaluation. Note: Triggered may also occur this frame.
	Started		= (1 << 1),	// ETriggerState (None -> Ongoing, None -> Triggered)

	// Triggering is still being processed
	Ongoing		= (1 << 2),	// ETriggerState (Ongoing -> Ongoing)

	// Triggering has been canceled
	Canceled	= (1 << 3),	// ETriggerState (Ongoing -> None)

	// The trigger state has transitioned from Triggered to None this frame, i.e. Triggering has finished.
	// NOTE: Using this event restricts you to one set of triggers for Started/Completed events. You may prefer two actions, each with its own trigger rules.
	// TODO: Completed will not fire if any trigger reports Ongoing on the same frame, but both should fire. e.g. Tick 2 of Hold (= Ongoing) + Pressed (= None) combo will raise Ongoing event only.
	Completed	= (1 << 4),	// ETriggerState (Triggered -> None)
};

6. 修饰符(Modifiers)

头像
不对啊!你这里只有一个IA_Move动作,可我明明需要WASD控制上下左右移动啊,难道不应该有4个Input Actions?
头像
这里需要引入一个Modifiers的概念,即同个动作在不同修饰函数状态下得到不同的结果。
头像
难怪一个IA_Move下有这么多Modifiers修饰的按键。

增强输入支持来自一维源的输入,例如键盘的方向键或常用的"WASD"键配置;可通过应用正确的输入修饰符来实现此控制方案。 具体而言,使用 负(Negate) 可以将某些键注册为负值, 而使用 交换输入轴值(Swizzle Input Axis Values) 可以将某些键注册为Y轴,而不是默认的X轴值:

7. 触发器(Triggers)

头像
我发现除了Modifiers,为什么上面还有一个叫Triggers的东西?
头像
先拿官方的图糊弄你一下!后面源码解析会有详细解释

8.绑定上下文

头像
哎!我才反应过来!这里只有动作事件和按键上下文绑定啊!我运行后没反应啊!
头像
这些上下文目前来说只是数据,别忘记咱的主角EnhancedInput子系统啊!
头像

子系统中的成员函数中有个叫AddMappingContext成员函数可以将这些数据传递进去注册。

头像
看来咱不康康源码是没法再进一步理解了!

快速上手

1. 插件

头像
老规矩先把插件开起来

2. Build.cs引入模块

PrivateDependencyModuleNames.AddRange(new string[] { "EnhancedInput" });

3. 准备Character

你也可以直接用第三人称模板作为参考,这里主要是康康基本流程为后续源码展开做一个铺垫

4. 引入头文件

MyCharacter.h
#include "InputActionValue.h"
头像
为什么不在.h中引入全部呢?
头像
原则上除了编译时间以外没有区别,主要是防止交叉引用问题。

5.子系统注册上下文

MyCharacter.h
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputMappingContext* DefaultMappingContext;

6.绑定InputAction

MyCharacter.h

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

    // APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	/** Called for movement input */
	void Move(const FInputActionValue& Value);

头像
我怎么感觉绑定的过程像委托呢?
//Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ATP_ThirdPersonCharacter::Move);
头像
你的直觉是对的!源码如下:
/**
* Binds to an object UFUNCTION
*/
FEnhancedInputActionEventBinding& BindAction(const UInputAction* Action, ETriggerEvent TriggerEvent, UObject* Object, FName FunctionName)
{
	TUniquePtr<FEnhancedInputActionEventDelegateBinding<FEnhancedInputActionHandlerDynamicSignature>> AB = MakeUnique<FEnhancedInputActionEventDelegateBinding<FEnhancedInputActionHandlerDynamicSignature>>(Action, TriggerEvent);
	AB->Delegate.BindDelegate(Object, FunctionName);
	AB->Delegate.SetShouldFireWithEditorScriptGuard(bShouldFireDelegatesInEditor);
	return *EnhancedActionEventBindings.Add_GetRef(MoveTemp(AB));
}

7.Triggers是什么?

头像
还记得之前糊弄你的Triggers图吗?现在给你康康他的庐山真面目。

先看源码

InputAction.h原文
/**
* Trigger qualifiers. If any trigger qualifiers exist the action will not trigger unless:
* At least one Explicit trigger in this list has been met.
* All Implicit triggers in this list are met.
*/
UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = Action)
TArray<TObjectPtr<UInputTrigger>> Triggers;
头像
从源文件中可以看到ETriggerEvent只是InputTrigger类中的一个枚举。
头像
接着配置的具体Trigger是继承自InputTrigger的子类或者孙类。
头像
这么说可能不够直观,我给你做个类图理解一下
头像
搜嘎!也就是这里的设置trigger其实就是切换不同版本的InputTrigger?
头像
是的,切换到对应的继承版本会有对应的效果。你不看源码根本不能理解这个trigger。
头像
这里为什么有个final关键字?

在C++中,关键字 final 用于表示某个类不能被其他类继承。例子中,UInputTriggerDown 类声明为 final,这意味着它是不可继承的,不能再派生出其他类。

  1. 性能优化: 编译器可以对 final 类进行更多的优化,因为它知道没有其他类会继承它。
  2. 代码安全性: 防止其他开发者错误地继承并修改该类的行为,从而确保该类的稳定性。

8.系统流程

头像
现在我们大致理解一下执行流程,不要求记住。

9.子系统

头像
最后请出我们的子系统。
UEnhancedInputLocalPlayerSubsystem : public ULocalPlayerSubsystem, public IEnhancedInputSubsystemInterface
UEnhancedInputWorldSubsystem : public UWorldSubsystem, public IEnhancedInputSubsystemInterface

Debug

showdebug enhancedinput

参考资料

UE5学习笔记|增强输入系统EnhancedInput_鹿野明的博客-CSDN博客open in new window

虚幻引擎中的增强输入 | 虚幻引擎5.1文档(unrealengine.com)open in new window

UE5 C++ Enhanced Input - 2 - Bind C++ Functions to Input Actions - YouTubeopen in new window