框架学习笔记:Unity3D的MVC框架——StrangeIoC
作为从AS3页游走过来的人,看见StrangeIoC会额外亲切,因为StrangeIoC的设计和RobotLegs几乎一致,作为一款依赖注入/控制反转(IoC)的MVC框架,StrangeIoC除了使我们的程序结构更加解耦合理外,还为我们提供了大量方便的功能(这里主要是和PureMVC进行对比)。
RobotLegs和Event
这一节是题外话,对AS3无感的童鞋请跳过。
StrangeIoC首页的文档有如下的记录:
以及:
我猜作者以前一定是一个ASer,除了设计上和RobotLegs一致外,StrangeIoC还设计了两种事件机制:AS3的内置原生事件机制(Dispatcher)和一个AS3的扩展事件类库(AS3-Signals)。
有兴趣的童鞋可以点击下面的连接了解这些技术:
RobotLegs
AS3-Signals
示例
下面我们来基于StrangeIoC框架搭建一个简单的示例,更多的示例可以参考框架自带的例子。
下载和导入StrangeIoC
开源地址:https://github.com/strangeioc/strangeioc
我们可以在Release页面下载到发布的版本,比如我这里下了v0.6.1的版本,我们把下载下来的文件进行解压;
导入框架:新建一个Unity3D的项目,新建一个名为StrangeIoC的文件夹,将解压文件夹下的StrangeIoC\scripts文件夹拷贝到我们新建的文件夹中即可(貌似有一个报错,我们先忽略它);
创建根容器
这里的容器其实就是一个GameObject对象,理论上游戏中所有要使用到StrangeIoC提供注入功能的GameObject都必须添加到这个根容器中,我们创建一个GameRoot的脚本,将该脚本绑定到根容器上,内容:
using strange.extensions.context.impl; /// <summary>
/// 游戏根容器类.
/// </summary>
public class GameRoot : ContextView
{
void Awake()
{
//创建游戏上下文对象并启动
context = new GameContext(this, true);
context.Start();
}
}
该类负责启动MVC框架,这里我们用到了一个名为GameContext的类,我们先创建这个类,代码如下:
using strange.extensions.context.api;
using strange.extensions.context.impl;
using UnityEngine; /// <summary>
/// 游戏上下文.
/// </summary>
public class GameContext : MVCSContext
{
public GameContext () : base()
{
} public GameContext(MonoBehaviour view, bool autoStartup) : base(view, autoStartup)
{
} protected override void mapBindings()
{
mapModel();
mapView();
mapController(); //调用 StartUp 命令启动程序
commandBinder.Bind(ContextEvent.START).To<StartUpCommand>().Once();
} /// <summary>
/// 映射模型.
/// </summary>
private void mapModel()
{
injectionBinder.Bind<IMainUIService>().To<MainUIService>().ToSingleton();
injectionBinder.Bind<IMainUIModel>().To<MainUIModel>().ToSingleton(); injectionBinder.Bind<ISkillService>().To<SkillService>().ToSingleton();
injectionBinder.Bind<ISkillModel>().To<SkillModel>().ToSingleton();
} /// <summary>
/// 映射视图.
/// </summary>
private void mapView()
{
mediationBinder.Bind<MainUIView>().To<MainUIMediator>(); mediationBinder.Bind<SkillUIView>().To<SkillUIMediator>();
} /// <summary>
/// 映射控制器.
/// </summary>
private void mapController()
{
commandBinder.Bind(NotificationCenter.OPEN_SKILL_UI).To<OpenSkillUICommand>();
commandBinder.Bind(NotificationCenter.CLOSE_SKILL_UI).To<CloseSkillUICommand>();
commandBinder.Bind(NotificationCenter.SEND_MSG_TO_SKILL_UI).To<SendMsgToSkillUICommand>(); commandBinder.Bind(NotificationCenter.SKILL_REQUEST).To<SkillRequestCommand>();
}
}
GameContext(游戏上下文)的作用是关联整个MVC的所有模块,可以说整个MVC架构各个模块之间是相互解耦的,但一定会有一个地方进行各个模块之间的关联,否则每个模块之间是无法通讯的,那么GameContext就是干这件事的,可以说GameContext是整个MVC系统中唯一耦合了所有模块的地方。
下面我们详解一下GameContext中注册的代码意义:
injectionBinder.Bind<ISkillModel>().To<SkillModel>().ToSingleton();
把类型SkillModel作为单例绑定到ISkillModel上,结果是每次通过[Inject]标签注入ISkillModel类型的对象时,获取的都是同一个SkillModel的实例。
mediationBinder.Bind<MainUIView>().To<MainUIMediator>();
把MainUIMediator绑定到MainUIView之上,结果是每次添加带有MainUIView脚本的GameObject到舞台时就会自动创建MainUIMediator对象绑定到该GameObject。
commandBinder.Bind(NotificationCenter.OPEN_SKILL_UI).To<OpenSkillUICommand>();
把NotificationCenter.OPEN_SKILL_UI绑定到OpenSkillUICommand之上,结果是当抛出NotificationCenter.OPEN_SKILL_UI时,就会创建一个OpenSkillUICommand的实例并运行。
commandBinder.Bind(ContextEvent.START).To<StartUpCommand>().Once();
添加了Once之后,表示立即执行且执行后马上解除绑定。
启动程序
StartUpCommand执行启动程序的指令,我们看看他的代码:
using strange.extensions.command.impl;
using strange.extensions.context.api;
using UnityEngine; /// <summary>
/// 程序启动命令.
/// </summary>
public class StartUpCommand : EventCommand
{
[Inject(ContextKeys.CONTEXT_VIEW)]
public GameObject contextView { get; set; } public override void Execute()
{
//获取 UI 画布
Transform canvas = contextView.transform.FindChild("Canvas");
//加载并添加 MainUI
GameObject go = Resources.Load("Prefabs/MainUI", typeof(GameObject)) as GameObject;
GameObject mainUI = GameObject.Instantiate(go) as GameObject;
//添加视图脚本, 或者直接绑定到预制件中都可以
mainUI.AddComponent<MainUIView>();
mainUI.transform.SetParent(canvas, false);
}
}
这里我们将绑定了ContextView的GameObject进行注入,然后添加我们的MainUI到场景,对应的中介类也会自动添加。
模块相关
接下来的开发就可以按模块划分了,每个模块和其它模块都可以做到不耦合。每个模块会分出3个层次,我们接下来依次来看看具体的实现,以Skill模块为例:
视图和中介类
视图负责显示相关的逻辑,但是视图无法访问到数据也无法直接调用请求接口,视图要和模型与其它模块交互需要借助中介类来完成。
视图类
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;
using UnityEngine.UI; public class SkillUIView : View
{
public const string REQUEST_BTN_CLICK = "requestBtnClick"; [Inject]
public IEventDispatcher dispatcher { get; set; } private Text outputText;
private Button requestBtn; public void Init()
{
outputText = this.gameObject.transform.FindChild("OutputText").GetComponent<Text>(); requestBtn = this.gameObject.transform.FindChild("RequestBtn").GetComponent<Button>();
requestBtn.onClick.AddListener(RequestBtnClickHandler);
} private void RequestBtnClickHandler()
{
dispatcher.Dispatch(REQUEST_BTN_CLICK);
} public void AddText(string content)
{
outputText.text += content + "\n";
}
}
视图类注入的IEventDispatcher是用来和中介类进行通信的,按照规则,视图类也只可以和中介类进行通信。
中介类
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl; public class SkillUIMediator : EventMediator
{
[Inject]
public SkillUIView view { get; set; } public override void OnRegister()
{
dispatcher.AddListener(NotificationCenter.SKILL_UI_ADD_MSG, skillUIAddMsgHandler); view.dispatcher.AddListener(SkillUIView.REQUEST_BTN_CLICK, requestBtnClickHandler); view.Init();
} public override void OnRemove()
{
dispatcher.RemoveListener(NotificationCenter.SKILL_UI_ADD_MSG, skillUIAddMsgHandler); view.dispatcher.RemoveListener(SkillUIView.REQUEST_BTN_CLICK, requestBtnClickHandler);
} private void skillUIAddMsgHandler(IEvent evt)
{
view.AddText((string)evt.data);
} private void requestBtnClickHandler(IEvent evt)
{
dispatcher.Dispatch(NotificationCenter.SKILL_REQUEST);
}
}
中介类接收到视图类的消息后可以转发给其它模块或进行远程请求和数据修改,其它模块发送的消息也由中介类进行接收后通知到视图类。
Command控制器
命令模式可以用来执行一段具体的代码,通常请求数据和修改数据也放在Command中,如下:
using strange.extensions.command.impl;
using strange.extensions.dispatcher.eventdispatcher.api; public class SkillRequestCommand : EventCommand
{
[Inject]
public ISkillModel model { get; set; } [Inject]
public ISkillService service { get; set; } public override void Execute()
{
//这里有异步操作, 为了使 Command 对象不被释放, 我们需要调用下面的方法持有当前 Command 对象的引用
Retain(); service.dispatcher.AddListener(SkillService.RECEIVE_DATA, OnReceiveDataHandler);
service.Request("http://www.game.com/mygame.php?id=1000");
} private void OnReceiveDataHandler(IEvent evt)
{
service.dispatcher.RemoveListener(SkillService.RECEIVE_DATA, OnReceiveDataHandler); model.data = ((SkillMsgVO)evt.data).msg;
dispatcher.Dispatch(NotificationCenter.SKILL_UI_ADD_MSG, model.data); //异步操作完成, 可以释放对象了
Release();
}
}
需要注意的是,如果有异步操作,需要调用Retain和Release来保证Command不被垃圾回收销毁。
Model和Service
Model用来储存数据,Service用来进行远程请求。
Model
public class SkillModel : ISkillModel
{
public string data { get; set; }
}
我们另外还创建了一个接口用来定义这个模型,这样的好处是方便后期替换具体的实现类。
Service
using System.Collections;
using strange.extensions.context.api;
using strange.extensions.dispatcher.eventdispatcher.api;
using UnityEngine; public class SkillService : ISkillService
{
public const string RECEIVE_DATA = "receiveData"; [Inject(ContextKeys.CONTEXT_VIEW)]
public GameObject contextView { get; set; } [Inject]
public IEventDispatcher dispatcher { get; set; } public void Request(string url)
{
contextView.GetComponent<GameRoot>().StartCoroutine(WaitASecond());
} private IEnumerator WaitASecond()
{
yield return new WaitForSeconds(1.0f); dispatcher.Dispatch(RECEIVE_DATA, new SkillMsgVO{msg="我是服务端返回的数据!"});
}
}
由于只是示例,所以并没有真正的请求远程,而是使用Unity3D的协程做了一个一秒延迟的模拟。
VO
即ValueObject,这并不是框架要求的内容,一般我们会把各个模块之间进行传递的数据定义为一个数据对象,而VO可以作为这类数据的命名后缀。
public class SkillMsgVO
{
public string msg;
}
和PureMVC的不同
- PureMVC是.Net平台下的开源MVC框架,可以用在Unity或其他基于C#语言的项目中。
- StrangeIoC是专门为Unity3D开发的MVC框架,其注入和中介类的设计都是针对Unity3D的特点开发,移植到其他非Unity3D的C#项目需要修改底层实现。
- PureMVC是MVC架构,其Model层是按代理模式设计的,我们会将数据保存和远程请求用一个代理类公开其部分的接口给到模块使用,比如SkillProxy只会公开技能相关的接口给到技能模块调用。
- StrangeIoC是MVCS架构,其Model层分为Model和Service两个,分别处理数据保存和远程请求,是分开的,一般使用Command来处理特定的数据修改和远程请求。
源码下载
下一篇博客我们来解析一下神奇的注入和自动生成中介类是如何实现的。
http://pan.baidu.com/s/1dDc0io5
框架学习笔记:Unity3D的MVC框架——StrangeIoC的更多相关文章
- spring mvc 及NUI前端框架学习笔记
spring mvc 及NUI前端框架学习笔记 页面传值 一.同一页面 直接通过$J.getbyName("id").setValue(id); Set值即可 二.跳转页面(bus ...
- phalcon(费尔康)框架学习笔记
phalcon(费尔康)框架学习笔记 http://www.qixing318.com/article/phalcon-framework-to-study-notes.html 目录结构 pha ...
- Yii框架学习笔记(二)将html前端模板整合到框架中
选择Yii 2.0版本框架的7个理由 http://blog.chedushi.com/archives/8988 刚接触Yii谈一下对Yii框架的看法和感受 http://bbs.csdn.net/ ...
- Spring框架学习笔记(5)——Spring Boot创建与使用
Spring Boot可以更为方便地搭建一个Web系统,之后服务器上部署也较为方便 创建Spring boot项目 1. 使用IDEA创建项目 2. 修改groupid和artifact 3. 一路n ...
- Spring框架学习笔记(1)
Spring 框架学习笔记(1) 一.简介 Rod Johnson(spring之父) Spring是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架, ...
- JavaSE中Collection集合框架学习笔记(2)——拒绝重复内容的Set和支持队列操作的Queue
前言:俗话说“金三银四铜五”,不知道我要在这段时间找工作会不会很艰难.不管了,工作三年之后就当给自己放个暑假. 面试当中Collection(集合)是基础重点.我在网上看了几篇讲Collection的 ...
- JavaSE中Collection集合框架学习笔记(3)——遍历对象的Iterator和收集对象后的排序
前言:暑期应该开始了,因为小区对面的小学这两天早上都没有像以往那样一到七八点钟就人声喧闹.车水马龙. 前两篇文章介绍了Collection框架的主要接口和常用类,例如List.Set.Queue,和A ...
- JavaSE中Map框架学习笔记
前言:最近几天都在生病,退烧之后身体虚弱.头疼.在床上躺了几天,什么事情都干不了.接下来这段时间,要好好加快进度才好. 前面用了三篇文章的篇幅学习了Collection框架的相关内容,而Map框架相对 ...
- JavaSE中线程与并行API框架学习笔记1——线程是什么?
前言:虽然工作了三年,但是几乎没有使用到多线程之类的内容.这其实是工作与学习的矛盾.我们在公司上班,很多时候都只是在处理业务代码,很少接触底层技术. 可是你不可能一辈子都写业务代码,而且跳槽之后新单位 ...
- JavaSE中线程与并行API框架学习笔记——线程为什么会不安全?
前言:休整一个多月之后,终于开始投简历了.这段时间休息了一阵子,又病了几天,真正用来复习准备的时间其实并不多.说实话,心里不是非常有底气. 这可能是学生时代遗留的思维惯性--总想着做好万全准备才去做事 ...
随机推荐
- textContent、innerText的用法,在文档中插入纯文本
有时候需要查询纯文本形式的元素内容,或者在文档中插入纯文本.标准的方法是用Node的textContent属性来实现: var para = document.getElementsByTagName ...
- 全球最受欢迎的十大Linux发行版(图)
帮助新的Linux用户在越来越多的Linux发行版中选择最合适的操作系统,是创建这个网页的原因.它列出了迄今为止最流行的10个Linux发行版(另外增加的是FreeBSD,到目前为止最为流行的BSD系 ...
- 用Java Servlets代替CGI
从网络的早期阶段开始,人们就希望他们的网络服务器不仅能够显示静态文档,通用网关接口(CGI) 扩展了服务器的能力,但其代价是增加了额外的处理进程.FastCGI则把外部CGI 处理进程同网络服务器更紧 ...
- Android基础_1 四大基本组件介绍与生命周期
Android四大基本组件分别是Activity,Service(服务),Content Provider(内容提供者),BroadcastReceiver(广播接收器). 一.四大基本组件 Acti ...
- 笨笨-歌词伴侣V1.2(酷狗KRC转LRC,LRC歌词批量下载)
最近由于某些热心博友在我CSDN博客上使用了我的软件,提出了一些建议,看到自己的成果有人使用并且提出了一些建议,焉有不高兴之理!刚好碰上最近研究UI界面,有了一个初步的框架,就顺手将歌词相关功能集 ...
- LeetCode Binary Tree Preorder Traversal 先根遍历
题意:给一棵树,求其先根遍历的结果. 思路: (1)深搜法: /** * Definition for a binary tree node. * struct TreeNode { * int va ...
- 新闻类App使用的组件
UI SlidingMenu:com.jeremyfeinstein.slidingmenu:滑动菜单 ActionBarSherlock:com.actionbarsherlock:Action B ...
- hdu 3938 Portal(并查集+离线+kruskal)2011 Multi-University Training Contest 10
搜了题解才把题搞明白.明白之后发现其实题意很清晰,解题思路也很清晰,只是题目表述的很不清晰…… 大意如下—— 给你一个无向图,图中任意两点的距离是两点间所有路径上的某一条边,这条边需要满足两个条件:1 ...
- [Everyday Mathematics]20150108
设 $f$ 在 $(a,b)$ 上 $n+1$ 次可导, 且 $$\bex \ln\frac{f(b)+f'(b)+\cdots+f^{(n)}(b)}{f(a)+f'(a)+\cdots+f^{(n ...
- gradle 学习
gradle是个构建工具,目的是为了更方便的管理项目. 学习gradle看下面的资料: 中文资料,总共六篇,看完之后基础差不多了: 简介 第一个Java项目 依赖管理 创建二进制发布版本 创建多项目构 ...