1 類圖
首先給出類間關系圖,如圖所示:
2 AInfo
在UE4源碼中,AInfo代碼如下:
/**
* Info is the base class of an Actor that isn't meant to have a physical representation in the world, used primarily
* for "manager" type classes that hold settings data about the world, but might need to be an Actor for replication purposes.
*/
UCLASS(abstract, hidecategories=(Input, Movement, Collision, Rendering, "Utilities|Transformation"), showcategories=("Input|MouseInput", "Input|TouchInput"), MinimalAPI, NotBlueprintable)
class AInfo : public AActor
{
GENERATED_UCLASS_BODY()
#if WITH_EDITORONLY_DATA
private:
/** Billboard Component displayed in editor */
UPROPERTY()
class UBillboardComponent* SpriteComponent;
public:
#endif
/** Indicates whether this actor should participate in level bounds calculations. */
virtual bool IsLevelBoundsRelevant() const override { return false; }
public:
#if WITH_EDITORONLY_DATA
/** Returns SpriteComponent subobject **/
ENGINE_API class UBillboardComponent* GetSpriteComponent() const;
#endif
};
首先將該類的注釋翻譯一下:
AInfo類是那些在world中沒有物理展示的Actor基類,主要作為保存world中設置數據的管理類來使用,同時也可能作為實現復制意圖的Actor使用。
理解:
前面我們說過,在UE4中一些“不可見的對象”也是Actor,代表着這個游戲世界的某種信息、狀態、規則等,它們雖然不能像其他Actor一樣被直接擺放到關卡中,但是也在為關卡的運行貢獻着自己的力量。而AInfo及其子類就是這樣的一類Actor。Ainfo類及其子類主要有以下一些特點:
- AInfo類是在關卡中“不可見”的Actor的基類
- Ainfo類及其子類主要用作保存world中的設置數據
- Ainfo類及其子類也可能用作實現復制
3 LevelScriptActor
在UE4源碼中,LevelScriptActor部分代碼如下:
/**
* ALevelScriptActor is the base class for classes generated by
* ULevelScriptBlueprints. ALevelScriptActor instances are hidden actors that
* exist within a level, and can execute level-wide logic (operating on specific
* actor instances within the level). The level-script's functionality is defined
* inside the ULevelScriptBlueprint itself (using the blueprint's node-based
* interface).
*
* @see AActor
* @see https://docs.unrealengine.com/latest/INT/Engine/Blueprints/UserGuide/Types/LevelBlueprint/index.html
* @see ULevelScriptBlueprint
* @see https://docs.unrealengine.com/latest/INT/Engine/Blueprints/index.html
* @see UBlueprint
*/
UCLASS(notplaceable, meta=(ChildCanTick, KismetHideOverrides = "ReceiveAnyDamage,ReceivePointDamage,ReceiveRadialDamage,ReceiveActorBeginOverlap,ReceiveActorEndOverlap,ReceiveHit,ReceiveDestroyed,ReceiveActorBeginCursorOver,ReceiveActorEndCursorOver,ReceiveActorOnClicked,ReceiveActorOnReleased,ReceiveActorOnInputTouchBegin,ReceiveActorOnInputTouchEnd,ReceiveActorOnInputTouchEnter,ReceiveActorOnInputTouchLeave"), HideCategories=(Collision,Rendering,"Utilities|Transformation"))
class ENGINE_API ALevelScriptActor : public AActor
{
GENERATED_UCLASS_BODY()
// --- Utility Functions ----------------------------
/** Tries to find an event named "EventName" on all other levels, and calls it */
UFUNCTION(BlueprintCallable, meta=(BlueprintProtected = "true"), Category="Miscellaneous")
virtual bool RemoteEvent(FName EventName);
/**
* Sets the cinematic mode on all PlayerControllers
*
* @param bInCinematicMode specify true if the player is entering cinematic mode; false if the player is leaving cinematic mode.
* @param bHidePlayer specify true to hide the player's pawn (only relevant if bInCinematicMode is true)
* @param bAffectsHUD specify true if we should show/hide the HUD to match the value of bCinematicMode
* @param bAffectsMovement specify true to disable movement in cinematic mode, enable it when leaving
* @param bAffectsTurning specify true to disable turning in cinematic mode or enable it when leaving
*/
UFUNCTION(BlueprintCallable, meta=(BlueprintProtected = "true"), Category="Game|Cinematic")
virtual void SetCinematicMode(bool bCinematicMode, bool bHidePlayer = true, bool bAffectsHUD = true, bool bAffectsMovement = false, bool bAffectsTurning = false);
// --- Level State Functions ------------------------
/** @todo document */
UFUNCTION(BlueprintImplementableEvent, BlueprintAuthorityOnly)
void LevelReset();
/**
* Event called on world origin location changes
*
* @param OldOriginLocation Previous world origin location
* @param NewOriginLocation New world origin location
*/
UFUNCTION(BlueprintImplementableEvent)
void WorldOriginLocationChanged(FIntVector OldOriginLocation, FIntVector NewOriginLocation);
//~ Begin UObject Interface
virtual void PreInitializeComponents() override;
//~ End UObject Interface
//~ Begin AActor Interface
virtual void EnableInput(class APlayerController* PlayerController) override;
virtual void DisableInput(class APlayerController* PlayerController) override;
#if WITH_EDITOR
virtual bool SupportsExternalPackaging() const override { return false; }
#endif
//~ End AActor Interface
bool InputEnabled() const { return bInputEnabled; }
private:
UPROPERTY()
uint32 bInputEnabled:1;
};
注釋翻譯:
ALevelScriptActor是那些從ULevelScriptBlueprints生成的類的基類。ALevelScriptActor類實例對象存在於關卡中、能夠執行level范圍內的邏輯操作(操作level內的特定actor實例對象)並且在關卡中是被隱藏起來的。level-script的函數功能已經在ULevelScriptBlueprints內部(使用藍圖的node-based 接口)本身完成定義。
4 Level
4.1 Level的定義
首先看官方對於Level的解釋:
關卡是Actor的容器,可以作為玩家活動的場景。
在游戲中,玩家看到的所有對象,交互的所有對象,都保存在一個世界中。這個世界我們稱之為 關卡(Level)。在虛幻引擎4中,關卡由靜態網格體(Static Mesh)、體積(Volume)、光源(Light)、藍圖(Blueprint)等內容構成。這些豐富的對象,共同構成了玩家的游戲體驗。在虛幻4中,關卡可以是廣袤無邊的開放式場景,也可以是只包含寥寥幾個Actor的小關卡。
4.2 Level和World的區別
在UE4中Actor、Component中講解了Actor和Component。在UE4的游戲世界中,小到地面上的一塊沙石,大到游戲世界中的規則、管理數據等都可以使用Actor來表示。那么怎么來布置一整個游戲世界呢?
最直接的反應,可能就是采用一個巨大的“world”容器,把這個游戲世界內的所有Actor對象全部都裝到這個“World”容器中並按照規則進行布置。這樣的方式簡單直接且暴力,在面對較小的虛擬游戲世界時或許是一種不錯的做法。但是當游戲所模擬的虛擬世界非常大時仍然采用這種做法會給PC帶來巨大的運算負擔,甚至導致PC性能無法滿足運算需求。同時,因為玩家的活動和可見范圍有限,為了最優性能,把即使是很遠的跟玩家無關的對象也考慮進來也明顯是不明智的。比如吃雞游戲,跟玩家相關的游戲對象只是在該玩家附近幾百米內的對象,超出范圍的對象顯然與玩家無關,將其考慮在內是十分多余的。
解決問題的方案: 采用更細粒度的概念來划分世界。
在UE4中對整個世界(world)進行拆分,將拆分出來的局部游戲世界稱為關卡(Level),一個World由一個或多個Level組成。后續的內容組織,玩家的管理,世界的生成、變換和毀滅,游戲引擎內部的資源的加載釋放也都是較細粒度的划分綁定在一起的。
Level與world的關系
- 為了游戲布置和其他因素,將整個游戲世界(world)划分為多個關卡(Level),一個World由一個或多個Level組成,World負責這些Level的加載和釋放,對它們進行管理。
- 多個Level拼接成為一個游戲世界。
4.3 Level源碼
在UE4中,Level的代碼定義過長,在這里不做展示,只給出本文想要說明的代碼部分。
Level.h中部分代碼
/**
* A Level is a collection of Actors (lights, volumes, mesh instances etc.).
* Multiple Levels can be loaded and unloaded into the World to create a streaming experience.
*
* @see https://docs.unrealengine.com/latest/INT/Engine/Levels
* @see UActor
*/
UCLASS(MinimalAPI)
class ULevel : public UObject, public IInterface_AssetUserData
{
GENERATED_BODY()
...
/** Array of all actors in this level, used by FActorIteratorBase and derived classes */
TArray<AActor*> Actors;
/** Reference to the blueprint for level scripting */
UPROPERTY(NonTransactional)
class ULevelScriptBlueprint* LevelScriptBlueprint;
/**
* The World that has this level in its Levels array.
* This is not the same as GetOuter(), because GetOuter() for a streaming level is a vestigial world that is not used.
* It should not be accessed during BeginDestroy(), just like any other UObject references, since GC may occur in any order.
*/
UPROPERTY(Transient)
UWorld* OwningWorld;
/** BSP Model components used for rendering. */
UPROPERTY()
TArray<class UModelComponent*> ModelComponents;
...
/** The level scripting actor, created by instantiating the class from LevelScriptBlueprint. This handles all level scripting */
UPROPERTY(NonTransactional)
class ALevelScriptActor* LevelScriptActor;
UPROPERTY()
AWorldSettings* WorldSettings;
/**
* Sorts the actor list by net relevancy and static behaviour. First all not net relevant static
* actors, then all net relevant static actors and then the rest. This is done to allow the dynamic
* and net relevant actor iterators to skip large amounts of actors.
*/
ENGINE_API void SortActorList();
...
}
翻譯注釋:
關卡是Actor的收集器(燈光、體積、網格體實例對象等)
多個關卡可以被加載到游戲世界中或者從游戲世界中卸載以便於獲得流暢的游戲體驗。
這個注釋沒有什么含義,繼續對其進行詳細分析。
TArray<AActor*> Actors
數組中保存了關卡中所有的Actor,這些Actor通過FActorIteratorBase或者它的派生類來使用。
UWorld * OwningWorld
一個世界是由一個或多個關卡組成,OwningWorld是這個關卡所屬的世界。
class ALevelScriptActor * LevelScriptActor
在前面對LevelScriptActor的介紹中,可知LevelScriptActor用於執行Level內的所有邏輯操作,也可操作Level內特定的Actor實例對象。所以在這里,LevelScriptActor就是關卡腳本Actor,由它來處理關卡中所有的腳本程序。
AWorldSettings * WorldSettings
由本文最開始的類圖可知,WorldSettings類繼承於AInfo類,在對AInfo類的介紹中說到,AInfo用於保存游戲世界中的設置數據,以便於實現對游戲世界的管理。
而在Level中,WorldSettings則是記錄着本Level的所有規則屬性,UE需要使用時可以直接從這里獲取。
注意: Actors中存儲了本Level內部的所有Actor。所以,在Actors中也存儲着WorldSettings和LevelScriptActor。
下面再看對Actors的排序代碼:
Lveel.cpp中對void SortActorList()的實現
void ULevel::SortActorList()
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_Level_SortActorList);
if (Actors.Num() == 0)
{
// No need to sort an empty list
return;
}
LLM_REALLOC_SCOPE(Actors.GetData());
TArray<AActor*> NewActors;
TArray<AActor*> NewNetActors;
NewActors.Reserve(Actors.Num());
NewNetActors.Reserve(Actors.Num());
if (WorldSettings)
{
// The WorldSettings tries to stay at index 0
NewActors.Add(WorldSettings);
if (OwningWorld != nullptr)
{
OwningWorld->AddNetworkActor(WorldSettings);
}
}
// Add non-net actors to the NewActors immediately, cache off the net actors to Append after
for (AActor* Actor : Actors)
{
if (Actor != nullptr && Actor != WorldSettings && !Actor->IsPendingKill())
{
if (IsNetActor(Actor))
{
NewNetActors.Add(Actor);
if (OwningWorld != nullptr)
{
OwningWorld->AddNetworkActor(Actor);
}
}
else
{
NewActors.Add(Actor);
}
}
}
NewActors.Append(MoveTemp(NewNetActors));
// Replace with sorted list.
Actors = MoveTemp(NewActors);
}
對本函數的理解:
在本排序函數中,把那些“非網絡”的Actor放在前面,而把“網絡可復制”的Actor們放在后面,同時在排序后第一個網絡可復制的Actor處添加一個起始索引標記iFirstNetRelevantActor,從而加速了網絡復制時的檢測速度。在此排序算法中,AWorldSettings放在了Actors[0]的位置。AWorldSettings是靜態的數據提供者,在游戲運行過程中不會改變,不需要網絡復制,所以也就可以一直放在前列。若保持AWorldSettings為第一個,就可以把AWorldSettings和其他非網絡Actor再度區分開,在進行調用時可加快調用和檢測速度。ALevelScriptActor因為是代表關卡藍圖,是允許攜帶“復制”變量函數的,所以也有可能被排序到后列。