最近,因為公司的項目一直在研究StrangeIoc框架,小有所得,略作記錄。
StrangeIoc是一款基於MVCS的一種框架,是對MVC思想的擴展,是專門針對unity開發的一款框架,非常好用。
一、先說下MVCS分別代表什么
1、框架其實就是一種模塊的分離,一種寫代碼的規則,所為的目的都是便於代碼的管理修改,更有利於編碼思維。
做游戲很重要的一點就是將UI和邏輯剝離出來,StrangeIoc框架就很好地實現了UI和邏輯的分離以及各個模塊的分離。下面我將自己的感想一一道來。
1、M 即Model 是本地數據類型 比如從服務器、表、xml等獲得的數據賦值給model,以后再取用數據時直接讀取model對象即可。一般為單例的對象。
2、V 即View 是視圖 一般就是UI這一塊,在StrangeIoc中View一般以組件的形式掛在物體身上的,負責查找UI上需要交互的對象。並且在view中提供更新UI的方法。
3、C 即Command 是命令 在Strangeioc中會和事件綁定,綁定后只要觸發事件就會執行對應的Command,Command是框架的核心 負責和service、model 以及 Mediator(中間層)的交互。
4、S 即Service 是服務層 屬於服務端數據,請求數據,更新數據,保存數據等。
2、大致清楚了各個層之間的關系和每層的功能以及互相交互的對象就很清晰了,下面這張圖是對上文的補充,是StrangeIoc官方提供的結構圖,類似UML類圖。這張圖真的很好,需要仔細研究。
我以自己的demo為例進行逐步解析。
1、Root 是整個框架的起點,是掛在物體上的,繼承自ContextView 間接繼承自MonoBehavior 他的主要功能是創建Context
1 1 /// <summary> 2 2 /// 框架啟動點 掛在場景物體上 3 3 /// </summary> 4 4 public class Demo01ContextView : ContextView { 5 5 void Awake() 6 6 { 7 7 this.context = new Demo01Context(this); //開啟框架 8 8 } 9 9 }
2、Context 這個類是整個框架中耦合的地方,繼承自MVCSContext ,主要功能負責進行綁定(mapBinding)。
1 using UnityEngine; 2 using strange.extensions.context.impl; 3 using strange.extensions.context.api; 4 5 6 /// <summary> 7 /// 上下文 做綁定 包括MVCS和一個StartCommand命令綁定 8 /// </summary> 9 public class Demo01Context : MVCSContext { 10 public Demo01Context(MonoBehaviour view) : base(view) { } 11 12 protected override void mapBindings() 13 { 14 //model 15 injectionBinder.Bind<ScoreModel>().To<ScoreModel>().ToSingleton(); //模型數據綁定為單例模式 16 17 //serivce 18 injectionBinder.Bind<IScoreSerivces>().To<ScoreSerivces>().ToSingleton(); //綁定接口具體實現的類 也是單例模式 19 20 //command 21 commandBinder.Bind(Demo01CommandEvent.RequestScore).To<RequestScoreCommand>(); //綁定事件命令 這個命令是自定義的 這里是請求分數 22 commandBinder.Bind(Demo01CommandEvent.UpdateScore).To<UpdateScoreCpmmand>(); //當dispatchar這個命令時 UpdateScoreCommand里的execute方法就會執行 23 //mediator 24 mediationBinder.Bind<CubeView>().To<CubeMediator>(); //視圖和中間層綁定 25 26 //綁定一個StartCommand事件 這個事件 是框架里定義的 是開始命令 27 commandBinder.Bind(ContextEvent.START).To<StartCommand>().Once(); //綁定事件命令用括號 是字段不是類型 //To<StartCommand>(); // Bind<ContextEvent.START>.To<StartCommand>(); 28 } 29 }
3、Mediator 類 負責和UI交互,和Command交互
1 /// <summary> 2 /// cubeView的中間層 和view,command交互 3 /// </summary> 4 public class CubeMediator : EventMediator 5 { 6 [Inject] 7 public CubeView cubeView { get; set; } //注入cubeView 8 9 //[Inject(ContextKeys.CONTEXT_DISPATCHER)] 10 //public IEventDispatcher dispatcher { get; set; } 11 12 //[Inject] 13 //public ScoreModel scoreModel { get; set; } 14 15 //view創建 mediator就會創建 這個方法就會執行 16 public override void OnRegister() 17 { 18 cubeView.Init(); 19 20 dispatcher.AddListener(Demo01MediatorEvent.ScoreChange, OnScoreChange); //給事件添加監聽 以及回調函數 全局的 21 22 cubeView.dispatcher.AddListener(Demo01MediatorEvent.ClickDown, UpdateScore); 23 24 dispatcher.Dispatch(Demo01CommandEvent.RequestScore); //請求分數的命令 25 26 27 } 28 //view銷毀 mediator就會銷毀 這個方法就會執行 29 public override void OnRemove() 30 { 31 Debug.Log("OnRemove"); 32 33 dispatcher.RemoveListener(Demo01MediatorEvent.ScoreChange,OnScoreChange); 34 35 cubeView.dispatcher.RemoveListener(Demo01MediatorEvent.ClickDown, UpdateScore); 36 }
4、 Command類 命令
1 /// <summary> 2 /// 請求分數的命令 3 /// </summary> 4 public class RequestScoreCommand : EventCommand { 5 6 [Inject] 7 public IScoreSerivces scoreSerivces { get; set; } 8 9 [Inject] 10 public ScoreModel scoreModel { get; set; } 11 12 public override void Execute() 13 { 14 Retain(); //因為請求分數會有延遲,這個可以保證這個命令不會被銷毀 15 16 scoreSerivces.dispatcher.AddListener(Demo01SerivcesEvent.RequestScore, OnComplete); 17 18 scoreSerivces.ResquestScore("http://xxx/xxx/xxx"); 19 } 20 21 private void OnComplete(IEvent evt) 22 { 23 Debug.Log("request score complete " + evt.data); 24 25 scoreModel.Score = (int)evt.data; //賦值 26 27 dispatcher.Dispatch(Demo01MediatorEvent.ScoreChange, evt.data); 28 29 scoreSerivces.dispatcher.RemoveListener(Demo01SerivcesEvent.RequestScore,OnComplete); 30 31 Release(); //釋放命令 32 } 33 }
5、View 類 視圖類
1 /// <summary> 2 /// 視圖會掛在對應的物體上 只會和對應的mediator交互 3 /// </summary> 4 public class CubeView : View 5 { 6 [Inject] 7 public IEventDispatcher dispatcher { get; set; } //局部的dispatchar 在對應的mediator中綁定回調方法 8 9 public Text scoreText; 10 11 /// <summary> 12 /// 初始化方法 13 /// </summary> 14 public void Init() 15 { 16 scoreText = GetComponentInChildren<Text>(); 17 } 18 /// <summary> 19 /// 自身移動 20 /// </summary> 21 void Update() 22 { 23 this.transform.Translate(new Vector3(Random.Range(-1, 2), Random.Range(-1, 2), Random.Range(-1, 2)) * 0.2f); 24 } 25 26 /// <summary> 27 /// 檢測點擊 加分 28 /// </summary> 29 void OnMouseDown() 30 { 31 Debug.Log("OnMouseDown"); 32 dispatcher.Dispatch(Demo01MediatorEvent.ClickDown); //觸發事件 33 } 34 /// <summary> 35 /// 更新分數方法 36 /// </summary> 37 /// <param name="score">新的分數</param> 38 public void UpdateScore(int score) 39 { 40 scoreText.text = score.ToString(); 41 } 42 }
6、Service類
1 /// <summary> 2 /// 服務端接口 定義了一些方法 3 /// </summary> 4 public interface IScoreSerivces 5 { 6 void ResquestScore(string url); //請求分數 7 void OnReceiveScore(); //收到分數 8 void UpdateScore(string url, int score); //更新分數 9 10 IEventDispatcher dispatcher { get; set; } 11 }
1 /// <summary> 2 /// IScoreSerivces的一種實現 3 /// </summary> 4 public class ScoreSerivces : IScoreSerivces 5 { 6 //請求分數 7 public void ResquestScore(string url) 8 { 9 Debug.Log("Resquest score serivces : " + url); 10 OnReceiveScore(); 11 12 } 13 14 public void OnReceiveScore() 15 { 16 int score = Random.Range(0, 100); 17 dispatcher.Dispatch(Demo01SerivcesEvent.RequestScore,score); 18 } 19 20 public void UpdateScore(string url, int score) 21 { 22 Debug.Log("Update the score : " + url + " new score : " + score); 23 } 24 25 [Inject] 26 public IEventDispatcher dispatcher { get; set; }
7、Model 保存本地數據
1 public class ScoreModel { 2 public int Score { get; set; } //分數 3 }
8、自定義的事件(枚舉類型) 用於dispatchar派發事件
1 public enum Demo01CommandEvent 2 { 3 RequestScore, 4 UpdateScore 5 }
二、代碼執行順序
第一次看框架都會被框架的代碼執行順序弄的很亂,我也是,剛開始根本摸不到頭腦,迷迷糊糊只知道這樣寫就能實現。
1、首先ContextView 會創建一個context,context會進行bind,之后當前面的類進行創建時,bind的類會按照你制定的規則自動創建,
2、之后程序會找到框架內的一個Start命令進行執行ContextEvent.START,他會執行綁定的Command里的Execute方法,
3、這在同時當View類被創建時Mediator類也會創建,並且執行OnRegister方法。
這三塊的方法執行順序弄清楚之后就有些眉目了。就可以在不同的方法里寫自己的實現了。
三、 其他重要的東西
1、Bind StrangeIoc提供了一個很重要的功能,就是綁定的功能,在context中做綁定,可以綁定類和類,接口和類,類和方法,事件和命令等等 都可以進行綁定,並且可以指定綁定后的規則
如單例,執行一次后銷毀,指定name等。
2、Inject 說了Bind不得不說,StrangeIoc是依賴注入的框架,Inject就是注入,他的功能就是我們想獲得某種類型的對象時不需要自己去創建,只要加上[Inject]標識就可以根據你bind時的規則獲得對象。
3、Mediator 中間層,負責和UI交互 和Command之間交互。他的功能就是隔離了UI和邏輯。UI只能和Mediator交互。
4、dispatcher 派發器,是StrangeIOC實現的消息發送機制,分為全局dispatcher和局部dispatcher,全局的dispatchar在任何地方都可以發送消息,發送消息后會觸發該消息的回調做出響應
局部的dispatchar通過對象進行發送消息。需要在Mediator中進行注冊消息,並制定消息的回調。
四、 結束
暫時先寫這么多,高深的我也沒有研究到,目前對框架的感覺是,框架做到了各個模塊的分離,使我在寫代碼的時候對功能划分更加清晰,並且在后期修改功能添加功能時,很方便,這就是框架的魅力吧。
隨着對框架的理解我會繼續更新,大家一起努力。
樹欲靜而風不止,子欲養而親不待。
2016年12月1日17:11:42
更新 (時間 2016年12月15日11:01:25)
隨着項目的進展,對框架也更加深入的了解。
增加幾點小的知識點
1、dispatchar 消息發送機制 分為全局消息和局部消息
a.局部消息: 指view層好對應的mediator層進行的通信,在view層聲明一個事件的名稱 (public const string On_MyBtn_Click = “On_MyBtn_Click ”)可以在mediator層為這個事件注冊方法
1 protected void UpdateListeners(bool enable) 2 { 3 view.dispatcher.UpdateListener(enable, GouJianBarView.On_MyBtn_Click, OnMyBtnClick); 4 }
是不是很簡單,這樣就能實現消息的注冊,調用的時候更簡單 在需要的地方 當然是在對應的view層中才能調用 dispatcher.Dispatch(On_MyBtn_Click);
b.全局消息:指的是可以在mediator層互相通信,需要一個發送事件命令的ENUM類型里管理這些命令,在具體功能實現的地方給這個消息注冊方法,
1 protected void UpdateListeners(bool enable) 2 { 3 dispatcher.UpdateListener(enable, UIEventSend.Send_GouJian_JiangJie_Refresh_UIStyle, RefreshUIData); 4 }
是不是也很簡單,其實原理都是一樣的,只是他的范圍更廣,可以在其他的也只能在mediator層發送這個消息,執行這個回調方法。
另外還有一種,當項目比較大的時候可以采用這種通信,就是一個消息對應一個Command,並且需要把這個消息在Context中進行bind 就像綁定開始命令一樣。這樣就可以把實現寫在Command中。
2.view層Awake和Start方法
在重寫view層的Start方法時,需要注意,一定要先執行他父類的方法base.Start();如果不先執行,就沒辦法自動創建改view層對應的meidator,自動創建是在框架中執行的。
如果你在view層進行注入數據,一般不推薦在view層直接注入官方推薦的在Mediator層進行注入,view去和mediator層交互。在view中注入數據在Awake方法中是調用不到的 會報空引用 因為,數據的
注入執行順序在Awake之后,在Start之前。從而可以看出,mediator層的OnRegister方法是晚於Awake執行,早於Start方法的。
今天就更新到這,有了新的發現再次更新,或開新的隨筆,歡迎交流指正。
樹欲靜而風不止,子欲養而親不待。
框架上 有其他問題 加群學習165628892(進群備注:博客) 隨時提出問題解決問題!