UClass에 등록된 속성과 함수들은 TFieldIterator에 의해 모두 검색이 가능하다

 

ex)

UClass* ClassInfo1 = WebConnection->GetClass();
UClass* ClassInfo2 = UWebConnection::StaticClass();

for (TFieldIterator<UProperty> It(ClassInfo1); It; ++It)
{
	AB_LOG(Warning, TEXT("Field : %s, Type : %s"), *It->GetName(), *It->GetClass()->GetName());
	UStrProperty* StrProp = FindField<UStrProperty>(ClassInfo1, *It->GetName());

	if (StrProp)
	{
		AB_LOG(Warning, TEXT("Value = %s"), *StrProp->GetPropertyValue_InContainer(WebConnection));
	}
}

 

 

<UFunction 같은 경우 위처러 TFieldIterator<UFUNCTION>을 사용할 수 있으며, 
아니면 아래 코드와 같이 
NativeFunctionLookupTable 배열을 사용해 현재 클래스에 어떤 C++ 함수가 있는지 파악할 수 있습니다.


for (const auto& Entry : ClassInfo1->NativeFunctionLookupTable)
{
   AB_LOG(Warning, TEXT("Function = %s"), *Entry.Name.ToString());
   UFunction* Func1 = ClassInfo1->FindFunctionByName(Entry.Name);
   if (Func1->ParmsSize == 0)
   {
     WebConnection->ProcessEvent(Func1, NULL);
   }
}

 

 

 

로그디버깅 업그레이드

 


이번 시간에는 언리얼 오브젝트가 가지는 특징 중 하나인 리플렉션 기능에 대해 살펴보겠습니다.
이를 위해서 앞으로 로그를 많이 사용할 예정인데, 로그 매크로를 추가해 기존 로그 기능을 업그레이드하겠습니다.

//OOO 메인게임.h

#include "Engine.h"

DECLARE_LOG_CATEGORY_EXTERN(ArenaBattle, Log, All);

#define AB_LOG_CALLINFO (FString(__FUNCTION__) + TEXT("(") + FString::FromInt(__LINE__) + TEXT(")"))
#define AB_LOG_CALLONLY(Verbosity) UE_LOG(ArenaBattle, Verbosity, TEXT("%s"), *AB_LOG_CALLINFO)
#define AB_LOG(Verbosity, Format, ...) UE_LOG(ArenaBattle, Verbosity, TEXT("%s %s"), *AB_LOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__)) 


//OOO 메인게임.cpp
#include "ArenaBattle.h"

DEFINE_LOG_CATEGORY(ArenaBattle);
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ArenaBattle, "ArenaBattle" );

 

이렇게 AB로 시작하는 매크로를 선언하고 컴파일을 하면 
이제 UE_LOG 매크로 대신 AB_LOG 혹은 AB_LOG_CALLONLY 매크로를 동일하게 사용해주면 됩니다.
아래는 새로운 매크로로 로그 코드를 변경한 예시입니다.
반드시 헤더를 추가해야 AB_LOG를 사용 할 수가 있다.

#include "ABC.h"
#include "ABCGameInstance.h"

UABCGameInstance::UABCGameInstance()
{
	AB_LOG(Warning, TEXT("Constructor Call Start"));
	WebConnection = CreateDefaultSubobject<UWebConnection>(TEXT("MyWebConnection"));
	AB_LOG(Warning, TEXT("Constructor Call End"));
}

void UABCGameInstance::Init()
{
	Super::Init();
	AB_LOG_CALLONLY(Warning);
}

 

이 매크로를 사용하면 아래 그림과 같이 로그 매크로를 사용한 시점의 함수와 라인 정보가 자동으로 함께 표시됩니다.

추가로 언리얼의 출력 로그 창에서 경고 메시지만 보이도록 필터링 하는 것도 가능합니다.
앞으로 강좌에서
함수 정보만 출력하고 싶은 경우에는 AB_LOG_CALLONLY
매크로를 추가 정보도 같이 포맷문자열로 출력하고싶은 경우에는 AB_LOG를 사용하겠습니다.


참고로 로그 매크로안에 설정할 수 있는 Verbosity 값은 총 Fatal, Error, Warning, Display등이 있습니다. Fatal을 사용하면 강제로 크래시를 일으킵니다. 하지만 로그에서 Fatal을 쓸바에야 check나 verify같은 Assertion 매크로를 사용하는게 좋습니다. 

언리얼에서 사용가능한 Assertion 매크로들은 아래 링크를 참고하시면 됩니다. 

https://docs.unrealengine.com/latest/INT/Programming/Assertions/




UClass

 

로그 기능이 준비되면 이제 본격적으로 언리얼 오브젝트의 타입 정보를 담고 있는 UClass에 대해 알아봅시다. 

UClass 정보는 컴파일타임이든 런타임이든 원할 때 아무 때나 가져올 수 있습니다.

컴파일타임에서는 언리얼 오브젝트 선언에 있는 StaticClass를 사용하고,
런타임에서는 언리얼 오브젝트 인스턴스에서 GetClass()함수를 사용해 가져올 수 있습니다.

(참고로 우리는 StaticClass함수를 선언한 적이 없지만, 이 함수는 언리얼 헤더툴에 의해 자동으로 생성됩니다. ) 

아래는 이를 사용해 본 코드입니다.
두 포인터의 값은 동일한 UClass 객체를 가리킵니다.

void UABCGameInstance::Init()
{
	Super::Init();
	AB_LOG_CALLONLY(Warning);


	UClass* ClassInfo1 = WebConnection->GetClass();       //UClass 런타임
	UClass* ClassInfo2 = UWebConnection::StaticClass();   //UClass 컴파일타임

	if (ClassInfo1 == ClassInfo2)
	{
	AB_LOG(Warning, TEXT("ClassInfo1 == ClassInfo2"));
	}
}

 
※ 빌드 중 오류 Trying to recreate class '%s' outside of hot reload! 발생
※ 그래서 빌드 폴더 삭제후 재실행 하니 잘됨

실행 화면 같다는 결과

 


리플렉션

 

UClass에는 언리얼 오브젝트의 타입 정보를 검색하고 함수를 호출하거나 속성에 값을 설정하는
리플렉션 기능이 있습니다.

다만 이를 위해서는 그냥은 안되고

