Unity3d之MVC框架的使用

Unity游戏的开发当中,我并没有刻意地采用MVC框架,因为不像网站开发那样,Model,View,Controller在游戏这个领域里还没有很清晰的定义。
究其原因,可能是由于不同游戏类型本身的软件架构可以相差很远,而且游戏里面的Object之间有大量的交互,所以垂直的MVC似乎不是十分应景。

然而,某种程度的分离代码逻辑是必要的,可以提高代码的可维护性和重用性。
下面我说说自己的一些经验。

假设我们在做一个马里奥:
对于游戏里的角色,我会采用这样一个结构。
Character Manager,它的作用是包含这个角色的Controller(s),并提供一个Dictionary
Controller,利用Reusable Models来处理角色在这个游戏中的某一状态的逻辑。
Reusable Model,是一个虚的概念,并不是一个父类,通常这类Model都负责某一个特定的功能,可以重复利用,可看做游戏引擎的延伸。
我会将Character Manager和Reusable Model继承MonoBehavior,这样我们就能够直观地知道这个角色是什么类型的Character,并且可以利用inspector调节Model的参数。



怎么将上面的架构应用在马里奥身上呢:
作为Character Manager,我们可以采用Finite State Machine或者Behavior Tree。一个好处是它们都天然地提供了“Controller”。
例如Finite State Machine,它的每一个State都可以看作一个Controller。
而Behavior Tree里面的Action Node,也可以看作是一个Controller。

在每一个Controller里面,都会有指针指向一些Reusable Model。
例如下图Move State可以有一个Move Motor,专门来实现GameObject的移动,而Sprite则封装GameObject的表现,如动画、旋转、位置等等。
这些Reusable Model通常都提供丰富的参数可供调整,可以用于不同游戏当中。

用户输入和游戏里面的消息,则会暂存在Character Manager里面的Blackboard里,供Character Manager使用,让它决定是否需要更换Controller。
例如马里奥里面我按左键,往左行动的信息会写在FSM的Blackboard里面,然后通过FSM的State转换机制 [2],从Idle State转换到Move State。
这样的好处是,往左的信息可以从Input Manager (图中没给出)那里得来,也可以从Enemy AI Manager(图中没给出)那里得来。
这样,一个类型(如拥有Idle,Move,Jump等状态)的FSM,就可以用在所有类似的角色身上,无论是玩家控制的还是AI控制的。


最终在Unity里面会是这样一个情况,FSM,Sprite,MoveMotor都作为Component,而Controllers则包含在FSM里面。


以上方案虽然并不严格,但是在一定程度上提高了代码的可复用性和可维护性。
例如现在我基本都把MoveMotor,Sprite等Model写好,新项目就直接扔进来就能用;
MoveState,IdleState,JumpState等一些在平台游戏里常用的状态封装好,留出一些可调参数,例如状态间的转换。

比较原始的FSM会将State转换直接放在State里面,但这样大大降低了State的可复用性。因此可以尝试将State的转换作为一个可调参数。一些可视化的FSM的原理也是这样,利用连线将两个State链接起来,然后通过定义一些转换的条件。




====================================================================================================

Unity是基于组件的,做多了代码难免混乱。因此需要有更好的思路。一个思路就是MVC。但是,在Unity下如何使用MVC的思路来编程,没太多思路。前两天看到一个老外的文章,可以借鉴下。

地址:https://www.toptal.com/unity-Unity3D/unity-with-mvc-how-to-level-up-your-game-development

例子下载:http://download.csdn.NET/detail/wuyt2008/9615891

下面的图显示了老外的思路


老外设计的结构其实是AMVCC

A是application,用来包含整个项目,并调度用哪个control来响应

M是数据

V是视图,只负责作为事件处理的入口

第一个C是控制器,对所有程序逻辑进行处理

第二个C是组件,不同的组件,组成视图、控制器或模型。




这么说,有点不明白,看下脚本,大概就能理解了。


BounceApplication.cs

[csharp] view plain copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. // 所有类的基类  
  5. public class BounceElement : MonoBehaviour  
  6. {  
  7.     // 让整个应用和所有实例便于访问  
  8.     public BounceApplication app {   
  9.         get {   
  10.             return GameObject.FindObjectOfType<BounceApplication> ();   
  11.         }  
  12.     }  
  13. }  
  14.       
  15. public class BounceApplication : MonoBehaviour  
  16. {  
  17.     // MVC的根实例  
  18.     public BounceModel model;  
  19.     public BounceView view;  
  20.     public BounceController controller;  
  21.   
  22.     //迭代所有控制器和通知数据  
  23.     public void Notify (string p_event_path, Object p_target, params object[] p_data)  
  24.     {  
  25.         BounceController[] controller_list = GetAllControllers ();  
  26.         foreach (BounceController c in controller_list) {  
  27.             c.OnNotification (p_event_path, p_target, p_data);  
  28.         }  
  29.     }  
  30.   
  31.     // 获取场景中的所有控制器  
  32.     public BounceController[] GetAllControllers ()  
  33.     {   
  34.         BounceController[] arr = { GameObject.FindObjectOfType<BounceController>() };  
  35.         return arr;  
  36.     }  
  37. }  

BounceModel.cs

