Showing

[Unreal] Pawn과 Character, EnhancedInput vs InputSystem 본문

Unreal

[Unreal] Pawn과 Character, EnhancedInput vs InputSystem

RabbitCode 2023. 11. 24. 22:20

폰 vs 캐릭터

인간형 폰을 구현하는 데에는 폰을 상속받은 캐릭터 클래스를 쓰는 것이 일반 폰 클래스보다 훨씬 용이할 것이다.

(언리얼엔진이 만든 게임플레이 프레임워 안에 캐릭터 클래스가 들어있다.)

 

캐릭터무브먼트 컴포넌트 덕분에 허공에 있으면 떨어짐-> 점프, 걷기, 수영하기, 기어가기 등 다양한 행동 양식 가능할 뿐만 아니라, 캐릭터 클래스를 사용하면 멀티 플레이 환경에서 캐릭터들 간의 움직임을 자동으로 동기화해주기 때문에 상당히 이점이 있다.

 

중력의 영향을 받는 움직임 모드(EMovementMode)

EMovementMode_ : None, Walking, Falling

None : 이동 기능을 끄고 싶을 떄

MaxWalkSpeed : 이동 모드에서의 이동 속도

JumpZVelocity : Falling 모드에서의 점프 속도

 

UEnhancedInputComponent vs 기존 입력 시스템

기존 입력 시스템은 입력 관련 설정을 해둔 뒤에 게임 로직에서 입력 값을 처리한다.

이러한 방식은 게임 플레이 중에 플레이어의 키세팅 변경에 대응할 수 없게 한다.

플레이어의 입력 관련 설정을 플레이 중에서도 변경할 수 있게끔 하기 위해서는 UEnhancedInputComponent 를 쓰는 것이 보다 용이하다.

UEnhancedInputComponent 

입력이 들어오면 인풋 매핑 컨텍스트에서 인풋을 인풋 액션과 대응시켜준다.(ex) w를 누르면 move와 대응)

매핑시에 입렵값을 어떻게 바꿀지(Modifier: 예를 들면 w면 1을 곱하고 s면 -1을 곱함),

어떤 상태일 때 트리거시킬지(Trigger : 처음 누르는 순간-pressed 눌러주는 상태-떼는 순간 이러한 상태별로 어떤 상태일 때 바인드되어 있는 함수를 호출할건지 결정)

case1. UEnhancedInputComponent + Character + tick

value 타입을 axis 2d로 해서 Move에서 업앤다운, 레프트라이트 2차원으로 관리할 수 있도록 설정 

void AMyPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) {
		EnhancedInputComponent->BindAction(MoveIA, ETriggerEvent::Triggered, this, &AMyPlayer::Move);
		EnhancedInputComponent->BindAction(LookUpIA, ETriggerEvent::Triggered, this, &AMyPlayer::LookUp);
		EnhancedInputComponent->BindAction(TurnIA, ETriggerEvent::Triggered, this, &AMyPlayer::Turn);
		EnhancedInputComponent->BindAction(JumpIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputJump);
		EnhancedInputComponent->BindAction(FireIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputFire);
		EnhancedInputComponent->BindAction(InteractionIA, ETriggerEvent::Started, this, &AMyPlayer::InteractionPositive);
	}
}

void AMyPlayer::Move(const FInputActionValue& Value)
{
	//UE_LOG(LogTemp, Log, TEXT("Log Message %f"), moveSpeed);
	const FVector _currentValue = Value.Get<FVector>();
	if (Controller) {
		moveDirection.Y = _currentValue.X;
		moveDirection.X = _currentValue.Y;
	}
	//FVector newLocation = GetActorLocation() + _currentValue;
	//SetActorLocation(newLocation);
}

관련 전체 코드

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "MyPlayer.generated.h"

class UInputMappingContext;
class UInputAction;
class UNiagaraComponent;

