C++ 기반 UE4 입문 - 게임플레이 프레임워크

LIHA·2023년 3월 13일
0

Unreal Engine

목록 보기
5/5
post-thumbnail

언리얼 에디터를 처음 켜면 나오는 녀석은 Pawn - '빙의'가 되어 내 키보드/마우스 조작을 입력받는 객체이다.
그렇다면 게임을 만들고 싶을 때 - 이 Pawn을 주인공 캐릭터로 설정하면 되지 않을까?🤔

게임 룰이 필요할때 - 월드 세팅의 Game Mode를 설정하자

이 녀석. 사실상 얘가 게임답게 설정하는 것의 알파고 오메가다. 여기서 폰을 어떻게 할건지, HUD(대강 UI라고 생각하자)는 어떻게 설정할건지 모두 조정할 수 있다.

롤은 넥서스 파괴하면 끝, 배그는 자기장과 적에게 죽지 않고 살아남으면 끝. 이런 룰들이 다 있다.
그렇다면 내가 만들 게임에는 내가 생각하는 룰이 적용되어야 할 것. 일종의 그 게임의 신? 같은게 필요하다.

  • 나만의 새로운 룰이 필요하다면 - Game Mode Base를 새로 만들어주자
    -> 콘텐츠 브라우저 우클릭 > 새 C++ 클래스 > Game Mode Base 선택

  • 이제 의자에 테이블 맵 그만 보고 새로운 필드를 만들고 싶은데😖
    -> Ctrl + n을 누르고 Default 선택

  • 앞으로 내 게임의 필드가 될 이 맵을 저장하고 싶은데🤔
    -> 콘텐츠 브라우저에서 콘텐츠 폴더 우클릭 > 새 폴더 > Maps로 이름짓기 > 월드 에셋 창 클릭 후 Ctrl + s > Maps 폴더 더블클릭 > 맵 이름 설정 후 저장(여기선 DefaultMap으로 함)

  • 언리얼 엔진 켜졌을때도 이 디폴트 맵이었으면 좋겠는데🤤
    -> 월드 에셋 위 세팅 메뉴 > 프로젝트 세팅 > 맵&모드 > 에디터 시작 맵 및 게임 기본 맵을 알아서 설정하세용


내가 만든 폰을 내 게임 룰의 디폴트로 쓰고 싶은데 - 코드를 써주자!

이전에 MyActor를 만들고 의자 모습을 입혔던것 처럼, 일단 얘도 필드에 어떤 모습으로 나타나게 해야 한다. 그러면 MyActor의 코드를 조금 복붙해오자.

private:

	UPROPERTY(VisibleAnywhere)
		UStaticMeshComponent* Mesh;

↑MyPawn.h에 위의 코드를 넣고,

AMyGameModeBase();

↑MyGameModeBase.h의 GENERATED_BODY( ) 밑에 이렇게 생성자를 넣고,

#include "MyPawn.h"

AMyGameModeBase::AMyGameModeBase()
{
	DefaultPawnClass = AMyPawn::StaticClass();

}

↑MyGameModeBase.cpp에 가서 생성자를 정의해주는 동시에 DefaultPawnClass를 누구로 쓸지 정해준다. 여기서 MyPawn을 기본으로 쓰겠다고 설정하면 된다. 그리고 상단부에 MyPawn.h를 include 해주면 끝.
-> 위 코드는 AMyPawn의 Static class로 받아오겠다는 얘기.

  • 근데 저 DefaultPawnClass 라는 변수 선언해 준적 없는데 어떻게 쓰는거에요?
    -> 얘는 GameModeBase가 기본적으로 다 들고 있다. GameModeBase.h 가보면 있음.

  • GameModeBase.h에 보니 TSubclassOf<APawn> DefaultPawnClass; 라고 되어있는데, TSubclassOf가 뭐에요?😵😵😵😵
    -> 이건 뭔지 모르겠는데, '서버 시간에 했던 '템플릿 흑마법'과 관련된 것' 이라고 한다. 템플릿에 익숙하지 않으면 지금은 봐도 분석하기가 어려울 것.

  • 아니 템플릿 흑마법은 또 뭐에요?! 이거 부두교인가요?😵😵😵😵
    -> '템플릿 메타 프로그래밍' 이라는게 있다. 이걸 템플릿 흑마법이라고 부른다.

  • 어쨌든, 결론은 DefaultPawnClass는 APawn을 상속받는 애를 받아줄 수 있는 거라고 생각하면 된다.
    -> 그리고 StaticClass는 UClass를 상속받는 애고, AMyPawn은 UCLASS() 라서 이미 언리얼엔진이 관리하는 대상이다. 그래서 여기서 Static객체를 받아와 DefaultPawnClass에 넣어주는 식으로 설정한 것.

