游戏中的虚拟世界是如何组织的呢?

这里,在玩家中流行使用一个词——关卡。玩家们进入关卡,探索关卡内的迷宫,击败关底boss,然后进入下一个关卡,周而复始。

在Unity3D里,这个关卡被称为是Scene,作为游戏内的一项项单独的资产而存在。这些Scene可以单独存在,也可以和其他Scene组合使用(多场景加载),同时,Scene和Scene之间又可以用SceneManager来做跳转(加载和卸载)。

在unreal engine里对于关卡的使用更加直白,就直接是一个个的Level,同样对于关卡本身所应该具备的逻辑操作(关卡的游玩,关卡的加载和卸载),虚幻也实现了一套架构来承载。

但是什么是World呢?单单用“世界”一词似乎太过简单,我们在游戏开发里做的一切——音乐、渲染、物理规则,这些所有都是为了让玩家感受到另一个不同于现实世界的世界。可以说玩家将在游戏内体会到的一切都应该是属于这个World的范畴内的。

虚幻本身也基本上是以这种思路来安排World的角色的,零散的Level将玩家的完整体验切割成段,所有的Level流程组合起来才成了对World的完整体验。(将World切割成一个个的Level的原因之一也是前一个时代机器性能不足而做出妥协,我们没办法把一整个游戏的所有东西全部一下子加载进来,带宽也好,内存也好。现如今出现了越来越多的开放世界游戏,正是业内的能工巧匠们利用硬件的进步和自己的各种奇思妙想来越来越妥善得解决这个问题)

文章目录

  • 编辑器内的Level
    • Level的创建
    • Level的保存
    • Level的打开
    • 设置游戏的默认Level
    • Level的跳转
  • World——多Level的管理
    • World Setting
    • ULevel和UWorld
  • 总结与未来的学习研究规划
  • 参考

编辑器内的Level

在虚幻引擎内,Level是作为一项资产而存在,也就是后缀为.umap的文件。也就是说,Level支持所有文件一级的操作——创建,保存,打开等等。

这一小节快速的罗列一下Level(或者说.umap文件)的基本操作和用法。

Level的创建

快捷键:Ctrl + N

键入快捷键命令后,调出如下的窗口,ue5中给出了一些默认的关卡模板,这些模板(除了Empty Level)会提供一些关卡所需的基本内容,如基本光照环境,基本的天空球等等。

选定模板后,点击创建即可。此时编辑器会最懂打开并加载选定的模板。

但是注意,此时该Level还没有被保存,资产面板中也还没有出现对应的.umap文件。

Level的保存

快捷键:Ctrl + S

键入快捷命令后,调出如下窗口,这一步就是要选定.umap文件的命名和保存位置。

此外,在切换当前编辑器显示的Level时,编辑器也会弹出窗口提示Level的相关保存事宜。

Level的打开

快捷键:Ctrl + O

键入快捷命令后,调出如下窗口,其实就是相当于加了Level过滤器的资产界面,只会显示Level类型的内容。

当然,直接在资产选择界面也可以选定相应的Level资产并且双击打开。

设置游戏的默认Level

我们的游戏打开时会加载哪个Level呢?总不能什么都不加载空荡荡吧?

相关的选项可以在Project Settings -> Maps & Modes -> Default Maps来进行配置。

  • Editor Startup Map定义了当打开编辑器时默认加载哪个Level,这是为了方便我们的开发编辑;
  • Editor Templete Map Overrides会去重写当我们调出创建Level的窗口时所现实的模板,我们可以将一些自己做的模板添加进去,方便开发工作;
  • Game Default Map定义了游戏的默认地图,也就回答了我们上面的那个问题,游戏开始时加载哪一个Level;
  • Transition Map定义了当我们从一个Level向另一个Level跳转时所使用的过渡地图;

Level的跳转

那么既然现在已经可以创建一个个的Level了,那么自然而然就会想到去做Level间的跳转,即我们游戏的主角结束一个Level的游玩后,通过某种装置或者传送门进入到另外的一个Level,这个跳转往往会需要一定的时间,甚至于某些情况下需要做一些跳转过渡画面来等候。

最简单的方案,可以使用GameplayStatics提供的了一个静态方法OpenLevel,可以用于进行关卡的打开。

可以利用TriggerBox制作一个简单的触发机制,等到玩家角色进入范围时进行关卡的跳转。

方法的核心部分方法的核心部分是GEngine的SetClientTravel,目前为止现在了解到这一层就足够了。

void UGameplayStatics::OpenLevel(const UObject* WorldContextObject, FName LevelName, bool bAbsolute, FString Options)
{...GEngine->SetClientTravel( World, *Cmd, TravelType );
}

World——多Level的管理