UCLASS()
class THIRDPERSONTEMPLATE_API AMyPlayer : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyPlayer();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	UPROPERTY(EditAnywhere, Category = "Anim")
	UAnimMontage* attackMontage;

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera")
	class USpringArmComponent* springArmComp;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
	class UCameraComponent* cameraComp;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputMappingContext* PlayerMappingContext;

	UPROPERTY(VisibleAnywhere, Category = "Weapon")
	class UStaticMeshComponent* staffMesh;

	UPROPERTY(EditAnywhere, Category = "FX")
	class UNiagaraComponent* jumpNiagara;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* MoveIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* LookUpIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* TurnIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* JumpIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* FireIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* InteractionIA;

public:
	void Move(const FInputActionValue& Value);
	void LookUp(const FInputActionValue& Value);
	void Turn(const FInputActionValue& Value);
	void InputJump(const FInputActionValue& Value);
	void InputFire(const FInputActionValue& Value);
	void InteractionPositive(const FInputActionValue& Value);
	void Fire();
	float fireTimerTime; // 타이머의 시간
	bool fireReady; // 타이머 도달 여부 
public:
	UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Status")
	float maxHp;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Status")
	float currentHp;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Status")
	float maxMp;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Status")
	float currentMp;

	UPROPERTY(EditAnywhere, Category = "Move")
	float moveSpeed;

	UPROPERTY(EditAnywhere)
	TSubclassOf<class APBullet> bulletFactory;

	UPROPERTY(EditAnyWhere)
	float fireCoolTime = 2.0f;

	bool isJump = false;

private:
	FVector moveDirection;

protected:
	void FireCoolTimer(float cooltime, float deltaTime);
public:
	void ShowFX(bool show);
};
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyPlayer.h"
#include "Components/ArrowComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputSubsystems.h" //https://docs.unrealengine.com/5.0/en-US/enhanced-input-in-unreal-engine/
#include "EnhancedInputComponent.h" //https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/Input/EnhancedInput/
#include "PBullet.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "DrawDebugHelpers.h"

// Sets default values
AMyPlayer::AMyPlayer()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	//ConstructorHelpers 라는 유틸리티로 에셋 가져오기(찾기)
	// InitMesh 굳이 이렇게 하기는 했으나 콘텐츠 브라우저에서 선택하는게 사실 더 좋음
	ConstructorHelpers::FObjectFinder<USkeletalMesh> InitMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/MyResouce/unitychan.unitychan'"));
	if (InitMesh.Succeeded()) { // 제대로 오브젝트를 가져왔다면~
		// GetMesh는 actor(근본)의 상속자인 character에서 제공!
		GetMesh()->SetSkeletalMesh(InitMesh.Object);
		GetMesh()->SetRelativeLocationAndRotation(
			FVector(0, 0, -88)
			, FRotator(0, -90, 0));
	}
	springArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
	springArmComp->SetupAttachment(RootComponent);
	springArmComp->SetRelativeLocationAndRotation(FVector(0, 0, 50), FRotator(-20,0,0));
	springArmComp->TargetArmLength = 530;
	springArmComp->bUsePawnControlRotation = true;

	//카메라도 생성 초기화
	cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	cameraComp->SetupAttachment(springArmComp);
	cameraComp->bUsePawnControlRotation = false;

	//staff mesh
	staffMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("staff meshComp"));// 스태프 외관 컴포넌트 등록
	// 두번째 인자 캐릭터 메쉬에 부착, FName이 문자열 속도가 빠름
	staffMesh->SetupAttachment(GetMesh(), FName("Character1_RightHandSocket"));
	//staffMesh->SetRelativeLocationAndRotation

	//pawn이 들고 있음
	bUseControllerRotationYaw = true;
	moveSpeed = 100.0f;

	maxHp = 100;
	maxMp = 100;
	currentHp = 50;
	currentMp = 50;
	fireTimerTime = 0;
	fireCoolTime = 1.85f;
	fireReady = true;    // 발사 준비 On
}

