폰은 액터를 부모 클래스로 가지고 있는 조종할 수 있는 가장 큰 액터이다. 플레이어가 직접 조종할 수도 있고 AI가 조종할 수도 있다.
반면에 캐릭터는 폰을 부모 클래스로 두고 있는 액터로, 기본적인 구현이 미리 되어있다. 하지만 폰과 달리 이족보행을 하는 캐릭터만 구현하기에 용이하다.


우선은 새로운 폰 클래스를 만들어 MyCharacter 라고 이름 지어준다.

이후 블루프린트에 상속시켜준 뒤, VS로 이동.
class UCapsuleComponent;
class USkeletalMeshComponent;
class USpringArmComponent;
class UCameraComponent;
헤더 파일에서는 전방 선언을 해주었고,
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UCapsuleComponent* CapsuleComp;
UPROPERTY(EditAnyWhere, BlueprintReadOnly, Category="Components")
USkeletalMeshComponent* SkeletalMeshComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USpringArmComponent* SpringArmComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UCameraComponent* CameraComp;
컴포넌트들을 모두 선언해주었다.
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
소스 파일에서는 이 4가지 헤더 파일을 인클루드 시켜주고,
CapsuleComp = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComp"));
SetRootComponent(CapsuleComp);
CapsuleComp->SetSimulatePhysics(false);
SkeletalMeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMeshComp"));
SkeletalMeshComp->SetupAttachment(CapsuleComp);
SkeletalMeshComp->SetSimulatePhysics(false);
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
SpringArmComp->SetupAttachment(CapsuleComp);
SpringArmComp->SetRelativeLocation(FVector::ZeroVector);
SpringArmComp->SetRelativeRotation(FRotator(-10.f, 90.f, 0.f));
SpringArmComp->TargetArmLength = 300.0f;
SpringArmComp->bUsePawnControlRotation = false;
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
CameraComp->SetupAttachment(SpringArmComp);
캡슐 컴포넌트를 루트 컴포넌트로 지정해주고 스켈레탈 메시, 스프링 암을 그 아래에 붙여주었다.
캡슐 컴포넌트와 스켈레탈 메시 컴포넌트는 물리 효과를 지워주기 위해 각각 SetSimulatePhsics() 함수를 통해 비활성화 시켜주었다.
스프링 암의 위치를 초기화하고, Pitch 값을 -10.f 로 하여 살짝 위에서 보게 설정하고, Yaw 값을 90.f 만큼 돌려 캐릭터 뒤에서 볼 수 있도록 위치를 조정해주었다.
그리고 스프링 암의 길이는 300.0f 로 지정해줬고, 폰의 자체 회전을 막기 위해 bUsePawnControlRotation = false 로 설정해주었다.
그러고 난 뒤, 스프링 암 아래에 카메라를 붙여줬다.
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
{
if (ULocalPlayer* LocalPlayer = PlayerController->GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
}
}
우선 IMC를 적용하기 위해서 PlayerController 변수를 만들어 가져오고 그걸 LocalPlayer에 담은 뒤, Enhanced Input System 을 적용하기 위해 Subsystem 이라는 변수를 만들어 LocalPlayer 를 적용 시켰다.
그러고 난 뒤, 그걸 IMC 에 적용시켜주었다.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inputs")
UInputMappingContext* DefaultMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inputs")
UInputAction* MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inputs")
UInputAction* LookAction;
그리고 헤더 파일에서 IA 들을 선언시켜주고,
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
if (UEnhancedInputComponent* EnhancedInputComp = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
EnhancedInputComp->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
EnhancedInputComp->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
}
}
소스 파일로 와서 IA 들을 바인딩 시켜주었다.
이제 캐릭터 이동과 회전을 구현할 차례이다.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Movement")
float MoveSpeed;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Movement")
float LookSensitivity;
헤더 파일에서 캐릭터의 이동속도와 마우스 감도 변수를 선언해주고,
MoveSpeed = 10.0f;
LookSensitivity = 1.0f;
소스 파일로 넘어와 생성자에서 기본값을 초기화 시켜주었다.
void AMyCharacter::Move(const FInputActionValue& Value)
{
FVector2D Input = Value.Get<FVector2D>();
FVector Movement = FVector(Input.Y, Input.X, 0.f) * MoveSpeed;
AddActorLocalOffset(Movement, true);
}
void AMyCharacter::Look(const FInputActionValue& Value)
{
FVector2D Input = Value.Get<FVector2D>();
YawInput = Input.X * LookSensitivity;
PitchInput += Input.Y * LookSensitivity;
PitchInput = FMath::Clamp(PitchInput, -89.f, 89.f);
AddActorLocalRotation(FRotator(0.f, YawInput, 0.f));
SpringArmComp->SetRelativeRotation(FRotator(PitchInput, 90.f, 0.f));
}
그리고 헤더 파일에서 선언 해놓은 Move, Look 함수를 소스 파일에서 구현하였다.
Move 함수는 아까 바인딩 해놓은 IA 가 Axis2D(Vector2D) 로 값을 가져오고, WASD 로 입력을 받도록 매핑을 해놓았기 때문에 이 값을 가져올 것이다.
그리고 FVector 로 Movement 를 선언하여 캐릭터 방향대로 방향 값을 가져오고, 이를 MoveSpeed 와 곱하여 이동을 구현하였다.
또한 AddActorLocalOffset 을 이용하여 Movement 의 값을 로컬 방향에 따라 이동하며, 충돌 또한 허용되게 설정했다.
Look 함수 또한 Move 와 같이 값을 가져오며, 마우스 상하좌우를 2D 로 입력 받게 매핑을 해놓았다.
YawInput 은 수평으로 움직이는 마우스의 입력 값을 받기에 Input.X 값을 LookSensitivity 와 곱하여 카메라 수평 회전을 구현하였고,
PitchInput 은 수직으로 움직이는 값을 받기에 똑같이 Input.Y 를 이용해 수직 회전을 구현하였다.
하지만 YawInput 은 그대로 값을 넣고 Pitch는 계속해서 값을 더해주었는데, AddActorLocalRotation() 함수가 Yaw 값을 알아서 더해주기 때문에 그렇게 설정했다.
그 다음으로 PitchInput 을 FMath::Clamp() 함수를 통해 카메라의 수직 회전이 일정 각도를 넘지 않도록 제한을 두었다.
AddActorLocalRotation() 함수는 수평 회전하는 값을 받아 알아서 더하도록 해 카메라 수평 회전을 구현했고,
Pitch 값은 카메라는 어차피 따라오기 때문에 스프링 암의 피치를 조정해 3인칭을 계속 유지할 수 있도록 스프링 암의 SetRelativeRotation() 함수에 PithchInput 값을 넣어 카메라 수직 회전을 구현하였다.

