게임 개발/언리얼 5

언리얼5 - 캐릭터 컨트롤 설정

싹난 감자 2024. 11. 25. 09:21

마네킹 캐릭터를 두 가지의 서로 다른 구도로 컨트롤하는 기능 구현.

 


캐릭터 컨트롤 요소

캐릭터 컨트롤

일반적으로 컨트롤러와 폰, 카메라, 스프링암, 캐릭터 무브먼트의 다섯 가지 요소를 사용해 설정.

컨트롤러 : 입력자의 의지(목표 지점)을 지정할 때 사용. ControlRotation 속성

폰 : 폰의 트랜스폼을 지정

카메라 : 화면 구도를 설정하기 위해 사용. 주로 1인칭 시점에서 사용

스프링 암 : 화면 구도를 설정하기 위해 사용. 주로 3인칭 시점에서 사용

캐릭터 무브먼트 : 캐릭터의 이동과 회전을 조정하는 용도롤 사용

Desired Rotation : 회전해야할 최종 목표, 의지를  나타내는 회전 값

Rotation : 현재 회전된 상태

현재 Rotation 값을 Desired Rotation 값으로 덮어 씌우면 캐릭터가 그 방향으로 회전하게 됨.

보통 바로 돌아버리면 부자연스러우니 Desired Rotation을 설정하고 필요 시에 Rotation Rate로 지정된 각속도로 서서히 회전하도록 구현함

언리얼이 제공하는 설정 값을 이용하면 자연스럽게 구현할 수 있음

 

폰의 이동 함수

Control Rotation이라는 컨트롤러의 속성값을 활용해 이동과 시점을 조절.

Look 함수 : 마우스 입력으로부터 컨트롤러의 컨트롤 회전을 설정

Move 함수 : 컨트롤러의 컨트롤 회전으로부터 Yaw값을 참고해 이동 방향을 설정

콘솔 커맨드 창(단축키 ~)으로 부터 Control Ratatoin 값을 확인할 수 있음.

 

Look 함수

// 컨트롤러의 회전을 설정함으로써 스프링암이 해당 컨트롤러를 바라보도록 설정
void AABCharacterPlayer::Look(const FInputActionValue& Value)
{
    FVector2D LookAxisVector = Value.Get<FVector2D>();

    AddControllerYawInput(LookAxisVector.X);
    AddControllerPitchInput(LookAxisVector.Y);
}

AddControllerYawInput에 값이 들어오면 [ABCharacterPlayer.cpp]  플레이어 컨트롤러의 포인터를 가져와서 AddYawInput으로 값을 넣어줌  [Pawn.cpp]    AddYawInput에 들어온 입력값은 RotationInput 속성의 Yaw 회전 값에 추가. ( RotationInput.Yaw += ) [PlayerController.cpp]   Rotation InputDaltaRot (FRotator)을 구축하는데 사용됨. [PlayerController.cpp]  DeltaRotViewRotation (FRotator) 이라는 값에 기여. [PlayerController.cpp] View Rotation 값이 완성되면 SetControlRataion 함수에 넣음 [Controller.cpp]  SetControlRatation에 들어온 값(NewRotation)은 최종적으로 ControlRotation이라는 값을 새로운 로테이션 값(NewRotation)으로 덮어씌움. [Controller.cpp]

 

AddControllerYawInput과 AddControllerPitchInput과 같은 함수들은 입력값을 받아서 컨트롤러의 Control Rotation 속성을 업데이트하는데 사용됨.

 

Move 함수

// InputActionValue에서 XY값을 가져와 무브먼트 컴포넌트와 연결
// 실질적으로 캐릭터를 이동
void AABCharacterPlayer::Move(const FInputActionValue& Value)
{
    FVector2D MovementVector = Value.Get<FVector2D>();

    const FRotator Rotation = Controller->GetControlRotation();
    const FRotator YawRotation(0, Rotation.Yaw, 0);

    const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

    AddMovementInput(ForwardDirection, MovementVector.X);
    AddMovementInput(RightDirection, MovementVector.Y);

}

const FRotator Rotatoin = Controller->GetControlRotation();

업데이트된 컨트롤 로테이션 값을 가져와서 해당 로테이션을 기점으로 전진 방향(ForwardDirection)과 오른쪽 방향(RightDirection)을 얻어오고 AddMovementInput 함수를 통해 캐릭터를 이동시킴.

 

 

 

