木匣子

Web/Game/Programming/Life etc.

使用 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:

2015/08/25 updated: