Files
BlueRoseNote/03-UnrealEngine/Gameplay/Lyra/UE5 Lyra学习笔记(8)—EnhanceInput(UE5.5).md
2025-08-31 23:34:27 +08:00

14 KiB
Raw Permalink Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
Untitled 2025-08-27 17:03:13

前言

调试命令

  • 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绑定:

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添加:

// 将映射上下文公开为头文件中的属性...
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 状态。

/** 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中的输入绑定

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);
}

按键设置与保存

相关类

  • 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与IMC中的UserSetting选项。
    1. 部分输入比如Axis2D这种需要在IMC中进行设置。
  4. 默认Profile GameplayTag 1.

加载按键设置逻辑

默认自动加载载入逻辑位于UEnhancedInputUserSettings::LoadOrCreateSettings(),调用顺序是: UEnhancedInputLocalPlayerSubsystem::Initialize -> UEnhancedInputLocalPlayerSubsystem::InitalizeUserSettings() -> UEnhancedInputUserSettings::LoadOrCreateSettings()

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;
}

鼠标灵敏度与反转设置实现

参考Lyra的实现方法

  1. 通过实现2个UInputModifier。
  2. 在ULyraSettingsShared(ULocalPlayerSaveGame)存储对应的灵敏度float数据。
    1. 可以考虑直接在UEnhancedInputUserSettings中来存储。
//////////////////////////////////////////////////////////////////////
// ULyraInputModifierGamepadSensitivity
FInputActionValue ULyraInputModifierGamepadSensitivity::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    // You can't scale a boolean action type
    ULyraLocalPlayer* LocalPlayer = LyraInputModifiersHelpers::GetLocalPlayer(PlayerInput);
    if (CurrentValue.GetValueType() == EInputActionValueType::Boolean || !LocalPlayer || !SensitivityLevelTable)
    {
       return CurrentValue;
    }
    
    ULyraSettingsShared* Settings = LocalPlayer->GetSharedSettings();
    ensure(Settings);

    const ELyraGamepadSensitivity Sensitivity = (TargetingType == ELyraTargetingType::Normal) ? Settings->GetGamepadLookSensitivityPreset() : Settings->GetGamepadTargetingSensitivityPreset();

    const float Scalar = SensitivityLevelTable->SensitivtyEnumToFloat(Sensitivity);

    return CurrentValue.Get<FVector>() * Scalar;
}

//////////////////////////////////////////////////////////////////////
// ULyraInputModifierAimInversion
FInputActionValue ULyraInputModifierAimInversion::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    ULyraLocalPlayer* LocalPlayer = LyraInputModifiersHelpers::GetLocalPlayer(PlayerInput);
    if (!LocalPlayer)
    {
       return CurrentValue;
    }
    
    ULyraSettingsShared* Settings = LocalPlayer->GetSharedSettings();
    ensure(Settings);

    FVector NewValue = CurrentValue.Get<FVector>();
    
    if (Settings->GetInvertVerticalAxis())
    {
       NewValue.Y *= -1.0f;
    }
    
    if (Settings->GetInvertHorizontalAxis())
    {
       NewValue.X *= -1.0f;
    }
    
    return NewValue;
}