您的位置:首页 > 移动开发

SRPG游戏开发(十九)第六章 基本框架 - 四 程序入口(Application Entry)

2018-03-18 00:39 567 查看
返回目录

第六章 基本框架(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上。

使用它。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: