Gameplay-02: Level And World
有了 Actor 和 Component,如何将他们有机组织起来形成一个 3D 世界?直观上看来可以使用一个World
将这些内容全部囊括起来,但将游戏的全部内容全部加载进去的操作显然是不现实的。
所以这就需要采取更细粒度的概念来划分世界
在 UE 中,把一个游戏世界拆分成了一个个Level
,这些Level
共同组成一个更大的``World`
一个游戏引擎的“世界观”关系到了一连串的后续内容组织:玩家的管理、世界的生成、变换和毁灭。以及资源的加载释放也都和这种划分(Level)绑定在一起
ULevel
Level
就是 Actor 的容器,是玩家活动的主要场景
可以看到,Level 和 Actor 之间是聚合关系(aggregation),二者不是强依赖,可以互相独立存在
在ULevel
中默认带有一个ALevelScriptActor
,它允许我们在关卡里编写脚本,其实就是关卡蓝图。
由于 Actor 可以表示一些在世界中没有物理表示的物体,AInfo
就负责 world 中的数据的管理的基类,和Level
直接相关的一个就是AWorldSetting
,其实就是在细节面板旁边的世界设置
思考:为什么AWorldSetting
放在Actors[0]
的位置?而ALevelScriptActor
却不用?
- 这里就涉及到
Level::SortActorList()
的内部实现了 - 在
ULevel
中,有一个TArray<AActor*> Actors;
负责储存全部的 Actor - 并且将“非网络”的 Actor 和“网络可复制”的 Actor 分开存放,
AWorldSetting
是静态的,不会在游戏运行中改变,并且将其放在第一位也可以起到与其他 Actor 区分的作用 - 至于
ALevelScriptActor
,它代表的是关卡蓝图,是允许携带“复制”变量函数的
思考:ALevelScriptActor
也是一个AActor
为什么不可以添加 Component?
- 其实是可以添加的,只是没有把这个功能暴露给界面
- 可以在 C++ 里往其中添加 Component,也可以在关卡蓝图中添加
- 隐藏起来的目的,我认为应当是为了突出
ALevelScriptActor
的Level
概念,从而与其他同样继承自AActor
的那些Actor
的功能区分开,在后续使用和设计时思路会更加清晰
UWorld
一个个Level
组装起来就是一个World
,方式有两种:
SubLevel
WorldComposition
对于 World,每个 Level 在什么位置,什么时候加载是它关注的重点
对于一个 World,存在一个PersistentLevel
和多个其他``Level`
Persistent
就是一开始加载进 WorldStreaming
则是后续动态加载
观察上图,
- Levels:保存当前已经加载的 Level
- StreamingLevels 保存整个 World 的 Levels 配置列表
- 需要注意的是 PersistentLevel 和 CurrentLevel,二者作为 Level 的快速引用
- 如果在编辑器中,二者可以指向不同的 Level
- 但是在运行时,CurrentLevel 必须指向 PersistentLevel
思考:主 PersistentLevel 存在的意义?
一个 World 至少存在一个 Level,同时不同的 Level 都有各自的一个 worldsetting,所以设置PersistentLevel
目的之一就是为了选取一个 WorldSetting 作为主要的设置
但其他的 Level 的 WordlSetting 也会需要用到,简单地说,要在整个世界内都起作用的配置要从这个PersistentLevel
中提取,对于一些需要在单独的 Level 中起作用的,比如编辑一个 Level 的光照就是针对一个个 Level 设置的
思考:Levels 的 Actors 和 World 有没有直接关系?
- 在编辑器的 WorldOutliner 中可以看到 Level 的 Actors,但这里的 Actors 是 World 通过
TActorIteratorBase
,World 的 Actor 迭代器,遍历每个 Level 来获取 Actor - Controllers 和 Pawn 则会保存引用
- 对于物理场景(Physical Scene),则是在各个 Level 之间共享的,毕竟要全局考虑物理碰撞(World 拼接 Level 的时候,也会把两个导航网格拼接起来)
思考:不把所有 Actor 都放到 World 里的原因
- 给每个 Actor 一个 Level 层级上的区分
- 如果需要动态处理一个 Level,释放的时候就需要从 World 整体中筛选出目标,损耗大影响性能
总结
Level 是 Actor 的容器,划分了 World,方便了 Level 的动态加载,也允许了团队的实时协作