从虚幻编辑器内的安排上我们似乎并不能够很好的看清World的存在,与之相关联的最直接的好像有一位World Setting的存在,那我们就先来看看World Setting里有什么内容。

World Setting

World Setting面板的打开路径是Window->World Setting,其粗略内容如下:

  • Precomputed Visibility,预先计算出的可见性。动态遮挡剔除相关的优化设置,可以为某些硬件性能较差的平台节省运行时内存的消耗,尤其是当关卡内容较少或较小时优化效果会比较好;
  • Game Mode,游戏模式。当前关卡的GameMode配置,可以覆盖掉全局设置的GameMode;
  • Lightmass,光照。当前场景的光照细节设定,主要针对静态光烘培、ao等相关内容的配置,是配置全局光照的位置;
  • World,世界。一些世界相关的配置,主要是游玩边界,寻路系统相关的内容;
  • Physics,物理。是否去覆盖全局的重力设定,以及物理碰撞的相关内容的设置;
  • World Partition,世界分区。ue5引入的新特性之一,主要负责大世界管理的相关内容,是一套全新的流式关卡管理和解决方案,相关内容我也会在后续做相关的内容提取和撰写;
  • Broadphase,Broadphase碰撞的配置选项,是NVIDIA的PhysX系统提供的特性;
  • Foliage,专门针对于世界分区中植被相关的设置,主要是划分的网格大小;
  • Navigation,关卡中寻路网格大小的相关设置;
  • VR,对VR来说单位对应相关的设置,虚幻中使用虚幻单位(Unreal Unit),该参数主要还需要参考虚拟世界构建时所使用的尺度;
  • Rendering,配置与距离场环境光遮蔽相关的以及动态间接阴影的配置;
  • Audio,设置项目中的默认音效配置,如音量、混响和渐变等;
  • Tick,Actor的Tick行为(每帧执行)的相关配置;
  • AI,AI系统的配置(开启,以及和核心类配置);
  • Cooking,构建游戏中的其中一个过程——烘培过程的配置;

从World Setting里的配置内容来看,World Setting更像是描述单个的Level的设置(而不是对整个World的配置),即如果将之成为是Level Setting也不过分。

事实上也的确如此,其背后原因从代码层面可以一窥端倪。

ULevel和UWorld

ULevel的部分核心属性:


// ULevel本身直接继承于UObject,所以本身也具备垃圾回收,反射,支持序列化,等等//
// The level object.  Contains the level's actor list, BSP information, and brush list.
// Every Level has a World as its Outer and can be used as the PersistentLevel, however,
// when a Level has been streamed in the OwningWorld represents the World that it is a part of.
///*** 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.*/
UCLASS(MinimalAPI)
class ULevel : public UObject, public IInterface_AssetUserData, public ITextureStreamingContainer
{...
public:// 前面提到了GameStatics里的OpenLevel方法,该方法的参数虽然是关卡名,// 但实际底层的SetClientTravel函数的接收的确是一个FURL类型的参数,而ULevel也维护着这样一个变量/** URL associated with this level. */FURL                   URL;// 关卡本身算是Actor们的载体(集合),这里用一个容器来容纳所有的Actor元素/** Array of all actors in this level, used by FActorIteratorBase and derived classes */TArray<AActor*> Actors;...// 关卡也存着一枚指向所在世界的指针/** * 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)TObjectPtr<UWorld> OwningWorld;...// 关卡蓝图(level blueprint),也对应着我们可以在编辑器内打开的那个关卡蓝图,// 它本身是一个ALevelScriptActor,继承自Actor,所以拥有着Actor的大部分特性(包括接收输入等)// 自然地,既然是Actor,LevelScriptActor也包含在上面Actors数组里/** The level scripting actor, created by instantiating the class from LevelScriptBlueprint.  This handles all level scripting */UPROPERTY(NonTransactional)TObjectPtr<class ALevelScriptActor> LevelScriptActor;...
private:// 关卡中保存着WorldSettings的一枚指针,正是前面编辑器中的那个World Setting// 自然地,既然是Actor,WorldSettings也包含在上面Actors数组里UPROPERTY()TObjectPtr<AWorldSettings> WorldSettings;...
}

Actor是我们已经比较熟悉的类,现在我们知道LevelBlueprint本身也是Actor,这也是属于“不可见的”Actor之一。

而LevelScriptActor直接继承自Actor,当然在Actor基础上,做了一定的补充和修改,比如说关于渲染和碰撞等,但是又允许继承自Actor的输入相关的接收等,我们在蓝图中使用时基本上就按Actor的理解来写即可。

WorldSetting则是继承自AInfo(也就是常见的允许存在于),不同于那些可以拖进编辑器场景中的,AInfo就是那类为场景做贡献但是不需要进入到场景中的Actor。