콘솔 커맨드 창으로 Control Ratation 값 확인하기

 

단축키 ~ 키를 눌러 콘솔 커맨드 창을 열고 명령어 입력

DisplayAll PlayerController(클래스 이름) ControlRotation(속성 이름)

 

해당 속성 값이 마우스 입력에 따라서 어떻게 변화되는지 창으로 확인할 수 있음.

 

 

캐릭터 컨트롤을 설정하는데 필요한 각 속성

ABCharacterPlayer를 상속받는 블루프린트 생성

 

폰의 컨트롤 옵션

Use Controller Rotation (Yaw/Roll/Pitch)

컨트롤러에 지정된 Control Rotation 값에 폰의 Rotation을 맞출 것인지를 체크해주는 옵션.

이 옵션을 켜면 폰의 회전은 컨트롤러의 Control Rotation과 동기화됨.

폰의 회전이 Control Rotation 회전값을 그대로 따름.

필요한 경우 사용.

 

스프링 암의 컨트롤 옵션

Use Pawn Control Rotation

폰의 컨트롤 회전 (컨트롤러의 Control Rotation)을 사용할 것인가?

  • 스프링암의 회전이 Control Rotation과 동기화되도록 설정
  • 옵션이 체크되어 있기 때문에 마우스를 움직일 때 스프링암도 같이 회전함.

Inherit (Yaw / Roll / Pitch)

부모 컴포넌트 (주로 RootComponent)의 회전을 그대로 따를 것인가?

  • 부모 컴포넌트의 회전값을 추가로 상속받아 최종 회전을 구현

Do Collision Test

스프링암 중간에 장애물이 있으면 앞으로 당길 것인가? (Camera라는 트레이스 채널을 사용)

  • 3인칭 시점 설정에 주로 사용

 

카메라의 컨트롤 옵션

Use Pawn Control Rotatoin

폰의 컨트롤 회전 (컨트롤러의 Control Rotation)을 사용할 것인가

스프링암에 달린 카메라의 회전이 컨트롤러의 Contro Rotation 값과 동기화되어 회전

스프링암의 회전을 함께 고려.

1인칭 카메라 회전에 주로 사용

 

캐릭터 무브먼트의 이동 옵션

 Movement Mode : None, Walking, Falling

땅(Ground) 위에 있으면 Walking 모드로 동작

땅 위에 없으면 Falling 모드로 전환

수동으로 이동 기능을 끄고 싶으면 None 모드

 

일반적으로 이동할 때는 Walking 모드

이동 모드에서 이동 최대 값 수치는 MaxWalkingSpeed 속성 사용

폴링 모드에서 많이 사용되는 점프를 최초로 도약할 때는 JumpZVelocity 속성의 수치 사용

 

캐릭터 무브먼트의 회전 옵션

Rotation Rata : 회전 속도 지정

Use Controller Desired Rotation : 컨트롤 방향을 목표 회전으로 삼고 지정한 속도로 돌리기

Orient Rotation To Movement : 캐릭터 이동 방향에 회전을 일치시키기.

 

폰의 회전 옵션과 충돌이 나지 않도록 주의.

 

데이터 애셋

UDataAsset을 상속받은 언리얼 오브젝트 클래스

에디터에서 애셋 형태로 편리하게 데이터를 관리할 수 있음.

흩어져있는 캐릭터 컨트롤에 관련된 주요 옵션을 모아 애셋으로 관리.

 


 

기본적인 데이터 애셋에 조금 더 기능이 확장된 PrimaryDataAsset 클래스를 상속받은 ABCharacterControlData 클래스 생성

 

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

#pragma once

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

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABCharacterControlData : public UPrimaryDataAsset
{
	GENERATED_BODY()
	
public:
	UABCharacterControlData();

	// Pawn
	// 캐릭터의 경우 주로 Control Rotation의 Yaw값을 체크함
	// 해당 값을 체크할지 안할지 결정하기 위한 변수
	UPROPERTY(EditAnywhere, Category = Pawn)
	uint32 bUseControllerRotationYaw : 1;

	// Movement
	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint32 bOrientRotationToMovement : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint32 bUseControllerDesiredRotation : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	FRotator RotationRate;
	
	// 앞으로 사용할 입력 매핑 컨텍스트
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	TObjectPtr<class UInputMappingContext> InputMappingContext;

	// SpringArm
	UPROPERTY(EditAnywhere, Category = SpringArm)
	float TargetArmLength;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	FRotator RelativeRotation;
	
	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bUsePawnControlRotaion : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritPitch : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritYaw : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritRoll : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bDoCollisionTest : 1;

};