아무튼 위와 같이 써주고 빌드를 하면 DefaultPawn에서 MyPawn으로 바뀐다. 빠밤!

  • 제 폰 클래스는 이름 안 바뀌는데요?😖😖
    -> 이럴땐 게임모드 오버라이드를 다른걸로 바꿨다가 다시 돌아오면 된다. (야매)

입력을 받는 폰을 만들려면 - 입력에 따른 함수를 맵핑해주자

  • Pawn이 다른 액터와 가장 다른 점은 '빙의'를 할 수 있다 - 입력을 받을 수 있다는 것.
    -> 유니티였다면 언리얼의 Tick처럼 업데이트 되는 함수에다가 입력받는 함수를 만들어놨을 것.
    if(INPUT.GETMOUSEBUTTONDOWN) 이런 함수를 사용해서, 어떤 입력을 받고 있는지를 체크하고 그에 따른 작업을 하는게 일반적이다.

  • 그런데 언리얼은 폰이 입력받는 함수가 Tick과는 별도로 존재하고, 이미 구조화되어 짜여있다.
    -> 이렇게 다 짜여있다는 부분이 처음 공부할 때 좀 머리아프긴 하지만, Tick함수에 입력이 다 짜여있는것보단 낫다.
    -> 이게 다 짜여있으면 변화가 생겼을 때 Tick을 다 뜯어고쳐야 한다. MyPawn클래스와 입력받는 함수 사이에 연관성과 종속성이 생김.

  • 그래서 입력받는 것도 별도의 컴포넌트로 빼놓고, 처음에 SetupPlayerInputComponent라는 함수를 이용해서 어떤 키를 눌렀을 때 어떤 함수가 호출되어야 하는지를 맵핑하는 식으로 작업하게 될것.
    -> 뭔소린지 모르겠다면? 일단 실습을 통해서 이렇게 쓰는거구나~ 하는 감을 익혀보자.

PlayerInputComponent -> BindAxis()

요렇게 PlayerInputComponent에 화살표를 땡기고 BindA까지만 써주면, BindAxis()와 BindAction()이 뜬다.

  • BindAxis()와 BindAction()은 어떻게 다른가요?🤔

-> 조이스틱을 생각하면 됨. 축을 가지는건 스틱이므로 BindAxis는 스틱을 움직이는 방향 이동이고, BindAction은 버튼 누르면 실행되는 동작을 말함.
지금 구현해볼건 일단 전후좌우 입력을 받는 것이므로, 조이스틱처럼 생각해서 Axis를 사용했음.

어? 내 코드는 LeftRight가 작동하지 않는데?😖 - 오타에 주의하자!

무슨 공백 한칸으로 코드가 안 먹히냐... 뭐 이런...

PlayerInputComponent->BindAxis(TEXT(" LeftRight"), this, &AMyPawn::LeftRight);

