使用 PureMVC 和 Cocos2d-js 构建游戏项目

Demand

刀塔传奇手游中的场景切换非常灵活:玩家可以通过任务窗口跳转至其它界面,甚至可以通过角色界面中缺失的材料顺滕摸瓜进入关卡界面,然后通过战斗获得自己需要的物品,最后重新回到角色界面——之前打开的窗口依然在那里,就像你没有离开过一样。如果你无聊,可以从任务窗口一层又一层的周转于各个界面,然后在任意时刻都能回退到之前的场景,这种无限后退的能力非常有趣。然而手机的内存是有限的,如何组织程序,保存之前的“现场”则是一种考验。显然由 cocos2d 框架提供的基于 Director/Scene 的场景管理不足以直接实现这样的需求。

Solutions

我考虑使用 MVC 模式来构建这样的场景。查阅了一些资料,大体上有两种做法:

  1. 直接使用 Cocos2d 框架现有的组件(CCScene/CCLayer/CCNotification)来实现 MVC,例如 子龙山人 翻译的 cocos2d里面如何实现mvc 系列;

  2. 或者使用一套完全独立的 MVC 框架,而将 Cocos2d 作为 View 层的实现。

我尝试了第一种方案,但是没有找到一种合适的方法组织 Controller 层。如果使用 CCScene 来作控制器,这在独立的场景下没什么问题。但要如何处理相关联的不同场景,则是个头疼的问题。此外 CCScene 受到 CCDirector 的管理,那么谁来控制 CCDirector 又是个大问题,于是无法实现灵活的场景切换。

对于第二种方案,在网络上的教程中并不多见,但却是一种非常好的思路。幸好在其它语言的开源项目中有许多可以学习的案例。其中 PureMVC 令我印象深刻:

PureMVC is a lightweight framework for creating applications based upon the classic Model, View and Controller concept.

就像它的名字一样,它是一个不含杂质的纯 MVC 框架实现。它最早是一个 ActionScript 项目。但由于它的纯粹性,很容易将它移植到其它主流语言中,于是有了各种语言的实现

在拜读了 PureMVC 最佳实践 后更是被它的理念所折服。我想如果能用它的 javascript 版本与 Cocos2d-js 擦出火花,那真是太棒了。

PureMVC 的 Model/View/Controller 都是单例,而具体的事务是由 Proxy/Mediator/Command 完成的,此外有一个 Facade 提供统一的接口给外部使用。它的内部由观察者模型实现所有消息的发送和接收。

Facade

程序的启动很容易,在 Cocos2d-js 框架加载完成后,在 cc.game.run() 中创建一个 Facade 对象,并编写一个 StartupCommand 完成 Proxies 和 Mediators 的初始化即可。接着由根 Mediator 完成第一个场景的载入。

View

Mediators 负责维护视图组件,以及管理 View 和 Model 之间的消息路由。所以理所当然由它来料理 Cocos2d-js

每一个 Mediator 维护一个 viewComponent —— 即具体的 Cocos2d 对象,在 onRegister 的时候创建并显示它;在 onRemove 的时候移除它。

我创建了一个 DirectorMediator 负责维护 cc.director,用于管理其它 SceneMediators 的切换,以及一些跨场景的 UI 组件,例如网络连接标识等。当然,DirectorMediator 不会被移除。

SceneMediators 主要是一些场景级别的界面,由 cc.Scene 组成,它们可以包含其它的由 cc.Layer 组成的 SubMediators —— 这里显然需要一个嵌套结构,于是我设计了一个 NestMediator,它可以包含其它 NestMediator。于是一个简单的场景树就形成了。

NestMediator 提供一个 push() 接口将另外的 SubNestMediator 注册并加入到自己维护的 Mediator 栈中。而 SubNestMediator 可以使用 NestMediator 提供的 pop() 接口将自己弹出栈,以此实现“后退”功能:SceneMediators 可以让 DirectorMediator 将自己 push()/pop() 实现场景的切换。

SceneMediators 在切换后,重新注册自己维护的子 Mediators 栈,递归调用实现“恢复现场”工作。

由于 Mediators 的管理是独立于 Cocos2d-js 的,所以不会受到它的任何影响。“恢复现场”所需要的数据保存在各个 Mediators,所以不会因为 Cocos2d-js 的组件被移除而丢失。至此得以实现与刀塔传奇类似的场景管理——甚至更加灵活,可以直接构造出任意场景树,并显示出来。

Model

Proxies 负责管理游戏对象数据,及与服务器进行通讯。可以将不同 Model 的 API 封装到相应的 Proxy 中,并提供一些接口给控制器和视图调用。此外当数据变化时,推送相应的消息给控制器和视图。

Controller

Commands 是控制器的主要执行者,用于创建 Proxy/Mediator 以及集中处理较复杂的逻辑。在实际项目中,由于手机网游的主要逻辑都在服务端,所以前端的控制器基本上被弱化,基本上 Model 和 View 的消息机制就足够承担大部分界面逻辑了。

Summary

通过这次尝试,我发现将 Cocos2d 的职责限制在 View 层之后,它能更好更加专注地完成自己的任务。毕竟它诞生之初的使命就是实现对 OpenGL 的封装,以便提供一组友好的图形接口供游戏使用。使用外部的框架能够更系统地管理项目的其它部分,实现更复杂的需求。

P.S.

