C++ 기반 UE4 입문 - 유니티 vs 언리얼?

LIHA·2023년 3월 7일
0

Unreal Engine

목록 보기
3/5
post-thumbnail

Unity vs UE - 자존심 강한 두 천재(?) 엔진의 비교🎮

※월드와 에셋들이 어떻게 만들어졌는지를 항상 되새김질 하자!
아무 생각없이 따라하면 나중에 혼자 만들때 막막함!

유니티와 비슷한점 - BeginPlay()와 Tick()

언리얼에서 Actor.h를 열어보면 BeginPlay() 와 Tick() 이라는 메소드가 있다.
이는 유니티의 Start() 와 Update() 라는 메소드와 비슷하다. 주석에도 달려있는데, BeginPlay()는 시작할때 혹은 스폰될때 한번만 호출되고, Tick()은 지속적으로 호출되어 상태를 갱신하는데 쓰인다는 것에서 같다.
BeginPlay()의 주석 - // Called when the game starts or when spawned
Tick()의 주석 - // Called every frame

유니티와 다른점 - 액터 삭제하려면 엔진을 끄라구요?🤯🤯😵😵

  • 결론부터 말하면 - 언리얼이 유니티에 비해 편의성 측면에선 굉장히 떨어진다. 이건 감안해야 한다!

-> 유니티의 경우 어떤 오브젝트에 움직임을 부여하는 스크립트를 붙이고 싶으면 그냥 새로 만들어서 붙이면 끝이다. 이름을 바꾸고 싶으면 바꾸면 되고, 삭제하고 싶으면 삭제하면 된다.
-> 그러나 언리얼은 기본적으로 수정 삭제가 자유롭게 안된다. 만약 오타로 이름 한글자 고치고 싶어도 엔진을 꺼야한다. 언리얼은 '만들땐 마음대로 였겠지만 바꿀땐 아니란다.' 가 기본이다.

▶ 엔진을 끄라구요? 아니 엔진을 끄면 어디서 삭제해요? 😨😨😨???
-> 네 끄세요. 일단 끄셔야함. 예상과 달리 엔진 상에서 삭제 못합니다. 마음을 비우십쇼.

메뉴가 있는데 왜 누르질 못하니! 어쩐지 오늘은 개발이 잘 되더라니만(?)

본격 Actor.h 삭제하는 방법 ~VS를 중심으로~

  1. VS에서 Actor.h 탭 우클릭 > 상위 폴더 열기 클릭하여 폴더 오픈
  2. VS 종료 - 저장 어쩌구저쩌구 나오거든 일단 저장 누른다(어차피 삭제할것임)
  3. 1에서 연 상위폴더로 가서 MyActor.h와 Myactor.cpp 파일을 시원하게 날려버린다
  4. 좀더 깔끔하게 하고싶으면 uproject파일이 있는 프로젝트의 메인폴더로 가서 Binaries, Saved, DerivedDataCache 등등 깃 커밋때 필요없다던 그 폴더들도 슉슉 날려준다.
  5. 필요하다면 VS Solution 파일도 삭제해준다. (이들 모두 어차피 프로젝트 빌드할때 다시 생성됨)

1인 개발에서 어지간하면 유니티를 추천하는 이유가 바로 이것 - 언리얼은 무겁고 번거롭고 느리다

  • 강사님 피셜 '이런 쓰잘데없는 짓거리(로딩, 빌드 등)에서 굉장히 시간을 많이 낭비하는' 엔진.
    -> 물론 잘만 쓰면 언리얼이 정말 좋은 툴인건 맞다. 특히 하이스펙으로 갈수록 언리얼의 진가는 빛을 발한다.
    -> 그러나 보다시피 액터 하나 삭제하는데 다섯 단계가 필요하다. 엔진끄고 파일 날리고 폴더 날리고 이렇게 오만 쌈바춤을 다 춰야 하기 때문.
    -> 또한, 엔진이 무겁다는건 이것저것 로딩하는데 시간이 오래 걸린단 말이므로, 그만큼 불필요한 곳에서 시간을 많이 잡아먹게 된다! 양날의 검인 셈. 그러니 안그래도 좋지않은 정신건강을 지키려면 어지간하면 1인개발때는 유니티를 쓰도록 하자.

▶ 그러므로 언리얼 엔지니어에게 주는 교훈 - 네이밍과 오타에 주의하자
잘못된 손가락 놀림 하나로 상기와 같은 복잡하고 다각적인 고통을 맛보게 될 수도 있다. 갓 블레스 유어 핑거.


언리얼에서도 상속구조는 아주 중요해 - Object > Actor > Pawn > Character 라구

