프로젝트에서 아무것도 설정하지 않았다면 월드 세팅 Game Mode의 게임모드 오버라이드는 None으로 설정되어 있을 것이다.

이것을 C++ 클래스의 Game Mode Base를 만들어서 바꿔주면 GameMode의 속성들을 정의해서 사용할 수 있다.

 


MyActor를 만들었던 때 처럼 Pawn의 생성자를 선언 및 정의하고 MyGameModeBase에서 DefaultPawnClass를 해당 폰으로 지정해주면 실행 시 해당 폰으로 설정 및 기본 생성이 된다.

 

// MyPawn
AMyPawn::AMyPawn()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MESH"));
	Movement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MOVEMENT"));

	RootComponent = Mesh;

	static ConstructorHelpers::FObjectFinder<UStaticMesh> SM(TEXT("StaticMesh'/Game/StarterContent/Props/SM_Couch.SM_Couch'"));
	if (SM.Succeeded())
	{
		Mesh->SetStaticMesh(SM.Object);
	}
}

// ---------------

#include "MyPawn.h"

// MyGameModeBase
AMyGameModeBase::AMyGameModeBase()
{
	DefaultPawnClass = AMyPawn::StaticClass();
}

 

 

Pawn과 Actor의 다른점은 Pawn은 입력을 받을 수 있다. (빙의 가능)

 

Pawn 클래스에는 Actor와 다르게 입력에 대한 부분을 처리하는 SetupPlayerInputComponent 메소드가 기본적으로 포함되어있다.

 

void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAxis(TEXT("UpDown"), this, &AMyPawn::UpDown);
	PlayerInputComponent->BindAxis(TEXT("LeftRight"), this, &AMyPawn::LeftRight);
    // C#의 delegate 문법과 유사하다

}

 

UpDown과 LeftRight를 작성하고 해당 메소드도 클래스에 선언 및 정의해준다.

그리고 Text의 UpDown과 LeftRight는 그냥 적은게 아니고 엔진 세팅과 맞춰주어야 한다.

 

 

Tick에서 입력 받는거나 위의 함수에서 받는거나 어차피 매 틱마다 실행되는거면 무슨 차이가 있는건가? 싶을 수 있지만 차후 PlayerController에 이전시켜 줄 수도 있다. 그러면 위의 함수에 들어올것도 없이 PlayerControll에서 이벤트를 선점해서 그쪽에서 관리할 수도 있다.

아직은 무슨 얘기인지 잘 모르겠지만 그렇다고 한다.

 

 

입력과 별개로 이동에 관련된 부분은 또 다른 컴포넌트로 관리된다.

 

UPROPERTY(VisibleAnywhere)
class UFloatingPawnMovement* Movement;
// 해당 컴포넌트는 CoreMinimal 헤더에 포함되어있지 않기 때문에 헤더에서는 전방선언 해주고
// cpp파일에서 "GameFramework/FloatingPawnMovement.h" 를 포함시켜주면 사용할 수 있다

// ----

// 생성자
Movement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MOVEMENT"));

 

 

이후에는 UpDown, LeftRight 메소드에서 해당 컴포넌트의 이동 메소드를 사용하면 된다.

 

void AMyPawn::UpDown(float Value)
{
	UE_LOG(LogTemp, Warning, TEXT("UpDown %f"), Value)

	AddMovementInput(GetActorForwardVector(), Value);
}

void AMyPawn::LeftRight(float Value)
{
	UE_LOG(LogTemp, Warning, TEXT("LeftRight %f"), Value)

	AddMovementInput(GetActorRightVector(), Value);
}

UE_LOG()

로그를 찍는 매크로

 

// 카테고리, 로깅 수준, 형식, 인자
UE_LOG(LogTemp, Warning, TEXT("BeginPlay %d"), 3)

 

출력 로그에 찍히게 된다.

일부 로깅 수준은 로그 파일에도 같이 남는다.

프로젝트의 Saved 파일에서 로그를 확인할 수 있다.

 

 

C++ 디버깅을 하는 방법은 로그를 추가해서 런타임 중에 찍히는 로그를 보고 파악하는 방법, 또는 엔진을 끄고 IDE상에서 디버깅을 하는 방법이 있다. (안꺼도 어차피 새로 실행 됨)

IDE 디버깅 - UE4 Editor 실행 - UE4 Editor 종료 - IDE 디버깅 종료 순서로 가기때문에 언리얼을 끄고 디버깅을 시작하더라도 디버그 종료시 UE4도 종료된다.

 

 

 

Debug와 Release 모드의 차이라고 보면 된다.

 

Debug는 최적화가 덜 되어있는 대신 디버그 심볼이 많아서 디버깅에 용이하다.

Development는 최적화가 더 되는대신 일부 디버그 심볼들이 없어서 디버깅에 애를 먹을 수 있다.

Shipping은 배포판이다. 디버깅에 더더욱 애를 먹게된다.

 

DebugGame과 DebugEditor의 차이

DebugGame은 실행파일을 생성하기는 하지만 그 외 리소스들이 포함되지 않아서 실행이 안된다.

DebugGame Editor는 에디터(UE4)를 실행 후 그 위에서 dll 형태로 돌아가기 때문에 정상적으로 실행할 수 있다.

Development도 마찬가지다.

 

