最近,因为公司的项目一直在研究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
/// <summary>
/// 框架启动点 挂在场景物体上
/// </summary>
public class Demo01ContextView : ContextView {
void Awake()
{
this.context = new Demo01Context(this); //开启框架
}
}
2、Context 这个类是整个框架中耦合的地方,继承自MVCSContext ,主要功能负责进行绑定(mapBinding)。
using UnityEngine;
using strange.extensions.context.impl;
using strange.extensions.context.api; /// <summary>
/// 上下文 做绑定 包括MVCS和一个StartCommand命令绑定
/// </summary>
public class Demo01Context : MVCSContext {
public Demo01Context(MonoBehaviour view) : base(view) { } protected override void mapBindings()
{
//model
injectionBinder.Bind<ScoreModel>().To<ScoreModel>().ToSingleton(); //模型数据绑定为单例模式 //serivce
injectionBinder.Bind<IScoreSerivces>().To<ScoreSerivces>().ToSingleton(); //绑定接口具体实现的类 也是单例模式 //command
commandBinder.Bind(Demo01CommandEvent.RequestScore).To<RequestScoreCommand>(); //绑定事件命令 这个命令是自定义的 这里是请求分数
commandBinder.Bind(Demo01CommandEvent.UpdateScore).To<UpdateScoreCpmmand>(); //当dispatchar这个命令时 UpdateScoreCommand里的execute方法就会执行
//mediator
mediationBinder.Bind<CubeView>().To<CubeMediator>(); //视图和中间层绑定 //绑定一个StartCommand事件 这个事件 是框架里定义的 是开始命令
commandBinder.Bind(ContextEvent.START).To<StartCommand>().Once(); //绑定事件命令用括号 是字段不是类型 //To<StartCommand>(); // Bind<ContextEvent.START>.To<StartCommand>();
}
}
3、Mediator 类 负责和UI交互,和Command交互
/// <summary>
/// cubeView的中间层 和view,command交互
/// </summary>
public class CubeMediator : EventMediator
{
[Inject]
public CubeView cubeView { get; set; } //注入cubeView //[Inject(ContextKeys.CONTEXT_DISPATCHER)]
//public IEventDispatcher dispatcher { get; set; } //[Inject]
//public ScoreModel scoreModel { get; set; } //view创建 mediator就会创建 这个方法就会执行
public override void OnRegister()
{
cubeView.Init(); dispatcher.AddListener(Demo01MediatorEvent.ScoreChange, OnScoreChange); //给事件添加监听 以及回调函数 全局的 cubeView.dispatcher.AddListener(Demo01MediatorEvent.ClickDown, UpdateScore); dispatcher.Dispatch(Demo01CommandEvent.RequestScore); //请求分数的命令 }
//view销毁 mediator就会销毁 这个方法就会执行
public override void OnRemove()
{
Debug.Log("OnRemove"); dispatcher.RemoveListener(Demo01MediatorEvent.ScoreChange,OnScoreChange); cubeView.dispatcher.RemoveListener(Demo01MediatorEvent.ClickDown, UpdateScore);
}
4、 Command类 命令
/// <summary>
/// 请求分数的命令
/// </summary>
public class RequestScoreCommand : EventCommand { [Inject]
public IScoreSerivces scoreSerivces { get; set; } [Inject]
public ScoreModel scoreModel { get; set; } public override void Execute()
{
Retain(); //因为请求分数会有延迟,这个可以保证这个命令不会被销毁 scoreSerivces.dispatcher.AddListener(Demo01SerivcesEvent.RequestScore, OnComplete); scoreSerivces.ResquestScore("http://xxx/xxx/xxx");
} private void OnComplete(IEvent evt)
{
Debug.Log("request score complete " + evt.data); scoreModel.Score = (int)evt.data; //赋值 dispatcher.Dispatch(Demo01MediatorEvent.ScoreChange, evt.data); scoreSerivces.dispatcher.RemoveListener(Demo01SerivcesEvent.RequestScore,OnComplete); Release(); //释放命令
}
}
5、View 类 视图类
/// <summary>
/// 视图会挂在对应的物体上 只会和对应的mediator交互
/// </summary>
public class CubeView : View
{
[Inject]
public IEventDispatcher dispatcher { get; set; } //局部的dispatchar 在对应的mediator中绑定回调方法 public Text scoreText; /// <summary>
/// 初始化方法
/// </summary>
public void Init()
{
scoreText = GetComponentInChildren<Text>();
}
/// <summary>
/// 自身移动
/// </summary>
void Update()
{
this.transform.Translate(new Vector3(Random.Range(-, ), Random.Range(-, ), Random.Range(-, )) * 0.2f);
} /// <summary>
/// 检测点击 加分
/// </summary>
void OnMouseDown()
{
Debug.Log("OnMouseDown");
dispatcher.Dispatch(Demo01MediatorEvent.ClickDown); //触发事件
}
/// <summary>
/// 更新分数方法
/// </summary>
/// <param name="score">新的分数</param>
public void UpdateScore(int score)
{
scoreText.text = score.ToString();
}
}
6、Service类
/// <summary>
/// 服务端接口 定义了一些方法
/// </summary>
public interface IScoreSerivces
{
void ResquestScore(string url); //请求分数
void OnReceiveScore(); //收到分数
void UpdateScore(string url, int score); //更新分数 IEventDispatcher dispatcher { get; set; }
}
/// <summary>
/// IScoreSerivces的一种实现
/// </summary>
public class ScoreSerivces : IScoreSerivces
{
//请求分数
public void ResquestScore(string url)
{
Debug.Log("Resquest score serivces : " + url);
OnReceiveScore(); } public void OnReceiveScore()
{
int score = Random.Range(, );
dispatcher.Dispatch(Demo01SerivcesEvent.RequestScore,score);
} public void UpdateScore(string url, int score)
{
Debug.Log("Update the score : " + url + " new score : " + score);
} [Inject]
public IEventDispatcher dispatcher { get; set; }
7、Model 保存本地数据
public class ScoreModel {
public int Score { get; set; } //分数
}
8、自定义的事件(枚举类型) 用于dispatchar派发事件
public enum Demo01CommandEvent
{
RequestScore,
UpdateScore
}
二、代码执行顺序
第一次看框架都会被框架的代码执行顺序弄的很乱,我也是,刚开始根本摸不到头脑,迷迷糊糊只知道这样写就能实现。
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层为这个事件注册方法
protected void UpdateListeners(bool enable)
{
view.dispatcher.UpdateListener(enable, GouJianBarView.On_MyBtn_Click, OnMyBtnClick);
}
是不是很简单,这样就能实现消息的注册,调用的时候更简单 在需要的地方 当然是在对应的view层中才能调用 dispatcher.Dispatch(On_MyBtn_Click);
b.全局消息:指的是可以在mediator层互相通信,需要一个发送事件命令的ENUM类型里管理这些命令,在具体功能实现的地方给这个消息注册方法,
protected void UpdateListeners(bool enable)
{
dispatcher.UpdateListener(enable, UIEventSend.Send_GouJian_JiangJie_Refresh_UIStyle, RefreshUIData);
}
是不是也很简单,其实原理都是一样的,只是他的范围更广,可以在其他的也只能在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(进群备注:博客) 随时提出问题解决问题!