SRPG游戏开发(十九)第六章 基本框架 - 四 程序入口(Application Entry)
2018-03-18 00:39
567 查看
返回目录
在这里,我们的游戏入口类,也是同样的作用。
但我们同时将场景的操作也写入其中,作为
说到场景的管理,主要功能就是加载场景,卸载场景与改变场景。
同时还能从
常用方法(Method)如下:
-
-
-
-
-
常用事件(Events)如下:
-
-
-
我们要做的主要工作是在入口中,执行这些操作时,利用
-
-
-
-
其中,
其他事件参数与
这些事件与扩展,按需添加,并不一定全部要添加。
如果在项目中混用,我们需要知道是使用了哪种类型。
LoadSceneType.cs:
OnLoadSceneArgs.cs:
游戏入口在整个游戏中只有一个,必然是个单例,所以我们让它继承
这样游戏入口成为了一个
其次,游戏开始后,我们需要让游戏能够运行Unity的场景事件。
你会看到,我们让
最后,创建我们的事件方法。
激活场景发生改变事件。
加载场景完成事件。
你会看到,在加载完成事件中,我使用了一个异步方法。如果存在Loading界面,这能更好的控制Loading界面。
举个例子:
场景加载完成,等待全局加载资源。
全局加载资源后,发送
Loading界面接收
Loading界面加载资源时,更新Loading界面信息。
Loading界面加载资源完成后,关闭并发送开始信号(例如告诉控制器可以加载入场动画或可以开始操作)。
其中,
场景卸载完成事件。
在卸载场景中,当卸载的场景
当要加载场景时,在加载场景之前的事件。
*这个事件的触发,我们要自己进行编写。在编写加载场景方法时再进行叙述。
而加载之前,我们要发送
如果是异步中,还应该有一个
首先,实现内部加载场景。
其中,
其次,进行加载场景对外接口的编写。
最后,进行卸载场景的编写。
在卸载场景时,只有加载的Scene超过1个时,才可以卸载。
同样的,也是加载的场景数量必须多余1个,才可能改变。
假设,初始化GameManager命名为GameMain.cs,类为
那么,代码应该写成
重写所需方法,添加所需方法、字段、属性等。
挂载在初始化场景的GameObject上。
使用它。
第六章 基本框架(Framework)
四 程序入口(Application Entry)
游戏入口,一般来说,作用有初始化游戏和一些游戏配置属性。在这里,我们的游戏入口类,也是同样的作用。
但我们同时将场景的操作也写入其中,作为
SceneManager的扩展。
1 了解场景管理器(SceneManager)
Unity管理场景的API都在SceneManager中,它在
UnityEngine.SceneManagement命名空间中。
说到场景的管理,主要功能就是加载场景,卸载场景与改变场景。
同时还能从
SceneManager中获取场景信息,场景信息类型为
Scene。
常用方法(Method)如下:
-
GetActiveScene,获取当前激活场景。
-
LoadScene,卸载所有场景后,加载场景。
-
LoadSceneAsync,卸载所有场景后,异步加载场景。
-
UnloadSceneAsync,当加载的场景多余1个时,异步卸载场景。
-
SetActiveScene,当加载的场景多余1个时,改变当前激活场景。
常用事件(Events)如下:
-
activeSceneChanged,当前激活场景发生改变时触发。
-
sceneLoaded,场景加载完成事件。
-
sceneUnloaded,场景卸载完成事件。
我们要做的主要工作是在入口中,执行这些操作时,利用
MessageCenter发送相应事件。
2 事件参数(Event Args)
在MessageCenter发送事件时,我们需要场景信息作为参数。
MessageCenter所需参数如下:
-
OnActiveSceneChangedArgs,激活场景改变时,事件参数。
-
OnSceneLoadedArgs,场景加载完成时,事件参数。
-
OnSceneUnloadedArgs,场景卸载完成时,事件参数。
-
OnLoadSceneArgs,加载场景之前,事件参数。
其中,
OnLoadSceneArgs在
SceneManager中没有对应事件,这是我们自己添加的。
其他事件参数与
SceneManager中对应事件参数是相同的。
这些事件与扩展,按需添加,并不一定全部要添加。
2.1 OnActiveSceneChangedArgs.cs
using UnityEngine.SceneManagement; namespace DR.Book.SRPG_Dev.Framework { public class OnActiveSceneChangedArgs : MessageArgs { public Scene scene1; public Scene scene2; } }
2.2 OnSceneLoadedArgs.cs
using UnityEngine.SceneManagement; namespace DR.Book.SRPG_Dev.Framework { public class OnSceneLoadedArgs : MessageArgs { public Scene scene; public LoadSceneMode mode; } }
2.3 OnSceneUnloadedArgs.cs
using UnityEngine.SceneManagement; namespace DR.Book.SRPG_Dev.Framework { public class OnSceneUnloadedArgs : MessageArgs { public Scene scene; } }
2.4 OnLoadSceneArgs.cs
在加载场景时,Unity提供两种方式——buildIndex与
sceneName。
如果在项目中混用,我们需要知道是使用了哪种类型。
LoadSceneType.cs:
namespace DR.Book.SRPG_Dev.Framework { public enum LoadSceneType { BuildIndex, SceneName } }
OnLoadSceneArgs.cs:
using UnityEngine.SceneManagement; namespace DR.Book.SRPG_Dev.Framework { public class OnLoadSceneArgs : MessageArgs { public Scene activeScene; // 当前Scene public int buildIndex; // 使用BuildIndex读取Scene public string sceneName; // 使用SceneName读取Scene public LoadSceneType type; // 使用BuildIndex或者SceneName public LoadSceneMode mode; // 加载方式 public bool async; // 是否异步加载 } }
3 程序入口基类(ApplicationEntry.cs)
到这里,我们就可以创建我们的游戏入口了。游戏入口在整个游戏中只有一个,必然是个单例,所以我们让它继承
UnitySingleton。
这样游戏入口成为了一个
MonoBehaviour,在游戏最初的场景(初始化场景)中,我们将它挂载在场景的物体上。
namespace DR.Book.SRPG_Dev.Framework { public abstract class ApplicationEntry<T> : UnitySingleton<T> where T : ApplicationEntry<T> { // TODO } }
3.1 事件(Event)
首先,事件需要事件名称与参数,而这些事件由于只在入口中发送,所以我们建立静态参数。public const string k_Event_OnActiveSceneChanged = "Event_OnActiveSceneChanged"; public const string k_Event_OnSceneLoaded = "Event_OnSceneLoaded"; public const string k_Event_OnSceneUnloaded = "Event_OnSceneUnloaded"; public const string k_Event_OnLoadScene = "Event_OnLoadScene"; private static readonly OnSceneUnloadedArgs s_OnSceneUnloadedArgs = new OnSceneUnloadedArgs(); private static readonly OnSceneLoadedArgs s_OnSceneLoadedArgs = new OnSceneLoadedArgs(); pr 1219b ivate static readonly OnActiveSceneChangedArgs s_OnActiveSceneChangedArgs = new OnActiveSceneChangedArgs(); private static readonly OnLoadSceneArgs s_OnLoadSceneArgs = new OnLoadSceneArgs();
其次,游戏开始后,我们需要让游戏能够运行Unity的场景事件。
protected override void Awake() { base.Awake(); SceneManager.activeSceneChanged += SceneManager_activeSceneChanged; SceneManager.sceneLoaded += SceneManager_sceneLoaded; SceneManager.sceneUnloaded += SceneManager_sceneUnloaded; } protected override void OnDestroy() { base.OnDestroy(); SceneManager.activeSceneChanged -= SceneManager_activeSceneChanged; SceneManager.sceneLoaded -= SceneManager_sceneLoaded; SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded; }
你会看到,我们让
SceneManager添加了事件,那么我们就需要创建我们的事件方法。
最后,创建我们的事件方法。
激活场景发生改变事件。
/// <summary> /// Unity 激活的场景改变事件. /// </summary> /// <param name="scene1"></param> /// <param name="scene2"></param> private void SceneManager_activeSceneChanged(Scene scene1, Scene scene2) { OnActiveSceneChanged(scene1, scene2); // 发送场景改变事件 s_OnActiveSceneChangedArgs.scene1 = scene1; s_OnActiveSceneChangedArgs.scene2 = scene2; this.SendByMessageCenter(k_Event_OnActiveSceneChanged, s_OnActiveSceneChangedArgs); } /// <summary> /// 用于override的全局激活场景改变事件 /// </summary> /// <param name="scene1"></param> /// <param name="scene2"></param> protected virtual void OnActiveSceneChanged(Scene scene1, Scene scene2) { }
加载场景完成事件。
/// <summary> /// Unity 场景读取完成事件,开始异步读取场景. /// </summary> /// <param name="scene"></param> /// <param name="mode"></param> private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode mode) { StartCoroutine(SceneManager_sceneLoadedAsync(scene, mode)); } /// <summary> /// 异步场景读取完成事件. /// </summary> /// <param name="scene"></param> /// <param name="mode"></param> /// <returns></returns> private IEnumerator SceneManager_sceneLoadedAsync(Scene scene, LoadSceneMode mode) { // 等待全局读取完成事件 yield return OnSceneLoaded(scene, mode); // 发送场景读取完成事件 s_OnSceneLoadedArgs.scene = scene; s_OnSceneLoadedArgs.mode = mode; this.SendByMessageCenter(k_Event_OnSceneLoaded, s_OnSceneLoadedArgs); } /// <summary> /// 用于override的全局异步读取场景完成事件 /// </summary> /// <param name="scene"></param> /// <param name="mode"></param> /// <returns></returns> protected virtual IEnumerator OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (mode != LoadSceneMode.Additive) { // 卸载并释放未使用的Unity Object的资源 yield return Resources.UnloadUnusedAssets(); // 卸载并释放垃圾,使用GC有时会造成卡顿,慎用 GC.Collect(); } }
你会看到,在加载完成事件中,我使用了一个异步方法。如果存在Loading界面,这能更好的控制Loading界面。
举个例子:
场景加载完成,等待全局加载资源。
全局加载资源后,发送
k_Event_OnSceneLoaded事件。
Loading界面接收
k_Event_OnSceneLoaded事件,开始加载场景所需的资源,如AssetBundle等。
Loading界面加载资源时,更新Loading界面信息。
Loading界面加载资源完成后,关闭并发送开始信号(例如告诉控制器可以加载入场动画或可以开始操作)。
其中,
Resources.UnloadUnusedAssets()与
GC.Collect()只是例子,你可以不在这里释放。
场景卸载完成事件。
在卸载场景中,当卸载的场景
BuildIndex为
-1时,卸载的场景是指UnityEditor的PreviewScene。如果不进行判断,并有
Debug,那么将会输出许多无用信息。
/// <summary> /// Unity 场景卸载事件 /// </summary> /// <param name="scene"></param> private void SceneManager_sceneUnloaded(Scene scene) { // -1是UnityEditor的Preview Scene if (scene.buildIndex < 0 /*scene.buildIndex == -1*/) { return; } OnSceneUnloaded(scene); // 发送场景卸载事件 s_OnSceneUnloadedArgs.scene = scene; this.SendByMessageCenter(k_Event_OnSceneUnloaded, s_OnSceneUnloadedArgs); } /// <summary> /// 用于override的全局场景卸载事件 /// </summary> /// <param name="scene"></param> protected virtual void OnSceneUnloaded(Scene scene) { }
当要加载场景时,在加载场景之前的事件。
*这个事件的触发,我们要自己进行编写。在编写加载场景方法时再进行叙述。
3.2 加载场景与卸载场景(Load/Unload Scene)
在加载场景中,无论是同步还是异步,Unity都是使用buildIndex或
sceneName来进行加载。
而加载之前,我们要发送
Event_OnLoadScene事件。
如果是异步中,还应该有一个
progress回调,来告知我们加载进度。
首先,实现内部加载场景。
/// <summary> /// 开始读取场景内部实现 /// </summary> /// <param name="scene"></param> /// <param name="mode"></param> /// <param name="async"></param> /// <param name="progress"></param> private void LoadScene(int buildIndex, string sceneName, LoadSceneType type, LoadSceneMode mode, bool async, Action<float> progress) { OnLoadScene(buildIndex, sceneName, type, mode, async); // 发送开始读取场景事件 s_OnLoadSceneArgs.activeScene = SceneManager.GetActiveScene(); s_OnLoadSceneArgs.buildIndex = buildIndex; s_OnLoadSceneArgs.sceneName = sceneName; s_OnLoadSceneArgs.type = type; s_OnLoadSceneArgs.mode = mode; s_OnLoadSceneArgs.async = async; this.SendByMessageCenter(k_Event_OnLoadScene, s_OnLoadSceneArgs); if (type == LoadSceneType.BuildIndex) { if (async) { AsyncOperation operation = SceneManager.LoadSceneAsync(buildIndex, mode); StartCoroutine(LoadingOrUnloadingScene(operation, progress)); } else { SceneManager.LoadScene(buildIndex, mode); } } else { if (async) { AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName, mode); StartCoroutine(LoadingOrUnloadingScene(operation, progress)); } else { SceneManager.LoadScene(sceneName, mode); } } } /// <summary> /// 用于override的全局开始读取场景事件,在读取场景之前执行 /// </summary> /// <param name="buildIndex"></param> /// <param name="sceneName"></param> /// <param name="type"></param> /// <param name="mode"></param> /// <param name="async"></param> protected virtual void OnLoadScene(int buildIndex, string sceneName, LoadSceneType type, LoadSceneMode mode, bool async) { }
其中,
LoadingOrUnloadingScene是异步读取或卸载场景的实现。
/// <summary> /// 异步读取/卸载场景 /// </summary> /// <param name="scene"></param> /// <param name="progress"></param> /// <param name="mode"></param> /// <returns></returns> private IEnumerator LoadingOrUnloadingScene(AsyncOperation operation, Action<float> progress) { if (operation == null) { Debug.LogError("[ApplicationEntry] UNEXPECTED ERROR! Argument named 'operation' is null."); yield break; } while (!operation.isDone) { yield return null; if (progress != null) { progress(operation.progress); } } }
其次,进行加载场景对外接口的编写。
/// <summary> /// 用BuildIndex同步读取场景 /// </summary> /// <param name="buildIndex"></param> /// <param name="mode"></param> public void LoadScene(int buildIndex, LoadSceneMode mode = LoadSceneMode.Single) { LoadScene(buildIndex, "Null", LoadSceneType.BuildIndex, mode, false, null); } /// <summary> /// 用SceneName同步读取场景 /// </summary> /// <param name="sceneName"></param> /// <param name="mode"></param> public void LoadScene(string sceneName, LoadSceneMode mode = LoadSceneMode.Single) { LoadScene(-2, sceneName, LoadSceneType.SceneName, mode, false, null); } /// <summary> /// 用BuildIndex异步读取场景 /// </summary> /// <param name="buildIndex"></param> /// <param name="progress"></param> /// <param name="mode"></param> public void LoadSceneAsync(int buildIndex, Action<float> progress = null, LoadSceneMode mode = LoadSceneMode.Single) { LoadScene(buildIndex, "Null", LoadSceneType.BuildIndex, mode, true, progress); } /// <summary> /// 用SceneName异步读取场景 /// </summary> /// <param name="sceneName"></param> /// <param name="progress"></param> /// <param name="mode"></param> public void LoadSceneAsync(string sceneName, Action<float> progress = null, LoadSceneMode mode = LoadSceneMode.Single) { LoadScene(-2, sceneName, LoadSceneType.SceneName, mode, true, progress); }
最后,进行卸载场景的编写。
在卸载场景时,只有加载的Scene超过1个时,才可以卸载。
/// <summary> /// 如果有多个场景,用BuildIndex卸载场景 /// </summary> /// <param name="buildIndex"></param> /// <param name="progress"></param> public void UnloadSceneAsync(int buildIndex, Action<float> progress = null) { if (SceneManager.sceneCount == 1) { Debug.LogErrorFormat("You tryed to unload scene(BuildIndex {0}), but there is only one scene.", buildIndex); return; } Scene scene = SceneManager.GetSceneByBuildIndex(buildIndex); AsyncOperation operation = SceneManager.UnloadSceneAsync(scene); StartCoroutine(LoadingOrUnloadingScene(operation, progress)); } /// <summary> /// 如果有多个场景,用SceneName卸载场景 /// </summary> /// <param name="sceneName"></param> /// <param name="progress"></param> public void UnloadSceneAsync(string sceneName, Action<float> progress = null) { if (SceneManager.sceneCount == 1) { Debug.LogErrorFormat("You tryed to unload scene(SceneName {0}), but there is only one scene.", sceneName); return; } Scene scene = SceneManager.GetSceneByName(sceneName); AsyncOperation operation = SceneManager.UnloadSceneAsync(scene); StartCoroutine(LoadingOrUnloadingScene(operation, progress)); }
3.3 改变激活场景(Change Active Scene)
改变激活的场景只需要对外接口。同样的,也是加载的场景数量必须多余1个,才可能改变。
/// <summary> /// 如果有多个场景,用BuildIndex改变当前激活的场景 /// </summary> /// <param name="buildIndex"></param> /// <returns></returns> public bool SetActiveScene(int buildIndex) { if (SceneManager.sceneCount == 1) { Debug.LogWarning("You tryed to change the active scene, but there is only one scene."); return false; } Scene scene = SceneManager.GetSceneByBuildIndex(buildIndex); return SceneManager.SetActiveScene(scene); } /// <summary> /// 如果有多个场景,用SceneName改变当前激活的场景 /// </summary> /// <param name="sceneName"></param> /// <returns></returns> public bool SetActiveScene(string sceneName) { if (SceneManager.sceneCount == 1) { Debug.LogWarning("You tryed to change the active scene, but there is only one scene."); return false; } Scene scene = SceneManager.GetSceneByName(sceneName); return SceneManager.SetActiveScene(scene); }
4 游戏入口的使用
举个例子:假设,初始化GameManager命名为GameMain.cs,类为
GameMain。
那么,代码应该写成
public class GameMain : ApplicationEntry<GameMain> {}。
重写所需方法,添加所需方法、字段、属性等。
挂载在初始化场景的GameObject上。
使用它。
相关文章推荐
- SRPG游戏开发(十六)第六章 基本框架 - 一 本章简介(Introduction)
- SRPG游戏开发(十七)第六章 基本框架 - 二 单例模式(Singleton)
- SRPG游戏开发(二十二)第六章 基本框架 - 七 视图(View&UI)
- SRPG游戏开发(十八)第六章 基本框架 - 三 消息事件系统(Message Center)
- DirectX&Direct 3D 游戏开发之——构建3D程序基本框架
- android游戏开发框架libgdx的使用(十九)—使用自定义配置改进AVG游戏开发
- Linux驱动程序开发001 - 驱动程序基本框架
- 小程序开发基本框架及其限制与优化
- ARM裸机程序开发13汇编语言的基本框架
- 《从业务新手到业务老手——游戏开发中CRUD框架的基本组成部分》
- android游戏开发框架libgdx的使用(十九)—使用自定义配置改进AVG游戏开发
- 学习Windows Mobile开发系列笔记(win32基本程序框架)
- 学习Windows Mobile开发系列笔记(win32基本程序框架)
- 微信小程序基本的开发框架抽取,包括网络请求的二次封装,页面状态管理,常见页面模板封装
- M8SDK教程-游戏开发心得(一):游戏程序框架(05-18更新)
- html5 Game开发系列文章之 三 搭建基本游戏框架(代码封装)
- Android程序开发的基本框架
- 学习Windows Mobile开发系列笔记(win32基本程序框架)
- 重现那款曾经为之痴迷的游戏[2] -- 程序基本框架
- 【cocos2d-x IOS游戏开发-捕鱼达人4】基本游戏框架