추가로, 스샷처럼 창이 너무 작아서 글자가 다 안보일때는 [사용자 지정 - 명령 - 도구모음 - 표준] 에서 솔루션 구성의 선택 사항 수정을 누르고 너비를 조정하면 된다.

 

개발 철학

 

유니티

바닥부터 끝까지 만들어나간다.

빈 깡통(GameObject)에 컴포넌트를 갖다 붙이면서 만드는 방식. (컴포넌트 패턴 : 부품을 갖다 붙이는것)

 

언리얼

이미 틀이 잡혀있는 상태. 반반이다.

상속구조(즉, 태생)를 정하고 컴포넌트 방식으로 붙여나가면서 완성한다.

각각의 역할에 대한 클래스들이 지정되어 있고 우리는 그것을 상속받아 조립하여 사용하게 된다.

 

언리얼에서 C++ 클래스를 잘못 만든 경우 (잘못 만들거나, rename 해야하는 경우 등등) 일단 언리얼을 꺼야한다...

이후 VS 프로젝트에서도 삭제를 해주고 해당 파일까지 지워준다.

좀 더 깔끔하게 하려면 언리얼 프로젝트의 Binaries, Intermediate, Saved, DerivedDataCache, .vs, 솔루션 파일을 날려준다.

Generate Visual Studio project files를 해주면 프로젝트가 다시 생성이 된다.

 

reflection: 컴파일러가 읽을 수 있는 주석. UCLASS(), UPROPERTY() 등등 이런것들이 일종의 reflection이다.

 

CoreMinimal.h에 온갖 헤더파일이 다 들어가있기 때문에 전부 필요하지 않다면 전방선언 후에 cpp 파일에서 필요한 헤더만 추가해서 사용해주면 된다.

 

 

 

-------------------------

 

 

 

private:
	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* Mesh;

UPROPERTY의 값을 위와 같이 지정해주면 디테일 창에서 속성들을 활성화 시켜준다.

단, 해당 디테일 창에서 값을 아무리 변경해도 해당 오브젝트만 적용되고 C++ 클래스에는 적용되지 않는다.

 

 

// 생성자에서 작성중
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MESH"));

위와 같이 TEXT("MESH") 를 사용하는 이유는, 멀티 플랫폼에 대응하기 위한 안전장치이다.

특정 환경에서는 문자 인코딩 방식이 다를수 있기 때문에 환경에 맞춰서 자동으로 인코딩 해주는 기능이라고 보면 된다.

 

 

// 생성자에서 작성중
static ConstructorHelpers::FObjectFinder<UStaticMesh> SM(TEXT("StaticMesh'/Game/StarterContent/Props/SM_Couch.SM_Couch'"));

파일의 경로를 가져오는 것은 콘텐츠 브라우저에서 오브젝트를 고르고 Ctrl+C 하면 경로가 복사되는데 그것을 붙여넣기 하면 된다.

참고) ConstuctorHelpers는 이름 그대로 생성자에서만 사용이 가능하다. CDO 제작에만 사용된다.

ClassFinder는 애셋의 Type 정보를 가져올 때,

ObjectFinder는 애셋의 내용물을 가져올 때 사용한다.

 

런타임에서 애셋을 로딩하려면 StaticLoadObject와 같은 다른 API를 사용하여야 한다.

 

static ConstructorHelpers::FObjectFinder<UStaticMesh> SM(TEXT("StaticMesh'/Game/StarterContent/Props/SM_Couch.SM_Couch'"));
if (SM.Succeeded()) // 파일 불러오기가 성공하면
{
    Mesh->SetStaticMesh(SM.Object); // 메시를 해당 파일로 설정한다
}

 

C++ 코드로 작성한것이 1차적으로 우선 적용되고, 디테일 창에서 변경한 속성들은 나중에 변경한 것들이라 그 후에 적용되지만 해당 액터 하나만 적용되게 된다.

 

 

// 생성자에서 작성중
RootComponent = Mesh;

그리고 컴포넌트를 추가할때는 그중 하나를 RootComponent로 설정해 주어야 한다.

RootComponent는 물리에 반응하는 물체가 된다.

 

 

UPROPERTY(VisibleAnywhere, Category = BattleStat)
int32 Hp;

UPROPERTY(VisibleAnywhere, Category = BattleStat)
int32 Mp;

해당 변수를 디테일 창에 보이고 싶으면 마찬가지로 UPROPERTY로 지정해주면 된다.

 

추가로 Category 지정으로 속성 카테고리 이름을 바꿔줄 수 있다.

VisibleAnywhere로 설정 시, 값을 조절할 수 없지만 EditAnywhere로 바꿔주면 값을 조절할 수 있다.

 

 

유니티와 다르게 위쪽이 z축이다

 

월드 아웃라이더 창에서 배치된 모든 오브젝트를 볼 수 있다.

 

기본적으로 시작하는 맵은 [세팅 - 프로젝트 세팅 - 맵 & 모드] 의 Default Maps를 변경함으로써 바꿀 수 있다.

 

콘텐츠 브라우저의 아이템들은 내가 따로 준비한 것들이고 액터 배치의 아이템들은 언리얼에서 제공해주는 기본 아이템이다.

 

유니티는 무에서 유를 창조해 나가는 방식이고,

언리얼은 이미 구조가 꽉 잡혀있는 곳에서 숟가락을 얹어가면서 작업하는 방식이라고 보면 된다.

+ Recent posts