明绍宗朱聿键鼻 发表于 2021-1-11 10:19

如何在Unity中实现MVC模式?

如何在Unity中实现MVC模式?

我放心你带套猛 发表于 2021-1-11 10:25

@梁伟国 老师提到的uFrame刚刚看了一下,很强大的感觉...

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

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

假设我们在做一个马里奥:
对于游戏里的角色,我会采用这样一个结构。
Character Manager,它的作用是包含这个角色的Controller(s),并提供一个黑板(Blackboard)。
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转换机制 ,从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等一些在平台游戏里常用的状态封装好,留出一些可调参数,例如状态间的转换。

希望有帮助 :)

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

----------Update 1-----------
1. 重新表述Controller的作用

伊索谗言 发表于 2021-1-11 10:34

有个C#的IoC框架叫StrangeIoC。
有个MVVM框架叫uFrame。
不过。。。。其实都不太好用的说。
StrangeIoC概念有点多,几乎无法在团队里推广,没有框架概念的程序员无法接受。
uFrame思路挺好的,但是代码简洁性还有待提高,写代码时对VS的依赖性太大。

目前还没发现易用性和功能都很nb的框架。还不如自己写的方便快捷。

poney 发表于 2021-1-11 10:42

仅仅用上MVC,解决不了什么问题,或许解决了1%,剩下的99%就被挪到了MVC的C里,当你庆祝MVC竣工时,99%的问题在那里默默的微笑的看着你。

普通人物怨 发表于 2021-1-11 10:51

何必呢。为了模式而模式,模式是死的。

iOS那套MVC,也没见带到SpriteKit和SceneKit里。相反,还专门引入了基于组件模式的GameplayKit。

答案是非常浅显易见了,就是模式只是针对解决问题,可以局部实现,C#本来就提供了无限可能,没有必要刻意去在MonoBehavior里去达到目标。

麻辣鸡翅 发表于 2021-1-11 10:51

MVC是UI专用的模式,适用于横向铺量的项目。游戏(非UI部分)显然不是这样的项目。
抛弃MVC迷信是每个游戏程序员的必经之路。

通常来讲,游戏项目最合理的方式是由一个主程根据项目需求制定一套新的结构和各部分的依赖关系,事先想好各部分的扩展和通信方式,上面那位说的就是一个例子。这是必须的步骤,但既然有了这个步骤,再硬套一个通用的MVC框架就是多此一举了。

也就是说,游戏正是因为结构不通用,所以才不能用MVC框架。或者说,由于游戏结构复杂度远高于MVC框架,你仅仅使用MVC框架是不能解决问题的。MVC框架里的概念当然是有用的,但只有它们是远远不够的。游戏需要用到的架构知识远比MVC这种基础玩意高得多得多。

而游戏之所以不可能设计通用框架是因为一个有用的通用框架的复杂度和使用难度会超出所有人的忍耐极限。某种类型是可以有分别的框架的,比如上帝视角ARPG,比如GAL,比如格斗,比如跑酷,比如卡牌,比如打飞机游戏。而他们使用的游戏架构自然也是不同的。

讨论这个会没完没了。

我看题主的困惑在于MonoBehavior。这个东西是挺讨厌。这样,你可以把它当作Button一类的东西处理,它只做和视觉有关的特定功能,比如震动,比如变色,比如控制动画,赋点值然后放它一边玩去,控制好它们的依赖,让它们拥有通用性。然后游戏主体是一个空GameObject上的脚本,它负责管理所有,分发事件。其他地方就不要出现MonoBehavior了。

这样的理由在于生命周期。MonoBehavior必须依附与GameObject,也就是必须要能看到,而逻辑往往会脱离于显示部分,GameObject未创建往往就需要有逻辑了。

也就是说你创建一个人物,应该先创建一个非MonoBehavior类,然后在这个类的初始化代码里创建一个GameObject然后再套上各式MonoBehavior,然后依然在这个非MonoBehavior类处理定时逻辑和其他。需要时再控制下其他MonoBehavior播播动画闪一闪之类。

这样和非Unity游戏也就没啥区别了。想干什么就干什么很自由,也不存在隐患。

---