언리얼 오브젝트 클래스의
멤버 변수에는 UPROPERTY 매크로를
멤버 함수에는 UFUNCTION 매크로를
지정해주어야 합니다.

이를 테스트하기 위해 WebConnection 언리얼 오브젝트에 아래와 같이 Host와 URI라는 속성을 문자열로 정의하고 인자가 없는 함수에 UFUNCTION 매크로로 등록해 준 후 CDO에서 속성의 기본 값을 지정하고, 함수는 호출 되었다는 로그를 남겨줍시다. 

 

//WebConnection.h

class WEBSERVICEK_API UWebConnection : public UObject
{
public:	
    UPROPERTY()
		FString Host;

	UPROPERTY()
		FString URI;

	UFUNCTION()
		void RequestToken();
}


////WebConnection.cpp
UWebConnection::UWebConnection()
{
    UE_LOG(WebConnection, Warning, TEXT("WebConnection Constructor Call!"));
    Host = TEXT("localhost:8000");
    URI = TEXT("/");
}

void UWebConnection::RequestToken()
{
    UE_LOG(WebConnection, Warning, TEXT("Request Token Call!"));
}

 

UClass에 등록된 속성과 함수들은 TFieldIterator에 의해 모두 검색이 가능합니다.

UProperty 타입을 사용하면 속성 값만 검색하는 것이 가능하며,
런타임에서 특정 인스턴스에 할당된 속성 값을 가져올 수 있습니다.

아래는 WebConnection 인스턴스에 들어있는 값을 출력한 예시입니다.

//ABCGameInstance.cpp
void UABCGameInstance::Init()
{
	Super::Init();
	AB_LOG_CALLONLY(Warning);


	UClass* ClassInfo1 = WebConnection->GetClass();
	UClass* ClassInfo2 = UWebConnection::StaticClass();
	if (ClassInfo1 == ClassInfo2)
	{
	 AB_LOG(Warning, TEXT("ClassInfo1 == ClassInfo2"));
	}

////추가
	for (TFieldIterator<UProperty> It(ClassInfo1); It; ++It)
	{

		AB_LOG(Warning, TEXT("Field : %s, Type : %s"), *It->GetName(), *It->GetClass()->GetName());

		UStrProperty* StrProp = FindField<UStrProperty>(ClassInfo1, *It->GetName());

		if (StrProp)
		{
			AB_LOG(Warning, TEXT("Value = %s"), *StrProp->GetPropertyValue_InContainer(WebConnection));
		}
	}

}

 

함수 목록은 TFieldIterator<UFunction>을 사용할 수 있으며, 
아니면 아래 코드와 같이
NativeFunctionLookupTable 배열을 사용해 현재 클래스에 어떤 C++ 함수가 있는지 파악할 수 있습니다.

  함수의 이름만 알면 FindFunctionByName 함수를 사용해 언리얼 함수 객체 UFunction을 얻어올 수 있으며 이를 사용해 특정 인스턴스 내 함수 호출이 가능합니다.
  아래는 리플렉션 기능으로 함수를 호출한 예시입니다.
이번 코드에서는 C++ 11부터 지원하는 범위기반 for 루프(Range based for loop)를 사용해 간결하게 표현해보았습니다.  auto문 캐스팅


범위기반 for 루프에 대해 궁금하시면 아래 링크를 참고하시는 것을 추천합니다.

https://www.unrealengine.com/ko/blog/ranged-based-for-loops 


//ABGameInstance.cpp
  for (const auto& Entry : ClassInfo1->NativeFunctionLookupTable)
   {
       AB_LOG(Warning, TEXT("Function = %s"), *Entry.Name.ToString());
       UFunction* Func1 = ClassInfo1->FindFunctionByName(Entry.Name);
       if (Func1->ParmsSize == 0)
       {
         WebConnection->ProcessEvent(Func1, NULL);
       }
    }

 

실행화면

Host 값과 Value이 출력된 결과

 




언리얼 오브젝트 클래스에서
UPROPERTY와 UFUNCTION 매크로로 지정된 멤버 변수와 멤버 함수는 모두 검색이 가능합니다.

그리고 이들 필드의 타입 정보이름만 알고 있으면,
특정 인스턴스 내 값을 변경하거나 함수를 호출하는 것이 가능합니다. 

 

 

[참고] [1-6] UClass와 리플렉션

이번 강좌에서는 하나의 모듈에서 다른 모듈을 참조하는 기능을 구현해보겠습니다. 

모듈간의 참조를 구현하기 위해서는 우선 언리얼 오브젝트의 초기화 과정에 대한 이해가 필요합니다. 


UCLASS, 리플렉션, 프레임워크


UClass에는 언리얼 오브젝트에 대한 클래스 계층 구조 정보와 멤버 변수, 함수에 대한 정보를 모두 기록

앞선 강좌에서 하나의 언리얼 오브젝트가 만들어지기 위해서는,
실제 컴파일 전에 언리얼 헤더 툴에 의해 헤더 파일을 분석하는 과정이 선행되며,
이 과정이 완료되면 Intermediate 폴더에 언리얼 오브젝트의 정보를 담은 메타 파일이 생성된다고 설명드렸습니다. 

언리얼 엔진이 컴파일 전에 먼저 메타 소스 파일과 헤더 파일을 생성하는 목적은 여러가지가 있겠지만,
기존의 C++ 문법에서 제공하지 못하는 런타임에서의 빠른 클래스 정보의 검색이라고 생각합니다. 

이 메타 정보는 언리얼 엔진이 지정한 UClass라는 특별한 클래스를 통해 보관됩니다. 

UClass에는 언리얼 오브젝트에 대한 클래스 계층 구조 정보와 멤버 변수, 함수에 대한 정보를 모두 기록하고 있습니다.

  하지만 단순히 검색하는 것에서 더 나아가, 런타임에서 특정 클래스를 검색해 형(Type)을 알아내, (-> 리플렉션)
인스턴스의 멤버 변수 값을 변경하거나 특정 인스턴스의 멤버 함수를 호출하는 것이 가능합니다. C++에서 말이죠. 
 

Java나 C#과 같은 C++ 다음 세대의 언어에서는 이와 유사한 기능을 리플렉션(Reflection)이라는 이름으로 제공합니다. 

정리하면 Java나 C#의 리플렉션 기능을 C++ 표준 문법에서는 제공하지 않으므로,
언리얼 엔진이 자체적으로 프레임웍을 만들어 제공한다고 이해하시면 되겠습니다.
 


