[Unreal]

[Unreal 4] 샘플 둘러보기 - 내려보기 샘플

럭키🍀 2023. 7. 17. 23:27

언리얼엔진의 장점이자 단점인 타이트하게 미리 정해져있는 구조! 이를 익히고 있으면 적응하기 빠르다

새 프로젝트>게임을 하면 이렇게 여러 템플릿 중 선택할 수 있는데 내려보기는 탑다운 구조로 디아블로 , 리니지 같이 쿼터뷰나 탑뷰로 이루어진 게임의 틀 처럼 보인다.

주로 마우스로 움직이고 싶은 위치를 찍어서 이동하게 하는데 레이캐스팅을 해서 그쪽으로 가면 되겠다는 감이 온다. 이렇게 만든 샘플을 키면 마우스 클릭한 위치로 움직이고 커서 부분에 데칼이 있는 샘플이 만들어진다. 이동하는 방법이랑 내 캐릭터에 로봇대신 진짜 캐릭터를 입히고 육면체 장애물 대신 건물을 입혀보고 AI몬스터도 배치해보자.

내려보기 샘플을 처음 시작했을 때 만들어져있는 플레이 화면

1. 내 캐릭터 살펴보기

내 캐릭터는 TopDownCharacter라는 이름으로 블루프린트로 만들어져 있다. 클릭해서 열어 보면 부모클래스가 [내가만든 프로젝트이름]Character 로 설정되어있다. 이는 C++클래스로 되어있다.

 

 

 

 

 

 

 

캐릭터의 블루프린트를 열어서 가지고 있는 컴포넌트를 보면 전에 내가 만들었던 캐릭터가 가지고 잇는 컴포넌트와 매우 유사하다. 캡슐컴포넌트, 하위에 메쉬,카메라(Camera Boom은 arm같은것)를 가지고 있고 Cursor to World라는 추가로 데칼 컴포넌트를 가지고 있는데 마우스의 위치에 표시되는 데칼임을 짐작할 수 있다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

2. 게임모드 - 디폴트 폰 클래스와 플레이어컨트롤러

프로젝트 세팅> 맵&모드에 가면 기본 게임모드로 C++클래스로 작성된 [프로젝트이름]GameMode가 선택되어있다. 그 안에서 DefaultPawnClass와 PlayerControllerClass가 지정되어있다. 

// testprojectGameMode.cpp

#include "testprojectGameMode.h"
#include "testprojectPlayerController.h"
#include "testprojectCharacter.h"
#include "UObject/ConstructorHelpers.h"

AtestprojectGameMode::AtestprojectGameMode()
{
	// use our custom PlayerController class
	PlayerControllerClass = AtestprojectPlayerController::StaticClass();

	// set default pawn class to our Blueprinted character
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/TopDownCPP/Blueprints/TopDownCharacter"));
	if (PlayerPawnBPClass.Class != nullptr)
	{
		DefaultPawnClass = PlayerPawnBPClass.Class;
	}
}

이렇게 vs로 testprojectGameMode.cpp를 열어보면 게임두으에서 C++로 작성한 PlayerControllerClass와 DefaultPawnClass를 지정해 놓은 걸 알 수 있다. (물론 PlayerPawnBPClass는 위에서 본 [프로젝트이름]Character를 상속받은 블루프린트 클래스이다.

 

3. 캐릭터의 움직임은 어떻게 처리할까? 

또 프로젝트 세팅> 입력에 보면 어떤 키를 누르면 어떤 정도로 가게 되어있는지 축맵핑과 간헐적 입력에 대한 처리인 액션 맵핑이 정의되어있다. 이 샘플은 VR인지에 따라 입력을 따로 받기 때문에 약간 복잡하게 되어있었다. => 여러 플랫폼에서도 지원 가능하게 되어있군아~!

 

마우스 클릭을 햇을 때 입력을 받아 움직이는 것은 어디서 일어날까? Character클래스에서는 보이지 않는 걸 보니 움직임을 따로 PlayerController로 꺼내있다는걸 짐작할 수 있다.

우선 PlayerCharacter의 Tick부분만 일부를 보면

// testprojectCharacter.cpp
// ...
void AtestprojectCharacter::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

	if (CursorToWorld != nullptr)
	{
		if (UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled())
		{
			// ... VR기계라면 머리에 쓰는 장치의 중심으로 커서를 잡는듯한 코드(?)
		}
		else if (APlayerController* PC = Cast<APlayerController>(GetController()))
		{
			FHitResult TraceHitResult;
			PC->GetHitResultUnderCursor(ECC_Visibility, true, TraceHitResult);
			FVector CursorFV = TraceHitResult.ImpactNormal;
			FRotator CursorR = CursorFV.Rotation();
			CursorToWorld->SetWorldLocation(TraceHitResult.Location);
			CursorToWorld->SetWorldRotation(CursorR);
		}
	}
}