PureMVC.js 的源码可以在这里获得。本文所涉及的其它源码用于商业项目不便公开,大家可以根据思路自由发挥。

2015/05/09 updated:
@nie341 项目结构大概是这样:

.
├── main.js
├── res
└── src
    ├── app.js
    ├── config
    │   └── data.js
    ├── controller
    │   └── command
    │       └── SomeCommand.js
    ├── lib
    │   └── puremvc.js
    ├── model
    │   ├── proxy
    │   │   └── SomeProxy.js
    │   └── vo
    ├── resource.js
    ├── utils
    └── view
        ├── mediator
        │   └── SomeMediator.js
        └── ui
            └── SomeScene.js

2015/05/14 updated:

在我写完篇文章这后,有人将 PureMVC 的 FSM 移植到 javascript 版了,可以在这里下载:
https://github.com/PureMVC/puremvc-js-util-statemachine

FSM 可以非常方便地用来做场景切换等工作。

2015/06/01 updated:

后续文章 使用 PureMVC 和 Cocos2d-js 构建游戏项目 II

2015/08/25 updated:

后续文章 使用 PureMVC 构建游戏项目 III
后续文章 使用 PureMVC 构建游戏项目 IV

标签: puremvc, cocos2d-js

已有 15 条评论

  1. 晨

    求楼主给个DEMO,最简单的就行。

  2. jakey jakey

    hi 木匣子大哥,看了你的文章深受启发。
    本人也在透过大哥的文章进行实现。遇到些问题请教下
    问题1, SceneMediators或SubNestMediator切换后, 为了后退功能或切换顺利,cocos的资源不释放,这样内存是否使用太大?
    问题2,按照你文章的说明,DirectorMediator应该就是你在文章中描述的根 Mediator吧?
    问题3,cocos2d已经提供了场景棧功能,再使用组合模式维护还有必要吗?
    问题4,按你的描述,pop,push就只是将mediator之间的关系维护,具体组件的显示已经再onregister时完成,pop,push还有其他功能吗?
    问题5,SceneMediators调用pop会发生什么呢?如果他的父节点就是根mediator?

    本人服务器开发,对前端不熟,如有问题不当,请多包涵!

    感谢

    1. 1)资源是有释放的;
      2)是的;
      3)NestMediator 是为了确保 cocos 的资源被释放后,保留原有的结构信息(及其它状态信息)而实现的;
      4)没有了;
      5)sceneMediators 调用 directorMediator.pop() 时,只是简单把自己从 directorMediator 的栈中移除,然后 directorMediator 取出另一个 sceneMediator 重新 onRegister 。

      1. jakey jakey

        感谢回复。非常清楚了。
        对于5), SubNestMediator可以理解为在一个场景下的弹窗,pop或push都自是增加或删除显示,不会进行场景切换吧

  3. allen allen

    楼主你好, 看了你这边帖子觉得获益良多, 也是在按照楼主帖子的思路跟pureMvc的设计模式去试着写出来, 但是有几个问题想请教楼主, 关于“恢复现场”所需要的数据保存在各个Mediators 这一段, 我从头看下来的思路会认为DirectorMediators 是不会被释放, 会是单类, 但是SceneMediators or SubSceneMediators 这类实体出来的类别应该会再被pop的情况下整个释放掉, 所以如果在被释放的情况下要如果做到纪录之前的状况, 然后现做到"SceneMediators 在切换后,重新注册自己维护的子Mediators 栈"这样子的实现呢?还是SceneMediators 也是属于被建立出来就不释放的单类?

    1. 从一个场景转到另一个场景的时候是 push 操作,原来的场景只有 CCScene 被释放掉,SceneMediator 留在栈里。而“返回”操作则是同时释放掉栈顶的 CCScene 及相关 SceneMediator,取出栈里下一个 SceneMediator 重新根据它保留的状态创建 CCScene。

      Mediators 都不是单例。DirectorMediator 也不是,因为做热更的话,DirectorMediator 也是可以被释放掉的。

      1. allen allen

        感谢楼主的回应, 豁然开朗的了解, 反正因为SceneMediator的权责切分得非常细, 管理资源在cocos2d里面不管是ccscene还是cclayer.这样子内存也可以不会被占用, 真的获益良多非常感谢

  4. fuhongxue fuhongxue

    请教楼主,这种方法支持原生开发方式吗?
    我的意思是使用jsb,在ios或安卓上,不使用浏览器

    1. 支持的,我们的项目就是用JSB,能够很好地在iOS和安卓上运行。

  5. nie341 nie341

    hi 木匣子大哥,使用 PureMVC 怎么用Cocos2d-js 构建游戏项目呢,直接把pureMVC文件夹放在cocos项目下的src文件夹里面么?最好能给个项目架构截图

    1. 评论没有格式,我追加在文章后面了。

  6. zxh zxh

    请教楼主,我根据你的教程,使用browserify 编译出来的代码,使用了debug输出source maps文件,但在webstorm里不能调试,想请教一下你是怎么做的?

    1. zxh zxh

      我下载最新版本的webstorm可以调试了,还想请问一下,在手机设备上通过firefox远程调试,能否使用sourcemap呢?

  7. jay jay

    你好,我在学习cocos-js,看到你的文章写的很少,但是是PureMVC的理解还不是很透彻,能提供一份简单的demo源码吗?谢谢。

评论已关闭