// Called when the game starts or when spawned
void AMyPlayer::BeginPlay()
{
	Super::BeginPlay();
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem
			= ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer())) {
			Subsystem->AddMappingContext(PlayerMappingContext, 0);
		}
	}
	jumpNiagara = GetComponentByClass<UNiagaraComponent>();
	jumpNiagara->SetVisibility(false);
}

// Called every frame
void AMyPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (fireReady == false) {
		FireCoolTimer(fireCoolTime, DeltaTime);
	}

	const TArray<USceneComponent*>& AttachChildren = GetRootComponent()->GetAttachChildren();
	const int32 Count = AttachChildren.Num();


	// 이동방향을 컨트롤 방향 기준으로 변환
	moveDirection = FTransform(GetControlRotation()).TransformVector(moveDirection);

	/*
	// 플레이어 이동 - 등속운동
	FVector P0 = GetActorLocation();//현재 위치
	FVector vt = moveDirection * moveSpeed * DeltaTime; //이동거리
	FVector P = P0 + vt;
	SetActorLocation(P);
	*/

	AddMovementInput(moveDirection);

	// 방향 초기화
	// 방향이 누적되지 않게
	moveDirection = FVector::ZeroVector;
}

// Called to bind functionality to input
void AMyPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) {
		EnhancedInputComponent->BindAction(MoveIA, ETriggerEvent::Triggered, this, &AMyPlayer::Move);
		EnhancedInputComponent->BindAction(LookUpIA, ETriggerEvent::Triggered, this, &AMyPlayer::LookUp);
		EnhancedInputComponent->BindAction(TurnIA, ETriggerEvent::Triggered, this, &AMyPlayer::Turn);
		EnhancedInputComponent->BindAction(JumpIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputJump);
		EnhancedInputComponent->BindAction(FireIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputFire);
		EnhancedInputComponent->BindAction(InteractionIA, ETriggerEvent::Started, this, &AMyPlayer::InteractionPositive);
	}
}

void AMyPlayer::Move(const FInputActionValue& Value)
{
	//UE_LOG(LogTemp, Log, TEXT("Log Message %f"), moveSpeed);
	const FVector _currentValue = Value.Get<FVector>();
	if (Controller) {
		moveDirection.Y = _currentValue.X;
		moveDirection.X = _currentValue.Y;
	}
	//FVector newLocation = GetActorLocation() + _currentValue;
	//SetActorLocation(newLocation);
}

void AMyPlayer::LookUp(const FInputActionValue& Value)
{
	// mouse y - 한 축의 값(float)
	const float _currentValue = Value.Get<float>();
	APawn::AddControllerPitchInput(_currentValue);
}

void AMyPlayer::Turn(const FInputActionValue& Value)
{
	// mouse x - 한 축의 값(float)
	const float _currentValue = Value.Get<float>();
	APawn::AddControllerYawInput(_currentValue);
}

void AMyPlayer::InputJump(const FInputActionValue& Value)
{
	Jump();
}

void AMyPlayer::InputFire(const FInputActionValue& Value)
{
	if (fireReady == true) {
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
		if (AnimInstance) {
			AnimInstance->Montage_Play(attackMontage);
		}
		fireReady = false;
	}
}

void AMyPlayer::InteractionPositive(const FInputActionValue& Value)
{
	//start와 end 가 중요
	FVector _Location;
	FRotator _Rotation;
	FHitResult _HitOut;

	//카메라가 보고 있는 위치
	GetController()->GetPlayerViewPoint(_Location, _Rotation);
	FVector _Start = _Location;
	int ViewDis = 2000;
	FVector _End = (_Rotation.Vector() * ViewDis); // 2000은 무작위 거리
	FCollisionQueryParams _traceParams; //trace의 params들
	GetWorld()->LineTraceSingleByChannel(_HitOut, _Start, _End, ECC_Visibility, _traceParams); // 충돌 결과, 시작점, 결과, 콜리전채널, params(안넣으면 디폴트)
	DrawDebugLine(GetWorld(), _Start, _End, FColor::Green, false, 2.0f);
}

