由淺入深,慢慢演化實現框架
兩個類的實現代碼完全一樣,就只有類名或類型不一樣的時候,而且還需要不斷擴展(未來會增加各種事件)的時候,這時候就用 泛型 + 繼承 來提取,繼承解決擴展的問題,泛型解決實現代碼一致,類不一致的問題,這是一個重構技巧。
表現和數據要分離
數據在大多數情況下需要在多個場景、界面、游戲物體之間是共享的,這些數據不但需要在空間上共享,還需要再時間上也需要共享(需要存儲起來),所以在這里,開發者的共識就是把數據的部分會抽離出來,單獨放在一個地方進行維護,而比較常見的開發架構就是使用 MVC 的開發架構,我們先不用 MVC 的開發架構,而只用 MVC 中的其中一個概念,就是 Model。
Model 就是管理數據、存儲數據,管理數據就是可以通過 Model 對象或類可以對數據進行增刪改查,有的時候還可以進行存儲。
public class GameModel{public static int KillCount = 0;public static int Gold = 0;public static int Score = 0;public static int BestScore = 0;}
- 用泛型 + 繼承 提取 Event 工具類
- 子節點通知父節點也可以用事件(根據情況)
- 表現和需要共享的數據分離
- 正確的代碼要放在正確的位置
如果是共享的數據就放在 Model 里,如果不是共享的就不需要。
這里共享的數據可以是配置數據、需要存儲的數據、多個地方需要訪問的數據。
對于配置數據來說,游戲中的場景和 GameObject、Prefab 在運行游戲之前也是一種配置數據,因為他們本質上是用 Yaml(類似 json、xml 的數據格式)存儲在磁盤上的。
而需要存儲的數據是從時間這個維度共享的數據,即現在可以訪問以前某個時刻存儲的數據。
?
?復用? 可綁定屬性
using System;namespace FrameworkDesign
{public class BindableProperty<T> where T : IEquatable<T>{private T mValue;public T Value{get => mValue;set{if (!mValue.Equals(value)){mValue = value;OnValueChanged?.Invoke(value);}}}public Action<T> OnValueChanged;}
}
BidableProperty 是 數據 + 數據變更事件 的合體,它既存儲了數據充當 C# 中的 屬性這樣的角色,也可以讓別的地方監聽它的數據變更事件,這樣會減少大量的樣板代碼
- 表現邏輯 適合用 事件 或 委托
- 表現邏輯用方法調用會造成很多問題,Controller 臃腫難維護、
- Model 和 View 是自底向上的關系
- 自底向上用事件或委托
- 自頂向下用方法調用
命令模式
用一個方法來寫的邏輯,改成用對象來實現,而這個對象只有一個執行方法。
我們先定義一個接口,叫做 ICommand,代碼如下:
namespace?FrameworkDesign
{public?interface?ICommand{void?Execute();}
}
實現接口:
public struct AddCountCommand : ICommand{public void Execute(){CounterModel.Count.Value++;}}
Command 模式就是邏輯的調用和執行是分離的
空間分離的方法就是調用的地方和執行的地方放在兩個文件里。
時間分離的方法就是調用的之后,Command 過了一點時間才被執行。
而 Command 模式由于有了調用和執行分離這個特點,所以我們可以用不同的數據結構去組織 Command 調用,比如命令隊列,再比如用一個命令的堆棧,來實現撤銷功能(ctrl + z)
引入單例
- 靜態類沒有訪問限制。
- 使用 static 去擴展模塊,其模塊的識別度不高。
public class Singleton<T> where T : class{public static T Instance{get{if (mInstance == null){// 通過反射獲取構造var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);// 獲取無參非 public 的構造var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);if (ctor == null){throw new Exception("Non-Public Constructor() not found in " + typeof(T));}mInstance = ctor.Invoke(null) as T;}return mInstance;}}private static T mInstance;}
模塊化優化--引入 IOC 容器
IOC 容器,大家可以理解為是一個字典,這個字典以 Type 為 key,以對象即 Instance 為 value,非常簡單。
而 IOC 容器最少有兩個核心的 API,即根據 Type 注冊實例,根據 Type 獲取實例。
實現一個簡單的 IOC 容器
public class IOCContainer{/// <summary>/// 實例/// </summary>public Dictionary<Type, object> mInstances = new Dictionary<Type, object>();/// <summary>/// 注冊/// </summary>/// <param name="instance"></param>/// <typeparam name="T"></typeparam>public void Register<T>(T instance){var key = typeof(T);if (mInstances.ContainsKey(key)){mInstances[key] = instance;}else{mInstances.Add(key,instance);}}/// <summary>/// 獲取/// </summary>public T Get<T>() where T : class{var key = typeof(T);object retObj;if(mInstances.TryGetValue(key,out retObj)){return retObj as T;}return null;}}
將這個代碼用起來:
我們先創建一個 CounterApp 類,用于注冊全部模塊,代碼如下:
using FrameworkDesign;namespace CounterApp
{public class CounterApp{private static IOCContainer mContainer = null;// 確保 Container 是有實例的static void MakeSureContainer(){if (mContainer == null){mContainer = new IOCContainer();Init();}}// 這里注冊模塊private static void Init(){mContainer.Register(new CounterModel());}// 提供一個獲取模塊的 APIpublic static T Get<T>() where T : class{MakeSureContainer();return mContainer.Get<T>();}}
}
接著我們把 CounterApp 類應用起來,代碼如下:
using FrameworkDesign;
using UnityEngine;
using UnityEngine.UI;namespace CounterApp
{public class CounterViewController : MonoBehaviour{private CounterModel mCounterModel;void Start(){// 獲取mCounterModel = CounterApp.Get<CounterModel>();// 注冊mCounterModel.Count.OnValueChanged += OnCountChanged;transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>{// 交互邏輯new AddCountCommand().Execute();});transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>{// 交互邏輯new SubCountCommand().Execute();});OnCountChanged(mCounterModel.Count.Value);}// 表現邏輯private void OnCountChanged(int newValue){transform.Find("CountText").GetComponent<Text>().text = newValue.ToString();}private void OnDestroy(){// 注銷mCounterModel.Count.OnValueChanged -= OnCountChanged;mCounterModel = null;}}/// <summary>/// 不需要是單例了/// </summary>public class CounterModel{public BindableProperty<int> Count = new BindableProperty<int>(){Value = 0};}
}
AddCountCommand.cs
using FrameworkDesign;namespace CounterApp
{public struct AddCountCommand : ICommand{public void Execute(){CounterApp.Get<CounterModel>().Count.Value++;}}
}
SubCountCommand.cs
using FrameworkDesign;namespace CounterApp
{public struct SubCountCommand : ICommand{public void Execute(){CounterApp.Get<CounterModel>().Count.Value--;}}
}
--dd,這就是框架嗎? orz
以下代碼容易重復
PiontGame.cs
namespace FrameworkDesign.Example
{public class PointGame {private static IOCContainer mContainer = null;// 確保 Container 是有實例的static void MakeSureContainer(){if (mContainer == null){mContainer = new IOCContainer();Init();}}// 這里注冊模塊private static void Init(){mContainer.Register(new GameModel());}// 提供一個獲取模塊的 APIpublic static T Get<T>() where T : class{MakeSureContainer();return mContainer.Get<T>();}}
}
優化一下:創建一個類,名字叫 Architecture.cs ,代碼如下:
namespace FrameworkDesign
{/// <summary>/// 架構/// </summary>/// <typeparam name="T"></typeparam>public abstract class Architecture<T> where T : Architecture<T>, new(){#region 類似單例模式 但是僅在內部課訪問private static T mArchitecture = null;// 確保 Container 是有實例的static void MakeSureArchitecture(){if (mArchitecture == null){mArchitecture = new T();mArchitecture.Init();}}#endregionprivate IOCContainer mContainer = new IOCContainer();// 留給子類注冊模塊protected abstract void Init();// 提供一個注冊模塊的 APIpublic void Register<T>(T instance){MakeSureArchitecture();mArchitecture.mContainer.Register<T>(instance);}// 提供一個獲取模塊的 APIpublic static T Get<T>() where T : class{MakeSureArchitecture();return mArchitecture.mContainer.Get<T>();}}
}