이 부분의 코드가 " LeftRight"로 한칸 띄어져 있었다😫😫
화살표 앞뒤에 공백을 주는 것은 문제가 되지 않았다.
(PlayerInputComponent -> BindAxis 이런 식으로)

  • 나중에 Game Mode에 있는 Player Controller Class도 이런식으로 Binding 함수를 써서 배치하는 식으로 선점해주면, PlayerInputComponent까지 넘어오지도 않고 그쪽에서 이벤트를 관리할 수도 있다. 요건 나중 얘기니 지금은 참고삼아 알아두자😋

  • 이렇게 어떤 축, 이벤트, 함수를 바인딩 해주는 것 - C#으로 치면 delegate 문법과 굉장히 유사한 것임!

이렇게 입력만 받으면 재미없으니, 실제로 이동을 시켜볼까?🤔

그 전에 일단 Log는 주석처리 해주자.

  • 언리얼은 엔진 자체가 FPS게임에서 왔다 보니, 환경에 따라서 이동에 차이가 있을 것 - 물리엔진의 작용을 상당히 많이 받을 것이다!
    (여기서 잠깐 - 언리얼과 유니티의 물리엔진은 PhysX 엔진을 쓴다. 폴아웃 시리즈나 헤일로에 쓰인건 래그돌 글리치로 유독 유명(?)한 하복 엔진.)
    -> 그래서 이동하는 것도 하나의 컴포넌트로 따로 빼서 다룰 것이다. MyPawn.h쪽에 Mesh처럼 UFloatingPawnMovement라는 것을 설정해주자.
    -> 그리고 MyPawn.cpp에 Movement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MOVEMENT")); 를 넣어주고 빌드를 하자.

어? 빌드가 안되는데요?😨 - 안되는게 당연하다. 헤더에 없기 때문.

-> 이럴땐 당황하지 말고 UFloatingPawnMovement 앞에 class를 붙여 전방 선언을 해주고, cpp에는 UFloatingPawnMovement를 찾아서 헤더를 추가해주면 된다.

  • 저번부터 전방 선언이 나오는데, 전역 변수/클래스를 만드는것 하고 다른건가요?
    -> 전혀 다름! 전방 선언은 Foward Declaration임. 전역은 global. C++의 전방 선언? 여기를 참고
    -> 전방 선언은 '함수의 몸체를 정의하기 전 함수의 존재를 컴파일러에 미리 알리는 것'.
    -> 컴파일러가 '나 얘 뭔지 모르는데?' 하고 빨간 줄 띄우고 빌드 실패하는 것을 막기 위함.

나만의 액터인 벤치도 WASD 입력을 받아 움직이게 하고 싶은데🤔

그렇다면 MyPawn.cpp에 AddMovementInput 함수를 써주고, 매개변수를 받아오자.
그리고 빌드를 하면 우리의 벤치도 힘차게(?) 움직일 것.

void AMyPawn::UpDown(float Value)
{
	if (Value == 0.f)
		return;

	// UE_LOG(LogTemp, Warning, TEXT("UpDown %f"), Value);
	AddMovementInput(GetActorForwardVector(), Value);
}

void AMyPawn::LeftRight(float Value)
{
	if (Value == 0.f)
		return;

	// UE_LOG(LogTemp, Warning, TEXT("LeftRight %f"), Value);
	AddMovementInput(GetActorRightVector(), Value);
}

위 코드를 통해 입력 받은대로 이동하여, 필드를 벗어나 허공답보를 하고 있다.
여기에 적용된 게임모드 설정은 우리가 설정한 MyGameModeBase라서 마우스로 시점을 전환하는 기능이 없기 때문에, 기본 데모처럼 마우스를 돌려도 시점이 전환되지 않는다.
-> 마우스 시점 전환은 추후에 컴포넌트로 추가할 때, FPS 게임처럼 사람의 일반적인 시야각을 고려하여 설정을 제한할 수 있지 않을까?🤔

아무튼, 이런 식으로 폰을 설정하여 컨트롤러 인풋을 받는 객체를 설정해 보았다.
다음엔 사람을 만들어서 좀더 재미있는(?) 것을 해보자.

profile
갑자기 왜 춤춰?

0개의 댓글