void AMyPlayer::FireCoolTimer(float cooltime, float deltaTime)
{
	if (fireTimerTime < cooltime) {
		fireTimerTime += deltaTime;
	}
	else
	{
		fireTimerTime = 0;
		fireReady = true;
	}
}

void AMyPlayer::ShowFX(bool show)
{
	if (nullptr == jumpNiagara) {
		jumpNiagara = GetComponentByClass<UNiagaraComponent>();
	}
	jumpNiagara->SetVisibility(show);
}

void AMyPlayer::Fire()
{
	FTransform firePosition = staffMesh->GetSocketTransform(TEXT("FirePosition"));
	GetWorld()->SpawnActor<APBullet>(bulletFactory, firePosition);
}

인풋 에셋 별로 생성해야 한다는 점이 특징!

turn과 Lookup jump은 1차원   

레이트레이스와 fire은 bool 이렇게 특성에 맞게 밸류 타입을 지정해주면 된다.

case2. setting in InputComponent + Pawn+ tick

void APlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	PlayerInputComponent->BindAxis("Horizontal",this, &APlayerPawn::MoveHorizontal);
	PlayerInputComponent->BindAxis("Vertical", this, &APlayerPawn::MoveVertical);
	PlayerInputComponent->BindAction("Fire", IE_Pressed,this, &APlayerPawn::Fire);

	//if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked< UEnhancedInputComponent>(PlayerInputComponent))
	//{
	//	EnhancedInputComponent->BindAction(MoveAxis, ETriggerEvent::Triggered, this, &APlayerPawn::Move);
	//	EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &APlayerPawn::Fire);
	//}
}

관련 전체 코드

// Fill out your copyright notice in the Description page of Project Settings.


#include "PlayerPawn.h"
#include "Components/BoxComponent.h"
#include "Components/ArrowComponent.h"
#include "Components/StaticMeshComponent.h"
//#include "EnhancedInputSubsystems.h"
//#include "EnhancedInputComponent.h"
#include "Bullet.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"

APlayerPawn::APlayerPawn()
{
	PrimaryActorTick.bCanEverTick = true;

	boxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("My Box Component"));
	SetRootComponent(boxComp);

	boxComp->SetCollisionProfileName(TEXT("Player"));
	//boxComp->SetGenerateOverlapEvents(true);
	//boxComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	//boxComp->SetCollisionObjectType(ECC_GameTraceChannel1);
	//boxComp->SetCollisionResponseToAllChannels(ECR_Ignore); 
	//boxComp->SetCollisionResponseToChannel(ECC_GameTraceChannel2, ECR_Overlap);
	//boxComp->SetCollisionResponseToChannel(ECC_GameTraceChannel4, ECR_Block);

	meshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	meshComp->SetupAttachment(boxComp);

	FVector boxSize = FVector(50.0f, 50.0f, 50.0f);
	boxComp->SetBoxExtent(boxSize);

	firePosition = CreateDefaultSubobject<UArrowComponent>(TEXT("FirePos"));
	firePosition->SetupAttachment(meshComp);
	
	// 총알 발사 변수 초기화
	fireTimerTime = 0;  
	fireCoolTime = 2.0f; 
	fireReady = true;    // 발사 준비 On
	fireMode = 1;
}

void APlayerPawn::BeginPlay()
{
	Super::BeginPlay();
}

void APlayerPawn::FireCoolTimer(float cooltime, float deltaTime)
{
	if (fireTimerTime < cooltime) {
		fireTimerTime += deltaTime;
	}
	else 
	{
		fireTimerTime = 0;
		fireReady = true;
	}
}

void APlayerPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	FVector dir = FVector(v,h,0);
	dir.Normalize();
	FVector newLocation = GetActorLocation() + dir * moveSpeed * DeltaTime;
	SetActorLocation(newLocation, true);
	
	if (fireReady == false) {
		FireCoolTimer(fireCoolTime, DeltaTime);
	}

	FHitResult _hitResult;
	UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetHitResultUnderCursor(ECC_Visibility, true, _hitResult);
	//	FRotator _dirRot = UKismetMathLibrary::FindLookAtRotation(this->GetActorLocation(),
//_hitResult.Location);
		//	FRotator _finalRot = FRotator(0, _dirRot.Yaw, 0);

	FVector Dest = FVector(_hitResult.Location.X, _hitResult.Location.Y, 0.0f);
	FVector Start = FVector(this->GetActorLocation().X, this->GetActorLocation().Y, 0.0f);
	FRotator _dirRot = UKismetMathLibrary::FindLookAtRotation(Start,
		Dest);
	SetActorRotation(_dirRot);
}

void APlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	PlayerInputComponent->BindAxis("Horizontal",this, &APlayerPawn::MoveHorizontal);
	PlayerInputComponent->BindAxis("Vertical", this, &APlayerPawn::MoveVertical);
	PlayerInputComponent->BindAction("Fire", IE_Pressed,this, &APlayerPawn::Fire);

	//if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked< UEnhancedInputComponent>(PlayerInputComponent))
	//{
	//	EnhancedInputComponent->BindAction(MoveAxis, ETriggerEvent::Triggered, this, &APlayerPawn::Move);
	//	EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &APlayerPawn::Fire);
	//}
}

void APlayerPawn::MoveHorizontal(float value) {h = value;}

void APlayerPawn::MoveVertical(float value) {v = value;}

void APlayerPawn::Fire()
{
	if (fireMode == 0) {
		// coolTime
		if (fireReady == true) {
			ABullet* bullet = GetWorld()->SpawnActor<ABullet>(bulletFactory, firePosition->GetComponentLocation()
				, firePosition->GetComponentRotation());
			fireReady = false;
			//fireSound
			//월드(어느 월드), 소리낼 사운드, 소리 날 위치
			UGameplayStatics::PlaySoundAtLocation(GetWorld(), fireSound, firePosition->GetComponentLocation());
		}
	}
	else if (fireMode == 1) {
		int bulletIndex = 1;
		if (fireReady == true) {
			for (int i = 0; i < 2; i++) {
				for (int j = 0; j < 3; j++) {
					// pos variable setting
					float zCod = -150.0f;
					float yCod = 150.0f;
					FVector posTemp = FVector(i * zCod, j * yCod, 0.0f);
					FVector targetPos = firePosition->GetComponentLocation() + posTemp;

					// produce bullet
					ABullet* bullet = GetWorld()->SpawnActor<ABullet>(bulletFactory, targetPos,firePosition->GetComponentRotation());
					bullet->SetActorScale3D(FVector(1, 1, 1));
					if (bulletIndex % 2 ==0) {
						bullet->SetActorScale3D(FVector(2, 2, 2)*2);
					}
					UGameplayStatics::PlaySoundAtLocation(GetWorld(), fireSound, targetPos);

					// bulletIndex counting
					bulletIndex += 1;
				}
			}
			// release fireReady
			fireReady = false;
		}
	}
}

 

case3. setting in InputComponent + Pawn

인풋 세팅에서 1차원씩 axis를 설정해준 모

void ASPlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    PlayerInputComponent->BindAxis("UpDown", this, &ASPlayerPawn::UpDown);
    PlayerInputComponent->BindAxis("LeftRight", this, &ASPlayerPawn::LeftRight);
}
void ASPlayerPawn::UpDown(float IinAxisValue)
{
    AddMovementInput(GetActorForwardVector(), IinAxisValue);
}

void ASPlayerPawn::LeftRight(float IinAxisValue)
{
    AddMovementInput(GetActorRightVector(), IinAxisValue);
}

