本來這周想介紹一些框架中自認為比較好用的小工具的,但是發現很多小工具都依賴一個類----App。
App 類的職責:
1. 接收 Unity 的生命周期事件。
2. 做為游戲的入口。
3. 一些框架級別的組件初始化。
本文只介紹App的職責2:做為游戲的入口。
## Why?
在我小時候做項目的時候,每次改一點點代碼(或者不止一點點),要看下結果就要啟動游戲-\>Loading界面-\>點擊各種按鈕-\>跳轉到目標界面看結果或者Log之類的。一天如果10次這種行為會浪費很多時間,如果按照時薪算的話那就是......很多錢(捂嘴)。
流程圖是這樣的:

## 為什么會出現這種問題呢?
1.模塊間的耦合度太高了。下一個模塊要依賴前一個模塊的一些數據或者邏輯。
2.或者有可能是這個模塊設計得太大了,界面太多,也會發生這種情況。
## 解決方案:
針對問題1:在模塊的入口提供一個測試的接口,用來寫這個模塊的資源加載或者數據初始化的邏輯,...什么!?...你們項目就一個模塊...來來來我們好好聊聊.....
針對問題2:在模塊的入口提供一個測試接口,寫跳轉到目標界面的相關代碼。
流程圖是這樣的:

雖然很low但是勉強解決了問題。
## 階段的劃分
資源加載亂七八糟的代碼和最好能一步跳轉到目標界面的代碼,需要在出包或者跑完整游戲流程的時候失效。
如何做到?答案是階段的劃分。
我的框架里分為如下幾個階段:
1.開發階段: 不斷的編碼-\>驗證結果-\>編碼-\>驗證結果-\>blablabla。
2.出包/真機階段: 這個階段跑跑完整流程,在真機上跑跑,給QA測測。
3.發布階段: 上線了,yeah!。
對應的枚舉:
```cs
public enum AppMode
{
Developing,
QA,
Release
}
```
很明顯,亂七八糟的代碼是要在開發階段有效,但是在QA或者Release版本中無效的。那么只要在游戲的入口處判斷當前在什么階段就好了。
開始編碼:
```cs
/// <summary>
/// 全局唯一繼承于MonoBehaviour的單例類,保證其他公共模塊都以App的生命周期為準
/// </summary>
public class App : QMonoSingleton<App>
{
public AppMode mode = AppMode.Developing;
private App() {}
void Awake()
{
// 確保不被銷毀
DontDestroyOnLoad(gameObject);
mInstance = this;
// 進入歡迎界面
Application.targetFrameRate = 60;
}
void Start()
{
CoroutineMgr.Instance ().StartCoroutine (ApplicationDidFinishLaunching());
}
/// <summary>
/// 進入游戲
/// </summary>
IEnumerator ApplicationDidFinishLaunching()
{
// 配置文件加載 類似PlayerPrefs
QSetting.Load();
// 日志輸出
QLog.Instance ();
yield return GameManager.Instance ().Init ();
// 進入測試邏輯
if (App.Instance().mode == AppMode.Developing)
{
// 測試資源加載
ResMgr.Instance ().LoadRes ("TestRes",delegate(string resName, Object resObj)
{
if (null != resObj)
{
GameObject.Instantiate(resObj);
}
// 進入目標界面等等
});
yield return null;
// 進入正常游戲邏輯
}
else
{
yield return GameManager.Instance ().Launch ();
}
yield return null;
}
}
```
首先App是Mono單例,要接收Unity的生命周期.
然后要維護一個AppMode類型的變量,便于區分。
之后在 ApplicationDidFinishLaunching 中有這么一段代碼:
```cs
// 進入測試邏輯
if (App.Instance().mode == AppMode.Developing)
{
// 測試資源加載
ResMgr.Instance ().LoadRes ("TestRes",delegate(string resName, Object resObj)
{
if (null != resObj)
{
GameObject.Instantiate(resObj);
}
// 進入目標界面等等
});
yield return null;
// 進入正常游戲邏輯
}
else
{
yield return GameManager.Instance ().Launch ();
}
```
在這段代碼中做了階段的區分。所有的邏輯都可以寫在這里。這樣基本的需求就滿足啦。
## 還有一個問題:
假如一個游戲的業務邏輯分為模塊A,B,C,D,E,分為5個不同的人來開發,那App是一個mono單例,除非不提交App代碼,否則每次都要解決沖突,同樣很浪費時間。怎么辦? 答案是通過多態來解決,先定義一個ITestEntry接口,只定義一個方法。
```cs
/// <summary>
/// 測試入口
/// </summary>
public interface ITestEntry
{
/// <summary>
/// 啟動
/// </summary>
IEnumerator Launch();
}
```
然后每個模塊分別實現ITestEntry接口,例如AModuleTestEntry,BModuleTestEntry等等。
看下項目中的實現:
```cs
/// <summary>
/// AR模塊測試入口
/// </summary>
public class ARSceneTestEntry :MonoBehaviour,ITestEntry
{
public IEnumerator Launch()
{
Debug.LogWarning ("進入AR場景開始");
yield return GameObject.Find ("ARScene").GetComponent<ARScene> ().Launch ();
yield return null;
}
}
```
App類中階段區分的代碼要改成這樣:
```cs
// 進入測試邏輯
if (App.Instance().mode == AppMode.Developing)
{
yield return GetComponent<ITestEntry> ().Launch ();
// 進入正常游戲邏輯
}
else
{
yield return GameManager.Instance ().Launch ();
}
```
因為Launch方法的返回類型是IEnumerator,所以很好控制跳轉的時間。
看下在Unity中是什么樣的:

