279 lines
12 KiB
Markdown
279 lines
12 KiB
Markdown
---
|
||
title: Untitled
|
||
date: 2025-08-27 17:03:13
|
||
excerpt:
|
||
tags:
|
||
rating: ⭐
|
||
---
|
||
# 前言
|
||
- 官方文档
|
||
- [Enhanced Input](https://dev.epicgames.com/documentation/en-us/unreal-engine/enhanced-input-in-unreal-engine?application_version=5.5)
|
||
- [Using CommonUI With Enhanced Input](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-commonui-with-enhnaced-input-in-unreal-engine?application_version=5.5)
|
||
- 知乎文章
|
||
- [UE5 -- Lyra中的输入模块(Input)](https://zhuanlan.zhihu.com/p/537949870)
|
||
- [UE5 Lyra的多模态输入和修改灵敏度配置方案](https://zhuanlan.zhihu.com/p/30566880169)
|
||
|
||
## 调试命令
|
||
- showdebug enhancedinput:会显示你的项目的可用输入动作和轴映射。
|
||
- showdebug devices
|
||
|
||
# 相关类
|
||
- **Input Actions**:配置输入动作 => 数据或者状态用的资产。
|
||
- **Input Mapping Contexts**:输入Action的集合。可以为不同GameMode配置不同的 **Input Mapping Contexts**来满足自定义输入的需求。
|
||
- **Input Modifiers**: 是一种预处理器,能够修改UE接收的原始输入值,然后再将其发送给输入触发器(Input Trigger)。增强输入插件随附多种输入修饰器,可以执行各种任务,例如更改轴顺序、实现"死区"、将轴输入转换为世界空间。
|
||
- **Input Triggers**:用于确定用户输入在经过一系列可选输入修饰器的处理后,是否会激活输入映射上下文中的相应输入动作。
|
||
- 多平台相关:位于Project Settings -> Enhanced Input -> Platform Settings -> Input Data
|
||
- Mapping Context Redirect:将不同的输入设置用于不同的平台。
|
||
- Enhanced Input Platform Data:为你的游戏添加特定于平台的选项。
|
||
|
||
**InputAction**绑定:
|
||
```c++
|
||
void AFooBar::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||
{
|
||
UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PlayerInputComponent);
|
||
// 你可以通过更改"ETriggerEvent"枚举值,绑定到此处的任意触发器事件
|
||
Input->BindAction(AimingInputAction, ETriggerEvent::Triggered, this, &AFooBar::SomeCallbackFunc);
|
||
}
|
||
|
||
void AFooBar::SomeCallbackFunc(const FInputActionInstance& Instance)
|
||
{
|
||
// 获取此处所需任意类型的输入动作的值...
|
||
FVector VectorValue = Instance.GetValue().Get<FVector>();
|
||
FVector2D 2DAxisValue = Instance.GetValue().Get<FVector2D>();
|
||
float FloatValue = Instance.GetValue().Get<float>();
|
||
bool BoolValue = Instance.GetValue().Get<bool>();
|
||
|
||
// 在此处实现你的精彩功能!
|
||
}
|
||
```
|
||
|
||
Runtime **InputMappingContext**添加:
|
||
```c++
|
||
// 将映射上下文公开为头文件中的属性...
|
||
UPROPERTY(EditAnywhere, Category="Input")
|
||
TSoftObjectPtr<UInputMappingContext> InputMapping;
|
||
|
||
// 在你的cpp中...
|
||
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player))
|
||
{
|
||
if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
|
||
{
|
||
if (!InputMapping.IsNull())
|
||
{
|
||
InputSystem->AddMappingContext(InputMapping.LoadSynchronous(), Priority);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Input Trigger Timed Base** 会检查输入是否已被按住一段时间,如是,则接受该输入并返回 **持续(Ongoing)** 状态。
|
||
```c++
|
||
/** UInputTriggerHold
|
||
触发器会在输入保持激活状态达到HoldTimeThreshold秒之后触发。
|
||
触发器可以选择触发一次或反复触发。
|
||
*/
|
||
UCLASS(NotBlueprintable, MinimalAPI, meta = (DisplayName = "Hold"))
|
||
class UInputTriggerHold final : public UInputTriggerTimedBase
|
||
{
|
||
GENERATED_BODY()
|
||
|
||
bool bTriggered = false;
|
||
|
||
protected:
|
||
|
||
virtual ETriggerState UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime) override;
|
||
|
||
public:
|
||
virtual ETriggerEventsSupported GetSupportedTriggerEvents() const override { return ETriggerEventsSupported::Ongoing; }
|
||
|
||
// 输入要保持多久才能导致触发?
|
||
UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings", meta = (ClampMin = "0"))
|
||
float HoldTimeThreshold = 1.0f;
|
||
|
||
// 此触发器应该仅触发一次,还是在满足保持时间阈值之后每帧触发?
|
||
UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings")
|
||
bool bIsOneShot = false;
|
||
|
||
virtual FString GetDebugState() const override { return HeldDuration ? FString::Printf(TEXT("Hold:%.2f/%.2f"), HeldDuration, HoldTimeThreshold) : FString(); }
|
||
};
|
||
|
||
ETriggerState UInputTriggerHold::UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime)
|
||
{
|
||
// 更新HeldDuration并派生基础状态
|
||
ETriggerState State = Super::UpdateState_Implementation(PlayerInput, ModifiedValue, DeltaTime);
|
||
|
||
// 在HeldDuration达到阈值时触发
|
||
bool bIsFirstTrigger = !bTriggered;
|
||
bTriggered = HeldDuration >= HoldTimeThreshold;
|
||
if (bTriggered)
|
||
{
|
||
return (bIsFirstTrigger || !bIsOneShot) ? ETriggerState::Triggered : ETriggerState::None;
|
||
}
|
||
|
||
return State;
|
||
}
|
||
```
|
||
|
||
# 按键设置
|
||
|
||
# Lyra中的相关实现
|
||
|
||
## ULyraHeroComponent中的输入绑定
|
||
```c++
|
||
void ULyraHeroComponent::InitializePlayerInput(UInputComponent* PlayerInputComponent)
|
||
{
|
||
check(PlayerInputComponent);
|
||
|
||
const APawn* Pawn = GetPawn<APawn>();
|
||
if (!Pawn)
|
||
{
|
||
return;
|
||
}
|
||
|
||
const APlayerController* PC = GetController<APlayerController>();
|
||
check(PC);
|
||
|
||
const ULyraLocalPlayer* LP = Cast<ULyraLocalPlayer>(PC->GetLocalPlayer());
|
||
check(LP);
|
||
|
||
UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
||
check(Subsystem);
|
||
|
||
Subsystem->ClearAllMappings();
|
||
|
||
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
||
{
|
||
if (const ULyraPawnData* PawnData = PawnExtComp->GetPawnData<ULyraPawnData>())
|
||
{
|
||
//获取ULyraInputConfig DataAesset,存储TArray<FLyraInputAction>,FLyraInputAction里为InputAction、GameplayTag。
|
||
if (const ULyraInputConfig* InputConfig = PawnData->InputConfig)
|
||
{
|
||
for (const FInputMappingContextAndPriority& Mapping : DefaultInputMappings)
|
||
{
|
||
if (UInputMappingContext* IMC = Mapping.InputMapping.Get())
|
||
{
|
||
if (Mapping.bRegisterWithSettings)
|
||
{
|
||
//将IMC注册给UEnhancedInputUserSettings
|
||
if (UEnhancedInputUserSettings* Settings = Subsystem->GetUserSettings())
|
||
{
|
||
Settings->RegisterInputMappingContext(IMC);
|
||
}
|
||
|
||
FModifyContextOptions Options = {};
|
||
Options.bIgnoreAllPressedKeysUntilRelease = false;
|
||
// Actually add the config to the local player
|
||
//实际添加IMC给LocalPlayer
|
||
Subsystem->AddMappingContext(IMC, Mapping.Priority, Options);
|
||
}
|
||
}
|
||
}
|
||
|
||
// The Lyra Input Component has some additional functions to map Gameplay Tags to an Input Action.
|
||
// If you want this functionality but still want to change your input component class, make it a subclass
|
||
// of the ULyraInputComponent or modify this component accordingly.
|
||
ULyraInputComponent* LyraIC = Cast<ULyraInputComponent>(PlayerInputComponent);
|
||
if (ensureMsgf(LyraIC, TEXT("Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it.")))
|
||
{
|
||
// Add the key mappings that may have been set by the player
|
||
LyraIC->AddInputMappings(InputConfig, Subsystem);
|
||
|
||
// This is where we actually bind and input action to a gameplay tag, which means that Gameplay Ability Blueprints will
|
||
// be triggered directly by these input actions Triggered events.
|
||
TArray<uint32> BindHandles;
|
||
LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);
|
||
|
||
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false);
|
||
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false);
|
||
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false);
|
||
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false);
|
||
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (ensure(!bReadyToBindInputs))
|
||
{
|
||
bReadyToBindInputs = true;
|
||
}
|
||
|
||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APlayerController*>(PC), NAME_BindInputsNow);
|
||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APawn*>(Pawn), NAME_BindInputsNow);
|
||
}
|
||
```
|
||
|
||
# 按键设置与保存
|
||
- [Player Mappable Keys using Enhanced Input](https://dev.epicgames.com/community/learning/tutorials/Vp69/unreal-engine-player-mappable-keys-using-enhanced-input)
|
||
|
||
## 相关类
|
||
- **UEnhancedInputUserSettings** (~~PlayerMappableInputConfig~~): 用户输入设置类。通过EnhancedInputLocalPlayerSubsystem->GetEnhancedInputUserSettings()获取。
|
||
- UEnhancedPlayerMappableKeyProfile:一个用户当前设置的实例。比如“默认”与“左撇子"。
|
||
|
||
## 步骤
|
||
1. 假设已经配置好了IMC以及IA。
|
||
1. 在Controller/Character中注册IMC与IA?
|
||
2. AddMappingContext()的选项OptionsNotifyUserSettings设置为true,会调用UEnhancedInputUserSettings::RegisterInputMappingContext()
|
||
2. ProjectSettings - Engine - EnhancedInput - UserSettings中的Enable User Settings
|
||
3. 配置IA中的UserSetting选项。
|
||
1. 1
|
||
|
||
## 加载按键设置逻辑
|
||
默认自动加载,载入逻辑位于UEnhancedInputUserSettings::LoadOrCreateSettings(),调用顺序是:
|
||
UEnhancedInputLocalPlayerSubsystem::Initialize
|
||
->
|
||
UEnhancedInputLocalPlayerSubsystem::InitalizeUserSettings()
|
||
->
|
||
UEnhancedInputUserSettings::LoadOrCreateSettings()
|
||
|
||
```c++
|
||
UEnhancedInputUserSettings* UEnhancedInputUserSettings::LoadOrCreateSettings(ULocalPlayer* LocalPlayer)
|
||
{
|
||
UEnhancedInputUserSettings* Settings = nullptr;
|
||
|
||
if (!LocalPlayer)
|
||
{
|
||
UE_LOG(LogEnhancedInput, Log, TEXT("Unable to determine an owning Local Player for the given Enhanced Player Input object"));
|
||
return nullptr;
|
||
}
|
||
|
||
// If the save game exists, load it.
|
||
if (UGameplayStatics::DoesSaveGameExist(UE::EnhancedInput::SETTINGS_SLOT_NAME, LocalPlayer->GetLocalPlayerIndex()))
|
||
{
|
||
USaveGame* Slot = UGameplayStatics::LoadGameFromSlot(UE::EnhancedInput::SETTINGS_SLOT_NAME, LocalPlayer->GetLocalPlayerIndex());
|
||
Settings = Cast<UEnhancedInputUserSettings>(Slot);
|
||
}
|
||
|
||
// If there is no settings save game object, then we can create on
|
||
// based on the class type set in the developer settings
|
||
const UEnhancedInputDeveloperSettings* DevSettings = GetDefault<UEnhancedInputDeveloperSettings>();
|
||
UClass* SettingsClass = DevSettings->UserSettingsClass ? DevSettings->UserSettingsClass.Get() : UEnhancedInputUserSettings::StaticClass();
|
||
|
||
// This property is marked as "NoClear", so this should be impossible.
|
||
if (!ensureMsgf(SettingsClass, TEXT("Invalid Enhanced Input User settings class!")))
|
||
{
|
||
return nullptr;
|
||
}
|
||
|
||
UE_CLOG((Settings && (Settings->GetClass() != SettingsClass)),
|
||
LogEnhancedInput,
|
||
Log,
|
||
TEXT("[%hs] Enhanced Input User Settings class has been changed from '%s' to '%s'. A new settings save object will be created (your saved settings will be reset)"),
|
||
__func__, *GetNameSafe(Settings->GetClass()), *GetNameSafe(SettingsClass));
|
||
|
||
// If the settings are null (they dont exist yet) or the class has changed, we need to create a new object.
|
||
// The class can change if you modify it in the editor to be something else
|
||
if (Settings == nullptr || (Settings->GetClass() != SettingsClass))
|
||
{
|
||
Settings = Cast<UEnhancedInputUserSettings>(UGameplayStatics::CreateSaveGameObject(SettingsClass));
|
||
}
|
||
|
||
if (ensure(Settings))
|
||
{
|
||
Settings->Initialize(LocalPlayer);
|
||
Settings->ApplySettings();
|
||
}
|
||
|
||
return Settings;
|
||
}
|
||
``` |