UE4中Info、LevelScriptActor和Level
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因为是代表关卡蓝图,是允许携带“复制”变量函数的,所以也有可能被排序到后列。
posted on 2022-04-11 23:33 hxh_space 阅读(1581) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理