InputAction 으로 Move 와 Look 을 만들어주고, InputMappingContext 를 생성해주었다.

Move, Look 의 Value Type 은 모두 Axis2D (Vector2D) 로 설정해주었다.

또한 IMC 에 IA 를 매핑하여 Move 는 WASD, Look 은 마우스 회전을 통해 입력 받도록 설정해줬다.
그런데 이후 테스트 과정에서 캐릭터가 WASD 뭘 누르든 앞으로만 가는 버그가 있어서 헤메고 있었는데,

Modifiers 를 설정하지 않고 있었다...
우선 캐릭터 메시가 전방이 Y+, 좌측이 X+이기 때문에
W는 Y(+1), A는 X(+1), S는 Y(-1), D는 X(-1) 로 설정해주어야한다.



우선 새로운 GameMode C++ 클래스를 생성해주고, 블루프린트로 상속시켜준다.
딱히 뭔가를 구현하지는 않을 것이기 때문에 코드를 작성하진 않는다.

이후 Project Settings 에서 Maps & Modes 로 들어가 Default Modes > Default GameMode 를 방금 만든 BP_MyGameMode 로 설정해주고, Selected GameMode 를 열어 Default Pawn Class 를 내 캐릭터인 BP_MyCharacter 로 설정해준다.
이러면 게임을 시작할 때 자동으로 내 블루프린트 클래스 캐릭터가 소환된다.
https://drive.google.com/file/d/1O4RC33cdKodoak0Qj84XtpZwH00NFi4D/view?usp=sharing
#1 의 내용과 합쳐놓은 테스트 플레이 영상이다.
액터들은 정상적으로 잘 이동/회전하며, 캐릭터 또한 정상적으로 움직이고 마우스 회전도 잘 작동하는 모습이다.
여러가지 시행 착오들이 있었지만 계속해서 검색하고 찾아보며 해결을 했다.
비록 코드를 직접 적긴 했어도 모두 내가 떠올린 것들은 아니지만, 이런 식으로 계속 코드를 작성하다 보면 언젠간 자연스럽게 이러한 코드들을 손쉽게 사용하지 않을까 싶다.
그리고 til을 작성하는데 모든 구현을 마치고 나서 작성하니 자꾸 누락시키는 부분이 있는 것 같다.
내가 구현한 코드들을 모두 til에 작성했는지도 의심되고, 이전에 작성했던 코드들을 모두 떠올리기가 힘들었다.
다음부턴 실시간으로 til을 작성하며 내가 어느 부분에서 실수를 했고, 어떤 생각을 하며 수정했는 지 파악해야할 것 같다.
https://github.com/dfdeer/HW06-07.git
추가로 여긴 내가 구현한 코드를 저장해놓기 위해 만든 깃 주소이다.