ABCharacterControlData.h

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


#include "Character/ABCharacterControlData.h"

UABCharacterControlData::UABCharacterControlData()
{
    TargetArmLength = 400.0f;
}

ABCharacterControlData.cpp

 

빌드 후 CharacterControl 폴더 만들고 우클릭 - Miscellaneous - Data Asset

작성한 ABCharacterControlData 선택하고 ABC_Quater와 ABC_Shoulder라는 이름으로 데이터 애셋 생성

 

각각 값 설정


데이터 애셋의 관리

두 가지의 컨트롤 모드를 제공

  • 현재 구현된 컨트롤 모드 : 3인칭 숄더뷰
  • 추가로 구현할 컨트롤 모드 : 3인칭 쿼터뷰

입력키 V를 통해 컨트롤 설정을 변경

ENUM을 통해 두 개의 컨트롤 데이터를 관리

 

데이터 애셋의 구성과 적용

관리자로부터 내가 적용할 컨트롤 데이터가 정해지면 각 섹션에 따라 정해진 값을 적용.

각 섹션별로 데이터를 저장

  • Pawn 카테고리
  • 캐릭터무브먼트 카테고리
  • 입력 카테고리
  • 스프링암 카테고리

Pawn과 캐릭터 무브먼트 데이터는 CharacterBase에서 설정

입력과 스프링암 데이터는 CharacterPlayer에서 설정

 

 


기본 함수 구현

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ABCharacterBase.generated.h"

// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
	Shoulder,
	Quater
};

UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter
{
	GENERATED_BODY()

public:
	AABCharacterBase();

protected:
	// 캐릭터 컨트롤 데이터 애셋을 입력으로 받음
	// Pawn과 Movement 데이터 설정
	virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData);

	// 두 가지 애셋 오브젝트를 얻어올 Map
	UPROPERTY(EditAnywhere, Category = CharacterControl, Meta = (AllowPrivateAccess = "true"))
	TMap<ECharacterControlType, class UABCharacterControlData*> CharacterControlManager;

};

ABCharacterBase.h

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


#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"

// Sets default values
AABCharacterBase::AABCharacterBase()
{
    ...
    
    // CharacterControlManager(Map)에 두 가지 컨트롤 데이터 추가
    static ConstructorHelpers::FObjectFinder<UABCharacterControlData> ShoulderDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Shoulder.ABC_Shoulder'"));
    if (ShoulderDataRef.Object)
    {
        CharacterControlManager.Add(ECharacterControlType::Shoulder, ShoulderDataRef.Object);
    }

    static ConstructorHelpers::FObjectFinder<UABCharacterControlData> QuaterDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Quarter.ABC_Quarter'"));
    if (QuaterDataRef.Object)
    {
        CharacterControlManager.Add(ECharacterControlType::Quater, QuaterDataRef.Object);
    }

}

// 컨트롤 데이터 세팅
void AABCharacterBase::SetCharacterControlData(const UABCharacterControlData* CharacterControlData)
{
    
    // Pawn
    bUseControllerRotationYaw = CharacterControlData->bUseControllerRotationYaw;

    // CharacterMovement
    GetCharacterMovement()->bOrientRotationToMovement = CharacterControlData->bOrientRotationToMovement;
    GetCharacterMovement()->bUseControllerDesiredRotation = CharacterControlData->bUseControllerDesiredRotation;
    GetCharacterMovement()->RotationRate = CharacterControlData->RotationRate;
}

ABCharacterBase.cpp

 

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

#pragma once

#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "InputActionValue.h"
// EnhancedInput 모듈 추가해야함
// IntelliSense가 인식하지 못할 경우
// Intermediate와 Binaries폴더를 삭제한 후 프로젝트 재생성
#include "ABCharacterPlayer.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase
{
	GENERATED_BODY()
	
public:
	AABCharacterPlayer();

	...
    
// Character Control Section
protected:
	// CharacterBase에서 상속받은 SetCharacterControlData 오버라이드해서
	// 스프링암 설정
	virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData) override;

	...

};

ABCharacterPlayer.h

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


#include "Character/ABCharacterPlayer.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
// 입력 설정 관련 헤더
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h" // Subsystem's'임
#include "ABCharacterControlData.h"


AABCharacterPlayer::AABCharacterPlayer()
{
	...
}

// 컨트롤 데이터 세팅
void AABCharacterPlayer::SetCharacterControlData(const UABCharacterControlData* CharacterControlData)
{
    Super::SetCharacterControlData(CharacterControlData);

    // SpringArm
    CameraBoom->TargetArmLength = CharacterControlData->TargetArmLength;
    CameraBoom->SetRelativeRotation(CharacterControlData->RelativeRotation);
    CameraBoom->bUsePawnControlRotation = CharacterControlData->bUsePawnControlRotation;
    CameraBoom->bInheritPitch = CharacterControlData->bInheritPitch;
    CameraBoom->bInheritYaw = CharacterControlData->bInheritYaw;
    CameraBoom->bInheritRoll = CharacterControlData->bInheritRoll;
    CameraBoom->bDoCollisionTest = CharacterControlData->bDoCollisionTest;

}



void AABCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	...    
}

void AABCharacterPlayer::Move(const FInputActionValue& Value)
{
	...
}

void AABCharacterPlayer::Look(const FInputActionValue& Value)
{
	...
}

ABCharacterPlayer.cpp

 


뷰의 전환

컨트롤을 변경할 때 서로 다른 입력 매핑 콘텍스트를 지정

숄더뷰 : 캐릭터의 움직임과 카메라 시점을 조종, ShoulderMove와 ShoulderLook 사용

쿼터뷰 : 캐릭터의 움직임만 조종, QuaterMove만 사용

입력 액션을 사용해 변경

 


입력에 따른 뷰 전환 구현

IA_Jump 버튼에 대한 입력을 복제해서 IA_ChangeControl 생성

InputMappingContext에 IA_ChangeControl 등록, 키보드 V 매핑

 

기존에 작업했던 Move와 Look은 ShoulderMove와 ShoulderLook로 변경, ShoulderMove를 복제해 QuaterMove생성.

IMC_Default(InputMappingContext)도 IMC_Shoulder로 변경 후 복제해 IMC_Quater까지 생성.

 

ShoulderMove는 QuaterMove로 바꾸고, ShoulderLook은 삭제.

 

Data Asset의 IMC도 각각에 맞게 설정.

 

 

각 InputMappingContext를 사용해 V키를 눌렀을 때 숄더 매핑 컨텍스트와 쿼터 매핑 컨텍스트가 런타임에서 전환되도록 구현.

 

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

#pragma once

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

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABCharacterControlData : public UPrimaryDataAsset
{
	GENERATED_BODY()
	
public:
	UABCharacterControlData();

	// Pawn
	// 캐릭터의 경우 주로 Control Rotation의 Yaw값을 체크함
	// 해당 값을 체크할지 안할지 결정하기 위한 변수
	UPROPERTY(EditAnywhere, Category = Pawn)
	// C++의 비트필드(Bit Field) 문법
	// 크기가 지정되어 있는 타입일지라도 : 구문을 사용해
	// 해당 변수가 차지하는 비트 크기를 지정
	uint32 bUseControllerRotationYaw : 1;

	// Movement
	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint32 bOrientRotationToMovement : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint32 bUseControllerDesiredRotation : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	FRotator RotationRate;
	
	// 앞으로 사용할 입력 매핑 컨텍스트
	// 컨트롤 데이터를 선택했을 때 컨트롤 데이터가 가지고 있는 InputMappingContext를 
	// 런타임에서 바꿀 수 있도록 선언.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	TObjectPtr<class UInputMappingContext> InputMappingContext;

	// SpringArm
	UPROPERTY(EditAnywhere, Category = SpringArm)
	float TargetArmLength;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	FRotator RelativeRotation;
	
	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bUsePawnControlRotation : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritPitch : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritYaw : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritRoll : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bDoCollisionTest : 1;

};

ABCharacterControlData.h

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

#pragma once

