Unity & MVC:如何提升游戏开发质量

Posted 游戏蛮牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity & MVC:如何提升游戏开发质量相关的知识,希望对你有一定的参考价值。

程序员们经常以经典的Hello World开始他们的编程之旅。接下来才会接触更复杂的任务。每个新的挑战都体现出一个重要的结论:工程越浩大,逻辑越复杂。


使大规模易于维护就是软件设计模式存在的意义,这些模式可以用一些简单的规则来制定一个软件工程的整体架构,也可以让一些程序员完成一个大型工程中的独立模块,之后单独的模块以标准化的方式来组织,从而避免代码库遇到一些不熟悉的部分会产生混乱。


当所有人都在遵循这些规则的时候,不仅可以很好的维护和应付旧代码,还可以更快的加入新代码,在设计开发规划上花费的时间也会减。在面对即将来临的挑战的时候,我们必须仔细考虑每一种模式的优缺点,然后找出一个最适合的。


Unity & MVC:如何提升游戏开发质量


我把我的游戏开发经验和流行的Unity游戏开发平台以及MVC模型联系起来,展示在这个教程中。在七年的游戏开发过程中我也遇到不少挑战,通过使用这种设计模式,我已经在代码结构和开发速度方面取得了很大的进展。


我首先介绍一些Unity的基础架构,Entity-Component模型。接下来以一个小型工程为例,解释MVC为何适用。

动机


在一些软件类文献中我们可以找到很多的设计模型。即使这些模型都有一系列规则,开发者经常会对这些规则做一些修改,这样会更好的适用于一些具体问题


我们目前为止还没有找到一个单一明确的方法来设计软件,因此才会有“自由编程”的说法。本文并不是给你提供一个最终解决方案,而是展示Entity-Component和MVC这两种大家很熟悉的模式可以利用和改进的地方。

Entity-Component模式


在Entity-Component(EC)模式中,我们首先定义好元素的层次结构,这些元素组成了一个应用(即Entities),之后我们定义每一个实体(Entity)所包含的的功能和数据(即Components)。按照更多的程序员的说法,实体(Entity)是一个有着不定量(0个或者多个)的Components的对象。下面描绘了一个实体(Entity):

some-entity[component0, component1, ...]


这是一个简单的EC树例子:


Unity & MVC:如何提升游戏开发质量

EC模型可以很好地解决多重继承问题。拿钻石问题举例,假如有一个类D, 继承了类B和类C,而类B和类C都继承了基类A,那么就会引进冲突,因为类B和类C可能会对基类A的同一个功能定义不同。


Unity & MVC:如何提升游戏开发质量


在经常广泛使用继承的游戏开发过程中这种问题是很常见的。


