unity 加载完场景继续加载场景中的物体_如何实现更高效的场景工作流?可编程对象不能错过...

在Unity中管理多个场景并不简单,改善场景工作流是改善游戏性能和团队生产力的关键。在本文中,我们将分享一些建立场景工作流的贴士,方便你管理大型项目。

大部分游戏都会有多个关卡,每个关卡又会有不止一个场景。如果场景较小,可以将其拆分为一个个预制件,在游戏运行时使用预制件来启用、激活场景。当游戏规模变大,预制件会逐渐占据更多内存,反而没有场景的效率来得高。

如果将关卡拆分为场景,就能避免Git、SVN、Unity Collaborate这类协作工具在合并工程时出现冲突。而Unity的多场景编辑(Multi-Scene editing)功能可在编辑器内打开多个场景,解决高效管理场景的问题。

组合多个场景为一个关卡

在下方视频中,我们将游戏逻辑和不同的关卡组件拆分为多个不同的场景,使得关卡加载速度更加高效。接着,使用叠加式场景加载 (LoadSceneMode.Additive) 模式在加载游戏逻辑的同时,完成关卡组件的加载和卸载。预制件在场景中扮演了“锚点”的角色,让团队能分别编辑各个场景,使团队协作变得更加灵活。

场景在编辑模式中同样可以加载、运行,在设计关卡时就实现所有场景的预览。

我们将展示两种不同的场景加载方法。第一种是基于距离的加载,适用于无内部场景的开放世界,也能借助视觉特效(像雾气)来隐藏加载和卸载流程。

第二种方法是触发式加载,适用于室内场景。

在设置好关卡元素的加载方式后,我们便能继续添加控制层,管理所有的关卡。

使用ScriptableObjects管理多个关卡

如果想要管理各个关卡的场景,或者游戏时所有的关卡,一个可能的方法是在MonoBehaviour脚本中使用静态变量和单例模式。但是使用单例模式会让系统之间产生刚性连接,无法实现模块化制作,系统不能独立存在,必定会相互依赖。

另一个问题出在静态变量上。变量无法在检视器中暴露出来,需要使用代码才能修改,使美术和关卡设计师难以测试游戏。而场景间的数据分享更是需要在DontDestroyOnLoad对象中使用静态变量,造成问题。

ScriptableObject是一种专门存储数据的序列化类,可用于储存不同场景的数据信息。MonoBehavior脚本一般作为GameObject的附属组件使用,而ScriptableObjects并不附着与任何GameObject,因此可以在不同场景间共享。

它不仅能用在关卡中,也可用在菜单场景中。你可以在脚本中加入一个GameScene类,将关卡和菜单共有的属性存储到其中。