#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "InputActionValue.h"
// EnhancedInput 모듈 추가해야함
// IntelliSense가 인식하지 못할 경우
// Intermediate와 Binaries폴더를 삭제한 후 프로젝트 재생성
#include "ABCharacterPlayer.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase
{
	GENERATED_BODY()
	
public:
	AABCharacterPlayer();

public:
	// 언리얼 엔진의 Input System에서 입력 액션과 우리가 선언한 함수(Move, Look)를 서로 매핑시켜주는 역할
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:
	// 입력 매핑 컨텍스트를 할당하는 역할
	// 입력을 키보드로 받을지, 패드로 받을지에 대해서는 
	// BeginPlay의 DefaultMappingContext를 통해 진행.
	virtual void BeginPlay() override;

// Character Control Section
protected:
	// V를 누르면 컨트롤러를 바꾸는 함수
	void ChangeCharacterControl();
	// 컨트롤러 변경이 발생했을 때 컨트롤에 관련된 모든 설정을 진행해주는 함수
	void SetCharacterControl(ECharacterControlType NewCharacterControlType);

	// CharacterBase에서 상속받은 SetCharacterControlData 오버라이드해서 스프링암 설정
	virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData) override;


// Camera 설정
protected:
	// UPROPERTY()의 Meta 지정자: private으로 선언된 언리얼 오브젝트 객체들을 블루프린트에서도 접근할 수 있게 함
	// 스프링 암. 카메라를 지탱해주는 지지대 역할 컴포넌트
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Camera, Meta=(AllowPrivateAccess = "true"))
	TObjectPtr<class USpringArmComponent> CameraBoom;
	// 실제 카메라 컴포넌트
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Camera, Meta=(AllowPrivateAccess = "true"))
	TObjectPtr<class UCameraComponent> FollowCamera;

// Input 설정
// 기존에 매핑했던 애셋들은 Jump를 제외하고 모두 바뀌었기 때문에 재조정.
protected:
	// 매핑 컨텍스트와 액션에 대한 애셋 지정
	// 다른 애셋으로 변경할 수 있도록 설계하기 위해 EditAnywhere로 설정
	// 1가지 매핑 컨텍스트와 3가지 액션. 3인칭 템플릿에서 제공하고 있는 것들을 사용
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
	TObjectPtr<class UInputAction> JumpAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
	TObjectPtr<class UInputAction> ChangeControlAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
	TObjectPtr<class UInputAction> ShoulderMoveAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
	TObjectPtr<class UInputAction> ShoulderLookAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
	TObjectPtr<class UInputAction> QuaterMoveAction;


	// 각 입력 액션에 대해서 매핑된 함수
	// FInputActionValue 구조체.
	void ShoulderMove(const FInputActionValue& Value);
	void ShoulderLook(const FInputActionValue& Value);

	void QuaterMove(const FInputActionValue& Value);

	// 현재 뷰 상태를 확인하기 위한 변수
	ECharacterControlType CurrentCharacterControlType;
};

ABCharacterPlayer.h

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


#include "Character/ABCharacterPlayer.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
// 입력 설정 관련 헤더
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h" // Subsystem's'임
#include "ABCharacterControlData.h"


