Unreal/C++ 과제 10 - 언리얼 모듈과 플러그인
Unreal/C++ 개인 과제 10 - 언리얼 모듈과 플러그인
과제 소개 및 목표
Unreal의 제품은
기본적으로 아래의 구조를 취함
솔루션 - 프로젝트/플러그인 - 모듈 - 소스코드
- 모듈 : 언리얼 프로젝트의 최소 단위
- 플러그인 : 하나 이상의 모듈을 가짐
(추가 개념에 대한 설명은 이쪽)
구현 기능
- Test 모듈 제작
- 모듈 연결
- Temporary 플러그인 제작
- 플러그인 연결
- UObject 용 플러그인 추가
- Character 와 연결하기
트러블 슈팅 1 - TestActor 가 빌드되지 않는 상황
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestActor.generated.h"
UCLASS()
class TEST_API ATestActor : public AActor
{
GENERATED_BODY()
// 왜 에러...??
public:
ATestActor();
protected:
virtual void BeginPlay() override;
};
-
간단하기 그지 없는 Actor 이나 이상하게 빌드 에러가 발생하였다
-
generated.h 가 존재하지 않는다는 ‘컴파일 에러’였는데
아무리 생각해도 빌드가 되지 않는것이 이상하였다
알고보니…
"AdditionalDependencies": [
"Engine",
"Test"
]
-
.uproject 에서 해당 부분을 추가하지 않아 발생했던 문제였다
- ‘Test’ 모듈 자체는 존재하나,
UHT가 ‘Test’ 모듈의 ‘빌드 필요성’을 인지하지 못하여
‘빌드’를 하지 않아 ‘Generated.h’가 만들어지지 않은것!
- “‘Test’ 모듈이 있는건 ok, 근데 이거 빌드할 필요 없잖아?”인 상태
- “‘Test’ 모듈이 있는건 ok, 근데 이거 빌드할 필요 없잖아?”인 상태
- 그렇기에 해당 방식으로
의존성을 걸어두게 되면
그제서야 빌드를 제대로 해준다
- ‘아 현재 메인 게임 모듈 사용하려면 저 Test 모듈이 필요하구나?’
- ‘아 현재 메인 게임 모듈 사용하려면 저 Test 모듈이 필요하구나?’
Build.cs 에서 설정하는 dependency와 비교?
- Build.cs 에 넣으면
‘컴파일’ 할때, 해당 모듈의 헤더와 라이브버리를 사용하겠다는 뜻!
- 대표적으로 ‘UMG’ 내부의 라이브러리를 사용하려 한다면
Build.cs 에 넣어주어야 컴파일 에러가 발생하지 않음
- 대표적으로 ‘UMG’ 내부의 라이브러리를 사용하려 한다면
- uproject 에서 설정한 경우
‘현재 모듈’ 실행 시, UHT가 같이 처리하도록 세팅하는 방식
- 이 모듈을 쓸거라면 ‘의존성 등록된’ 모듈들도 미리 ‘준비’해줘
(해당 모듈들이 ‘로드’되어 있어야 함)
- 이 모듈을 쓸거라면 ‘의존성 등록된’ 모듈들도 미리 ‘준비’해줘
정리 표
| 구분 | 위치 | 누가 읽음? | 언제 쓰임? | 의미 |
|---|---|---|---|---|
| Build.cs | Source/MyModule/MyModule.Build.cs |
UBT (빌드툴) | C++ 컴파일/링크 할 때 | “이 모듈이 빌드되려면 어떤 모듈의 헤더/라이브러리가 필요하다” |
| .uproject / .uplugin 의 AdditionalDependencies | MyGame.uproject, MyPlugin.uplugin |
UHT + 모듈 로더 | 리플렉션 생성, 에디터/게임에서 모듈 로드할 때 | “이 타겟/모듈이 실행/리플렉션 될 때, 어떤 모듈이 반드시 같이 준비되어 있어야 한다” |
트러블 슈팅 2 - UObject 타입을 사용할 때, 엔진이 다운되는 현상
APluginTestCharacter::APluginTestCharacter()
{
CharaData = NewObject<UUCharacterData>(this);
CharaData->SetHP(1000);
CharaData->SetAttack(200);
CharaData->SetDefend(100);
}
-
해당 코드를 넣고, 빌드 완료 후 엔진을 실행하니 크래쉬가 발생하였다!
-
찾아보니 생성자에서 NewObject를 쓰는 것은 금지된 패턴이라 한다
(사용시 Engine 쪽에서 Fatal Error 를 발생시킴) -
왜 그럴까?
- CDO(Class Default Object) 떄문에 그렇다!
- CDO(Class Default Object) 떄문에 그렇다!
[1] 클래스 로드
↓
[2] CDO 생성 (생성자 실행 1회)
↓
[3] Actor Spawn 요청
↓
[4] CDO 메모리 복제 (MemCopy)
↓
[5] 생성자 다시 실행 (인스턴스용)
↓
[6] PostInitProperties
↓
[7] PostInitializeComponents
↓
[8] BeginPlay
- 기본적으로 인스턴스들이 CDO 기반 복제 -> 생성자 호출 을 통해 제작됨
- CDO 로 ‘기본 데이터 템플릿’ 을 세팅
- 생성자를 통해 ‘인스턴스화 된 시점’의 초기화 로직 실행
- CDO 로 ‘기본 데이터 템플릿’ 을 세팅
- 그런데 생성자에서 NewObject 를 사용하면 안되는 이유?
- CDO 만들 때 한번, 이후 인스턴스를 만들때마다 호출
- 그런데 NewObject 생성 시, CDO에 Outer로 붙어버림
- 그리고, 인스턴스 에 호출된 것은 인스턴스에 Outer
- CDO 만들 때 한번, 이후 인스턴스를 만들때마다 호출
- CDO에 NewObject 하나 붙는 건데 뭔가 문제 있나??
- CDO에 붙는 UObject 는 ‘DefaultSubObject’ 취급
- 규칙 상으로 ‘고유 이름 + 계층 구조’가 필요
- CDO <-> 인스턴스 간의 ‘복제/매핑’ 룰 유지 필요
- 규칙 상으로 ‘고유 이름 + 계층 구조’가 필요
- 이는 ‘생성 순서’ 고정 등 다양한 규칙을 통해 ‘SubObject’들의 안정성을
NewObject 키워드로는 규칙을 보장하지 못함
- 생성자에서 사용 금지!
- 생성자에서 사용 금지!
- CDO에 붙는 UObject 는 ‘DefaultSubObject’ 취급
-
NewObject는 런타임에 객체를 생성하는 함수이며,
이는 Subobject인지, 일시적인 런타임 객체인지 확인할 수 없음 - 그렇기에
CreateDefaultSubobject<T>()가 생성자에서 강제됨
- ‘생성 순서’, ‘타입 결정’, ‘이름’ 등의 규칙이 보장되기 때문
- 다만 Actor/Component 기반이기에, 내가 만든 UObject는 BeginPlay에 넣어서 문제 해결
- ‘생성 순서’, ‘타입 결정’, ‘이름’ 등의 규칙이 보장되기 때문
CreateDefaultSubobject<USceneComponent>(TEXT("Root")); // ✅
CreateDefaultSubobject<USceneComponent>(); // ❌ 컴파일 불가
해결 코드
APluginTestCharacter::BeginPlay()
{
CharaData = NewObject<UUCharacterData>(this);
CharaData->SetHP(1000);
CharaData->SetAttack(200);
CharaData->SetDefend(100);
}
댓글남기기