클래스 기본객체

컴파일 단계에서 언리얼 오브젝트마다 UClass가 생성된다면,

실행 초기의 런타임 과정에서는 언리얼 오브젝트마다 클래스 정보와 함께 언리얼 오브젝트의 인스턴스가 생성됩니다. 

이 특별한 인스턴스는 언리얼 오브젝트의 기본 세팅을 지정하는데 사용되는데, 
이를 클래스 기본 객체 ( Class Default Object ) 줄여서 CDO라고 합니다. 


UCLASS 와 CDO

  언리얼 엔진에서 CDO를 만드는 이유는 언리얼 오브젝트를 생성할 때마다 매번 초기화 시키지 않고,
기본 인스턴스를 미리 만들어 놓고 복제하는 방식으로 메커니즘이 구성되어 있기 때문입니다. 

  지금 우리가 실습하는 단순한 언리얼 오브젝트라면 이러한 복제 과정이 불필요할 수도 있지만,
하나의 언리얼 오브젝트가, 예를 들어 복잡한 기능을 수행하는 캐릭터까지 담당할 정도로 기능이 확장되면,
굉장히 큰 덩어리의 객체로 커질 수 있습니다. 

  만일 게임 실행 중, 런타임에서 이 캐릭터를 한번에 100명을 스폰시킨다고 가정해봅시다. 
캐릭터를 하나씩 처음부터 생성하고 초기화시키는 방법보다, 미리 큰 기본 객체 덩어리를 복제한 후에 속성 값만 변경하는 방법이 보다 효과적이겠지요?

또 다시 정리하자면 하나의 언리얼 오브젝트가 초기화 될 때에는 두 개의 인스턴스가 항상 생성됩니다. 

아래는 이를 정리한 도식입니다.

 

하나의 언리얼 오브젝트에 생성되는 두 객체 UClass와 CDO 

 

이러한 언리얼 오브젝트는 언리얼 엔진에서 항상 모듈 단위로 관리됩니다. 
언리얼 에디터를 띄우면 초기화라는 문구 옆에 %가 증가하는 것을 볼 수 있는데,
대부분의 과정이 에디터에 사용할 모듈들을 로딩하는 사용됩니다.

모듈 간의 의존성에 따라 모듈이 로딩하는 순서가 정해지며, 모듈이 로딩될 때마다,
모듈에 속한 언리얼 오브젝트가 모두 초기화됩니다. 

 

에디터 로딩 화면

 


언리얼 오브젝트 로딩과정



앞선 강좌에서 언리얼 오브젝트 클래스에서 생성자는 특별한 역할을 가진다고 설명드렸는데,
언리얼 오브젝트의 생성자인스턴스를 초기화해 CDO를 제작하기 위한 목적으로 사용됩니다.

이 생성자 코드는 초기화에서만 실행되고 실제 게임 플레이에서 생성자 코드는 사용할 일이 없다고 보면 됩니다.
(언리얼 엔진에서 게임 플레이에서 사용할 초기화 함수는 생성자 대신 Init 이나 혹은 BeginPlay 함수를 제공합니다. ) 

모듈내 언리얼 오브젝트의 로딩은 아래 그림과 같은 순서로 진행됩니다. 

 

모듈내 언리얼 오브젝트의 진행

 

에디터가 사용하는 모든 모듈의 로딩이 완료되면 초기화 수치는 100%가 되며, 이 때서야 비로소 에디터가 뜨게 됩니다. 
이를 확인해보기 위해 ABGameInstance 코드에 아래와 같이 생성자 선언과 구현을 추가해봅시다. 

 

//생성자 만들고 추가
UABCGameInstance::UABCGameInstance()
{
	UE_LOG(LogClass, Warning, TEXT("%s"), TEXT("Game Instance Constructor Call!"));
}

 

코드를 추가하고 붉은 라인에 F9로 브레이크 포인트를 걸고 F5로 에디터를 실행해봅시다.
에디터 로딩이 완료되기 전에 브레이크 포인트가 걸림을 확인할 수 있습니다.

이는 모듈이 자신이 속한 언리얼 오브젝트 초기화를 진행하기 위해 CDO를 생성하기 위해 생성자 코드를 실행한 화면입니다. 저의 경우 약 71% 로딩 중에 브레이크 포인트가 걸렸습니다.

 

게임인스턴스 생성자 71% 브레이크 걸림

 


언리얼 엔진 로그디버깅


이번에는 WebService 모듈로 가서 WebConnection 언리얼 오브젝트에도 동일하게 생성자를 만들어줍시다.
마찬가지로 생성자에만 로그를 찍겠습니다.

다만 이번에는 조금 다르게 로그 출력을 위한 카테고리를 직접 지정해봅시다.

로그 카테고리를 생성하기 위해 언리얼 엔진은
h에서, 사용하는     DECLARE_LOG_CATEGORY_EXTERN  매크로와
Cpp에서, 사용하는 DEFINE_LOG_CATEGORY                매크로를 제공합니다.


코드는 다음과 같습니다. 

//WebConnection.h
UCLASS()
class WEBSERVICEK_API UWebConnection : public UObject
{
	GENERATED_BODY()

public:
	UWebConnection();

};
DECLARE_LOG_CATEGORY_EXTERN(WebConnection, Log, All);



//WebConnection.cpp
#include "WebConnection.h" 

DEFINE_LOG_CATEGORY(WebConnection);

UWebConnection::UWebConnection()
{
	UE_LOG(WebConnection, Warning, TEXT("WebConnection Constructor Call!"));
}

 

아래 그림과 같이 WebConnection 객체를 생성하고 로그를 찍어봅시다. 
모듈이 초기화된 후에 에디터가 로딩하므로 에디터에서 플레이 버튼을 누르지 않아도 생성자에 넣은 로그 코드가 실행된 것을 확인할 수 있습니다. 아래는 로그가 나온 결과입니다. 

 

에디터에 출력된 생성자 로그


 

생성자 코드에는 단순히 멤버 변수의 기본 값을 지정하는 데에도 사용되지만,
하나의 언리얼 오브젝트가 다른 언리얼 오브젝트를 생성하고 포함하는 형태로도 많이 사용됩니다.
즉 여러 개의 언리얼 오브젝트들이 합쳐진 거대한 언리얼 오브젝트를 생성하는 형태라고 보면 됩니다. 