这一部分代码的阅读也着重参考了大钊的文章(参考文底连接):

除了其他零零碎碎的成员,还有一个比较关键的就是OwningWorld指针,它指向该关卡所属的UWorld对象。

UWorld的部分核心属性:

// 相较于Level,World更像是一个管理者,Level的管理者
// Level在它上面可以自由组合,可以流式加载,从而作为更庞大的世界的基础
// 而且最重要的,World同样可以不只有一个,游戏的世界,编辑器的世界,PIE实例世界等等// /** * The World is the top level object representing a map or a sandbox in which Actors and Components will exist and be rendered.  ** A World can be a single Persistent Level with an optional list of streaming levels that are loaded and unloaded via volumes and blueprint functions* or it can be a collection of levels organized with a World Composition.** In a standalone game, generally only a single World exists except during seamless area transitions when both a destination and current world exists.* In the editor many Worlds exist: The level being edited, each PIE instance, each editor tool which has an interactive rendered viewport, and many more.**/UCLASS(customConstructor, config=Engine)
class ENGINE_API UWorld final : public UObject, public FNetworkNotify
{// PersistantLevel将是我们后面一个小节的核心内容之一,也可以看作所谓的主关卡// 其主要作用体现在关卡的流式管理(Level Streaming)中// 它本身的话代表了当前世界中的核心关卡(代表着当多关卡之间的设置有冲突时,优先以PersistantLevel为依据)/** Persistent level containing the world info, default brush and actors spawned during gameplay among other things          */UPROPERTY(Transient)TObjectPtr<class ULevel>                                PersistentLevel; ...// 相对于PersistantLevel,就会有StreamingLevels,表示那些动态加载和卸载的关卡们// PersistantLevel只有一个,而StreamingLevels的数量就很灵活了/** Level collection. ULevels are referenced by FName (Package name) to avoid serialized references. Also contains offsets in world units */UPROPERTY(Transient)TArray<TObjectPtr<ULevelStreaming>> StreamingLevels;...// 世界的类型/** The type of world this is. Describes the context in which it is being used (Editor, Game, Preview etc.) */TEnumAsByte<EWorldType::Type> WorldType;...// 当前的GameMode,当有多个关卡加载到世界中时,对应也会有多个World Setting// 那么以哪个配置为准呢,这里负责维护着对应的指针/** The current GameMode, valid only on the server */UPROPERTY(Transient)TObjectPtr<class AGameModeBase>                        AuthorityGameMode;...// 同上/** The replicated actor which contains game state information that can be accessible to clients. Direct access is not allowed, use GetGameState<>() */UPROPERTY(Transient)TObjectPtr<class AGameStateBase>                       GameState;...// 世界就是关卡的集合,这里就是关卡们组成的数组/** Array of levels currently in this world. Not serialized to disk to avoid hard references. */UPROPERTY(Transient)TArray<TObjectPtr<class ULevel>>                       Levels;...// PersistentLevel和CurrentLevel只是个快速引用。// 在编辑器里编辑的时候,CurrentLevel可以指向其他Level,但运行时CurrentLevel只能是指向PersistentLevel。/** Pointer to the current level being edited. Level has to be in the Levels array and == PersistentLevel in the game. */UPROPERTY(Transient)TObjectPtr<class ULevel>                             CurrentLevel;...// 该World所从属的GameInstanceUPROPERTY(Transient)TObjectPtr<class UGameInstance>                      OwningGameInstance;

这部分代码的阅读同样参考了大钊的文章:

总结与未来的学习研究规划

本文从使用层面总结了Level的基础用法,从编辑器层面罗列了Level和World相关的配置选项,还从代码层面了解几个核心类(Level,World等)之间的架构关系。

其中尤其,从Level和World的代码中,可以清晰得看到Level是如何承载Actor的(包括一些看不见的Actor),看到World的组成又是怎样的。当然虚幻中这两者的应用远不止于此,这篇文章仅仅算是将Level和World相关内容破了个题,相关的内容还包括World是如何组织Level的——Level Streaming相关内容,大世界相关的优化问题——World Composition和World Partition相关的配置,等等。

本文更多的是对几个基本概念进行拆解,对代码结构有一个基本的认识,在此基础之上,后续还会再跟进Level Streaming、World Composition和World Partition相关的内容和文章。

参考

虚幻 5.0 Documentation - Levels

知乎作者 大钊 的文章《InsideUE4 GamePlay架构(二)Level和World》

虚幻基础之Gameplay游戏框架之Level和World相关推荐

  1. 【读书笔记《Android游戏编程之从零开始》】11.游戏开发基础(SurfaceView 游戏框架、View 和 SurfaceView 的区别)...