当这些功能和数据处理程序被分解成更小的组件时,它们就可以不依赖多重继承绑定并重用于不同的实体(但是在Unity上使用的主流语言C#或者javascript中,并没有该功能)。

EC的不足


作为面向对象编程的上层,EC能够更好的整理组织你的代码结构。但是对我们来说,在大项目中我们发现在一个‘功能海洋’中漫游,我们很难找到正确的实体和组件或者弄清楚他们应该如何交互。对于给定任务有无数种实体结合组件的方式来完成。


Unity & MVC:如何提升游戏开发质量


为了避免混乱,我们可以在EC的开始就给定一些额外的指导,比如我喜欢在三个不同的方面来考虑软件项目:


对原始数据增删改查的处理(例如CRUD概念)


实现与其他元素交互的接口,这些接口会检测与它们范围内相关的事件并适时触发通知。


最后,一些元素负责接收这些通知,做出业务逻辑策略,决定如何处理数据


好在我们已经有了一个符合这样要求的模型。

MVC模型


MVC模型将软件分为3个重要部分:模型(数据的增删改查),视图(接口/监测)和控制器(决定/动作)。MVC十分灵活,可以在ECS和OOP上层实现。


游戏和UI开发通常的工作流程是等待用户的输入或者其他触发条件,然后在适当的地方发送这些事件的通知,决定做什么,并相应地更新数据。从中我们可以很明显的看出使用MVC模型开发出的应用的灵活性。


这个方法引进了一个抽象层,这个抽象层有助于软件策划,还可以更好的引导新程序员即便代码库更大。当开发人员想要增加或者修复功能时,可以通过将思考过程分解为数据,接口和决策来减少必须搜索的源文件数量。

Unity和EC


首先我们来看你一下Unity能够给予我们什么。


Unity是一个基于EC的开发平台,所有的实体都是游戏对象的实例,一些可以使得它们‘可见’‘可移动’‘可交互’等等的功能都是由扩展类组件提供的。


Unity编辑器中的Hierarchy面板和Inspector面板提供了一个很强大的方法来组装你的应用程序,链接组件,配置初始化状态,并且不需太多代码就能实现你的游戏。


Unity & MVC:如何提升游戏开发质量

右侧的Hierarchy面板中有4个游戏对象


Unity & MVC:如何提升游戏开发质量

Inspector面板显示游戏对象上的组件


然而,我们也会面临功能过多问题,导致层次异常复杂,大多功能都是零散的,加大了开发难度。


从MVC角度出发,我们可以根据事物的功能来划分,构建我们的应用程序,例如:


Unity & MVC:如何提升游戏开发质量

在游戏开发环境中采用MVC模型


现在我会介绍在通用的MVC模型上的两处小修改,这样的修改有利于用MVC模型建立Unity工程的时候遇到的特殊情况。


MVC类的引用会很容易在代码中分散开来。


在Unity中,开发者通常必须拖拽实例来进行访问,否则就得通过繁琐的查找语句如GetComponent( ... )。


如果Unity崩溃或者一些其它bug使得所有引用丢失,下场就比较悲催了。


因此我们需要一个单独可靠的根引用对象,通过该对象我们可以找到并恢复该应用程序中的所有实例。


一些封装了通用功能的元素应该高度可重用,不应该自然地分到三个重要的部分:模型,视图,控制器之中。可以将它们称之为简单组件。在EC意义中,它们也是组件,但充当的角色仅仅是MVC框架中的助手。


例如一个旋转组件,它只是通过给定的角速度来旋转物体,但不会通知、存储或者决定任何事情。


为了协调这两个问题,作者提出了一个改进的模式,称之为AMVCC,或者Application-Model-View-Controller-Component(应用—模型—视图—控制器—组件).


Unity & MVC:如何提升游戏开发质量


Application –应用程序的入口,所有关键的实例集合的入口,应用程序相关的数据入口


MVC – 现在你应该了解了


Component - 精简的,封装得很好的一些可重用的脚本


我将这个改进的模型应用在工程中,已经满足了我的需求。


举个简单的例子,我们来看一个叫做10 Bounces的小游戏,我将在这个游戏中利用AMVCC模式的核心元素。


这个游戏的设置很简单:一个有着SphereCollider和Rigidbody的小球(当你点击‘Play’之后就开始下落),一个立方体作为地面,5个脚本组成了AMVCC。

层次结构


在编写脚本之前,我会先设计层次结构,创建类和资源的大纲。在设计的过程中我会一直遵循着这个新的AMVCC风格。


Unity & MVC:如何提升游戏开发质量


我们可以看到,在view游戏对象中包括了所有的可视化元素和一些其他视图的脚本。在一些小项目中,模型和控制器的游戏对象通常只包括与其相关的脚本。而在一些大项目中,模型和控制器的游戏对象会包含更多具体的脚本。


当有人浏览你的项目时希望看到的目录结构如下:


Data存放在application>model>下

Logic/Workflow存放在application > controller >下

Rendering/Interface/Detection存放在application > view > 下


如果所有团队都遵循这些简单的规则,旧版项目就不会成为问题。


值得注意的是,这里没有组件容器。如同我们之前讨论的,组件容器更加灵活,可以按照开发者的喜好来附加到不同的元素上。

编写脚本


注:下面展示的脚本是实际项目实现的抽象版本。授人以鱼不如授人以渔。如果你想了解更多,这里有我个人的专门为unity设计的MVC框架的一个链接。你可以找到实现AMVCC结构框架的核心类,大多数应用程序都会需要它。


我们来看一下10 Bounces的结构脚本。


在开始之前,我们来简要描述一下脚本和游戏对象如何一起工作的。在Unity中,MonoBehavior类代表EC意义中的组件。为了使得一个对象可以在运行期间中存在,开发者应该将源文件拖到一个游戏对象(也就是EC模型中的Entity)中,或者使用命令AddComponent<YourMonobehaviour>()。然后实例化该脚本,并准备在执行期间使用它。


首先我们定义应用类(AMVCC中的A), 它将作为包含所有实例化的游戏元素引用的主类。同时我们也要创建一个协助基类,称作Element。Element会让我们去访问应用程序中的实例以及其子节点的MVC实例。


在上面所述的基础上,我们来定义应用类(AMVCC中的A),这个类有一个特别的实例。在这个实例中有三个变量:模型、视图和控制器,这三者将会让我们在运行时候访问所有MVC实例。对于我们所需要的脚本来说,这些变量应该是有着公共引用的MonoBehavior。


接下来我们也要创建一个叫做Element的协助基类,我们可以通过它来访问应用程序的实例。同时每一个MVC类也可以访问其他MVC类。


需要注意的是,所有的类都扩展了MonoBehaviour。他们都是附加到游戏对象“实体”上的组件。点击原文查看代码~


我们可以根据BounceElement创建MVC核心类。BounceModel、BounceView和BounceController的脚本通常作为更多的特定实例的容器,由于这是一个简单的例子,只有View会有一个嵌套结构。Model和Controller这两者都可以分别由一个脚本来完成。点击原文查看代码~


创建完所有的脚本之后,我们可以继续添加并配置它们。


层次结构布局应该是这样的:


Unity & MVC:如何提升游戏开发质量

以BounceModel为例,我们来看看在unity编译器中是如何来展示的:BounceModel脚本有bounces和winCondition两个字段。


当所有脚本和游戏在运行的时候,我们可以在控制台面板中看到这样的输出:



Unity & MVC:如何提升游戏开发质量


通知


如上面的例子所示,当小球接触到地面上的时候,视图就会执行方法app.controller.OnBallGroundHit()。无论如何,对于应用程序中所有的通知都去做app.controller.OnBallGroundHit()并不是‘错误’的。然而以我的经验来看,在AMVCC应用类上实现一个简单的通知系统要更好。


为了实现他,我们来更新BounceApplication的布局。点击原文查看代码~


接下来,我们需要一个新的脚本,所有的开发者都要在这个脚本中添加通知事件名称,这些名称可以在执行期间调用。


我们很容易看到通过这种方式代码的可读性更好,因为开发者不需要搜索所有的controller.OnSomethingComplexName函数的源代码来理解在执行过程中可能发生什么类型的动作。只需要查看一个文件就可以理解应用程序中的所有行为。点击原文查看代码~


现在,我们只需要调整BallView和BounceController来处理这个新系统。点击原文查看代码~


大项目会有很多的通知。为了避免设计成一个庞大的switch-case结构,我们最好创建不同的控制器,让它们去处理不同范围的通知。

现实生活中的AMVCC


这个例子展示了一个AMVCC模式的简单用例。我们应该调整对MVC三个元素的思维方式,并学会将实体看作一个有序的层次结构。


在大工程中,开发者都面临着更加复杂的场景,对一些物体是否应该是视图或控制器,或者是否应该将一个给定的类更彻底的分为多个小类等有太多疑问。

经验法则(Eduardo)


不存在任何“MVC分类通用指南”。但是还是有一些简单的规则,它们帮助我决定了是否去将一些事物定义为Model,View还是Controller,并且帮助我何时将一个给定的类切分为一个个小部分。


当我在思考软件架构或者编写脚本的时候经常会发生这样的事情。

分类


模型

  • 保存应用程序的核心数据和状态,例如玩家的生命值或枪的弹药。

  • 序列化,反序列化,与/或类型之间的转换。

  • 从本地或网上进行载入或者保存数据。

  • 通知控制器操作的进度。

  • 有限状态机中存储游戏的状态。

  • 从不访问视图。

视图

  • 可以从Model获得数据来表示用户的最新游戏状态。比如视图中的一个方法player.Run()可以在内部使用model.speed来展示游戏者的能力。

  • 不能改变Model。

  • 严格地实现类中功能,例如:

u 一个玩家视图(PlayerView)不能实现输入检测功能或者修改游戏状态功能。

u 视图应该是一个黑盒,只有一个接口和重要事件的通知。

u 不存储核心数据(比如速度,健康值,生命等)


控制器

  • 不存储核心数据。

  • 有时候能够过滤不期望的视图的通知。

  • 更新和使用Model的数据。

  • 管理unity的场景工作流程。

类层次结构


这方面我没有很多的步骤去遵循。通常我认为在变量前缀太多,或出现相同元素的多种不同形式(比如MMO中的Player 类或FPS中的Gun类)时,这些类就需要切分为更多的类。


例如,一个模型中包含的Player数据中有很多类型,比如playerDataA,playerDataB,…,或者一个控制器处理Player的通知有很多类型比如OnplayerDidA,OnplayerDidB,…。我们想减少脚本代码并摆脱player和Onplayer前缀。


为了更容易理解,我使用一个只包含数据的Model类来演示。


在编程过程中,我通常以一个包括所有游戏数据的类开始。点击原文查看代码~


我们可以看出游戏越复杂变量越多。当复杂到一定程度,最终会变成一个巨大的类,这个类包含了许多model.playerABCDFoo变量。嵌套元素会简化代码的实现,并且为数据变量之间的转换提供空间。点击原文查看代码~


在这种类的配置下,开发者可以直观地每次在代码中查看一个概念。假设在一个第一人称射击游戏中武器和配置很多。实际上GunModel允许为每个种类创建一个预制的列表(在游戏中可以快速复制并重用一些预先配置的游戏对象),存储起来备用。


相反,如果枪支信息都在一个GunModel类中存放,那么这个类需要包含许多变量,比如gun0Ammo,gun1Ammo, gun0Clips等。当玩家需要存储特殊枪支信息时,将需要存储整个模型,包括一些不需要的玩家信息。在这种情形下,很显然建立一个新的GunModel类会更好。


Unity & MVC:如何提升游戏开发质量

改善类层次结构


任何事都有对立面。有时不需要划分过细以免增加代码的复杂性。只有实践才能够很好的锻炼技能,这样才能够为工程找到最好的MVC分类。

总结


目前有成千上万中软件模式,在本文中我试着展示一种模型,这个模型在我经历过大多数的项目中帮助良多。开发者应该积极接纳新事物,但同时要持有怀疑态度。我希望这个教程能让你有些收获,同时也可以作为一个跳板以便开发自己的风格。


同时,我鼓励你去研究其它模式,找到一个最适合你的模型。维基百科上的这篇文章是一个很好的学习起点。


如果你喜欢AMVCC模式,可以试用我的库Unity MVC,这里包含了一个AMVCC应用程序的所有核心类。


本文由蛮牛社区()nicekiller翻译发布,除合作社区及合作媒体外,禁止转载。

蛮牛社区()分享最新的游戏研发和虚拟现实相关技术内容。


以上是关于Unity & MVC:如何提升游戏开发质量的主要内容,如果未能解决你的问题,请参考以下文章

Unity 框架QFramework v1.0 使用指南 工具篇:05. ResKit 资源管理&开发解决方案 | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

如何快速的提升自己的技能

U3D:如何使用collision pipeline提升游戏开发优势

2、Unity引擎的特点是啥?

Unity 框架QFramework v1.0 使用指南 架构篇:19. 心中有架构 | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

UNITY3D 游戏开发之三NGUI && HUDText 的练习源码及资源