이번에는 ArenaBattle 모듈에 있는 ABGameInstance 언리얼 오브젝트를 초기화할 때,
WebService 모듈에 있는 WebConnection 언리얼 오브젝트를 생성하고 이를 보조 언리얼 오브젝트로 만들어봅시다. 

이를 제작하기 위해서는 ArenaBattle 모듈이 WebService 모듈을 참조해야 합니다. 
우리가 지금까지 제작한 ArenaBattle 모듈과 WebService 모듈은 하나의 프로젝트에서 만들었지만

둘은 엄연히 물리적인 DLL파일로 분리된 전혀 다른 모듈이기 때문입니다. 

우리는 ArenaBattle 모듈에서 WebService 모듈 내의 WebConnection 언리얼 오브젝트를 사용해야 하기 때문에 ArenaBattle.Build.cs파일의 PublicDependencyModuleNames 프로퍼티에 WebService 모듈을 추가해줍시다. 

이렇게 Build.cs 파일을 설정하면 언리얼 빌드 툴에 의해 WebService의 Include와 Library 경로는
자동으로 ArenaBattle 모듈의 내부 빌드 설정에 추가됩니다.

이제는 아래와 같이 ArenaBattle 모듈 내 모든 폴더에서 WebService 모듈 내, Public과 Classes 폴더에 있는
헤더파일을 별도의 경로 지정 없이 바로 사용할 수 있게 되었습니다. 링크 과정도 마찬가지고요. 

//ABC.Build.cs

using UnrealBuildTool;

public class ABC : ModuleRules
{
	public ABC(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] {
        "Core", "CoreUObject", "Engine", 
        "InputCore", "HeadMountedDisplay", "WebServiceK" });
	}
}

 

이제 ABGameInstance에서 WebConnection을 선언하고 마음껏 사용해줍시다.
사용할 때 한가지 참고할 부분은 
언리얼 엔진에서는 언리얼 오브젝트를 생성하고 관리할 때 특별한 일이 없는 한 거의 대부분 포인터를 사용합니다.

"WebConnection.h" 헤더파일을 인클루드한 후 언리얼 오브젝트의 포인터를 정의해줍시다.
포인터로 동작할 때 가장 큰 문제는 메모리 관리라고 할 수 있는데,
멤버 변수에 UPROPERY 매크로를 사용해주면 언리얼 엔진이 알아서 메모리를 관리해줍니다. 

 

아래는 완성된 ABGameInstance의 헤더 파일입니다.
참고로 generated.h 헤더는 가장 마지막에 선언되어야 하는 규칙이 있다고 말씀드렸지요?
그래서 맨 마지막이 아닌 두 번째에 WebConnection.h를 추가했습니다. 

참고로 클래스 선언 시에 헤더 순서를 일일히 지정하는 번거로움을 없애기 위해
class 라는 전방 선언(Forward Declaration) 키워드를 추가했습니다.
언리얼 오브젝트 선언이 다 클래스 포인터이기 때문에 가능한 일이지요~

//ABCGameInstance .h
#include "Engine/GameInstance.h"
#include "WebConnection.h"
#include "ABCGameInstance.generated.h"

UCLASS()
class ABC_API UABCGameInstance : public UGameInstance
{
	GENERATED_BODY()
	

public:
	UABCGameInstance();
	virtual void Init() override;


	UPROPERTY()
	class UWebConnection* WebConnection;
};

 


인스턴스 생성


이제 소스 파일의 생성자 코드에서는 WebConnection의 인스턴스를 생성해서 멤버 변수로 지정해주어야 합니다.
A라는 언리얼 오브젝트가 초기화를 위해, B라는 언리얼 오브젝트를 생성할 때 B는 A의 서브오브젝트(Subobject)라고 합니다.

B의 외부 참조(Outer)는 A가 되고요. 따라서 생성자 코드에서 언리얼 오브젝트의 인스턴스를 생성하고,
관리하고자 한다면 언리얼 엔진이 제공하는 API인 CreateDefaultSubobject라는 API를 쓰는 것이 좋습니다.
참고로 게임 실행 코드에서는 NewObject를 사용해 언리얼 오브젝트의 인스턴스를 생성합니다. 

아래는 완성된 소스 파일 코드입니다.

//ABCGameInstance.cpp
#include "ABCGameInstance.h"

UABCGameInstance::UABCGameInstance()
{
	UE_LOG(LogClass, Warning, TEXT("Game Instance Constructor Call Start!"));
	WebConnection = CreateDefaultSubobject<UWebConnection>(TEXT("MyWebConnection"));
	UE_LOG(LogClass, Warning, TEXT("Game Instance Constructor Call End!"));
}

void UABCGameInstance::Init()
{
	Super::Init();
	UE_LOG(LogClass, Warning, TEXT("%s"), TEXT("Game Instance Init!"));
}

 

CreateDefaultSubobject 함수에서 사용하는
첫 번째 문자열 인자는 서브오브젝트를 관리하기 위한 내부 해시(Hash)값을 생성하는데 사용합니다.

따라서 아무 문자열 값을 사용해도 무방하지만, 다른 서브오브젝트를 생성할 때 이전에 사용한 값을 사용하면 안됩니다. 바로 뻗으니 주의하시기 바랍니다. 주로 코드를 복붙할 때 많이 에러가 발생합니다. 

 

이제 빌드를 걸어서 실행하면 아래와 같은 순서로 초기화가 진행됩니다. 

1. WebConnection 언리얼 오브젝트의 CDO생성   사용하는 모듈이 초기화되서 CDO가 생성되어 로그가 찍힘)

2. ABGameInstance 언리얼 오브젝트의 CDO생성 시작

3. WebConnection 언리얼 오브젝트의 CDO생성 (에디터와 독립된 게임을 시뮬레이션 해야하기 때문에 또 CDO를 생성)

4. ABGameInstance 언리얼 오브젝트의 CDO 생성 종료

로그를 확인해봅시다.

 

 

생성자 호출 순서 로그의 확인

 

두 모듈간의 의존성이 생기면서 아까와 반대로 WebConnection 모듈이 먼저 초기화됩니다.

 


 

WebConnection 2번 호출???

1) 모듈 DLL이 초기화될 때 언리얼 오브젝트의 UClass 인스턴스가 초기화되고 CDO가 생성됩니다.
추가로 CDO는 런타임에서 GetClass()->ClassDefaultObject로 가져올 수 있습니다. 