관련 전체 코드

#include "Characters/SPlayerPawn.h"

#include "Components/CapsuleComponent.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

// Sets default values
ASPlayerPawn::ASPlayerPawn()
{
	PrimaryActorTick.bCanEverTick = false;
    float CharacterHalfHeight = 90.f;
    float CharacterRadius = 40.f;
#pragma region capsule component
    CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComponent"));
    SetRootComponent(CapsuleComponent);
    CapsuleComponent->SetCapsuleHalfHeight(CharacterHalfHeight);
    CapsuleComponent->SetCapsuleRadius(CharacterRadius);
#pragma endregion
#pragma region skeletal mesh component
    SkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
    SkeletalMesh->SetupAttachment(GetRootComponent());
    FVector PivotPosition(0.f, 0.f, -CharacterHalfHeight);
    FRotator PivotRotation(0.f, -90.f, 0.f);
    SkeletalMesh->SetRelativeLocationAndRotation(PivotPosition, PivotRotation);

#pragma endregion
#pragma region spring arm component
    SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
    SpringArmComponent->SetupAttachment(GetRootComponent());
    SpringArmComponent->TargetArmLength = 400.0f;
    SpringArmComponent->SetRelativeRotation(FRotator(-15.f, 0.f, 0.f));
#pragma endregion
#pragma region camera component
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(SpringArmComponent);
#pragma endregion
    // floating pawn movement component
    FloatingPawnMovement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("FloatingPawnMovement"));

    // Don't rotate the controller's yaw
    //bUseControllerRotationYaw = false;
}

void ASPlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    PlayerInputComponent->BindAxis("UpDown", this, &ASPlayerPawn::UpDown);
    PlayerInputComponent->BindAxis("LeftRight", this, &ASPlayerPawn::LeftRight);
}

void ASPlayerPawn::BeginPlay()
{
    Super::BeginPlay();
}

void ASPlayerPawn::UpDown(float IinAxisValue)
{
    AddMovementInput(GetActorForwardVector(), IinAxisValue);
}

void ASPlayerPawn::LeftRight(float IinAxisValue)
{
    AddMovementInput(GetActorRightVector(), IinAxisValue);
}

 

UEnhancedInputComponent Setting

A모듈에다가 B모듈을 추가하고 싶다면 A 모듈의 Build.cs를 수정해야 한다.

프로젝트 모듈에다 EnhancedInput 모듈을 추가하고 싶다면 먼저 관련 플러그인을 추가해준 다음에 모듈을 추가한다. 다 한 뒤에 빌드 한번 해주는 것도 잊지 말자.

프로젝트 플러그인 추가

{
	"FileVersion": 3,
	"EngineAssociation": "5.2",
	"Category": "",
	"Description": "",
	"Modules": [
		{
			"Name": "TracesoftheFox",
			"Type": "Runtime",
			"LoadingPhase": "Default",
			"AdditionalDependencies": [
				"Engine",
				"CoreUObject"
			]
		}
	],
	"Plugins": [
		{
			"Name": "ModelingToolsEditorMode",
			"Enabled": true,
			"TargetAllowList": [
				"Editor"
			]
		},
		{
			"Name": "EnhancedInput",
			"Enabled": true
		}
	]
}

모듈 추가

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class TracesoftheFox : ModuleRules
{
	public TracesoftheFox(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Json", "JsonUtilities", "EnhancedInput" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
		
		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}

 

ThirdPerson  iA 삭제

써드 퍼슨을 ADD했다면 기존 ia_ 들을 다 삭제해준다.

삭제 경고 창에서 래퍼런스는 전부 None으로 Replace References 한번 눌러주고 Force를 누른다.

InputAction 에셋 생성

IA_Move를 만든다.

value 타입을 axis 2d로 해서 Move에서 업앤다운, 레프트라이트 2차원으로 관리할 수 있도록 설정 

InputConfig 에셋 생성

플레이어가 쓸 액션들을 관리하는 클래스

c++을 아래와 같이 설정해서 생성해준다. 헤더파일 작성 후 빌드

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "SInputConfigData.generated.h"

/**
 * 
 */
UCLASS()
class TRACESOFTHEFOX_API USInputConfigData : public UDataAsset
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TObjectPtr<class UInputAction> MoveAction;
};

