UE5新增了增强输入框架(EnhancedInput)用来替代旧的输入框架,主要变化是将输入操作模块化,使得输入操作的按键绑定更加灵活,可以动态的修改按键绑定。
本文基于UE 5.2.1。将创建一个角色(C++类ABlacterCharacter和继承的蓝图类BP_BlacterCharacter),实现行走、跳跃和摄像机控制。
参考文章:
- https://dev.epicgames.com/community/learning/tutorials/aqrD/unreal-engine-enhanced-input-binding-with-gameplay-tags-c
- UE5 — EnhancedInput(增强输入系统)
- Unreal Engine 增强输入框架 EnhancedInput
组成模块
增强输入框架由多个模块组成,这里仅介绍基本的两个组件:输入操作(UInputAction)和输入映射情境(UInputMappingContext)。
输入操作(UInputAction)
输入操作是指角色的具体动作事件,例如移动、跳跃、射击等。对应的数值类型包括布尔、浮点、Vector2D等。与具体的按键无关。下面分别创建角色的前后移动(IA_MoveForward
)、左右移动(IA_MoveRight
)、转向(IA_Turn
)、抬头(IA_LookUp
)、跳跃(IA_Jump
)的输入操作(内容浏览器->鼠标右键->输入->输入操作)。
其中,除了跳跃(IA_Jump
)的值类型为布尔外,其余均为浮点。
在C++头文件(BlacterCharacter.h)中增加相应的关联代码:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_MoveForward; // 前后移动
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_MoveRight; // 左右移动
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_Turn; // 转向
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_LookUp; // 抬头
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_Jump; // 跳跃
之后,在CPP文件(BlacterCharacter.cpp)的SetupPlayerInputComponent()
函数中增加关联代码:
if (UEnhancedInputComponent* inputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
if (IA_MoveForward)
{
inputComponent->BindAction(IA_MoveForward, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionMoveForward);
}
if (IA_MoveRight)
{
inputComponent->BindAction(IA_MoveRight, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionMoveRight);
}
if (IA_Turn)
{
inputComponent->BindAction(IA_Turn, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionTurn);
}
if (IA_LookUp)
{
inputComponent->BindAction(IA_LookUp, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionLookUp);
}
if (IA_Jump)
{
inputComponent->BindAction(IA_Jump, ETriggerEvent::Started, this, &ABlacterCharacter::OnActionJump);
}
}
编译后,在蓝图类BP_BlacterCharacter的属性中赋值。
输入映射情境(UInputMappingContext)
输入映射情境指的是将按键与输入操作进行关联。并且,能通过”触发器“设置按键的触发条件(持续触发、只触发一次、组合键触发等等),通过”修改器“修改按键传递的数值。
创建一个输入映射情境IMC_PlayerCharacter
(内容浏览器->鼠标右键->输入->输入映射情景)。如下图所示,关联各个输入操作。
之后,在CPP文件(BlacterCharacter.cpp)的SetupPlayerInputComponent()
函数中增加关联代码:
if (APlayerController* pc = CastChecked<APlayerController>(GetController()))
{
if (UEnhancedInputLocalPlayerSubsystem* subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(pc->GetLocalPlayer()))
{
subsystem->AddMappingContext(InputMappingContext, 100);
}
}
完整示例代码
BlacterCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "../Plugins/EnhancedInput/Source/EnhancedInput/Public/inputActionValue.h"
#include "BlacterCharacter.generated.h"
UCLASS()
class UE_NETWORKDEMO_API ABlacterCharacter : public ACharacter
{
GENERATED_BODY()
public:
ABlacterCharacter();
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
protected:
UPROPERTY(VisibleAnywhere, Category = "Camera")
class USpringArmComponent* _cameraBoom;
UPROPERTY(VisibleAnywhere, Category = "Camera")
class UCameraComponent* _followCamera;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="EnhancedInput")
class UInputMappingContext* InputMappingContext;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_MoveForward;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_MoveRight;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_Turn;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_LookUp;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput|Action")
class UInputAction* IA_Jump;
virtual void BeginPlay() override;
UFUNCTION()
void OnActionMoveForward(const FInputActionValue& inputActionValue);
UFUNCTION()
void OnActionMoveRight(const FInputActionValue& inputActionValue);
UFUNCTION()
void OnActionJump(const FInputActionValue& inputActionValue);
UFUNCTION()
void OnActionLookUp(const FInputActionValue& inputActionValue);
UFUNCTION()
void OnActionTurn(const FInputActionValue& inputActionValue);
};
BlacterCharacter.cpp
#include "BlacterCharacter.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "../Plugins/EnhancedInput/Source/EnhancedInput/Public/EnhancedInputSubsystems.h"
#include "../Plugins/EnhancedInput/Source/EnhancedInput/Public/EnhancedInputComponent.h"
ABlacterCharacter::ABlacterCharacter()
{
PrimaryActorTick.bCanEverTick = true;
_cameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
_cameraBoom->SetupAttachment(GetMesh());
_cameraBoom->TargetArmLength = 600.f;
_cameraBoom->bUsePawnControlRotation = true; // 使用Pawn身上的PlayerController控制弹簧臂
_followCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
_followCamera->SetupAttachment(_cameraBoom, USpringArmComponent::SocketName);
_followCamera->bUsePawnControlRotation = false;
}
void ABlacterCharacter::BeginPlay()
{
Super::BeginPlay();
}
void ABlacterCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ABlacterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 参考教程 https://dev.epicgames.com/community/learning/tutorials/aqrD/unreal-engine-enhanced-input-binding-with-gameplay-tags-c
if (APlayerController* pc = CastChecked<APlayerController>(GetController()))
{
if (UEnhancedInputLocalPlayerSubsystem* subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(pc->GetLocalPlayer()))
{
subsystem->AddMappingContext(InputMappingContext, 100);
}
}
if (UEnhancedInputComponent* inputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
if (IA_MoveForward)
{
inputComponent->BindAction(IA_MoveForward, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionMoveForward);
}
if (IA_MoveRight)
{
inputComponent->BindAction(IA_MoveRight, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionMoveRight);
}
if (IA_Turn)
{
inputComponent->BindAction(IA_Turn, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionTurn);
}
if (IA_LookUp)
{
inputComponent->BindAction(IA_LookUp, ETriggerEvent::Triggered, this, &ABlacterCharacter::OnActionLookUp);
}
if (IA_Jump)
{
inputComponent->BindAction(IA_Jump, ETriggerEvent::Started, this, &ABlacterCharacter::OnActionJump);
}
}
}
void ABlacterCharacter::OnActionMoveForward(const FInputActionValue& inputActionValue)
{
float inputValue = inputActionValue.Get<float>();
if (Controller && inputValue != 0)
{
const FRotator controllerYawRotator(0, Controller->GetControlRotation().Yaw, 0);
const FVector direction = controllerYawRotator.RotateVector(FVector::ForwardVector); //const FVector direction(FRotationMatrix(controllerYawRotator).GetUnitAxis(EAxis::X));
AddMovementInput(direction, inputValue);
}
}
void ABlacterCharacter:: OnActionMoveRight(const FInputActionValue& inputActionValue)
{
float inputValue = inputActionValue.Get<float>();
if (Controller && inputValue != 0)
{
const FRotator controllerYawRotator(0, Controller->GetControlRotation().Yaw, 0);
const FVector direction = controllerYawRotator.RotateVector(FVector::RightVector);
AddMovementInput(direction, inputValue);
}
}
void ABlacterCharacter::OnActionJump(const FInputActionValue& inputActionValue)
{
Jump();
}
void ABlacterCharacter::OnActionLookUp(const FInputActionValue& inputActionValue)
{
float inputValue = inputActionValue.Get<float>();
AddControllerPitchInput(inputValue);
}
void ABlacterCharacter::OnActionTurn(const FInputActionValue& inputActionValue)
{
float inputValue = inputActionValue.Get<float>();
AddControllerYawInput(inputValue);
}