AABCharacterPlayer::AABCharacterPlayer()
{
    // 카메라
    // 뼈대 오브젝트 생성
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    // 루트 컴포넌트에 부착
    CameraBoom->SetupAttachment(RootComponent);
    // 4미터 길이로 설정
    CameraBoom->TargetArmLength = 400.0f;
    // 회전 설정
    CameraBoom->bUsePawnControlRotation = true;

    // 카메라 생성
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    // 스프링 암에 부착
    // 부착할 때 특정 위치를 지정하는 것이 아니라 소켓이라는 특별한 이름 지시자를 지정하면
    // 스프링 암의 끝에 자동으로 붙음
    // F12로 확인해보면 스프링 암의 소켓이름은 SpringEndpoint.
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
    // 회전 설정
    FollowCamera->bUsePawnControlRotation = false;

    // Input(입력)
    // 기존에 매핑했던 애셋들은 Jump를 제외하고 모두 바뀌었기 때문에 재조정
    static ConstructorHelpers::FObjectFinder<UInputAction> InputActionJumpRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Jump.IA_Jump'"));
    if (InputActionJumpRef.Object)
    {
        JumpAction = InputActionJumpRef.Object;
    }

    static ConstructorHelpers::FObjectFinder<UInputAction> InputActionChangeControlRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_ChangeControl.IA_ChangeControl'"));
    if (InputActionChangeControlRef.Object)
    {
        ChangeControlAction = InputActionChangeControlRef.Object;
    }

    static ConstructorHelpers::FObjectFinder<UInputAction> InputActionShoulderMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_ShoulderMove.IA_ShoulderMove'"));
    if (InputActionShoulderMoveRef.Object)
    {
        ShoulderMoveAction = InputActionShoulderMoveRef.Object;
    }

    static ConstructorHelpers::FObjectFinder<UInputAction> InputActionShoulderLookRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_ShoulderLook.IA_ShoulderLook'"));
    if (InputActionShoulderLookRef.Object)
    {
        ShoulderLookAction = InputActionShoulderLookRef.Object;
    }

    static ConstructorHelpers::FObjectFinder<UInputAction> InputActionQuaterMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_QuaterMove.IA_QuaterMove'"));
    if (InputActionQuaterMoveRef.Object)
    {
        QuaterMoveAction  = InputActionQuaterMoveRef.Object;
    }

    CurrentCharacterControlType = ECharacterControlType::Quater;


}

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

    SetCharacterControl(CurrentCharacterControlType);
}

// V키를 눌러 컨트롤러를 변경하는 함수
void AABCharacterPlayer::ChangeCharacterControl()
{
    if (CurrentCharacterControlType == ECharacterControlType::Quater)
    {
        SetCharacterControl(ECharacterControlType::Shoulder);
    }
    else if (CurrentCharacterControlType == ECharacterControlType::Shoulder)
    {
        SetCharacterControl(ECharacterControlType::Quater);
    }
}


// 컨트롤러가 변경됐을 때 모든 설정을 진행하는 함수
void AABCharacterPlayer::SetCharacterControl(ECharacterControlType NewCharacterControlType)
{
    // 상위 클래스에 선언된 ControlManager에 있는 컨트롤 데이터 애셋 가져오기
    UABCharacterControlData* NewCharacterControl = CharacterControlManager[NewCharacterControlType];
    check(NewCharacterControl);

    // 기본 속성 호출
    SetCharacterControlData(NewCharacterControl);

    // 컨트롤러가 플레이어를 대상으로 설계된 전용 캐릭터 클래스이기 때문에 CastChecked 사용
    APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
    // Subsystem이라는 InputSystem을 가져와서 매핑 컨텍스트 애셋을 추가
    if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
    {
        // 기존에 있던 모든 Input Mapping Context를 모두 제거
        Subsystem->ClearAllMappings();

        // 새로 ControlManager에서 가져온 데이터 애셋으로 MappingContext를 바꾸기
        UInputMappingContext* NewMappingContext = NewCharacterControl->InputMappingContext;
        if (NewMappingContext)
        {
            // 우선순위를 지정해 입력이 서로 겹칠 때도 우선순위가 높은 입력에 대해
            // 액션들이 바인딩에서 수행할 수 있도록 지정할 수 있음
            Subsystem->AddMappingContext(NewMappingContext, 0);

            // 언제든지 자유롭게 RemoveMappingContext를 사용해 런타임에서 빼거나 추가할 수 있다.
            //Subsystem->RemoveMappingContext(DefaultMappingContext);
        }
    }

    // 모든 변경이 완료되면 현재의 컨트롤 데이터 타입을 변경
    CurrentCharacterControlType = NewCharacterControlType;
}

// 컨트롤 데이터 세팅
void AABCharacterPlayer::SetCharacterControlData(const UABCharacterControlData* CharacterControlData)
{
    Super::SetCharacterControlData(CharacterControlData);

    // SpringArm
    CameraBoom->TargetArmLength = CharacterControlData->TargetArmLength;
    CameraBoom->SetRelativeRotation(CharacterControlData->RelativeRotation);
    CameraBoom->bUsePawnControlRotation = CharacterControlData->bUsePawnControlRotation;
    CameraBoom->bInheritPitch = CharacterControlData->bInheritPitch;
    CameraBoom->bInheritYaw = CharacterControlData->bInheritYaw;
    CameraBoom->bInheritRoll = CharacterControlData->bInheritRoll;
    CameraBoom->bDoCollisionTest = CharacterControlData->bDoCollisionTest;

}


