1、Unity3D控制反转之Get Start StrangeIOC Unity3D控制反转之Get Start StrangeIOC Strange是一个Unity3D中用于控制反转的第三方框架,控制反转(IOC-Inversion of Control)思想是类间解耦的一个重要方法,对于我来说,任何解耦技术都值得去学习。什么是IOC?这里有详细解答。IOC框架已经在企业级开发和其他非游戏软件的开发中成为了主流,并且可以说已经非常成熟。我觉得它可以帮助游戏开发变得更加容易测试,更好的进行协作开发。我非常想尝试它看看到底可以在游戏开发过程中起到多大的帮助程度。 Strange使用起来真的像他的名字
2、一样,非常奇怪。我发现它对于初学者来说,使用起来真的非常闹心,比如你想试着去写一个Hello World都非常不容易。这里是StrangeIOC框架的说明页面,但是这上面并没有一个真正意义上的新手引导来帮助我们了解Strange的工作机制,这就是你现在看到现在这篇文章的意义-用StrangeIOC框架写一个HelloWorld。一些提醒: 在阅读本篇文章之前,最好先去上面提到的官方说明页面了解一下Strange框架的架构(看看它的每个部分的功能以及怎么整合到一块工作的)。 这篇文档使用的是signal(消息)而非event(事件)(因为相比event我更喜欢signal) 我不会把文档中的Un
3、ity项目提供出来,因为我希望大家自己动手去做,这样肯定会学到更多:) 这个Hello World示例只是简单的提供注入绑定(injection binding)、命令绑定(command binding)、调解绑定(mediation binding)的示例。Signal 建立一个空Unity项目,下载并且解压Strange框架到Assets文件夹中,我们只需要框架的脚本,把examples和.doc文件夹去除,在Unity的的结构应该是这样的: Assets StrangeIoC scripts 在Assets文件夹下创建Game文件夹,即用来创建Hello World示例的文件夹。文件夹
4、的的结构应该是这样的:Assets Game Scenes Scripts在Scripts文件夹下新建名为HelloWorldSignals.cs的c#脚本,这个类将包含所有用到的signal,让我们coding起来:using System; using strange.extensions.signal.impl; namespace Game public class StartSignal : Signal 在Strange中,这个signal的概念非常像观察者模式(observer pattern)中的事件(events)。在这里,它以命名类的方式实现了继承Strange的Signa
5、l类.别急,我们马上会看到怎么去使用它。 Strange采用Contexts的概念来识别不同的问题域或者子模块。在实际的游戏项目中,你可以有多个Contexts,比如游戏逻辑、资源、持久层、统计分析、社交模块等等。我们在这个实例中只用了一个Context。 一个预构建的context在Strange中称为MVCSContext,MVCSContext默认使用event机制,我们来创建另外一种context父类,改造成使用signal机制,我们其他的context要继承这个SignalContext。 在Scripts下创建名为SignalContext.cs的脚本:文章出处狗刨学习网using
6、 System; using UnityEngine; using strange.extensions.context.impl;using mand.api;using mand.impl;using strange.extensions.signal.impl; namespace Game public class SignalContext : MVCSContext /* * Constructor */ public SignalContext (MonoBehaviour contextView) : base(contextView) protected override v
7、oid addCoreComponents() base.addCoreComponents(); / bind signal command binder injectionBinder.Unbind(); injectionBinder.Bind().To().ToSingleton(); public override void Launch() base.Launch(); Signal startSignal = injectionBinder.GetInstance(); startSignal.Dispatch(); 在Scripts文件夹下创建一个新文件夹Controller,
8、到这里有了一点MVC模式的特征。Strange作者建议我们应该以指令类(Command Class)的形式实现各个Controller接口,这个文件夹将包含所有的Command类,现在我们创建一个在StartSignal指令调用时执行的指令。在Controller文件夹下创建名为HelloWorldStartCommand.cs的类:using System; using UnityEngine; using strange.extensions.context.api;using mand.impl; namespace Game public class HelloWorldStartCo
9、mmand : Command public override void Execute() / perform all game start setup here Debug.Log(Hello World); 现在我们为这个HelloWorld示例创建一个自定义的context类HelloWorldContext.cs:using System; using UnityEngine; using strange.extensions.context.impl; namespace Game public class HelloWorldContext : SignalContext /*
10、* Constructor */ public HelloWorldContext(MonoBehaviour contextView) : base(contextView) protected override void mapBindings() base.mapBindings(); / we bind a command to StartSignal since it is invoked by SignalContext (the parent class) on Launch() commandBinder.Bind().To().Once(); 在这里,我们把StartSign
11、al类绑定(bind)给了HelloWorldStartCommand类。这样在StartSignal的实例被调用时,HelloWorldStartCommand会进行实例化(instantiated)和执行(executed),注意在我们的示例中StartSignal信号会在SignalContext.Launch()方法中调用发出。 最后一步就是创建一个MonoBehaviour来在Unity中管理context,在Scripts文件夹下创建HelloWorldBootstrap.cs:using System; using UnityEngine; using strange.exten
12、sions.context.impl; namespace Game public class HelloWorldBootstrap : ContextView void Awake() this.context = new HelloWorldContext(this); 用于在Unity中管理Strange context的接口类通常命名为“xxxBootstrap”,当然这只是一个建议,如果你乐意你可以随意起名字。这里唯一需要注意的是继承Strange框架的ContextView类的类需要是一个MonoBehaviour,我们在Awake()里分配了一个我们自定义好的context实例
13、给继承的变量context。创建一个空场景命名为HelloStrange,创建一个EmptyObject命名为Bootstrap,把我们之前创建的HelloWorldBootstrapadd上来。可以跑一下这个场景,之前程序正确的话,你应该看到控制台的Hello World输出了。Injection in Mediator到目前为止写这么一大堆东西只是输出一句“HelloWorld”,是不是被Strange搞得头都大了?其实做到现在这一步已经大致为你梳理出来Strange的一些机制了。首先我们有了一个能跑的context,从这一步开始,我们就可以添加view和相应的mediator,还可以使用
14、injection binder把一个实例映射到一些可注入controllers/commands和mediators的接口中,而这些接口并不需要关心这个实例是怎么来的。接下来就是见证奇迹的时刻了! 一般我们做游戏编程的时候,会有一堆单例管理器(singleton managers)比如EnemyManager、AsteroidManager、CombatManager等等,假如需要同一个实例给任意一个管理器去调用有很多解决方案,比如我们可以使用GameObject.Find() 或者为这个类添加一个静态单例(GetInstance() static method),OK,让我们看看有了Str
15、ange以后,这样的情形可以怎么去解决:创建一个名为ISomeManager的接口,模拟一个上面说的那种manager,在Scripts文件夹创建ISomeManager.cs脚本namespace Game public interface ISomeManager /* * Perform some management */ void DoManagement(); 这就是我们示例当中的manager接口,注意:Strange的作者建议我们总是使用一个接口然后通过injectionBinder将它映射到一个真正的实现类,当然,你也可以使用多对多的映射。接下来我们创建一个具体实现类,在Sc
16、ripts文件夹下创建ManagerAsNormalClass.cs脚本:using System;using UnityEngine;namespace Game public class ManagerAsNormalClass : ISomeManager public ManagerAsNormalClass() #region ISomeManager implementation public void DoManagement() Debug.Log(Manager implemented as a normal class); #endregion 如果你仔细在看你可能会发现这
17、是一个没有MonoBehaviour的manager,别急,一会再介绍怎么bind有MonoBehaviour的 现在我们来创建一个简单的交互场景,效果是当一个Button按下时,ISomeManager的DoManagement函数执行,这里我们有一个要求:用MVC思想-对controll层(ISomeManager)和view层(控制Button触发事件的脚本)完全解耦,view层只需要通知controll层:hey!button被点击了,至于接下来发生什么交由controll层进行逻辑处理。 现在缺一个view层,把它创建出来吧-在Game文件夹下创建View文件夹,创建HelloWor
18、ldView.cs脚本:using System;using UnityEngine;using strange.extensions.mediation.impl;using strange.extensions.signal.impl;namespace Game public class HelloWorldView : View public Signal buttonClicked = new Signal(); private Rect buttonRect = new Rect(0, 0, 200, 50); public void OnGUI() if(GUI.Button(b
19、uttonRect, Manage) buttonClicked.Dispatch(); 这里继承的Strange框架中的View类已经包含了MonoBehaviour。所有使用Strange context的View层类都必须继承这个Strange的View类,我们刚刚创建的View类只有一个交互功能:在点击名为Manage的Button后,调用一个 generic signal(通用信号) 。 Strange作者建议对每个View创建对应的Mediator。Mediator是一个薄层,他的作用是让与之对应的View和整个程序进行交互。mediation binder的作用是把View映射到
20、它对应的mediator上。所以接下来为View层创建对应的mediator-在view文件夹下创建HelloWorldMediator.cs脚本:using System;using UnityEngine;using strange.extensions.mediation.impl;namespace Game public class HelloWorldMediator : Mediator Inject public HelloWorldView view get; set; Inject public ISomeManager manager get; set; public o
21、verride void OnRegister() view.buttonClicked.AddListener(delegate() manager.DoManagement(); ); 在这段代码里我们可以看到神奇的Inject标注(Inject attribute)。这个Inject标注只能和变量搭配使用,当一个变量上面有Inject标注时,意味着Strange会把这个变量的一个实例自动注入到它对应映射的context中。据此从我们上面的代码来分析,在这里我们获取到了view和manager的实例,并且不用去关心这些个实例是怎么来的。OnRegister()是一个可以被重写的方法,它用来
22、标记实例注入完成已经可以使用了,它的意义主要是进行初始化,或者说做准备。在上面的类中,OnRegister方法中为HellowWorldView.buttonClicked signal添加了一个监听器,这个监听器的逻辑是按下就执行manager.DoManagement方法。文章出处狗刨学习网 接下来就是最后的工作,我们需要把待绑的类映射到Strange Context中。打开我们之前写的HelloWorldContext脚本,在mapBindings()方法中添加代码:protected override void mapBindings() base.mapBindings(); / w
23、e bind a command to StartSignal since it is invoked by SignalContext (the parent class) during on Launch() commandBinder.Bind().To().Once(); / bind our view to its mediator mediationBinder.Bind().To(); / bind our interface to a concrete implementation injectionBinder.Bind().To().ToSingleton();在Hello
24、World scene中,添加一个名为View的GameObject,addHelloWorldView 脚本,运行场景,你应该能看到当我们按下Manage按钮时,控制台输出Manager implemented as a normal class。文章出处狗刨学习网你会发现Strange自动把HelloWorldMediator脚本挂载到了ViewGameObject上面。注意我们之前并没有手动把HelloWorldMediator脚本挂载到ViewGameObject上。MonoBehaviour Manager 大部分时候,我们需要类似于上面的manager但是实现类是一个MonoBeh
25、aviour,这样我们才能使用例如协程、序列化的Unity特性。接下来创建实现MonoBehaviour接口的manager实例,看看怎么在Strange中进行bind。 创建一个实现MonoBehaviour接口的manager,在Script文件夹下,命名为ManagerAsMonobehaviour.csusing System; using UnityEngine; namespace Game public class ManagerAsMonoBehaviour : MonoBehaviour, ISomeManager #region ISomeManager implement
26、ation public void DoManagement() Debug.Log(Manager implemented as MonoBehaviour); #endregion 在HelloStrangeScene中,创建一个新的GameObject名为Manager,add 上面创建好的 ManagerAsMonobehaviour脚本 编辑HelloWorldContext脚本的mapBindings()方法:protected override void mapBindings() base.mapBindings(); / we bind a command to StartS
27、ignal since it is invoked by SignalContext (the parent class) during on Launch() commandBinder.Bind().To().Once(); / bind our view to its mediator mediationBinder.Bind().To(); / REMOVED! /injectionBinder.Bind().To().ToSingleton(); / bind the manager implemented as a MonoBehaviour ManagerAsMonoBehavi
28、our manager = GameObject.Find(Manager).GetComponent(); injectionBinder.Bind().ToValue(manager);与把ISomeManager映射为一个类型相反,我们把这个ManagerAsMonobehaviour映射为一个实例值(instance value)。Injection in Command 到目前为止我们为HelloWorldMediator注入了一个ISomeManager的一个实例,并且可以直接使用它。这样做其实并不是很理想,一个Mediator应该是在view层和controller层之间的一个薄
29、层。我们需要尽量使Mediator层不去关心应该在Manager类去做的那部分复杂的逻辑处理代码。虽然这么做也可以,我们还是用signal把这部分映射到command层吧。 编辑HelloWorldSignals.cs脚本,添加一个DoManagementSignal:using System; using strange.extensions.signal.impl; namespace Game public class StartSignal : Signal public class DoManagementSignal : Signal / A new signal! 我们创建com
30、mand映射到signal:在Controller文件夹下创建一个脚本DoManagementCommand.csusing System; using UnityEngine; using strange.extensions.context.api;using mand.impl; namespace Game public class DoManagementCommand : Command Inject public ISomeManager manager get; set; public override void Execute() manager.DoManagement(); 在这个类,我们把ISome
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1