有了 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,也可以在关卡蓝图中添加
  • 隐藏起来的目的,我认为应当是为了突出ALevelScriptActorLevel概念,从而与其他同样继承自AActor的那些Actor的功能区分开,在后续使用和设计时思路会更加清晰

UWorld

一个个Level组装起来就是一个World,方式有两种:

  • SubLevel
  • WorldComposition

对于 World,每个 Level 在什么位置,什么时候加载是它关注的重点

对于一个 World,存在一个PersistentLevel和多个其他``Level`

  • Persistent就是一开始加载进 World
  • Streaming则是后续动态加载

观察上图,

  • 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 的动态加载,也允许了团队的实时协作

参考链接