원래는 ULevel이나 ClientTravel 부분부터 상세히 찾아들어가보려 했는데 그러기에 앞서 Level 정보를 담고 있는 FWorldContext의 데이터나 대략적인 변수 용처를 먼저 알고 들어가는 것이 개념 정리에 좋을 것 같아 FWorldContext를 먼저 공부 해보았다.
/** FWorldContext
* A context for dealing with UWorlds at the engine level. As the engine brings up and destroys world, we need a way to keep straight
* what world belongs to what.
*
* WorldContexts can be thought of as a track. By default we have 1 track that we load and unload levels on. Adding a second context is adding
* a second track; another track of progression for worlds to live on.
*
* For the GameEngine, there will be one WorldContext until we decide to support multiple simultaneous worlds.
* For the EditorEngine, there may be one WorldContext for the EditorWorld and one for the PIE World.
*
* FWorldContext provides both a way to manage 'the current PIE UWorld*' as well as state that goes along with connecting/travelling to
* new worlds.
*
* FWorldContext should remain internal to the UEngine classes. Outside code should not keep pointers or try to manage FWorldContexts directly.
* Outside code can still deal with UWorld*, and pass UWorld*s into Engine level functions. The Engine code can look up the relevant context
* for a given UWorld*.
*
* For convenience, FWorldContext can maintain outside pointers to UWorld*s. For example, PIE can tie UWorld* UEditorEngine::PlayWorld to the PIE
* world context. If the PIE UWorld changes, the UEditorEngine::PlayWorld pointer will be automatically updated. This is done with AddRef() and
* SetCurrentWorld().
*
*/
FWorldContext 구조체 선언 위에 적혀 있는 설명 주석입니다.
대략 번역해보면 다음과 같다.
- 엔진 단위에서 UWorlds를 관리하기 위한 Context
- 기본적으로 1개를 소지 하고 있다.
- World간의 이동 뿐만 아니라 현재 PIE(Play In Editor, 에디터에서 실행하는 경우)의 UWorld까지 관리하는 방안을 제공
- 반드시 UEngine 내부에서만 접근되어야 하며 외부에서 직접적으로 관리되어서는 안됨.
- 외부에서는 UWorld*를 사용할 수 있으며 이를 엔진 단위 함수로 전달할 수 있다.
이때 엔진 코드는 주어진 UWorld*와 연관된 WorldContext를 탐색할 수 있다. - 편의를 위해 UWorld*의 외부 포인터를 유지할 수 있다.
예를 들어, PIE는 UEditorEngine::PlayWorld 포인터를 PIE WorldContext에 의존시킬 수 있다.
만약 PIE UWorld가 바뀌면, PlayWorld 포인터는 AddRef()와 SeetCurrentWorld()함수로 인해 자동으로 갱신이 된다.
정확히 이해 못하겠지만 여기서 확실한 것이 하나 있습니다.
우리는 ULevel을 공부하기 전에 UWorld를 먼저 알아야 합니다.
이에 대해 매우 잘 정리된 블로그 포스트를 공유드립니다.
https://www.zhihu.com/column/insideue4
InsideUE5
深入UE5剖析源码,浅出游戏引擎架构理念
www.zhihu.com
중국에서 작성된 포스트인데 구글 번역으로 보면 내용 설명과 더불어 구조적인 고민이나 설명들이 여타 튜토리얼이나 문서에서는 볼 수 없는 것들이 많습니다.
원래는 출처 넣고 번역을 하려고 했으나 펌을 금지하기 때문에 링크로 공유 하겠습니다.
UWORLD
https://docs.unrealengine.com/5.0/en-US/API/Runtime/Engine/Engine/UWorld/
UWorld
The World is the top level object representing a map or a sandbox in which Actors and Components will exist and be rendered.
docs.unrealengine.com
UWorld는 일종의 ULevel의 대표 오브젝트다.
하나의 Persistent Level과 함께 월드 볼륨상에서 로드되거나 취소될 수 있는 Streaming Level을 사용할 수 있고, 여러가지 Level들을 WorldComposition으로 정리해 사용할 수 있게 해준다.
Stand Alone Game에서는 일반적으로 하나의 World만 존재하나, 에디터 안에서는 많은 World들이 수정될 수 있다.
ULEVEL
https://docs.unrealengine.com/4.27/en-US/API/Runtime/Engine/Engine/ULevel/
ULevel
The level object.
docs.unrealengine.com
말 그대로 Level 오브젝트이다.
Level 내에 존재하는 Actor 리스트나 BSP 정보, Brush List들을 들고 있다.
모든 Level은 Outer로 World을 가지고 있고, Persistent Level로 지정될 수 있으며, 여러 Sub Level들을 가질 수 있다.
몇가지 용어 설명을 추가하자면
Persistent Level: 아무것도 없을 때에 호출되는 기본 Level.
Sub Level: Persistent Level에서 일정부분을 담당하는 Level. 지역 뿐 아니라 사운드나 레이아웃 등 기능 별로도 분리가 가능하다. 이는 동시 작업이 불가한 Binary 파일인 레벨 작업에서 최소한의 동시작업을 지원한다.
즉 UWorld와 ULevel들의 관계는 대략 이런 식인 것이다.
이에 대한 부분은 차후 UWorld를 공부 할 때 자세히 다뤄보겠다.
본론으로 돌아가, FWorldContext는 위에서 우리가 길게 핥아본 UWorld를 관리하는 구조체이다.
이제 코드 부분을 다시 살펴보자.
USTRUCT()
struct FWorldContext
{
GENERATED_USTRUCT_BODY()
/**************************************************************/
TEnumAsByte<EWorldType::Type> WorldType;
FSeamlessTravelHandler SeamlessTravelHandler;
FName ContextHandle;
/** URL to travel to for pending client connect */
FString TravelURL;
/** TravelType for pending client connects */
uint8 TravelType;
/** URL the last time we traveled */
UPROPERTY()
struct FURL LastURL;
/** last server we connected to (for "reconnect" command) */
UPROPERTY()
struct FURL LastRemoteURL;
UPROPERTY()
TObjectPtr<UPendingNetGame> PendingNetGame;
/** A list of tag/array pairs that is used at LoadMap time to fully load packages that may be needed for the map/game with DLC, but we can't use DynamicLoadObject to load from the packages */
UPROPERTY()
TArray<struct FFullyLoadedPackagesInfo> PackagesToFullyLoad;
/**
* Array of package/ level names that need to be loaded for the pending map change. First level in that array is
* going to be made a fake persistent one by using ULevelStreamingPersistent.
*/
TArray<FName> LevelsToLoadForPendingMapChange;
/** Array of already loaded levels. The ordering is arbitrary and depends on what is already loaded and such. */
UPROPERTY()
TArray<TObjectPtr<class ULevel>> LoadedLevelsForPendingMapChange;
/** Human readable error string for any failure during a map change request. Empty if there were no failures. */
FString PendingMapChangeFailureDescription;
/** If true, commit map change the next frame. */
uint32 bShouldCommitPendingMapChange:1;
/** Handles to object references; used by the engine to e.g. the prevent objects from being garbage collected. */
UPROPERTY()
TArray<TObjectPtr<class UObjectReferencer>> ObjectReferencers;
UPROPERTY()
TArray<struct FLevelStreamingStatus> PendingLevelStreamingStatusUpdates;
UPROPERTY()
TObjectPtr<class UGameViewportClient> GameViewport;
UPROPERTY()
TObjectPtr<class UGameInstance> OwningGameInstance;
/** A list of active net drivers */
UPROPERTY(transient)
TArray<FNamedNetDriver> ActiveNetDrivers;
/** The PIE instance of this world, -1 is default */
int32 PIEInstance;
/** The Prefix in front of PIE level names, empty is default */
FString PIEPrefix;
/** The feature level that PIE world should use */
ERHIFeatureLevel::Type PIEWorldFeatureLevel;
/** Is this running as a dedicated server */
bool RunAsDedicated;
/** Is this world context waiting for an online login to complete (for PIE) */
bool bWaitingOnOnlineSubsystem;
/** Handle to this world context's audio device.*/
uint32 AudioDeviceID;
/** Custom description to be display in blueprint debugger UI */
FString CustomDescription;
// If > 0, tick this world at a fixed rate in PIE
float PIEFixedTickSeconds = 0.f;
float PIEAccumulatedTickSeconds = 0.f;
/**************************************************************/
/** Outside pointers to CurrentWorld that should be kept in sync if current world changes */
TArray<UWorld**> ExternalReferences;
/** Adds an external reference */
void AddRef(UWorld*& WorldPtr)
{
WorldPtr = ThisCurrentWorld;
ExternalReferences.AddUnique(&WorldPtr);
}
/** Removes an external reference */
void RemoveRef(UWorld*& WorldPtr)
{
ExternalReferences.Remove(&WorldPtr);
WorldPtr = nullptr;
}
/** Set CurrentWorld and update external reference pointers to reflect this*/
ENGINE_API void SetCurrentWorld(UWorld *World);
/** Collect FWorldContext references for garbage collection */
void AddReferencedObjects(FReferenceCollector& Collector, const UObject* ReferencingObject);
FORCEINLINE UWorld* World() const
{
return ThisCurrentWorld;
}
FWorldContext()
: WorldType(EWorldType::None)
, ContextHandle(NAME_None)
, TravelURL()
, TravelType(0)
, PendingNetGame(nullptr)
, bShouldCommitPendingMapChange(0)
, GameViewport(nullptr)
, OwningGameInstance(nullptr)
, PIEInstance(INDEX_NONE)
, PIEWorldFeatureLevel(ERHIFeatureLevel::Num)
, RunAsDedicated(false)
, bWaitingOnOnlineSubsystem(false)
, AudioDeviceID(INDEX_NONE)
, ThisCurrentWorld(nullptr)
{ }
private:
UWorld* ThisCurrentWorld;
};
WorldType
WorldType은 현재 World가 어떤 케이스인지 지칭한다.
타입인 EWorldType은 다음과 같다.
/** Specifies the goal/source of a UWorld object */
namespace EWorldType
{
enum Type
{
/** An untyped world, in most cases this will be the vestigial worlds of streamed in sub-levels */
None,
/** The game world */
Game,
/** A world being edited in the editor */
Editor,
/** A Play In Editor world */
PIE,
/** A preview world for an editor tool */
EditorPreview,
/** A preview world for a game */
GamePreview,
/** A minimal RPC world for a game */
GameRPC,
/** An editor world that was loaded but not currently being edited in the level editor */
Inactive
};
}
여기서 잘 모르는 것이 몇 있다.
Preview가 붙은 것은 오브젝트나 머테리얼에서 미리보기를 하는 World들을 지칭한다.
SeamlessTravelHandler
Seamless World Travel을 관리하는 구조체의 변수이다.
https://docs.unrealengine.com/5.0/ko/travelling-in-multiplayer-in-unreal-engine/
멀티플레이어에서의 이동(Travel)
멀티플레이어에서의 travel, 이동 작동방식에 대한 개요입니다.
docs.unrealengine.com
구조체인만큼 추가적으로 확인해야 할 사항이 많기 때문에 차후 추가로 공부하는 기회를 갖도록 하겠다.
ContextHandle
FWorldContext를 구분짓는 일종의 Key이다.
이 값으로 WorldContext를 검색한다.
TravelURL
아래 포스트에서 어느정도 설명을 했으나 한번 더 짚어보겠다.
https://redchiken.tistory.com/310
UGameplayStatics::OpenLevel 분석 - 1
UGamePlayStatics::OpenLevel은 C++에서 레벨을 변경하고자 할 때 사용하는 함수이다. /** * Travel to another level * * @param LevelName the level to open * @param bAbsolute if true options are reset, if..
redchiken.tistory.com
TravelURL은 client connect를 할 Level의 URL이다.
형식은 [LevelName]?[Option]으로 구성되어 있다.
TravelType
TravelURL과 마찬가지로 어떤 형태로 Travel을 할지에 대한 정보이다.
enum ETravelType
{
TRAVEL_Absolute,
TRAVEL_Partial,
TRAVEL_Relative,
TRAVEL_MAX,
}
Absolute와 Relative는 각각 절대경로, 상대경로로 이해하면 된다.
하지만 Partial은 어떤 경로인지 감이 잘 안잡힐 수 있다.
TRAVEL_Partial의 의미를 유추할 수 있는 코드 부분은 UEngine::HandleDisconnect이다.
void UEngine::HandleDisconnect( UWorld *InWorld, UNetDriver *NetDriver )
{
// There must be some context for this disconnect
check(InWorld || NetDriver);
// If the NetDriver that failed was a pending netgame driver, cancel the PendingNetGame
CancelPending(NetDriver);
// InWorld might be null. It might also not map to any valid world context (for example, a pending net game disconnect)
// If there is a context for this world, setup client travel.
if (FWorldContext* WorldContext = GetWorldContextFromWorld(InWorld))
{
// Remove ?Listen parameter, if it exists
WorldContext->LastURL.RemoveOption( TEXT("Listen") );
WorldContext->LastURL.RemoveOption( TEXT("LAN") );
// Net driver destruction will occur during LoadMap (prevents GetNetMode from changing output for the remainder of the frame)
SetClientTravel( InWorld, TEXT("?closed"), TRAVEL_Absolute );
}
else if (NetDriver)
{
// Shut down any existing game connections
if (InWorld)
{
// Call this to remove the NetDriver from the world context's ActiveNetDriver list
DestroyNamedNetDriver(InWorld, NetDriver->NetDriverName);
}
else
{
NetDriver->Shutdown();
NetDriver->LowLevelDestroy();
// In this case, the world is null and something went wrong, so we should travel back to the default world so that we
// can get back to a good state.
for (FWorldContext& PotentialWorldContext : WorldList)
{
if (PotentialWorldContext.WorldType == EWorldType::Game)
{
FURL DefaultURL;
DefaultURL.LoadURLConfig(TEXT("DefaultPlayer"), GGameIni);
const UGameMapsSettings* GameMapsSettings = GetDefault<UGameMapsSettings>();
if (GameMapsSettings)
{
PotentialWorldContext.TravelURL = FURL(&DefaultURL, *(GameMapsSettings->GetGameDefaultMap() + GameMapsSettings->LocalMapOptions), TRAVEL_Partial).ToString();
PotentialWorldContext.TravelType = TRAVEL_Partial;
}
}
}
}
}
}
마지막 부분을 보면 주석으로 "무언가 잘못 되었을 때 이전 World로 되돌린다"는 설명이 되어있다.
그리고 이 때 TravelType은 TRAVEL_Partial로 지정되어 있다.
이를 통해 TRAVEL_Partial은 일부 정보만 리셋하는 것으로 예상해볼 수 있다.
실제 도큐먼트에서도 carry name, reset server로 기재가 되어있기도 하구 말이다.
LastURL
가장 마지막에 접근했던 Level의 URL이다.
LastRemoteURL
가장 마지막에 connect 했던 Level의 URL이다.
일반적으로는 사용하지 않지만 reconnect를 할 때에는 이 값을 저장한다.
PendingNetGame
이 변수에 대해서는 이렇다 할 정보를 찾지 못했다.
우선 UPendingNetGame 타입이라 별도 클래스이므로 나중에 추가로 확인해보도록 하겠다.
PackagesToFullyLoad
'UE5 > Level' 카테고리의 다른 글
[Level] UGameplayStatics::OpenLevel 분석 - 1 (0) | 2022.07.02 |
---|