PlayerController를 PC로 받아와서 GetHitResultUnderCursor라는 함수로 FHitResult타입에 담는다. 이름으로 추측해봤을 때 커서가 레이캐스트를 쏴서 바닥에 닿은 위치를 TraceHitResult에 저장했다가 그 위치와 회전값에 CursotRoWorld(데칼의 변수명)을 맞추는 걸 볼 수 있다. GetHitResultUnderCursor함수의 내부를 보면 ViewportClient로 MousePosition을 얻어서 ScreenPosition을 개입하는 걸 보면 레이캐스트부분임을 더 짐작할 수 있다.

 

입력 관련 부분인 플레이어 컨트롤러를 보자.

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "testprojectPlayerController.generated.h"

UCLASS()
class AtestprojectPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	AtestprojectPlayerController();

protected:
	/** True if the controlled character should navigate to the mouse cursor. */
	uint32 bMoveToMouseCursor : 1;

	// Begin PlayerController interface
	virtual void PlayerTick(float DeltaTime) override;
	virtual void SetupInputComponent() override;
	// End PlayerController interface

	/** Resets HMD orientation in VR. */
	void OnResetVR();

	/** Navigate player to the current mouse cursor location. */
	void MoveToMouseCursor();

	/** Navigate player to the current touch location. */
	void MoveToTouchLocation(const ETouchIndex::Type FingerIndex, const FVector Location);
	
	/** Navigate player to the given world location. */
	void SetNewMoveDestination(const FVector DestLocation);

	/** Input handlers for SetDestination action. */
	void OnSetDestinationPressed();
	void OnSetDestinationReleased();
};

위는 플레이어 컨트롤러 클래스의 헤더이고 아래는 cpp파일이다. 오버라이드된 SetupInputComponent에서 액션맵핑으로 SetDestination이란 이름으로 설정한 입력이 들어왔을 때 OnSetDestinationPressed가 들어오고 땟을 때 OnSetDestionationReleased가 들어오도록 바인딩해 두었다.

각 함수에선 bMoveToCursor값을 True로 하다가 False로 하다가를 결정하는데 매 틱마다 bMoveToCursor가 true라면

 

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

#include "testprojectPlayerController.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "Runtime/Engine/Classes/Components/DecalComponent.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "testprojectCharacter.h"
#include "Engine/World.h"

AtestprojectPlayerController::AtestprojectPlayerController()
{
	bShowMouseCursor = true;
	DefaultMouseCursor = EMouseCursor::Crosshairs;
}

void AtestprojectPlayerController::PlayerTick(float DeltaTime)
{
	Super::PlayerTick(DeltaTime);

	// keep updating the destination every tick while desired
	if (bMoveToMouseCursor)
	{
		MoveToMouseCursor();
	}
}

