본문 바로가기

언리얼엔진/UE5

UE5 C++ enhanced input 플레이어 컨트롤러에 적용하기

들어가기전에..

대부분의 강의나 정보들은 인풋을 컨트롤러가 아닌 캐릭터 클래스에 추가하고 있어요!

그래서 어느 캐릭터 클래스든 자유롭게 쓸 수 있는 이점을 살리기 위해
플레이어 컨트롤러에 인풋을 추가하는 방법을 적어둘게요!

 

최종적으로 필요한 BP및 클래스는 다음과 같아요!

 

1. 인풋 데이터를 담는 DataAsset 클래스

2. 1번의 클래스를 파생시켜 만든 BP

3. 키보드, 마우스 등 맵핑을 위한 InputMappingContext BP

4. 인풋매핑 컨텍스트 및 인풋 데이터를 넣고 사용할 PlayerController 클래스

5. 에디터에서 알맞게 설정

 

 

 

 

 

 

 

먼저 인풋 데이터를 담을 데이터 에셋 클래스를 만들어주세요.

저는 이름을 DNInputConfigData라고 했어요!

 

 

 

// DNInputConfigData.h

#pragma once

// Engine
#include <CoreMinimal.h>
#include <Engine/DataAsset.h>
#include <EnhancedInput/Public/InputAction.h>

// Generated
#include "DNInputConfigData.generated.h"

/**
 * 
 */
UCLASS()
class UE5_TRAINING_PROJECT_API UDNInputConfigData : public UDataAsset
{
	GENERATED_BODY()

public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputMove;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputLook;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputFire;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputReload;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputJump;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputCrouch;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputSprint;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputAiming;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputArmed;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    UInputAction* InputInterAction;

};

저는 InputMove부터 InputInterAtion까지 변수를 선언했어요!

 

 

 

 

 

방금 만든 데이터 에셋 클래스를 간편하게 사용하기 위해 다음과 같이 BP로 생성해주세요!

 

 

 

 

 

 

 

 

 

 

이전 단계에서 만든 인풋 데이터 BP에 담을 InputAction들을 생성해줄거에요.

위의 사진처럼 여러분들이 추가하고 싶은 행동들을 만들어보세요!

 

 

 

 

 

 

 

그렇게 만든 인풋액션 BP들을 인풋데이터 BP에 담아줍니다!

 

 

 

 

 

 

이제 인풋액션들을 만들고 담아줬으니 키보드나 마우스, 패드등에 매핑을 해줘야겠죠?

에디터에서 입력 매핑 컨텍스트 BP를 다음과 같이 만들고

그 전 단계에서 여러분들이 만든 인풋액션BP들을 담아주세요!

이때 플레이어매핑 가능 여부는 True로 해두세요.

 

 

 

 

 

 

 

 

 

 

부모 클래스가 PlayerController인 플레이어 컨트롤러 클래스를 만들어줍니다.

저는 DNPlayerController라고 이름을 지었어요.

 

 

 

// DNPlayerController.h

#pragma once

// Engine
#include <CoreMinimal.h>
#include <GameFramework/PlayerController.h>

// Data
#include "UE5_Training_Project/Data/DNInputConfigData.h"


// generated
#include "DNPlayerController.generated.h"

/**
 *  플레이어가 조종하게 될 컨트롤러입니다.
 */

//DECLARE_DYNAMIC_DELEGATE(FJumpInputEvent);


class UInputMappingContext;
class UDNInputConfigData;

UCLASS()
class UE5_TRAINING_PROJECT_API ADNPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	ADNPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

public:
	void Move(const FInputActionValue& Value);
	void Sprint(const FInputActionValue& Value);
	void StopSprint(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
	void Fire(const FInputActionValue& Value);
	void StopFire(const FInputActionValue& Value);
	void Reload(const FInputActionValue& Value);
	void Jump(const FInputActionValue& Value);
	void StopJumping(const FInputActionValue& Value);
	void Armed(const FInputActionValue& Value);
	void Crouch(const FInputActionValue& Value);
	void Aiming(const FInputActionValue& Value);
	void StopAiming(const FInputActionValue& Value);
	void Interaction(const FInputActionValue& Value);

protected:
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	virtual void Tick(float DeltaTime) override;

	virtual void SetupInputComponent() override;

protected:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Enhanced Input")
	UInputMappingContext* InputMapping;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Enhanced Input")
	UDNInputConfigData* InputActions;

 

 

이 클래스에 InputMapping, InputActions 변수를 선언해줄게요.

둘 다 이전 단계에서 만든 BP들의 클래스에요!

마지막으로 위의 Move부터 Interaction까지 여러분이 추가하고자 하는 기능들의 함수를 추가해주세요.

 

 

// DNPlayerController.cpp

#include "UE5_Training_Project/Controller/DNPlayerController.h"

// Engine
#include "Kismet/GameplayStatics.h"
#include <GameFramework/CharacterMovementComponent.h>

// GameMode
#include "UE5_Training_Project/GameMode/DNGameModeBase.h"

// Character
#include "UE5_Training_Project/Character/DNCommonCharacter.h"


// EnhancedInput
#include "EnhancedInput/Public/InputMappingContext.h"
#include "EnhancedInput/Public/EnhancedInputSubsystems.h"
#include "EnhancedInput/Public/EnhancedInputComponent.h"
#include "EnhancedInput/Public/InputActionValue.h"


// Character
#include "UE5_Training_Project/Character/DNUnEnemyCharacter.h"


ADNPlayerController::ADNPlayerController(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	
}


void ADNPlayerController::BeginPlay()
{
	Super::BeginPlay();
	

}

void ADNPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
}