곧이어서 Miscellaneous>Data Asset

방금 만든 부모를 상속한 IC_PlaterCahracter 만들어준다.(Input Config라는 뜻)

InputMappingContext 생성

 

Tirgger가 처음 누르는 순간 + Pressed를 합친 상태일 때 트리거된다(바인드 함수가 호출된다)는 뜻인데,

이동과 맥락이 똑같기 때문에 default 그대로 두면 된다.

(다만 처음 누르는 순간만 필요할 경우에는 세팅을 조금 바꾸어야 한다.)

Modifiers는 일반 인풋 시스템에서 -1, 1 값을 준 것과 유사하다.

WS는 Swizzle시에 X가 앞에,

AD는 Y가 앞으로 오기만 하면 된다.

010으로 받을 것인가, 100으로 받을 것인가 결정해주는 것이기 때문이다.

코드 예시

// Fill out your copyright notice in the Description page of Project Settings.


#include "Characters/SViewCharacter.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "DrawDebugHelpers.h"
#include "Components/InputComponent.h"
#include "GameFramework/Controller.h"
#include "EnhancedInputSubsystems.h" //https://docs.unrealengine.com/5.0/en-US/enhanced-input-in-unreal-engine/
#include "EnhancedInputComponent.h" //https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/Input/EnhancedInput/
#include "Inputs/SInputConfigData.h"

ASViewCharacter::ASViewCharacter()
{
}

void ASViewCharacter::BeginPlay()
{
	Super::BeginPlay();
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		//향상된 인풋 시스템들을 관리하는 단 하나의 싱글톤 매니저 같은 UEnhancedInputLocalPlayerSubsystem
		UEnhancedInputLocalPlayerSubsystem* Subsystem
			= ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer());
		if (true == IsValid(Subsystem)) {
			Subsystem->AddMappingContext(PlayerCharacterInputMappingContext, 0);
		}
	}
}

void ASViewCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
	if (true == ::IsValid(EnhancedInputComponent)) {
		EnhancedInputComponent->BindAction(PlayerCharacterInputConfigData->MoveAction, ETriggerEvent::Triggered, this, &ASViewCharacter::Move);
		EnhancedInputComponent->BindAction(PlayerCharacterInputConfigData->LookAction, ETriggerEvent::Triggered, this, &ASViewCharacter::Look);
	}
}

void ASViewCharacter::Move(const FInputActionValue& InValue)
{
	// 이동시 캐릭터는 위 아래로 쳐다보지 않을 것이므로
	// GetController의 rot에서 pitch가 아닌 Yaw값만 가지고 온다.
	const FRotator ControlRotation = GetController()->GetControlRotation();
	const FRotator YawRotation(0.f, ControlRotation.Yaw, 0.f);

	// GetController의 의도에서 Yaw값만을 추출한 벡터 자체의 회전행렬의 결과 매트릭스에서,
	// 유닛 벡터 중 x축을 ForwardVector로 지정하겠다. 
	const FVector ForwardVector = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	const FVector RightVector = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

	FVector2D MovementVector = InValue.Get<FVector2D>();
	AddMovementInput(ForwardVector, MovementVector.X); // 전, 후
	AddMovementInput(RightVector, MovementVector.Y); // 좌, 우
}

void ASViewCharacter::Look(const FInputActionValue& InValue)
{
	if (nullptr != GetController()) {
		FVector2D LookVector = InValue.Get<FVector2D>();
		AddControllerYawInput(LookVector.X);
		AddControllerPitchInput(LookVector.Y);
	}
}