public class GameScene : ScriptableObject{    [Header("Information")]    public string sceneName;    public string shortDescription;    [Header("Sounds")]    public AudioClip music;    [Range(0.0f, 1.0f)]    public float musicVolume;    [Header("Visuals")]    public PostProcessProfile postprocess;}
右滑查看完整代码 注意类继承了ScriptableObject而不是MonoBehaviour。该类可储存任意数量的属性。在设置完毕后,创建继承GameScene类的Level和Menu类,对象就也会成为ScriptableObject。
[CreateAssetMenu(fileName = "NewLevel", menuName = "Scene Data/Level")]public class Level : GameScene{    //Settings specific to level only(关卡设置)    [Header("Level specific")]    public int enemiesCount;}
右滑查看完整代码

在上方添加CreateAssetMenu属性可以生成Unity的Asset菜单,在菜单中创建新关卡。你也能加入一个enum(枚举)函数来实现在检视器中选择菜单类型。

public enum Type{    Main_Menu,    Pause_Menu} [CreateAssetMenu(fileName = "NewMenu", menuName = "Scene Data/Menu")]public class Menu : GameScene{    //Settings specific to menu only(菜单设置)    [Header("Menu specific")]    public Type type;}
右滑查看完整代码

在创建关卡和菜单后,接下来需要加入一个列出所有关卡和菜单的数据库。加入索引值来确定玩家所在的关卡。再添加加载新游戏(即加载第一关)、重玩当前关卡和进入下一关的方法。这三种方法只有索引值会更改,所以可以同个方法多次套用。

[CreateAssetMenu(fileName = "sceneDB", menuName = "Scene Data/Database")]public class ScenesData : ScriptableObject{    public List levels = new List();    public List
menus = new List (); public int CurrentLevelIndex=1; /* * Levels */ //Load a scene with a given index(根据所给索引值加载场景) public void LoadLevelWithIndex(int index) { if (index <= levels.Count) { //Load Gameplay scene for the level(加载关卡游戏场景) SceneManager.LoadSceneAsync("Gameplay" + index.ToString()); //Load first part of the level in additive mode(以叠加形式加载关卡第一部分) SceneManager.LoadSceneAsync("Level" + index.ToString() + "Part1", LoadSceneMode.Additive); } //reset the index if we have no more levels(没有更多关卡时重置索引值) else CurrentLevelIndex =1; } //Start next level(开始下一关) public void NextLevel() { CurrentLevelIndex++; LoadLevelWithIndex(CurrentLevelIndex); } //Restart current level(重启当前关卡) public void RestartLevel() { LoadLevelWithIndex(CurrentLevelIndex); } //New game, load level 1(开始新游戏,加载第一关) public void NewGame() { LoadLevelWithIndex(1); } /* * Menus */ //Load main Menu(加载主菜单) public void LoadMainMenu() { SceneManager.LoadSceneAsync(menus[(int)Type.Main_Menu].sceneName); } //Load Pause Menu(加载暂停菜单) public void LoadPauseMenu() {        SceneManager.LoadSceneAsync(menus[(int)Type.Pause_Menu].sceneName);} 右滑查看完整代码

上方也包含了菜单的方法,你也能使用前边的enum类型来加载特定菜单——注意enum中的顺序需要与菜单列表的顺序相同。

这下你就能在Project窗口中右击在Asset菜单下创建关卡、菜单或数据库ScriptableObject了。

4e7a78de3a44c0602edac471be5413a4.png 然后,你就能添加关卡、菜单,调整设置,再将其添加到场景数据库中了。下方图例展示了Level1、MainMenu和Scenes Data的检视器。 5c99df5971afa078a55817bf44bc7cbd.png

接下来要做的就是调用方法。在下方例子中,当玩家到达关卡末尾时,UI中会出现Next Level(下一关)按钮,按下后就会调用NextLevel方法。要给按键添加方法,需要在Button组件的On Click事件部分点击“+”按钮来添加新事件,再将Scenes Data ScriptableObject拖到对象字段中,并选择NextLevel方法。下方为图例。

ab76f4da15ce591dbe2c4076596ed742.png

重新开始、回到主菜单等其它按键也是同样的操作。其它脚本,如背景音乐的AudioClip或后期处理设置,都能引用ScriptableObject来获取属性,在关卡中使用。

使用提示

最小化加载/卸载流程

在视频中,玩家在多次进入、离开碰撞体时,触发了重复的场景加载和卸载。要想避免此类问题,可在调用场景加载/卸载方法前运行一个协同程序,在玩家离开触发位置后再停止程序。

命名规则

另一个提示是在项目中使用靠得住的命名规则。团队最好事前就确定如何命名不同类型的资源——从脚本、场景,到材质等所有内容。这样一来,场景管理,尤其是ScriptableObject的管理都会容易很多。在示例中我们直接根据场景名称来命名。最好避免使用字符串方法,不然在重命名场景后,场景可能无法加载。

自定义工具

一种避免名称依赖的方法是让场景以Object类型被引用。如此一来,你就能在检视器中拖动场景资源到脚本,让脚本安全地取得场景名称。但由于其为编辑器类,运行时又无法访问AssetDatabase类,需要将两种数据结合起来,才能让方案在编辑器和运行时运行。你可以参考ISerializationCallbackReceiver接口示例来查看如何让对象在序列化之后从Scene资源中提取字符串路径,储存数据用在运行时中。

此外,你还能自制一个检视器,使用按键给Build Settings快速添加场景,而不必在菜单中手动添加、管理。

JohannesMP开发的开源应用是一个非常好的例子(注意这不是Unity官方资源)。

欢迎大家积极反馈

本文仅展示了ScriptableObject在使用多场景和预制件制作时的一种使用方法。不同游戏管理场景的方式大相径庭,一个方案是无法满足所有游戏结构的。请根据自己项目的组织结构来制作合适的工具。希望本文能帮助、激励你制作自己的场景管理工具。

如果你有任何疑问,也欢迎在留言区留言,我们非常希望了解大家在游戏中管理场景的方法。

63ceeffdf3fefa46e2dd6bb99bde3a53.png

文中提及的相关链接:

[1] 多场景编辑文档:

https://docs.unity3d.com/Manual/MultiSceneEditing.html

[2] 叠加式场景加载文档:

https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.Additive.html

[3] 触发式加载文档:

https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html

[4] ScriptableObject 文档:

https://docs.unity3d.com/Manual/class-ScriptableObject.html

[5] CreateAssetMenu文档:

https://docs.unity3d.com/ScriptReference/CreateAssetMenuAttribute.html

[6] Object文档:

https://docs.unity3d.com/ScriptReference/Object.html

[7] AssetDatabase文档:

https://docs.unity3d.com/ScriptReference/AssetDatabase.html

[8] ISerializationCallbackReceiver文档:

https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html

[9] Build Settings文档:

https://docs.unity3d.com/Manual/BuildSettings.html

[10] JohannesMP开发的开源应用(非官方资源):

https://gist.github.com/JohannesMP/ec7d3f0bcf167dab3d0d3bb480e0e07b

b06ea07d93f550bb55dad54a9ee579b5.png

23bd810b9685ff5038ef6d003a89c327.png

19ca3d0a602f4a9f1632a394f7a6af96.png

977f1ffa9be1ec13d9fe6413e7e50694.png

586c53e04f83f11faa502df860033d3e.png

34da5d6538ce8292532168f59b5fa904.png

e6c3ac6312fe9a3050851f8ae968f3c4.png

 每一个“在看”,都是我们前进的动力 

657cab75cf091bb1e536159c93bccc63.png
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值