另外我并没有针对的意思啊,比如说伍一峰将Character Manager继承于MonoBehavior其实是有问题的。它是MonoBehavior就必须依附于GameObject,那通常只能是人物资源的那个GameObject。那么我有个需求是换状态的时候切换为完整的另一个资源,那么要不是Compent复制要不是创建子级GameObject,无论哪个都不太好吧。
或者,我想隐藏这个人物,包括里面的动画啊和纯显示用逻辑,但是基本逻辑又想留着(否则什么时候才能恢复显示?),本来直接将GameObject禁用就好了,现在这样就不行了。
或者说,它虽然有状态,但并没有显示,而是一个虚拟对象。
但如果它不是MonoBehavior就没有任何问题了。

这就是我所说的使用MonoBehavior的隐患,其实很多人心中都默默的有这样的感觉吧?这个隐患也是确实存在的。当然我也不是说MonoBehavior就完全不能用,比如受创变色(切换以及根据color对所有Renderer赋值以及恢复为原材质)肯定是可以的,还有什么动画顺序播放,部件绑定。但是涉及到逻辑的部分,真的建议排除掉MonoBehavior。它确实是毒瘤。

此外有些人不喜欢MonoBehavior是因为start发生的不确定性,还有初始化没有构造函数不便。那你需要端正下思路。你看unity默认的那些组件,你会在乎它们哪个先初始化吗?你会在乎它有没有构造函数参数吗?你不会。因为它们设计成了你不需要考虑这些东西。所以如果你写MonoBehavior,就应该设法按他们的模式走,让初始化无需顾忌顺序参数直接赋值就能解决,而不是抛弃start awake自己搞个create。
而MonoBehavior的主要优点就在于可以独立使用,不依赖于环境。你建个预置拖上去设设参数就能看效果。这是它最大也是唯一的优点,它编码上的不便也都是为了让这一点成为可能。如果你的MonoBehavior做不到这一点,或者你认为不需要,那费老大劲搞成MonoBehavior又没用,何苦。

顺势而为47 发表于 2021-1-11 10:55

感觉做游戏用mvc非常不合适。
本人虽然没有参与过大项目大游戏开发,但是单独做过几个小游戏。一开始代码也是到处用很凌乱,最后始终会把数据处理这块和视图处理分开的。但是视图处理和用户交互一直都是放在一起的,因为他们联系太紧密,如果量太大的话可以分离出来,但绝不等同于那个controller。
后来到处讨论mvc的时候也跟风研究一下,非要把视图和用户交互分开。看了一些吃饱非要强行用mvc做的游戏例子代码,一些简单的逻辑一下看下这边,又要看下那边,跳来跳去,看得我头都晕。这样真的好继续好维护吗。
后来无论做小游戏还是应用都没有使用mvc这种模式。
后来又出现了mvp模式,一看简介这不就是自己一直用的习惯性模式吗,虽然细节有部分差异。

芊芊551 发表于 2021-1-11 10:59

我一般都是将需要的类都设计成独立模块,然后使用继承MonoBehaviour的类来和那些类对接。然后独立模块按照独立代码设计。没做过什么大项目,所以还没遇到过乱成麻的情况。

David902 发表于 2021-1-11 11:02

我猜题主想问的是 ”如何在Unity中实现MVP模式“,因为大部分同学并不真的熟悉MVC,像那种用Controller来操作View的模式是MVP,而MVC是用View观察Model自动更新自己的,不会操作View。如果题主说的是后者,那么对于游戏来说没有必要,因为游戏中不存在复杂的业务逻辑(其实游戏的业务就是绘制UI/模型本身),如果使用MVC徒增复杂度。MVP是比较直观容易理解的模式,其实现在大家写的代码几乎都是MVP(比如在 XXXBehavior里面操作各种XXXEntity/Component),只是代码没有做好合适的分类/解耦让代码看起来很复杂,解决这里的问题其实用各种UI架构是不行的,多关注一些优秀的游戏源码,看看人家是怎么管理游戏对象的,这里需要的是抽象能力...

十二音阶囤 发表于 2021-1-11 11:07

给大家推荐一个新的解决方案
MarkUI, 使用xml标记语言绑定数据的MVVM架构
Asset Store
目前只支持uGUI;优化部分待作者更新;比uFrame学习曲线低,类似Android M的开发模式。
个人认为做轻量级App完全够用;只是里面加入3D和特效的坑需要作者去完善。
页: [1] 2
查看完整版本: 如何在Unity中实现MVC模式?