cocos2d里面如何实现MVC(一)
前言:
众所周知,现在MVC非常流行。现在只要随便搜索一下,哪里都是MVC的影子。刚开始在j2ee里面,然后是rails,后面居然.net也出来了,ios更不用说,哪里都是mvc,而且强制你必须使用mvc。但是,我们写的那些程序,真正完全符合mvc吗?呵呵,这个不好说,看个人理解程度而异。mvc实在是太火了,马上就有人在cocos2d社区里面讨论,cocos2d该怎么实现mvc呢?大家你一言,我一语,讨论的是热火朝天。有人支持,也有人反对。不管咋样,今天让我们也来见识一下cocos2d里面的mvc,看看到底这玩意儿好使不。
Model-View-Controller (MVC) 在web应用开发中非常流行,它是一种组合设计模式,目前被广泛应用于带有图形交互用户界面程序开发中。一些web开发框架,比如Ruby On Rails,Django 和 ASP.NET MVC, 它们是不同语言平台上面的web开发框架,但是,它们都共用同样的原则--那就是把用户表示层和逻辑层分离开来。关注点分离(SoC),这个原则在现代软件工程方法中是一个非常重要的设计理念--不要迷失于实现细节,遇到一个实际问题的时候,要划分不同的关注点,且这些关注点必须隔离开来,这样才能达到更好的代码重用度,以获得鲁棒性、可适配性和可维护性。所有这些软件属性对于软件质量来说都是至关重要的。
Cocos2d本身并不是基于mvc的理念来设计的,但是,这并不防碍你在自己的游戏开发中使用mvc。实现方式肯定是多种多样的,在这篇博文中, 我只是向大家分享一下我是怎么在cocos2d里面实现mvc的,同时,在最后,我会写一个简单的游戏demo,当然,里面使用的是cocos2d+mvc。
现有问题
cocos2d里面有这样一些类,CCSprite,CCLayer,CCScene,所有这些,都是CCNode的子类。基本上,大家在使用cocos2d开发游戏的时候,都会采用下面的步骤来实现游戏逻辑:
- 通过应用程序代理类来初始化第一个CCScene(即AppDelegate里面的第一个CCScene),
- CCScene里面实例化一个或者多个CCLayer,并把它们当作孩子添加进去。
- CCLayer 里面实例化一个或者多个CCSprite,也调用addChild添加进去
- CCScene 处理用户输入(比如touch事件和加速计的改变),同时更新CCLayer和CCSpirte的属性,比如更改CCSprite的position,让sprite运行一个或多个actioin等。
- CCScene里在运行一个游戏循环(game loop,一般是1/60更新一次),然后CCLayer和CCSprite就在这个game loop里面做一些更新和游戏逻辑。
这个过程看起来非常简单,而且也可以很快地做出游戏来。这也是为什么cocos2d这么流行的原因,它实在是太简单了。但是,当你的游戏逻辑越来越复杂的时候,你的代码会变得越来越难以维护。这里面最突出的问题就是,CCScene这个类负责的事情太多了---同时要处理用户交互,还有负责游戏逻辑(逻辑层)和画面显示(表示层)。(译者:根据SoC的原则,这显然是不合理的,我们应该把职责分离开来,这样代码才更容易维护。同时SRP(单一职责原则)也是这么要求的,一个类只负责一件事情)
模型(Model)
MVC它会把一个系统划分为以下几个组件:
- Model ,它负责与领域相关的逻辑处理代码,也可以说是逻辑层,或者领域层。
- View ,只负责界面显示。
- Controller ,它负责处理用户交互。
让我们先从model开始。Model代表了游戏逻辑。因为我现在正在制作一个platform游戏,所以,我讲的一些东西也是与platform游戏相关联的。我的游戏里面的model包含下面一些类(当然,仅仅是一部分类)
- Player,
- 包含一些属性,比如:player的位置、当前速度(x轴速度、y轴速度)等。
- 包含一些与player有关的处理逻辑,比如:run,walk,jmup等。
- 包含一个update方法,该方法会被游戏主循环每一帧刷新时所调用,它主要负责更新player model。
- Platform,
- 包含一些属性,比如:platform位置、宽度、高度等。
- 包含一些与platform有关的处理逻辑,比如:倾塌等
- 包含一个update方法,该方法会被游戏主循环每一帧刷新时所调用,它主要负责更新patform的model。
- GameModel,
- 包含一些游戏世界的属性,比如重力等。
- 包含一些方法来执行游戏逻辑。
- 包含一个update方法,该方法会在每一帧刷新的时候被game loop所调用,然后它就可以更新自己的状态,同时还会触发游戏世界里面的其它对象也相应地更新自己的状态。
你可能会问:有些属性你完全没有必要重复定义,你可以直接从CCSprite里面得到,比如position、width、height等。我想说:有对有错。说对呢,是因为它们确实差不多,可以拿来就用。说不对呢,那是因为,model有可能使用一些不同的计量单位,比如米,而不是像素。(比如box2d这样的,就不是使用像素作为单位)。在我的model里面,我使用的是米,当然,你也可以使用英尺,或者其它单位。渲染引擎对于model来说是透明的,model完全不用关心。
视图(View)
根据mvc的原则,view应该只负责界面显示。它实际上也是在cocos2d里面实现mvc时,最简单的一个。如果你有一个model,你可以使用CCLayer,然后添加一些CCSprite或者其它coocs2d类来处理显示问题。把model和view分开的好处就是,你没必要把model的属性直接映射到view的属性上面去。比如,你的玩家在x轴方向上移动,但是,你想让它总是在距离屏幕左边10px的位置。这时候,你就可以移动CCLayer了,而不是真的在移动sprite。当把model对象显示出来的时候,你必须考虑单位,如果你使用的是米作为计量单位,你在渲染的时候必须转化为像素。(你可以像box2d里面一样,定义一个PTM_RATIO)那么你的model怎么和view打交道呢?你可以从controller里面得到view,或者你可以把game model制作成一个单例,然后使用静态方法来处理它。
控制器(Controller)
controlller负责把view和model联系起来。它的主要职责就是处理用户输入。由于我们需要实例化model和view,我发现在controller里面来做非常合适。我是把controller类继承到CCScene类,然后我们需要建立一个初始的controller类,它由appDelegate来实例化。然而,这里会有一个问题,touch事件是由CCLayer来处理的,而它在我的设计里面的角色是view。而我又不想让view来处理用户输入,所以,我需要传递一个view的引用给controller(不是直接传递,而是通过delegate),然后通过delegate来执行controller的touch事件处理代码,以此来处理view里面的touch事件。好了,现在我的controller类就能够处理来自view的用户事件了。然后,它可以根据用户的输入来操作model,要么通过修改model的属性,或者调用model的方法。再更新完model之后,我们的view也需要得到通知并更新。所有这些,我都在game loop里面完成,实际上它就是一个controller。controller的职责只是负责调用view的update方法,然后剩下的就交给view去完成啦。
还有一件事情…
游戏并不仅仅是根据model状态的更改来更新一下view就可以了,它还需要播放音乐和音效。由于controller负责处理用户交互,它肯定知道何时该播放什么音效。但是,有些时候也会有例外。如果一个player掉到platform上面,但是controller并不知道,因为这部分逻辑判断在model里面。那我们可以从model里面播放音效吗?。。。不,我们不能这样做。因为这样就破坏了SoC的原则了,model就应该只负责游戏逻辑。那么,我们该怎么做呢?在下一篇博文中,我将向大家展示我是怎么做的,我打赌,你肯定差不多也想到呢,对吧?
后记:cocos2d mvc这个系列一共有7篇,我会统一更新到这个目录,方便大家索引。