2) 게임 에디터도 일종의 언리얼 어플리케이션이다보니,
사용하는 모듈이 초기화되서 CDO가 생성되어 로그가 찍히고요,
플레이버튼을 누르면 에디터와 독립된 게임을 시뮬레이션 해야하기 때문에 또 CDO를 생성합니다.

1번은 모듈 초기화 떄
2번은 에디터 플레이 떄 ?

 

에디터에서 게임 실행시 호출화면

에디터에서 실행시에는 WebConnection 1번만 호출된다.

실행시 모듈을 만든게 빠져서, 1번만 호출된다?

 


[참고] [1-5] 클래스 기본 객체 (Class Default Object)

 

 

 

언리얼오브젝트


언리얼 엔진의 관리를 받는 특수한 객체

 

이번 시간에는 언리얼 엔진에서 모든 오브젝트의 기본을 이루는 언리얼 오브젝트에 대해 살펴보겠습니다. 

지난 강좌에서 WebService 모듈에 있는 WebConnection 객체를 생성할 때,
부모를 Object로 지정했던 것을 기억하실 겁니다. 

이는 일반 오브젝트가 아닌 언리얼 오브젝트를 의미하는데,
비주얼 스튜디오에 가서 이 언리얼 오브젝트를 상속받은 WebConnection의 클래스 선언을 살펴보겠습니다.

우리는 지정하지 않았는데, WebConnection과 Object클래스 이름의 앞에 U자가 붙어 있음을 확인할 수 있습니다.
이는 클래스가 언리얼 오브젝트로 선언되었다는 것을 의미합니다.

WebConnection 클래스 선언

언리얼 오브젝트는 언리얼 엔진의 관리를 받는 특수한 객체입니다. 
그렇다고 해서 언리얼 프로젝트에 모든 클래스가 언리얼 오브젝트가 되어야 한다는 것은 아닙니다.

일반적으로 입력에 따라 결과 값만 받고 싶은 C++ 클래스도 언리얼 프로젝트에서 전혀 문제 없이 사용할 수 있습니다. 언리얼 엔진에서는 이 둘을 구별하기 위해서 클래스 이름에 붙은 접두사를 사용하는데,

언리얼 오브젝트는 반드시 U로 시작하고
일반 C++ 클래스는 F로 시작하는 것을 권장하고 있습니다. 

언리얼 프로젝트의 접두사 규칙 

 

언리얼 오브젝트는 언리얼 엔진 내에서 객체를 쉽고, 효율적으로 관리하기 위해서 고안되었습니다.
C++을 깊숙히 몰라도 언리얼 오브젝트의 성격을 알면 마치 Java나 C#과 같은 언어처럼 사용할 수 있게 되는데요,

 

C++ 객체가 언리얼 오브젝트가 되면 자동으로 향상되는 기능은 다음과 같습니다. 

1. CDO(Class Default Object) : 객체의 초기 값을 자체적으로 관리합니다.
2. Reflection : 객체 정보를 런타임에서 실시간 조회가 가능합니다.
3. GC(Garbage Collection) : 참조되지 않는 객체를 메모리에서 자동 해제할 수 있습니다.
4. Serialization : 객체와 속성 정보를 통으로 안전하게 보관하고 로딩합니다.
5. Delegate : 함수를 묶어서 효과적으로 관리하고 호출할 수 있습니다. 
6. Replication : 네트워크 상에서 객체간에 동기화를 시킬 수 있습니다.
7. Editor Integration : 언리얼 에디터 인터페이스를 통해 값을 편집할 수 있습니다. 



언리얼 오브젝트 구성 및 생성

언리얼 오브젝트는 C++표준이 아니고, 언리얼 엔진이 자체적으로 만들어 제공하는 프레임웍이기 때문에,
일반적인 방법으로는 만들 수 없고, 언리얼 헤더 툴(Unreal Header Tool)이라는 프로그램의 도움을 받아야 합니다.

여러분들이 헤더 파일에서 클래스 선언을 언리얼 오브젝트 규격에 맞게 선언해주면,
바로 컴파일하는 것이 아닌, 언리얼 헤더 툴이 이를 파싱하고 분석합니다. 

헤더 파일의 선언이 규격에 맞지 않으면 언리얼 헤더 툴에 의해 에러가 발생되고,규격에 맞게 선언하였다면,
언리얼 헤더 툴부가적인 메타 정보를 담은 소스코드를 프로젝트의 Intermediate 폴더에 생성해 줍니다.

이 작업이 모두 끝나면 이제 본격적으로 컴파일을 진행하게 됩니다. 아래는 이러한 구성을 담은 그림입니다. 

 

언리얼 오브젝트의 구성

 

Ctrl+Alt+F7 키를 눌러 프로젝트를 다시 빌드해봅시다. 
다시 출력 로그를 자세히 살펴보면 빌드 과정의 초기에 언리얼 빌드 툴이 동작한다는 로그가 보이게 됩니다. 

 

언리얼 헤더 툴의 로그

 


언리얼 오브젝트 선언규칙

UObject 클래스는 언리얼 오브젝트 최상단에 위치한 기본 클래스



이렇다면 이렇게 언리얼 헤더 툴이 인식할 수 있는 언리얼 오브젝트의 선언 규칙은 다음과 같습니다.
WebConnection 선언과 한번 비교해보시기 바랍니다.  

 

언리얼 오브젝트의 선언 규칙 

 

1. 클래스이름.generated.h를 반드시 가장 마지에 include 시켜주어야 합니다.

2. 클래스 선언 전에 UCLASS매크로를 사용해야 합니다.

3. 언리얼 오브젝트의 접두사는 U, A 그리고 S가 있습니다.
- 액터 기반이라면 A를 
- 액터 기반이 아니라면 모두 U 
- UI를 담당하는 슬레이트만 S를 사용하면 됩니다.

4. UObject 클래스는 언리얼 오브젝트 최상단에 위치한 기본 클래스입니다.
이로 상속받은 클래스는 모두 언리얼 오브젝트가 됩니다. 

5. 클래스 선언 내부에 GENERATED_BODY() 매크로도 선언해줍니다. 

여기까지는 언리얼 오브젝트를 생성할 때 필수적으로 선언해야 할 규칙이고,
아래와 같이 추가적으로 매크로를 지정해 언리얼 오브젝트를 확장시켜 나갈 수 있습니다