[csharp] view plain copy
  1. using UnityEngine;  
  2.   
  3. // 包含与应用相关的所有数据  
  4. public class BounceModel : BounceElement  
  5. {  
  6.     // 数据  
  7.     public int bounces;   
  8.     public int winCondition;  
  9. }  


BounceController.cs

[csharp] view plain copy
  1. using UnityEngine;  
  2.   
  3. // 控制应用的工作流  
  4. public class BounceController : BounceElement  
  5. {  
  6.     // 处理小球碰撞事件  
  7.     public void OnNotification(string p_event_path,Object p_target,params object[] p_data)  
  8.     {  
  9.         switch(p_event_path)  
  10.         {  
  11.         case BounceNotification.BallHitGround:  
  12.             app.model.bounces++;  
  13.             Debug.Log("Bounce"+app.model.bounces);  
  14.             if(app.model.bounces >= app.model.winCondition)  
  15.             {  
  16.                 app.view.ball.enabled = false;  
  17.                 app.view.ball.GetComponent<Rigidbody>().isKinematic=true// 停止小球  
  18.                 //通知自身或者其他控制器处理事件  
  19.                 app.Notify(BounceNotification.GameComplete,this);              
  20.             }  
  21.             break;  
  22.   
  23.         case BounceNotification.GameComplete:  
  24.             Debug.Log("Victory!!");  
  25.             break;  
  26.         }     
  27.     }  
  28. }  

BallView.cs

[csharp] view plain copy
  1. using UnityEngine;  
  2.   
  3. // 小球视图  
  4. public class BallView : BounceElement  
  5. {  
  6.   
  7.     void OnCollisionEnter() {   
  8.         app.Notify(BounceNotification.BallHitGround,this);  
  9.     }  
  10. }  


BounceView.cs

[csharp] view plain copy
  1. using UnityEngine;  
  2.   
  3. // 包含与应用相关的所有视图  
  4. public class BounceView : BounceElement  
  5. {  
  6.     public BallView ball;  
  7. }  

BounceNotification.cs

[csharp] view plain copy
  1. // 给予事件静态访问的字符串  
  2. public class BounceNotification  
  3. {  
  4.     public const string BallHitGround = "ball.hit.ground";  
  5.     public const string GameComplete  = "game.complete";  
  6.     /* ...  */  
  7.     public const string GameStart     = "game.start";  
  8.     public const string SceneLoad     = "scene.load";  
  9.     /* ... */  
  10. }  

如果用这个思路来做,会清晰很多,但是感觉就是程序的耦合度太高了。

======================================================================================
浅谈unity3d中使用MVC框架模式

MVC框架模式,相信很多人都不会陌生,数据-控制-显示分离的工作方式或者叫做代码结构会使软件(游戏)的结构清晰化,逻辑更明了。但由于MVC框架模式各部件都可以与彼此进行沟通,造成了很多新人在使用MVC的时候消息满天飞,解耦没成,耦合度更高了。我建议在使用MVC的时候,制定策略,让消息单向化,不要双向或形成网状。

好了,我们下面讨论一下Unity3D是否可以使用MVC,如何使用会比较好?(方法有很多种,这里我只写我比较认同的一种)
既然我写了有我比较认同的方式,那么在Unity3D中使用MVC至少我个人持赞成态度,任何东西没有好与坏,只有适用不适用。

Unity3D本身的MonoBehavior脚本是一个重大突破,达到了组件式开发的目的。但是我依然要说,东西虽好,不能乱搞。我个人认为:组件式开发是好的方式,但组件本身却是依靠传统的编程方式建立的,所以除开组件最适用的地方外,强制使用组件进行开发是违和的。MonoBehavior脚本,我们可以将它理解为界面层与界面直接沟通的上层脚本,在他底部的控制、逻辑、数据等有必要用MonoBehavior脚本么?至少我认为是不必要的(为什么要用用不到的东西管理数据和逻辑呢?)。底部的控制、逻辑用什么实现好呢?方式很多,至少MVC框架模式是一个选择。而在使用MVC时,我个人认为模块内小规模使用MVC更合理,模块内的数据、控制、界面关系比较紧密,模块之间提供合理的接口进行跳转即可(不排除模块间消息沟通)。而模块内使用MVC时,也要遵循消息流向单向化,即:接收方永不发送消息,发送方永不接收消息。有人说不太可能,我给大家提供一个模型供参考:

1.      定义M只提供两种函数接口:操作、获取数据;并可以发送更新消息
2.      定义V只接收消息并控制界面显示、跳转、效果等
注:界面(暂且称之为UI)自身处理界面点击等操作直接调用M层进行处理或内部消化或发送给V进行控制跳转
3.      定义C实现必要逻辑(非界面自身逻辑)
我们来看以上模型:
a. 用户点击->UI响应控制->调用M更改数据->发送更新界面消息->V接收消息->更新界面
b. 用户点击->UI响应控制->发送界面跳转消息->V接收消息->更新界面
c. 用户点击->UI响应控制->UI自消化
其实,一般的界面模块,用以上的模型就足够了。但有些模块比较复杂,需要不断的与数据和界面交互,这时候C才有意义。
以上方式,纯属个人比较认可的方式,在实际开发中,每个人都会有自己的观点存在,但一个项目只能用统一的方式才方便沟通、交接、扩展…



展开阅读全文

没有更多推荐了,返回首页