[Lost Kingdom] 개발일지 - 4

조재훈·2024년 3월 20일

개발일지 - 4

어제 못했던 캐릭터를 이어서 설정해보자. 어제까지는 캐릭터의 베이스가 될 클래스를 만들었는데 결과가 공중에 떠있어서 문제가 있었다

CharacterBase 수정

이것을 고치려면 메시의 트랜스폼들을 조절해야할 것 같은데 사실 에디터에서 보면서 직관적으로 수정하는게 좋으니까 C++ 클래스를 블루프린트 클래스가 상속받게 해서 에디터에서 수정해보자

Character 블루프린트 클래스를 만들어 Base 클래스를 상속받게 했다

클래스 세팅에서 설정

블루프린트 클래스를 열어보자(메시가 안보이면 에디터 껐다키기)

지금 문제가 메시가 캡슐 컴포넌트의 중간점에 위치해있고 저기 보이는 화살표가 캐릭터의 전방을 나타내는데 메시는 다른 곳을 보고 있다

  • 메시의 Transform 값을 바꾸자!
    • 적당히 메시의 발바닥이 캡슐의 바닥과 일치하게끔 z값을 조절한다
    • 시계 반대 방향으로 90도 Yaw 회전해야하니까 Rotation의 z값을 -90도로 설정

드디어 똑바로 보게 되었다. 이제 게임 화면을 확인해보자! 근데 그전에 뭐 빼먹었지 않나? 바로 게임 모드의 DefaultPawn을 만든 블루프린트 클래스로 바꿔주기. 생성자 바꾸는거니까 또 껐다 켜야 초기화됨(귀찮다 귀찮아)

이제 바닥에 잘 붙어있다. 애니메이션은 언리얼에서 기본으로 제공하는 ABP_QUEEN 사용했다 일단은(나중에 바꿀 것)

이제 플레이어 캐릭터를 만들어서 조작 잘 되는지 확인을 해보자

PlayerCharacter

LKPlayerCharacter 클래스를 생성해준다.

가장 먼저 필요한 작업은 플레이어 캐릭터가 월드에서 돌아다니는 것을 관찰하는 카메라를 달아줘야겠다. 1인칭 게임이면 카메라만 달아줘도 괜찮지만 내가 만드려는 게임은 로스트아크처럼 멀리서 캐릭터에 고정되어 위에서 아래로 내려다보는 식이니까 스프링 암을 이용해서 카메라와 플레이어 캐릭터 사이를 연결하자

먼저 헤더파일에서 카메라 컴포넌트와 스프링암 컴포넌트를 담을 변수를 만들자

protected:
	/** Spring Arm */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;
	/** Top down camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FollowCamera;

그리고 이제 생성자 부분으로 가자. 먼저 스프링 암 설정 부분이다

ALKPlayerCharacter::ALKPlayerCharacter()
{
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->SetUsingAbsoluteRotation(true);
	CameraBoom->TargetArmLength = 800.f;
	CameraBoom->SetRelativeRotation(FRotator(-60.f, 0.f, 0.f));
	CameraBoom->bDoCollisionTest = false; 
}
  • CreateDefaultSubobject
    • 클래스의 생성자에서 컴포넌트를 초기화할 때 사용. CDO의 인스턴스가 생성된다
  • SetupAttachment(RootComponent)
    • 이 스프링암 컴포넌트를 클래스의 루트 컴포넌트 아래 자식으로 붙이겠다는 얘기다. 보통 스프링암을 루트 컴포넌트 아래에 붙인다더라
  • SetUsingAbsoluteRotation(true)
    • 스프링암 컴포넌트가 절대회전을 사용할지(true), 상대회전을 사용할지(false)를 결정
    • true로 설정하면 스프링암이 상위 액터의 회전에 영향을 받지 않고 자체 절대 회전을 하게 된다
    • 카메라가 캐릭터 회전과 독립적으로 작동. 3인칭 게임에서 캐릭터의 방향에 관계없이 카메라는 플레이어 캐릭터만 바라보고 있게 할 수 있음
  • TargetArmLength은 스프링암 길이를 정하는 부분. 즉 얼마나 멀리서 볼 것이냐
  • SetRelativeRotation는 캐릭터와의 각도? 캐릭터를 비스듬하게 멀리서 볼 수 있을 것이고 아예 탑-뷰로 가면 Pitch -90도를 하게 되면 캐릭터의 정수리만 찍겠지
  • bDoCollisionTest
    • 스프링암을 다른 액터들과 충돌 테스트할거냐는 의미인데 true를 설정하고 만약 지형에 의해 플레이어 캐릭터가 카메라에 가려져 안보이면(커다란 벽이 있거나) 카메라를 캐릭터를 찍을 수 있게 보이는 곳 가까이 와서 당겨진다(길이를 800으로 설정했었는데 극단적으로 0까지 줄어들 수 있다는 의미)
    • 그렇게 되면 지형을 클릭할 수 없게 되어 캐릭터를 이동 못시킨다. 그러니까 false로 하는 편

이제 카메라를 세팅해보자

FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
  • 첫 번째는 생략, 두 번째는 SetupAttachment을 다시 사용해서 CameraBoom에 붙일건데 두 번째 매개변수가 추가되었다
    • 이렇게 소켓 이름을 넣으면 카메라가 스프링암의 끝 부분에 붙여진다
    • 언리얼 엔진에서 소켓은 다른 컴포넌트를 부착할 수 있는 컴포넌트의 위치를 나타낸다(예시로 캐릭터가 무기를 들 때 손에 딱 맞게 설정하듯이)
  • bUsePawnControlRotation
    • 카메라가 폰이 회전할 때 같이 회전되는 옵션같음. 3인칭 게임에서 캐릭터가 방향을 틀면 카메라도 그 방향으로 회전해서 전방이 일치하도록
    • 내가 만드는 게임은 카메라 고정이라 false

이제 생성자 부분을 건드렸으니 에디터 끄고 빌드 후 실행해서 세세하게 조정해보자
아까 만든 블루프린트 클래스 부모를 플레이어 캐릭터 클래스로 바꾸자

제대로 카메라가 플레이어 캐릭터를 촬영한다

다음으로 추가할 기능은 로스트아크에서 플레이어 캐릭터를 조종하다가 내 캐릭터가 예뻐서 확대해서 보고싶을 때 마우스 휠을 올리면 캐릭터 앞으로 카메라가 이동해 확대되고 휠을 내리면 다시 원상태로 돌아가는 기능이 있다

입력 시스템에서 휠 올리고 내리는 이벤트를 추가해 보간을 통해 스프링암을 조종하면 될 것 같으니까 해보자

플레이어의 입력(Input)은 플레이어 컨트롤러에 있으니까 마우스 휠을 움직이는 액션을 추가해보자

// LKPlayerController.h
/** Zoom Action **/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* ZoomAction;