추가 매크로

 

언리얼 오브젝트에 사용하는 추가 매크로

 

1. 모듈이름_API 매크로는 상황에 따라 부가 설정을 해주는 매크로입니다. 
예를 들어 
윈도우 에디터에서 사용할 수 있게 dll로 빌드할 때에는 이 매크로는 MS의 DLL문법인 __declspec(dllexport)구문으로 자동 치환됩니다.
  이렇게 선언해야 다른 모듈에서 현재 모듈 내 클래스에 접근할 수 있게 됩니다.
게임 빌드시에는 굳이 외부에 공개할 필요 없이 모든 모듈이 하나의 exe에 통합되므로 이 때는 
빈 문자열로 치환됩니다.

2. 언리얼 오브젝트의 생성자는 오브젝트의 기본 값을 지정하는데 사용됩니다.
차후에 설명할 클래스 기본 객체인 CDO를 생성할 때 이 생성자 코드가 한번 실행됩니다. 

3. 멤버 변수 위에 UPROPERTY 매크로를 얹어주면 이 변수는 앞으로 언리얼 엔진의 관리를 받게 됩니다.
향후 언리얼 오브젝트가 소멸되더라도 언리얼 엔진의 관리를 받아 해당 멤버 변수의 메모리도 같이 자동으로 소멸되며, 메모리 사용량도 체크할 수 있습니다.

4. 멤버 함수 위에 UFUNCTION 매크로를 얹어주면 블루프린트와 연동되게 할 수 있으며,
딜리게이트나 리플리케이션과 같은 함수를 사용할 수 있어서 멤버 함수의 활용폭이 늘어납니다. 

 


게임인스턴스

 

시작하면 엔진을 초기화하고 가장 먼저 실행하는 오브젝트
그리고 게임이 종료될 때 까지 GameInstance는 살아있고 프로그램이 종료될 때 가장 마지막에 소멸 


이제 처음 시작에 제작한 ArenaBattle 모듈에 있는 ABGameInstance 클래스에 대해 살펴봅시다.
ABGameInstance의 클래스 이름 앞에 ‘U’접두사가 붙어 있으니,
이제 우리는 액터가 아닌 언리얼 오브젝트라는 것을 쉽게 파악할 수 있습니다. 

 

ABGameInstance 클래스의 선언 

 

ABGameInstance는 GameInstance 오브젝트를 상속받고 있습니다.
언리얼 엔진에서 GameInstance 오브젝트는 어플리케이션(혹은 앱)을 관리하는데 사용됩니다.

사용자가 게임을 시작하면 엔진을 초기화하고 가장 먼저 실행하는 오브젝트가 GameInstance입니다.
그리고 게임이 종료될 때 까지 GameInstance는 살아있고 프로그램이 종료될 때 가장 마지막에 소멸됩니다. 

이러한 특징으로 인해 GameInstance의 멤버를 확장해나가면,
게임의 전체 라이프싸이클(LifeCycle)에서 사용되는 데이터를 관리할 수 있습니다.

GameInstance 오브젝트가 초기화될 때,
Init이라는 함수를 호출하는데, 이를 상속받으면 우리가 어플리케이션의 초기화 루틴을 만들 수 있게 됩니다. 

이제 에디터를 열고 툴바에 있는 [세팅 -> 프로젝트 세팅] 메뉴를 눌러,
프로젝트 설정 다이얼로그를 연 후에 에디터와 게임의 초기화 설정을 주로 지정하는데 사용되는 맵 & 모드로 갑니다. 

여기서 하단의 Game Instance Class 항목의 값을 우리가 제작한 ABGameInstance로 변경하면 게임이 시작될 때 ABGameInstance의 인스턴스가 자동 생성되고 이 인스턴스로 어플리케이션이 관리됩니다. 

 게임 인스턴스 클래스의 설정

ABGameInstance에서 부모의 Init 함수를 오버라이드(Override)하여 짤막하게 로그를 남기도록 구현해봅시다.
언리얼에서 로그를 남기기 위해서는 언리얼 엔진에서 제공하는 UE_LOG라는 매크로를 사용하면 편리합니다.  
UE_LOG 매크로는 아래 세 가지 인자로 구성되어 있습니다.

1. Log Category : 로그를 구분할 수 있는 카테고리를 지정합니다.
언리얼 엔진에서는 LogClass , LogTemp와 같은 기본 카테고리를 제공하고 있으며, 모듈마다 로그를 남길 수 있도록 모듈 별로 대부분 카테고리가 지정되어 있습니다. 물론 우리가 원하는 카테고리를 직접 선언할 수 있습니다.

2. Verbosity : Log, Warning, Error로 나뉘며, 각각 흰색, 노란색, 빨간색으로 표시됩니다.
Error타입 로그의 경우 프로그램을 멈출 수 있게 설정하는 것도 가능합니다.

3. Format String : C에서 제공하는 Printf 문과 유사하게 사용합니다.
뒤에 Variable Argument를 추가할 수 있으며, 언리얼이 제공하는 2바이트 문자를 지원하는 TEXT 매크로를 사용해 문자열로 로그를 지정하거나 포맷을 정의합니다.

아래는 ABGameInstance 언리얼 오브젝트에 로그를 찍는 기능을 추가한 코드입니다.

//h
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "ABCGameInstance.generated.h"

UCLASS()
class ABC_API UABCGameInstance : public UGameInstance
{
	GENERATED_BODY()

public:
	virtual void Init() override;
	
};

//cpp
#include "ABCGameInstance.h"
void UABCGameInstance::Init()
{
	Super::Init();
	UE_LOG(LogClass, Warning, TEXT("%s"), TEXT("Game Instance Init!"));
}

 

이제 메뉴의 [창 > 개발자 툴 > 출력 로그]를 선택해 로그를 확인할 수 있는 창을 열어봅시다. 

 

[그림] 출력 로그 메뉴

 

이제 플레이 버튼을 누르면 로그 창에서 Game Instance Init! 이라는 노란 로그를 확인할 수 있게 됩니다.

 

로그의 확인

 

 

 

 

 


[참고] [1-4] 언리얼 오브젝트

지난 시간에언리얼 엔진은 특정 플랫폼에 종속적이지 않은 바이너리 제작을 위해
언리얼 빌드 툴이라는 도구를 사용해 빌드한다고 설명드렸습니다.  