// 입력 설정
void AABCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    // 헤더 추가
    // EnhancedInputComponent를 사용하지 않은 경우 에러를 발생시키도록 CastChecked 함수 사용
    // 반드시 EnhancedInputComponent를 사용하도록 함.
    UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);

    // 정상적으로 캐스팅되었다면 함수와 바인딩
    // Move와 Look은 직접 구현, Jump와 StopJumping은 캐릭터가 제공하는 함수와 연결
    EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
    EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
    EnhancedInputComponent->BindAction(ChangeControlAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ChangeCharacterControl);
    EnhancedInputComponent->BindAction(ShoulderMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderMove);
    EnhancedInputComponent->BindAction(ShoulderLookAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderLook);
    EnhancedInputComponent->BindAction(QuaterMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::QuaterMove);
}

// InputActionValue에서 XY값을 가져와 무브먼트 컴포넌트와 연결
// 실질적으로 캐릭터를 이동
void AABCharacterPlayer::ShoulderMove(const FInputActionValue& Value)
{
    FVector2D MovementVector = Value.Get<FVector2D>();

    // 업데이트된 컨트롤 로테이션 값을 가져와서
    const FRotator Rotation = Controller->GetControlRotation();

    const FRotator YawRotation(0, Rotation.Yaw, 0);

    // 해당 로테이션을 기점으로 전진 방향(ForwardDirection)과 오른쪽 방향(RightDirection)을 얻어옴
    const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

    // 얻은 방향값으로 AddMovementInput 함수를 통해 캐릭터를 이동시킴.
    AddMovementInput(ForwardDirection, MovementVector.X);
    AddMovementInput(RightDirection, MovementVector.Y);

}

// 컨트롤러의 회전을 설정함으로써 스프링암이 해당 컨트롤러를 바라보도록 설정
void AABCharacterPlayer::ShoulderLook(const FInputActionValue& Value)
{
    FVector2D LookAxisVector = Value.Get<FVector2D>();

    // 입력값으로 컨트롤러의 Control Rotation 속성을 업데이트함.
    AddControllerYawInput(LookAxisVector.X);
    AddControllerPitchInput(LookAxisVector.Y);
}

void AABCharacterPlayer::QuaterMove(const FInputActionValue& Value)
{
    // 현재 MovementVector를 받아옴
    FVector2D MovementVector = Value.Get<FVector2D>();

    // 크기가 1이 되도록 조정
    float InputSizeSquared = MovementVector.SquaredLength();
    float MovementVectorSize = 1.0f;
    float MovementVectorSizeSquared = MovementVector.SquaredLength();
    if (MovementVectorSizeSquared > 1.0f)
    {
        MovementVector.Normalize();
        MovementVectorSizeSquared = 1.0f;
    }
    else
    {
        MovementVectorSize = FMath::Sqrt(MovementVectorSizeSquared);
    }

    FVector MoveDirection = FVector(MovementVector.X, MovementVector.Y, 0.0f);
    // ControlRotation을 Forward 방향을 사용해 지정해주면
    // 무브먼트 컴포넌트에서 설정한 옵션에 의해 캐릭터가 자동으로 이동하는 방향을 향해 회전함.
    GetController()->SetControlRotation(FRotationMatrix::MakeFromX(MoveDirection).Rotator());
    AddMovementInput(MoveDirection, MovementVectorSize);



}

ABCharacterPlayer.cpp

 

잘 움직이고 v키로 시점 변환까지 되는 것을 확인할 수 있음.

다만 V키를 눌렀을 때 이벤트가 굉장히 많이 발생해 화면이 깜빡거림.

InputAction의 ChangeControl Action의 Trigger를 Pressed로 설정해 눌렀을 때 정확하게 한번만 동작하도록 할 수 있음.

 

 


https://www.inflearn.com/course/%EC%9D%B4%EB%93%9D%EC%9A%B0-%EC%96%B8%EB%A6%AC%EC%96%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-part-2/dashboard

 

이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 강의 | 이득우 - 인프런

이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 C++ 프로그래밍을 사용해 핵&슬래시 로그라이크 게임 예제를 처음부터 끝까지 체계적으로 제작하는 방법을

www.inflearn.com