이렇게 하면 이 클래스를 상속받은 블루프린트 클래스 컨트롤러에 Action을 매핑할 수 있다

이렇게 Action을 만들고 Value Type을 Axis1D로 하면 휠을 위로 돌렸는지 아래로 돌렸는지 값을 받아올 수 있음

이것을 블루프린트 클래스에 ZoomAction에 매핑하고 이 액션과 매핑할 함수들을 선언하자

/* Zoom */
void OnZoomTriggered(const FInputActionValue& Value);
void OnZoomIn();
void OnZoomOut();

액션과 매핑할 함수는 OnZoomTriggered 함수인데 여기서 진짜 주의할 점으로 매개변수 저 형식을 그대로 따라하자
처음에 뭣도 모르고 axis의 float값을 받아오는 거니까 void OnZoomTriggered(float Value);로 하면 되겠지? 하고 했다가 에러나서 구글링해도 정확하게 알려주지 않았다. 어디서 에러나는지는 밑에서....

액션과 함수를 매핑하는 함수는 바로

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

	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent))
	{
    	...
		EnhancedInputComponent->BindAction(ZoomAction, ETriggerEvent::Triggered, this, &ALKPlayerController::OnZoomTriggered);
	}
}

ZoomAction이 Triggered 되었을 때 OnZoomTriggered 함수를 실행한다라는 매핑이다
만약 위에서 말했듯이 매개변수 형식을 제대로 적지 않았다면?

이렇게 떠서 뭐가 잘못되었는지도 모르고 방법을 찾다 헤맬 수도 있다(나처럼 1시간 내내 찾아다닐 수도..)

이제 저 함수로 가보면

void ALKPlayerController::OnZoomTriggered(const FInputActionValue& Value)
{
	float AxisValue = Value.Get<float>();

	if (AxisValue > 0.0f && !bZooming)
	{
		OnZoomIn();
	}
	else if (AxisValue < 0.0f && bZooming)
	{
		OnZoomOut();
	}
}

Value.Get<얻어오고자 하는 타입>으로 값을 얻어와서 그 값이 양수이면 휠이 올라간 것(확대), 음수이면 휠이 내려간 것(축소)으로 보면 된다

그리고 현재 Zoom In / Out 상태인지를 기록하기 위해 헤더 파일에 부울 변수를 하나 두겠다

/* Is Zoom in? */
uint8 bZooming : 1;

헤더 파일에서 부울 변수를 나타낼 땐 uint8 변수명 : 1 이렇게 하라고 배웠음

이 변수를 통해 현재 줌 인 상태이면 휠을 올려도 할 필요가 없으니 Zoom In 명령이 실행안되게 해야 좋지 않을까해서 추가했다
이제 줌 인/아웃을 구현해야 하는데 역시 시간 상 여기까지 하고 나중에 해야겠다
지금은 컨트롤러에서 줌 인/아웃인지 로그를 찍어보겠다(나중엔 캐릭터 클래스로 가서 스프링암을 조절해야 하니까)

void ALKPlayerController::OnZoomIn()
{
	bZooming = true;

	const ALKPlayerCharacter* PlayerCharacter = CastChecked<ALKPlayerCharacter>(GetPawn());

	if (PlayerCharacter)
	{
		UE_LOG(LogTemp, Log, TEXT("Zoom In"));
	}
}

void ALKPlayerController::OnZoomOut()
{
	bZooming = false;

	const ALKPlayerCharacter* PlayerCharacter = CastChecked<ALKPlayerCharacter>(GetPawn());

	if (CurrentPawn)
	{
		UE_LOG(LogTemp, Log, TEXT("Zoom Out"));
	}
}

부울 변수 값을 바꾸고 밑에 캐스팅하는 부분은 지금 쓸 건 아닌데 미리 해두었다
현재 폰을 받아와 ALKPlayerCharacter 클래스로 캐스팅하는 건데 캐릭터 클래스의 함수를 쓰려면 부모 클래스를 자식 클래스로 캐스팅해야 하기 때문임

이제 실행해보면

마우스를 계속 올려도 줌 인/아웃이 한 번만 되는 것을 확인할 수 있다

오늘은 여기까지

profile
나태지옥

0개의 댓글