언리얼 엔진의 모든 소스는 모듈이라는 단위로 구성되어 있으며,
이 중에서 필요한 모듈을 타겟으로 묶어서, 최종 빌드를 만들어냅니다

지금까지 우리가 제작한 ArenaBattle이라는 게임 프로젝트는 ArenaBattle이라는 동일한 이름의 C++ 모듈이 추가된 형태입니다이렇게 언리얼 엔진의 C++ 개발 환경은 하나의 게임 프로젝트에 하나의 모듈을 사용하는 것이 일반적입니다

언리얼 엔진에서는 ArenaBattle 모듈과 같이
게임 제작에 사용되는 로직을 담은 기본 모듈을 주 게임 모듈(Primary Game Module)이라고 합니다
이를 확인하기 위해 프로젝트의 ArenaBattle 폴더에 있는 ArenaBattle.cpp를 열어봅시다.


//cpp

#include "ArenaBattle.h" 

//주 게임 모듈임을 지정하는 모듈 매크로
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ArenaBattle, "ArenaBattle" );

 

하지만 게임 프로젝트에서는 주 게임 모듈외에도 개발자가 모듈을 추가할 수 있습니다.
 
언리얼 에디터에서는 모듈을 추가하는 메뉴를 제공하지 않지만
언리얼 빌드 시스템을 이해한다면 커맨드라인으로 모듈을 제작할 수 있습니다

 이번 시간에서는 에디터 없이 웹 서비스에 접속하는 모듈을 직접 제작해보겠습니다

WebService라는 이름의 보조 모듈을 직접 만들어봅시다
언리얼 엔진에서 모듈이 하나 생성되기 위해서는 아래와 같이 세 가지의 요소가 필요합니다.

1.     모듈의 이름과 동일한 폴더
2.     모듈의 빌드 규칙 파일 : 모듈이름.Build.cs 
3.     프리컴파일드 헤더와 소스파일 : 모듈이름.h , 모듈이름.cpp

먼저 프로젝트의 Source 폴더 아래에 WebService라는 폴더를 하나 생성합시다

새폴더와 cpp, h 

 

//WebServiceK.h
#pragma once
#include "Engine.h"

//WebServiceK.cpp
#include "WebServiceK.h"
IMPLEMENT_MODULE( FDefaultGameModuleImpl, WebServiceK);
 

 

소스파일에는 헤더파일을 인클루드해주고 이 소스가 모듈이다라는 매크로를 추가해줍시다

우리가 생성하고자 하는 것은 보조 모듈이므로
IMPLEMENT_PRIMARY_GAME_MODULE 매크로 대신에 
IMPLEMENT_MODULE 
매크로를 대신 사용합시다

이 매크로의 첫 번째 인자는 모듈을 관리할 클래스를 지정하는 데 사용합니다
FDefaultModuleImpl
 이미 엔진에서 제공하고 있는 모듈 제작을 위한 제공하는 간단한 클래스입니다
우리는 특별한 모듈을 만들 예정은 없으므로 엔진이 제공하는 구현 클래스를 그대로 사용하겠습니다
매크로의 두 번째 인자에는 모듈 이름을 넣어주면 됩니다

여기까지 완성되면 마지막 빌드 규칙인 모듈이름.Build.cs 파일을 추가합니다.

이는 처음부터 직접 만들기 복잡하니 ArenaBattle에 있는 ArenaBattle.Build.cs 파일을 복사해와 이름을 WebService.Build.cs로 변경한 후에 내용만 변경하도록 합시다

아래는 최종 구성이 완료된 모습입니다.

 

빌드 규칙 파일의 복사

 

ArenaBattle.Build.cs 파일을 WebService.Build.cs로 변경한 후에는
파일을 편집해 ArenaBattle로 되어 있는 클래스 이름과 생성자를 모두 새로운 모듈의 이름인 WebService로 교체합니다.

C# 소스 파일과 동일하며코드를 보면 
PublicDependencyModuleNames
 PrivateDependencyModuleNames 프로퍼티가 있습니다

이는 현재 모듈(WebService)이 참조할 외부 모듈의 목록을 지정하는데 사용합니다
이들 프로퍼티는 각각 Public Private 폴더에서 참고할 모듈을 지정하는데 사용됩니다.

처음 사용하는 분들은 이 구조에 대해서 조금 혼동이 있는데
언리얼 소스 코드 구조는 일반적으로 Public에는 헤더파일을 Private는 소스파일을 넣으므로 처음에는

PublicDependencyModuleNames 프로퍼티는 헤더파일이 참고할 모듈,

PrivateDependencyModuleNames 소스코드에서만 참고할 모듈이라고 이해하면 편합니다.


 

//WebServiceK.Build.cs
using UnrealBuildTool;

public class WebServiceK : ModuleRules
{
	public WebServiceK(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay" });
	}
}

 

현재는 Core, CoreUObject, Engine, InputCore 네 가지 모듈이 있는데,

앞의 세 가지는 언리얼 엔진과 연동하는 기능을 만들 예정이라면 거의 대부분 사용해야 할 모듈입니다
나머지 한 가지는 입력에 관련된 모듈입니다만우리는 웹에 접속해서 데이터만 가져올 예정이므로,
InputCore 
모듈은 제거합시다.

아래는 수정된 최종 WebService.Build.cs 파일의 내용입니다

//WebServiceK.Build.cs
using UnrealBuildTool;

public class WebServiceK : ModuleRules
{
	public WebServiceK(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine",  });
	}
}




 

모듈에 오브젝트 추가 명령 1

 

 

이제 탐색기에서 프로젝트 폴더로 돌아가 ArenaBattle.uproject 프로젝트 파일을 우클릭해 
Generate Visual Studio project files
를 선택해 소스코드를 다시 재생성합시다
재생성하고 비주얼 스튜디오 솔루션을 다시 열면 아래와 같이 프로젝트의 구성이 업데이트된 것을 볼 수 있습니다

 

 모듈이 추가된 비주얼 스튜디오의 구성

 

하지만 빌드를 걸어보면 아래 그림과 같이 아무 반응이 없거나 ArenaBattle에 관련된 바이너리만 빌드가 됩니다 


이는 언리얼 빌드 툴에 의해서 소스 구조는 파악이 되었지만
빌드할 대상에 우리가 만든 WebService 모듈이 추가되지 않았기 때문입니다.