void ADNPlayerController::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

void ADNPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	// Get the local player subsystem
	UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
	// Clear out existing mapping, and add our mapping
	Subsystem->ClearAllMappings();
	Subsystem->AddMappingContext(InputMapping, 0);

	// Get the EnhancedInputComponent
	UEnhancedInputComponent* PEI = Cast<UEnhancedInputComponent>(InputComponent);

	// Bind the actions

	// Completed : 눌렀다 뗐을 때, Triggered : 누르고 있을 때 
	PEI->BindAction(InputActions->InputMove, ETriggerEvent::Triggered, this, &ADNPlayerController::Move);
	PEI->BindAction(InputActions->InputLook, ETriggerEvent::Triggered, this, &ADNPlayerController::Look);
	PEI->BindAction(InputActions->InputJump, ETriggerEvent::Triggered, this, &ADNPlayerController::Jump);
	PEI->BindAction(InputActions->InputJump, ETriggerEvent::Completed, this, &ADNPlayerController::StopJumping);
	PEI->BindAction(InputActions->InputFire, ETriggerEvent::Triggered, this, &ADNPlayerController::Fire);
	PEI->BindAction(InputActions->InputFire, ETriggerEvent::Completed, this, &ADNPlayerController::StopFire);
	PEI->BindAction(InputActions->InputReload, ETriggerEvent::Completed, this, &ADNPlayerController::Reload);
	PEI->BindAction(InputActions->InputAiming, ETriggerEvent::Triggered, this, &ADNPlayerController::Aiming);
	PEI->BindAction(InputActions->InputAiming, ETriggerEvent::Completed, this, &ADNPlayerController::StopAiming);
	PEI->BindAction(InputActions->InputArmed, ETriggerEvent::Completed, this, &ADNPlayerController::Armed);
	PEI->BindAction(InputActions->InputCrouch, ETriggerEvent::Completed, this, &ADNPlayerController::Crouch);
	PEI->BindAction(InputActions->InputSprint, ETriggerEvent::Triggered, this, &ADNPlayerController::Sprint);
	PEI->BindAction(InputActions->InputSprint, ETriggerEvent::Completed, this, &ADNPlayerController::StopSprint);
	PEI->BindAction(InputActions->InputInterRaction, ETriggerEvent::Completed, this, &ADNPlayerController::Interaction);
	
}


void ADNPlayerController::Move(const FInputActionValue& Value)
{
	// input is a Vector2D
	FVector2D MovementVector = Value.Get<FVector2D>();


	// find out which way is forward
	const FRotator Rotation = GetControlRotation();
	const FRotator YawRotation(0, Rotation.Yaw, 0);

	// get forward vector
	const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);

	// get right vector 
	const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

	// add movement 
	GetCharacter()->AddMovementInput(ForwardDirection, MovementVector.Y);
	GetCharacter()->AddMovementInput(RightDirection, MovementVector.X);
}


void ADNPlayerController::Sprint(const FInputActionValue& Value)
{
	ADNCommonCharacter* character = dynamic_cast<ADNCommonCharacter*>(GetCharacter());
	character->sprint();
	UE_LOG(LogTemp, Warning, TEXT("Sprint"));
}

void ADNPlayerController::StopSprint(const FInputActionValue& Value)
{
	ADNCommonCharacter* character = dynamic_cast<ADNCommonCharacter*>(GetCharacter());
	character->stop_sprint();
	UE_LOG(LogTemp, Warning, TEXT("StopSprint"));
}

SetupInputComponent() 함수는 컨트롤러가 캐릭터 또는 폰에 빙의할때 호출이됩니다.

따라서 해당 함수에서 매핑 컨텍스트를 연결시켜줘야해요.

 

또한 이곳은 플레이어 컨트롤러이기 때문에 '달리기' 라는 행동을 하게된다면

캐릭터에서 해당 달리기에 대한 함수를 호출하는 방식이 좋습니다.

그럼 캐스팅을 해줘야겠네요!

 

ADNCommonCharacter* character = dynamic_cast<ADNCommonCharacter*>(GetCharacter());

와 같은 방식으로 이 컨트롤러에 붙어있는 캐릭터를 가져옵니다.

 

 

 

 

여기서 의문!? 

이러면 결국 DNCommonCharacter를 직접적으로 적어줘야하는데 컨트롤러에다가 Input을 넣은 의미가 없지않아?

라고 생각할 수도 있어요. 

다양한 방법들이 있겠지만 이 경우, 델리게이트를 선언해서
ADNPlayerController::Sprint()함수가 호출될 경우

캐릭터 클래스 안에서 이것을 받는 방식으로 해줘도 좋아요.

일단은 이렇게 하고 넘어갈게요!

 

 

 

 

 

다 왔습니다!

마지막으로 우리가 만든 플레이어 컨트롤러를 BP화 한다음

클래스 디폴트로 들어가서 오른쪽의 향상된 입력 탭을 사진과 같이 적용하시면 끝입니다!

 

이제 게임모드에서 알맞게 배치하고 실행하면 잘될거에요!

'언리얼엔진 > UE5' 카테고리의 다른 글

UE5 Delay 함수를 C++로 써보자  (0) 2023.05.17
UE5 액터의 BeginPlay 호출 순서에 대해서  (0) 2023.05.12