    1. SurfaceView 游戏框架实例 实例效果:就是屏幕上的文本跟着点击的地方移动,效果图如下: 步骤: 新建项目"GameSurfaceView",首先自定义一个类&quo ...

  2. 虚幻引擎C++编程游戏开发基础

    流派:电子学习| MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:23.8 GB |时长:44h 59m ...

  3. 基础知识漫谈(3) 组合基础知识,设计游戏框架

    如何让画面动起来? 都知道动画的原理,想想看跑马灯,在任意时刻把跑马灯按停,面对眼睛的那附图,它叫做帧(Frame). 帧,就是一串儿连贯动画里的单个截面. 玩儿游戏的人知道,要达到基本流畅的水准,3 ...

  4. swift语言注册非免费苹果账号iOS游戏框架Sprite Kit基础教程

    swift语言注册非免费苹果账号iOS游戏框架Sprite Kit基础教程 1.2.3  注册非免费苹果账号swift语言注册非免费苹果账号iOS游戏框架Sprite Kit基础教程 免费的苹果账号在 ...

  5. Swift版iOS游戏框架Sprite Kit基础教程下册

    Swift版iOS游戏框架Sprite Kit基础教程下册 试读下载地址:http://pan.baidu.com/s/1qWBdV0C  介绍:本教程是国内唯一的Swift版的Spritekit教程 ...

  6. iOS游戏框架Sprite Kit基础教程第1章编写第一个Sprite Kit程序

    iOS游戏框架Sprite Kit基础教程第1章编写第一个Sprite Kit程序 程序是为了实现特定目标或解决特定问题而用计算机语言编写的命令序列的集合.本章将以编写第一个Sprite Kit程序为 ...

  7. iOS游戏框架Sprite Kit基础教程——Swift版上册

    iOS游戏框架Sprite Kit基础教程--Swift版上册 试读下载地址:http://pan.baidu.com/s/1qWBdV0C  介绍:本教程是国内唯一的Swift版的Spritekit ...

  8. Python基础-Pygame游戏框架之飞机大战

    Pygame游戏框架 Pygame Pygame是一套跨平台的Python模块,专为编写游戏而设计. 它包括计算机图形和声音库,旨在与Python编程语言一起使用. 展示窗体 引入pygame模块 i ...

  9. 《分布式虚拟现实系统(DVR)》(Yanlz+Unity+SteamVR+分布式+DVR+人工智能+边缘计算+人机交互+云游戏+框架编程+立钻哥哥+)

    <分布式虚拟现实系统(DVR)> <分布式虚拟现实系统(DVR)> 版本 作者 参与者 完成日期 备注 YanlzVR_DVR_V01_1.0 严立钻 2019.07.11 # ...

最新文章

  1. 初学Python(一)——数据类型
  2. MapInfo之格式说明(转载)
  3. OpenGL 面剔除Face culling
  4. moosefs分布式文件系统
  5. CT流程与CT图像的windowing操作(转载+整理)
  6. python基本对象_python基础教程:运算对象、运算符、表达式和语句
  7. matlab guide实现多级界面
  8. 云计算:大数据时代的系统工程
  9. 从第一范式(2nf)到第二范式(3nf)_关系数据模型-范式
  10. php使用referer,PHP利用REFERER根居访问来地址进行页面跳转
  11. 计算机ppt制作培训心得,计算机培训心得
  12. 【知识图谱】OpenKG开源系列 | 海洋鱼类百科知识图谱(浙江大学)
  13. php 汉字转拼音类,汉字与拼音转换PHP类
  14. 汽车电子电气TARA分析从入门到放弃
  15. 子网划分以及网络号的计算
  16. 1、张龙netty学习 第一个netty服务端
  17. 浏览器查看请求与响应报文
  18. 工业3d相机选型指南(新手向)
  19. IDM Internet Download Manager 无法下载此文件 问题
  20. 2015十大最具影响力的推广—兄弟连IT教育

热门文章

  1. 苹果手机计算机怎样拉到桌面,20个你应该知道的iPhone 7实用小技巧
  2. Redis(设计与实现):28---事件之文件事件(AE_READABLE事件、AE_WRITABLE事件)
  3. 关于系统分析师的考试感想
  4. 巨星传奇更新招股书:业务绑定歌手周杰伦 上半年营收降24%
  5. 【附资料】PMP证书有用吗?
  6. 关于百度网盘离线下载链接无效的问题
  7. 全渠道营销与多渠道营销:定义、比较、示例
  8. 手淘推出“店铺二楼”
  9. 破解水卡最省钱!超详细!--解决小白烦恼
  10. ds18b20程序c语言,单片机中使用DS18B20温度传感器C语言程序(参考6)