빌드할 대상 모듈을 지정하는 공간은 프로젝트이름.Target.cs파일입니다
현재 우리는 게임 빌드와 에디터 빌드가 각각 ArenaBattle.Target.cs 파일과 ArenaBattleEditor.Target.cs 파일에 설정되어 있습니다.

이들을 열고 SetupBinaries 함수의 OutExtraModuelNames 프로퍼티에 우리가 제작한 모듈을 추가합시다

 

Target.cs

 

Editor.Target.cs

 

(Add가 아닌 AddRange를 사용해서 추가해야 한다. 아래 코드 참고)

 ExtraModuleNames.AddRange(new string[] { "ABC", "WebServiceK" });

 

수정과 저장을 완료한 후빌드를 다시 시작하면 비로소 우리가 제작한 모듈을 컴파일하는 것을 볼 수 있을 겁니다.

먼저 빌드 세팅을 Development Editor로 변경한 후에 Ctrl+Alt+F7키를 눌러 "솔루션 다시 빌드 옵션"으로 빌드하면
아래와 같이 에디터용 바이너리로
 2개의 dll파일이 생성된 것을 볼 수 있습니다 

 

빌드가 완성된 모듈


 

모듈에 오브젝트 추가 명령 2

우리가 제작한 모듈이 완성된 것을 확인했으니이제 ArenaBattle.uproject를 더블 클릭해 에디터를 실행합시다.
컴파일이 완료됬으니 우리가 제작한 모듈이 에디터에 뜰 것 같았는데아쉽게도 목록에 보이질 않습니다.  

 

이는 언리얼 에디터를 가동시킬 때언리얼 에디터가 기동할 때,
우리가 새롭게 제작한 모듈을 추가하라는 명령을 uproject 파일에 넣지 않았기 때문입니다.

첫 강좌에서 봤던 uproject 파일의 Modules 항목에 우리가 만든 모듈을 수동으로 추가해주어야
에디터는 프로젝트의 Binaries 폴더를 뒤져서 우리가 빌드한 모듈을 에디터 프로세스에 포함시켜 띄웁니다.  

에디터를 닫고 uproject 파일을 아래와 같이 변경해줍시다 

 

빨강색 부분 추가함


모듈에 오브젝트 추가 명령 3

 

프로젝트 파일을 변경했음에도 불구하고 여전히 모듈 목록에는 우리가 제작한 모듈 목록이 존재하지 않습니다.

사실 우리가 만든 모듈은 로딩이 되었습니다하지만 해당 모듈에는 언리얼 오브젝트라는 객체가 없기 때문에
목록에서 안 보이는 것 뿐입니다

에디터에서 새롭게 만든 모듈에 언리얼 오브젝트를 하나 추가해주면 해결됩니다.

에디터를 닫지 말고 파일 메뉴에서 "새로운 C++ 클래스 추가메뉴를 눌러서 모든 클래스 표시를 누른 후 최상단에 있는 Object를 선택합시다Object는 다음 챕터에서 설명할 언리얼 오브젝트의 최상위 기본 클래스입니다 

언리얼 오브젝트 기본형 지정

 

다음 버튼을 누른 후이제 이번 강좌에서 가장 중요한 부분을 진행합시다.

WebConnection이라고 새롭게 생성할 언리얼 오브젝트의 이름을 입력하고오른쪽의 모듈 목록을 보면 우리가 만든 WebService라는 모듈이 뜬 것을 볼 수 있습니다이를 선택하여, WebConnection 언리얼 오브젝트가 WebService 모듈로 추가되도록 설정합시다. 

 

언리얼 오브젝트의 목표 모듈 지정

이제 클래스 생성 버튼을 눌러서 변화를 확인해봅시다에디터에서 추가된 모듈의 컴파일을 다시 시작하는데현재 환경에 따라 컴파일이 성공할 수도 있고 실패할 수도 있습니다결과에 신경쓰지 말고 에디터를 일단 닫읍시다

다시 비주얼 스튜디오를 살펴보면 WebConnection 헤더파일과 소스파일이 추가된 것을 볼 수 있습니다 

추가된  WebConnection 언리얼 오브젝트

 

비주얼 스튜디오에서 F7키를 눌러 다시 빌드를 걸어줍시다
이제 에디터를 다시 실행해보면 드디어 아래와 같이 모듈에 WebService가 새로 생기고 WebConnection이라는 언리얼 오브젝트가 추가된 것을 확인할 수 있습니다.

 WebService  모듈의 확인

 


모듈 폴더의 구성

 


이제 새로운 모듈의 생성이 성공적으로 완성되었습니다.
하지만 그 전에 
모듈 내 파일들을 언리얼 엔진의 구성에 맞게 정리하고 마무리합시다. 

언리얼 모듈 폴더의 구성에는 3가지의 특별한 폴더 이름이 존재합니다 

1.     Public : 외부 모듈에 공개할 파일을 관리합니다

2.     Private : 내부 모듈에서 사용할 파일을 관리합니다

3.     Classes : Public과 동일합니다만주로 프로젝트에서 사용할 언리얼 오브젝트 선언을 별도로 모아 관리합니다안 써도 무방합니다.

우리 모듈에도 이 규칙을 적용시킵시다. WebService 폴더에 Public, Private, Classes 폴더를 생성합시다

 

현재 모듈 폴더에 있는 파일들을 아래와 같이 각각의 폴더로 옮깁시다

1.     Public 폴더 : WebService.h

2.     Private 폴더 : WebService.cpp, WebConnection.cpp 

3.     Classes 폴더 : WebConnection.h 

그리고 다시 프로젝트 폴더의 ArenaBattle.uproject 파일을 우클릭해 Generate Visual Studio project files 메뉴를 선택해 프로젝트를 재생성합시다아래는 재생성된 프로젝트의 결과물입니다.

 

  최종 생성된 모듈의 구성

 

반드시 이렇게 모듈의 폴더 구조를 생성해야 하는 것은 아닙니다
변경하기 전대로 모듈 폴더에 모든 파일을 몰아넣어도 제작에는 아무 문제 없습니다만,
언리얼 엔진의 소스 코드의 구성이 대부분 이렇게 되어 있습니다

참고하라는 의미에서 넣었보았습니다이제 비주얼 스튜디오에서 F7키를 눌러 최종 빌드를 마무리합시다.

 


[참고] [1-3] 모듈의 제작

+ Recent posts