void AtestprojectPlayerController::SetupInputComponent()
{
	// set up gameplay key bindings
	Super::SetupInputComponent();

	InputComponent->BindAction("SetDestination", IE_Pressed, this, &AtestprojectPlayerController::OnSetDestinationPressed);
	InputComponent->BindAction("SetDestination", IE_Released, this, &AtestprojectPlayerController::OnSetDestinationReleased);

	// support touch devices 
	InputComponent->BindTouch(EInputEvent::IE_Pressed, this, &AtestprojectPlayerController::MoveToTouchLocation);
	InputComponent->BindTouch(EInputEvent::IE_Repeat, this, &AtestprojectPlayerController::MoveToTouchLocation);

	InputComponent->BindAction("ResetVR", IE_Pressed, this, &AtestprojectPlayerController::OnResetVR);
}

void AtestprojectPlayerController::OnResetVR()
{
	UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}

void AtestprojectPlayerController::MoveToMouseCursor()
{
	if (UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled())
	{
		//...
	}
	else
	{
		// Trace to see what is under the mouse cursor
		FHitResult Hit;
		GetHitResultUnderCursor(ECC_Visibility, false, Hit);

		if (Hit.bBlockingHit)
		{
			// We hit something, move there
			SetNewMoveDestination(Hit.ImpactPoint);
		}
	}
}

void AtestprojectPlayerController::MoveToTouchLocation(const ETouchIndex::Type FingerIndex, const FVector Location)
{
	FVector2D ScreenSpaceLocation(Location);

	// Trace to see what is under the touch location
	FHitResult HitResult;
	GetHitResultAtScreenPosition(ScreenSpaceLocation, CurrentClickTraceChannel, true, HitResult);
	if (HitResult.bBlockingHit)
	{
		// We hit something, move there
		SetNewMoveDestination(HitResult.ImpactPoint);
	}
}

void AtestprojectPlayerController::SetNewMoveDestination(const FVector DestLocation)
{
	//...
}

void AtestprojectPlayerController::OnSetDestinationPressed()
{
	// set flag to keep updating destination until released
	bMoveToMouseCursor = true;
}

void AtestprojectPlayerController::OnSetDestinationReleased()
{
	// clear flag to indicate we should stop updating the destination
	bMoveToMouseCursor = false;
}

 

GetHitResultUnderCursor로 화면에 충돌되는 위치정보를 가져와 그 위치로 움직이도록 SetNewMoveDestination으로 움직인다.

 

 

 

4. 캐릭터의 애니메이션은 어디에 있을까?

캐릭터 클래스 내에는 애니메이션을 재생시키는 코드는 없었는데 캐릭터클래스를 상속받은 캐릭터 블루프린트로 가보자.

컴포넌트의 구조를 보면 애니메이션은 스켈레탈 메시를 움직이는 것이므로 메쉬를 눌러보면

Animation mode에 애니메이션 블루프린트를 사용할 것이라고하고 그 애니메이션 블루프린트 클래스가 선택되어있다. ThirdPerson_AnimBP_C는 이미 만들어져있는 애니메이션 블루프린트인데 

ThirdPerson_AnimBP를 열어서 애님그래프를 보면 이렇게 스테이트 머신들과 조건들로 이루어져 있다. Default스테이트 머신 안에 4가지 하위 스테이트 머신이 있는데 Idle/Run에서 JumpStart로 넘어가는 조건이 IsInAir?가 true일 때이며 JumpLoop에서 JumpEnd로 넘어가는 조건은 IsInAir가 false일 때이다. 또 JumpStart->JumpLoop로 변환이나 JumpEnd->Idle/Run으로의 변환은 애니메이션 재생시간이 얼마 남았냐로 정하고 있다. 그리고 이 IsInAir? 판정은 우리는 전에 코드에서 작성했지만 여기선 애니메이션 블루프린트의 이벤트 그래프 탭에 가보면 

 폰으로부터 IsInAir값과 Speed값을 세팅하고 있었다.

 

이렇게 간단히 첫번째 샘플인 내려보기(탑다운뷰게임)를 둘러보았다.

'[Unreal]' 카테고리의 다른 글

[Unreal 4] 샘플 둘러보기#2 - 일인칭(First Player)  (0) 2023.07.21