每個模塊都要有個 App 的 GameObject,原因是因為,框架的其他的組件依賴于App,也想過把依賴的部分抽離出來,那樣的話可能命名為 QMonoLifeCircleReceiver 和 ModuleEntry 之類的,這樣遵循了單一職責原則。不過孰優孰略各有千秋。我覺得叫App更直觀一些,因為入口、組件初始化、啟動某個模塊應該是通常放在一起更人性化,還有一些 ApplicationDidEnterBackground之類的事件還是模仿 iOS 的 AppDelegate 人性化一些。
如果要跑完整流程,那么把模塊的 App GameObject 關掉就好了。要注意一點是:在整個游戲的入口場景要有個 App GameObject 放在上面,并且 AppMode 要為Release 或者 QA。這樣才能正常地跑起來。
OK就這樣....
## 對未來的一些暢想:
1. 最近在想著如何為項目引入自動化測試,有一個思路是這樣的,界面的所有輸入包括點擊事件等都包裝成一個命令或者一個消息。測試的時候只要不斷地自動發送消息或者命令就好了。當然只是個暢想。 那和這個有毛關系呢,有啊!界面跳轉的時候可以發命令或者消息就夠了啊,這樣還很方便。 但實際上有很多問題,包括模塊的最上層如何拿到一些界面組件的權限比如按鈕等等。處理命令或者消息的話那么所有的輸入都要經過一層過濾。。。。額。。想想好麻煩。。。以后吧。。。以后吧。。
2. 框架的很多組件都是基于字典實現的。字典真好用,23333。以后還是想辦法能改的都改成List吧。
## 歡迎討論!
轉載請注明地址:涼鞋的筆記:[liangxiegame.com](http://liangxiegame.com)
## 更多內容
* QFramework 地址:[https://github.com/liangxiegame/QFramework](https://github.com/liangxiegame/QFramework)
* QQ 交流群:[623597263](http://shang.qq.com/wpa/qunwpa?idkey=706b8eef0fff3fe4be9ce27c8702ad7d8cc1bceabe3b7c0430ec9559b3a9ce66)
* **Unity 進階小班**:
* 主要訓練內容:
* 框架搭建訓練(第一年)
* 跟著案例學 Shader(第一年)
* 副業的孵化(第二年、第三年)
* 權益、授課形式等具體詳情請查看[《小班產品手冊》](https://liangxiegame.com/master/intro):https://liangxiegame.com/master/intro
* 關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。

- 正文
- Unity 游戲框架搭建 2017(一)概述
- Unity 游戲框架搭建 2017(二)單例的模板
- Unity 游戲框架搭建 2017(三)MonoBehaviour 單例的模板
- Unity 游戲框架搭建 2017(四)簡易有限狀態機
- Unity 游戲框架搭建 2017(五)簡易消息機制
- Unity 游戲框架搭建 2017 (六) 關于框架的一些好文和一些思考
- Unity 游戲框架搭建 2017 (七) 減少加班利器-QApp類
- Unity 游戲框架搭建 2017 (八) 減少加班利器-QLog
- Unity 游戲框架搭建 2017 (九) 減少加班利器-QConsole
- Unity 游戲框架搭建 2017 (十) QFramework v0.0.2小結
- Unity 游戲框架搭建 2017 (十一) 簡易 AssetBundle 打包工具 (一)
- Unity 游戲框架搭建 2017 (十二) 簡易 AssetBundle 打包工具 (二)
- Unity 游戲框架搭建 2017 (十三) 無需繼承的單例的模板
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (零) QuickStart
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (一) Singleton 單例實現
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (二) MonoSingleton單例實現
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSignleton (三) 通過屬性器實現 Singleton
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (四) 屬性器實現 Mono 單例
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (五) 優雅地進行GameObject命名
- Unity 游戲框架搭建 2017 (十五) 優雅的 QChain (零)
- Unity 游戲框架搭建 2017 (十六) v0.0.3 架構調整
- Unity 游戲框架搭建 2017 (十七) 靜態擴展GameObject 實現鏈式編程
- Unity 游戲框架搭建 2017 (十八) 靜態擴展 + 泛型實現 transform 的鏈式編程
- Unity 游戲框架搭建 2017 (十九) 簡易對象池
- Unity 游戲框架搭建 2017 (二十) 安全的對象池
- Unity 游戲框架搭建 2017 (二十一) 使用對象池時的一些細節
- Unity 游戲框架搭建 2017 (二十二) 簡易引用計數器
- Unity 游戲框架搭建 2017 (二十三) 重構小工具 Platform
- Unity 游戲框架搭建 2017 (二十四) 小結