下面的内容為 [中文直播]第39期 | 虎跳龍拿–新一代增強輸入架構EnhancedInput | Epic 大钊 的學習筆記,大量的内容來自視訊中的。
增強輸入系統(Enhanced Input System)是對預設輸入系統做了一個擴充,通過子產品化的方式,解耦了從輸入的按鍵配置到事件處理的邏輯處理過程,提供了更靈活、便利的輸入配置和處理功能,同時又能向後相容虛幻引擎4(UE4)中的預設輸入系統。
問題由來
目前的系統輸入的流程
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SZzEmN5YzYlJzN0EmZ4QGM0ADO3Q2MxMWNxAzMwETY28CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
舊系統問題
- 舊系統實作基礎的功能比較簡單,但在想建構更複雜機制上就得需要在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
Unreal Engine 增強輸入架構 EnhancedInput問題由來基礎用法核心概念EnhancedInputSubsystem最佳實踐參考
- 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改進
參考
- UE5 – EnhancedInput(增強輸入系統)
- [中文直播]第39期 | 虎跳龍拿–新一代增強輸入架構EnhancedInput | Epic 大钊