유니티는 빈 깡통에서 시작해서 내가 원하는대로 만들어나가는 반면, 언리얼은 이미 어느정도 내부에 갖춰진 것을 가져와 쓰기 때문에 기본적으로 상속구조가 있다. 그리고 그 상속구조가 아주 중요하다.
물론 그 갖춰진 것을 가져와서 어떻게 쓰느냐는 사용자에게 달린 것이므로 상속구조라고는 해도 게임 제작이나 패턴이 다 정해진것만은 아니고 그 후는 유니티처럼 만들 수 있지만, 아무튼.

class TESTUE426_API AMyActor : public AActor

VS를 열어보면 위와 같이 되어있다. 이는 MyActor가 Actor 클래스를 상속받았다는 것!
-> TESTUE426_API라는 저 이름은 모듈과 관련이 있는데, 나중에 빌드를 할때 이 모듈명을 이용해서 뭔가를 한다는 개념이 있다.
지금은 몰라도 되니 있구나 정도만 보고 넘어가자😎

▶ C++은 C#과 달리 reflection을 기본적으로 지원하지 않는다. C++ 20가 되어서야 나왔다.
(reflection이 뭔데요?! -> 대충 컴파일러가 읽을 수 있는 주석이라고 생각하자. 이건 C#이나 다른언어를 좀 해봐야 이해할 수 있는 부분이긴 하다. 나 자바 하다가 왔는데 왜 모르지? 들었던 기억은 나는데... 가물가물... 어어어...)

▶ 아무튼 그래서 C++에는 reflection이 없으니까, 언리얼 엔진 내부에서 자체적으로(야매) 리플렉션을 만들어준게 UCLASS(), GENERATEDBODY(), UPROPERTY() 등등으로 보면 된다. 일단 U가 붙는건 대충 언리얼엔진의 클래스, 언리얼 엔진의 프로퍼티다 라는 식으로 생각하자.
-> 일단 UCLASS()와 GENERATEDBODY()는 셋트로 함께 간다고 기억해두자.

private:

	UPROPERTY()
	UStaticMeshComponent* Mesh;

▶ MyActor.h에 위와 같은 코드를 추가해주었다. 이는 내가 Mesh라는 이름의 언리얼엔진에서 쓰는 스태틱 메시 컴포넌트를 쓸 예정이란 얘기. 그런데 포인터로 써줬기 때문에 지금 당장 뭐가 일어나는건 아니다.
(아니 저기 잠깐만요 저 포인터가 뭔지 모르는데요!? 그냥 많은 사람들이 포인터에서 좌절하고 C++을 포기한다는것만 아는데요?!
-> int가 정수를, char가 문자 1개를 저장하듯 포인터는 주소값을 저장하는거라고 보면 된다.)
일단 이것을 참고하고 넘어가자 - 포인터에 대해

  • 이거 왜 UPROPERTY() 붙어있나요?
    -> 얘는 특수한 부품(언리얼 컴포넌트)이라서, 언리얼에서 자동으로 관리하게 해주겠다는 것!
    그래서 MyActor.cpp에서도 보통 쓰는것처럼 Mesh = new UStaticMeshComponent(); 이런 식으로 선언하지 않는다! (이렇게 쓸수 없음)
    -> 이때 MyActor.cpp에는 아래와 같이 써줘야 한다.
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MESH"));

그리고 솔루션 탐색기의 프로젝트명을 우클릭해서 빌드를 눌러보자.

이때 빌드가 실패할 수도 있다 - 이건 버전에 따라 다를 수 있음!

지금은 #include "CoreMinimal.h" 가 상단에 들어가 있기 때문에 문제없이 빌드가 되었지만, 경우에 따라 헤더에 뭔가가 없는 경우 C++이 UStaticMeshComponent를 인지하지 못해, 이게 뭔지 몰라서 빌드에 실패하는 경우도 있다.

-> 그리고 당연한 얘기지만, CoreMinimal.h에 포함된게 많으면 많을수록 빌드 및 로딩이 오래 걸리므로, 경우에 따라서 CoreMinimal.h를 사용하지 않고 직접 추가해서 쓰기도 한다.
-> 만약 직접 추가해서 쓴다면 일반 C++처럼 전방 선언을 해준 다음에, UStaticMeshComponent가 추가된 이 헤더를 MyActor.cpp 상단에도 직접 추가해야 한다.

(이게 뭔소리에요? -> 아래를 참고하자.)

private:

	UPROPERTY()
	class UStaticMeshComponent* Mesh;
    // 이렇게 쓴 다음에, MyActor.cpp 상단에 
    // #include Components/StaticMeshComponents.h 를 직접 써줘야 함 

어? UStaticComponent의 경로가 어디있는지는 어떻게 알아요?🤔

강사님의 경우 '비주얼 어시스트'를 사용하기 때문에 단축키로 진입 가능.
똑같이 비주얼 어시스트를 사용해도 되고, 구글링을 해서 직접 경로를 찾아 입력해도 되고 그건 본인 선택.
그러나 이건 include가 안됐을때 얘기고, 무사히 빌드가 됐다면 딱히 따로 추가할거 없이 그냥 쓰면 된다.

아무튼, 이런식으로 CreateDefaultSubobject라는 함수를 이용하면, 다음부터는 이 Mesh라는 대상의 메모리 관리를 우리가 직접 해줄 필요가 없게 된다. 포인터로 선언해줬으므로 일종의 스마트 포인터로 보면 된다. 자체적으로 관리해주는 메모리가 된 것.
(기억 나는가? 원래 C/C++은 메모리 관리도 프로그래머가 알아서 해야함. malloc free 라고 검색해보면 나와용.)

▶ 아무리 C++이라고 해도 new / delete를 써서 쌩포인터를 관리하던 시대는 지났다!
이젠 그렇게 하면 안됨. 언리얼엔진에서도 다 스마트 포인터를 사용하고, 그렇게 쌩포인터로 직접 관리하는 경우는 사실상 없다고 보면 된다. 너무 위험하기 때문! 정말정말 비추.


언리얼의 관리를 받는 나만의 액터 완성. 근데 속성이 잠겨있네?

빌드하는건 VS상에서 빌드를 눌러도 되고, 언리얼엔진 상에서 컴파일을 눌러도 된다. 똑같음.

이렇게 빌드하고 엔진을 보면 MyActor1 인스턴스 밑에 Mesh(MESH)(상속됨) 이라고 써있는 것을 볼 수 있다! 오호라, 너도 이제 언리얼엔진의 관리 하에 들어온 메시가 되었구나.😎
그러나 안타깝게도 하단의 트랜스폼, 스태틱 메시, 피직스 등등 모든 것이 다 비활성화 되어있어 건드릴 수 없다😥 못 고치나?
-> 고칠 수 있다. 속성을 조금 바꿔주면 된다. VS로 돌아가 UPROPERTY() 안에 뭔갈 써주자.
UPROPERTY()는 언리얼의 자체 reflection 기능 덕분에 이런저런 옵션을 넣어줄 수 있다.
-> UPROPERTY(VisibleAnywhere) 이라고 수정해주고 빌드하면 수정할 수 있게 된다!
-> 혹시라도 오해하지 않아야 할 것은, VisibleAnywhere이라고 쓴다고 해서 이게 보이기만 하고 수정 불가능한 것이 아니다. 수정할 수 있다.

▶ 이러한 처리의 문제점 - MyActor를 드래그 앤 드롭으로 새로 만들면, 걔는 아무 설정이 적용되어 있지 않다!

-> 내가 첫번째로 끌어온 Actor는 스태틱 메시 모양도 타원으로 설정해주고, 머티리얼도 벽돌무늬로 설정해주었다.
그런데 새로 끌어와 만든 애는 VisibleAnywhere이 무색하게 보이지도 않잖아?! (설정은 할 수 있는데, 처음부터 다 다시 해줘야 한다. 무슨 모양인지, 어떤 무늬인지...)
-> 확장성이 아주 낮은 설정이라서, 이 방법은 좋지 않다.

일단, 툴에서 뭔가 할수 있다면 코드상에서도 할 수 있다는 믿음을 가지자 (가져도 되는건가...?)

(TEXT("MESH")); 에서 TEXT는 왜 쓰는거에요? - 우리의 게임은 어디든 갈수 있으므로

▶ 마우스 올려보면 '다음으로 확장: L"MESH" 라고 되어있다. 이 말은, 내 C++ 환경에서는 저렇게 쓰면 L"MESH" 라는 형태로 치환된단 얘기임.
(근데요? 그게 뭔 상관이에요? 뭐라는겨?😕🤔)
-> 언리얼 엔진같은 경우는 PC게임만 만드는 툴이 아니다. 즉, 이 코드를 PC에서만 돌릴거라는 보장이 없다는 것. L"MESH" 로 치환되어 작동하는건 PC환경에서만 그럴수도 있다는 얘기임!
-> 그러니까 이걸 섣불리 '나는 PC게임 만들거니까 여따가 L"MESH" 라고 써야징 힣힣' 했다간 빌드가 안되고 에러가 불꽃처럼 터지는 모습을 볼 수도 있다는 것.

  • 지금 보고있는 VS야 UTF-16방식임을 예상할 수 있지만, 다른 환경에서도 꼭 그렇다는 보장은 없다. 그러므로 TEXT로 감싸줘서 빌드 설정에 따라 각기 다른 인코딩에서 유동적으로 동작할 수 있도록 해주는 것이다.

가능하면 이 TEXT는 꼭 붙여주는 습관을 들이자! CPP 지향 게임이라면 특히

이걸 붙여주지 않을 경우 인코딩에 유동적 대응이 안되어, 특정 환경에서만 빌드가 안되고 에러가 왕창 터지는 모습을 볼 수 있을 것이다. 당신이 만약 안드로이드와 iOS와 PC와 콘솔과 기타등등 그 어디서라도 플레이할 수 있는 게임을 만들고 싶다면 특히. 궰뛣쒧꿹 수준으로 끝나지 않을것이야.


안보이는 액터는 싫어잉😫 리소스 로드, 메시 로드해서 받아버리는 실습을 해보자

제목만 보면 겁나 어려운 것 같다. 허미 이게 뭔소리여? 뭘 받는다고?
하지만 겁먹지 말자. 이건 '아까 새 MyActor를 꺼낼때마다 아무 형태가 없었던 상황'을 해결하기 위해서, 새 MyActor에 애초에 뭔가 스태틱 메시의 모습중 하나를 지정해줌으로써 '꺼낼때부터 모습이 있는채로 꺼내지게 하는' 작업을 하려는 것!

그래서 대강 아래와 같은 코드를 작성하였다.
-> 이때 경로를 받아오는건, 원하는 스태틱 메시의 콘텐츠를 클릭한 다음에 그대로 Ctrl + C 하면 된다. 그게 오타가 없고 편하다.

static ConstructorHelpers::FObjectFinder<UStaticMesh> SM(TEXT("StaticMesh'/Game/StarterContent/Props/SM_Couch.SM_Couch'"));

// 앞으로 새로 꺼낼 액터는 스태틱 메시 이름이 SM_Couch인 모습을 받아서 나갈거야

	if (SM.Succeeded()) {
		Mesh->SetStaticMesh(SM.Object);
	}
    
    //소파 찾았어? 그러면 로드한 오브젝트의 스태틱 메시를 걔(소파)로 아예 Set해줘

처음에 컴포넌트를 추가할때는, 그 컴포넌트중 하나를 루트 컴포넌트로 만들어주자!

이것도 약간 규칙임. 컨벤션중 하나인듯. 그래서 RootComponent = Mesh; 라고 써주었다.

컴포넌트뿐만 아니라 인게임에서 사용할 정보도 액터에 넣어줄 수 있어요 - HP나 MP같은 것들

▶ 어라, 그런데 int32 Hp; int32 Mp; 를 넣고 빌드해도 엔진 안에서 보이지 않는데? 🤔
-> 이 부분 또한 유니티와 비슷한 점인데, 이 변수들 또한 reflection 기능을 통해 위에 UPROPERTY(VisibleAnywhere) 를 붙여서 외부로 노출시켜주는 작업이 필요하다.

와오. 벤치에 HP와 MP가 붙었어. 멋지다.

  • 근데 이름이 별로에요! Battle Stat 같은걸로 바꾸고 싶은데?
    -> reflection을 다음과 같이 작성해보자.
    UPROPERTY(VisibleAnywhere, Category = BattleStat)

다시한번 말하지만 이는 어디까지나 언리얼 엔진에만 있는 야매 리플렉션 문법임.
C++은 공식적으로는 20 미만의 버전에 reflection이 존재하지 않음.

  • HP, MP가 보이긴 하는데 수정이 안돼요!
    -> reflection을 또한 다음과 같이 수정해보자.
    UPROPERTY(EditAnywhere, Category = BattleStat)

잠깐만 출력 창을 살펴보자 - 언리얼 헤더 툴이 열일중!

Running UnrealHeaderTool 
"C:\Users\lihao\Desktop\UEproject\CPP-UE4TEST\testue426\testue426.uproject"

UnrealHeaderTool이 뭔가를 parsing해서 만들어주고 있구나. 열일하는군.
언리얼도 가진 툴이 많다. 빌드하는 툴도 있고, 헤더를 parsing하는 툴도 있고.

  • 근데 이 얘기는 갑자기 왜 해요? 🤔
    -> 이 reflection을 가능하게 해주는게 UHT이거든! 이 리플렉션은 '프로퍼티 시스템' 이라고도 한다.

▶ 뼈대 짚을겸 간단 정리 - 언리얼은 상속으로 만들고, 프로퍼티로 개량한다!

PROPERTY는 추가적인 정보기입용, 자동 메모리 관리는 CreateDefaultSubobject로!

-> 언리얼은 PROPERTY()라는 굉장히 묘한 문법을 이용해 reflection 기능을 구현하는 것이다.

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* Mesh;

	UPROPERTY(EditAnywhere, Category = BattleStat)
	int32 Hp;

	UPROPERTY(EditAnywhere, Category = BattleStat)
	int32 Mp;

그래서 위와 같이 썼다고 해서 Hp, Mp가 메모리 관리가 되는게 아니다.
MyActor.cpp 쪽에 따로 선언해서 사용하는 Mesh만 메모리 관리가 되는 것이라 생각하면 된다.

profile
갑자기 왜 춤춰?

0개의 댓글