目的
APlayerControllerを継承したクラスからPlayerの移動、攻撃などを操作できるようにする。
Playerの動きは自身に実装し、それを動かすのはController側でやりたい方向け。
序盤で説明しているInputTriggerの実装はしませんので記事中の参考リンクをご確認ください。
環境
UE 5.4.4
VisualStudio 2022
EnhancedInputについて
UE5.1から標準Inputとして採用されているもの。
従来のInputMappingと大きく違うのは
- Action/Axis Mappingsで設定していたものをInputActionとしてアセット管理できる
(ProjectSettingsに大量のInputが並ぶことなく、見やすいので管理がしやすい) - InputActionに割り当てるキーや発火条件をInputMappingContextでまとめて管理できる
(IMCを入れ替えることで手軽に入力を変更できる) - Trigger/Modifierを細かく設定することで離したときにのみ入力イベントを発火させたり、入力のデッドゾーンを設定できたりする
- また、Trigger/Modifierはクラスを継承して自身独自のものも作成可能
と挙げられると思います。
また最後の独自のInputTriggerの作成についてはヒストリア様の記事が一番わかりやすいと思います。
こちらではたくさんあるUIを長押しすることで高速で見られるようにするといったゲームではよくあるような動作を独自実装されています。よくある表現ではあるもののUEに標準では搭載されていないですし、InputActionで実装を完結できるのはキレイでいいですね。
実装コード
あまり説明することもないので先にコードを貼っておきます。(今回のものに必要な物だけ抜粋し、ほかは省略)
Player側のコード
struct FInputActionValue;
UCLASS()
class TOONTANKS_API APlayerPawn : public APawn
{
GENERATED_BODY()
public:
void Move();
void Turn(const FInputActionValue&);//Inputの入力値を受け取りたい場合はこのような形にする
void Fire();
};
#include "PlayerPawn.h"
#include "Kismet/GameplayStatics.h"
#include "InputActionValue.h"
//実装内容に関してはありきたりなものなので、Turn以外は無視で構わないと思います。
void APlayerPawn::Move()
{
FVector DeltaLocation(0.f);
DeltaLocation.X = Speed * UGameplayStatics::GetWorldDeltaSeconds(this);
AddActorLocalOffset(DeltaLocation, true);
}
void APlayerPawn::Turn(const FInputActionValue& Val)
{
FRotator DeltaRotation = FRotator::ZeroRotator;
//FInputActionValueから入力の値をとる
FVector2D InputVector = Val.Get<FVector2D>();
DeltaRotation.Yaw = InputVector.X * RotateRate * UGameplayStatics::GetWorldDeltaSeconds(this);
AddActorLocalRotation(DeltaRotation, true);
}
void APlayerPawn::Fire()
{
UE_LOG(LogTemp, Display, TEXT("Fire"));
}
PlayerController側のコード
class UInputMappingContext;
class UInputAction;
class APlayerPawn;
UCLASS()
class TOONTANKS_API AMyController : public APlayerController
{
GENERATED_BODY()
protected:
virtual void OnPossess(APawn* InPawn)override;
private:
void SetupInput();
APlayerPawn* OwnedPlayer;
//Input Mapping Context
UPROPERTY(EditDefaultsOnly, Category = "Input")
const UInputMappingContext* InputMapping;
//Input Action
UPROPERTY(EditDefaultsOnly, Category = "Input")
const UInputAction* InputAction_Move;//Digital
UPROPERTY(EditDefaultsOnly, Category = "Input")
const UInputAction* InputAction_Fire;//Digital
UPROPERTY(EditDefaultsOnly, Category = "Input")
const UInputAction* InputAction_Rotate;//Axis1D
};
#include "MyController.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "InputMappingContext.h"
#include "PlayerPawn.h"
//OnPossessの時にPlayerのクラスを参照しておくのが一番安全だと思っている....
void AMyController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
OwnedPlayer = Cast<APlayerPawn>(GetPawn());
SetupInput();
}
void AMyController::SetupInput()
{
//LocalPlayerのサブシステムを利用したいためあらかじめ参照を取得しておく
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player);
if (IsValid(LocalPlayer) && IsValid(OwnedPlayer))
{
if (IsValid(InputComponent))
{
//キャスト
UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(InputComponent);
//それぞれのInputActionへ関数をバインドする
//ETriggerEventで発火タイミングを設定できる。
Input->BindAction(InputAction_Fire, ETriggerEvent::Started, OwnedPlayer, &APlayerPawn::Fire);
Input->BindAction(InputAction_Move, ETriggerEvent::Triggered, OwnedPlayer, &APlayerPawn::Move);
Input->BindAction(InputAction_Rotate, ETriggerEvent::Triggered, OwnedPlayer, &APlayerPawn::Turn);
}
//LocalPlayerSubsystemからEnhancedInputのサブシステムを参照
if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (IsValid(InputMapping))
{
//MappingContextを登録
InputSystem->AddMappingContext(InputMapping, 0);
}
}
}
}
まとめ
こんな感じで従来のInputMappingでInputを実装した方ならわかる通り、実はあまり使い方は変わりません。さらに言えばInputMappingContextを使用しないのであればサブシステムへの参照などもしなくていいのでさらに従来通りの書き方に近くなるかと思います。
実装内容については入力とそれに対する動作を分けることで柔軟に変更に対応できるようになりつつ、InputMappingContextの力でキーバインドについてもInputMappingContextの入れ替えなどで対応できるようになっています。
参照
勉強の参考になったもの
公式ドキュメント
InputComponent
UEnhancedInputComponent
ETriggerEvent
FInputActionValue