手机跑酷游戏开发小记

观看演示视频

为了参加比赛,这个暑假开发了这款跑酷类手机小游戏。虽然辛苦,也学到不少东西,至少作品还算满意吧。

从准备到开发确实花了不少时间,虽说真正开发大概只有十来天的样子,前期也需要熟悉框架,了解开发的流程,团队开发的磨合等等。

这款手游是基于Cocos2d-JS开源引擎,通过SpiderMonkey + js-binding的方式调用底层Cocos2d-x的引擎。

可能有的人会质疑这样操作的效率,后来事实证明,这样的方式效率虽然有待改善,但至少也轻松虐了一番Canvas在手机的表现。

本篇文章不过多的介绍 cocos2d-js引擎的使用,而仅讨论一些心得。

效率的问题一方面可能是引擎引起的,肯定也有开发者的问题。起初开发时没有过多的考虑游戏效率问题,到后来发布APK发现手机表现差强人意才进行了一些优化。优化主要考虑到以下几个方面:

  1. 图像资源的预加载

  2. plist文件预加载

  3. 减少粒子数目

  4. 资源的复用

还有其他的资源需要加载,例如动画资源等,但由于前期的疏忽加之时间紧迫,这次没有进行预加载。

预加载实现

我们可以在游戏开始前的场景或者消耗少的地方进行资源预热,事先载入缓存中。这里只交代cc(cocos2d简称)的加载方式:

    cc.spriteFrameCache.addSpriteFrames(res.gold.plist); //plist预加载方式
    /*以下加载图片至内存*/
    //获取所有图像
    String.prototype.endWith=function(s){
        if(s==null||s==""||this.length==0||s.length>this.length)
        return false;
        if(this.substring(this.length-s.length)==s)
            return true;
        else
            return false;
        return true;
    }

    //变量获得图像,资源文件使用对象字面量嵌套的原因,略显啰嗦
    var temp = [];
    for (var i in res) {
        if(typeof res[i] == "object"){
            for(var j in res[i]){
                if(res[i] instanceof Array) {
                    continue;
                } else {
                    if(typeof res[i][j] == "string"){
                        if(!res[i][j].endWith("plist") &&
                        !res[i][j].endWith("mp3")) {
                            temp.push(res[i][j]);
                        }
                    }
                }
            }
        } else {
            if(typeof res[i][j] == "string"){
                if(!res[i][j].endWith("plist") 
                    && !res[i][j].endWith("mp3")) {
                        temp.push(res[i]);
                }
            }
        }
    }

    //载入内存,这步是关键
    for(var i in temp) {
        cc.textureCache.addImage(temp[i]);
    }

这里主要使用两个方法,cc.spriteFrameCache.addSpriteFrames(PLIST);cc.textureCache.addImage(IMAGE)进行载入。

对象池资源复用

关于对象池,是我们讨论的重点。由于缺乏经验和游戏前辈指导,我们起初就选错了方式,对于跑酷,飞行等这些需要频繁重复使用同样对象的游戏来说,应该使用对象池的方式进行重复利用,提高效率。这里感谢一下俊文学长的点拨。

虽说如此,我仍然犯了一个致命的错误,这个bug到最后上交作品仍没有解决,为时已晚。

我先说我使用的方式,游戏开始时,产生一只小鸟,一只青蛙,若干个道具,以及需要用到的大概20个左右的金币道具,一次性全部载入内存,并加入场景中。角色奔跑之后,动态的根据条件和概率更新这些物体的位置,从而多次复用。其实想想这样的做法好像也挺好,但不幸的是我们游戏使用了Chipmunk物理引擎。

物理引擎中频繁操作物体,删除或者移动body等操作容易造成空指针等一系列bug。

用js开发游戏确实非常便利,尤其是调试,所见即得。但要是发生例如操作内存的错误,却是那么无奈。其实最终我们也没有找到真正的原因,游戏运行一段时间就会自动崩溃退出,崩溃的原因为 EXC_BAD_ACCESS,指针访问了一块无权的访问的内存。但我采用频繁移动body的方式进行复用确实过于暴力。cc中有个对象缓冲池,我们可以利用它进行对象复用。

编写支持cc.pool的类

首先,类中必须包含unuse()reuse()函数,当你将unuse()函数放入缓冲池中时,cc.pool将会调用该函数;当你从缓冲池中检索一个对象,并使用给定参数对其初始化时,cc.pool则将调用reuse()函数。

下面是一个名为MySprite的类。

var MySprite = cc.Sprite.extend({
    _hp: 0,
    _sp: 0,
    _mp: 0,
ctor: function (f1, f2, f3) {
    this._super(s_grossini);
    this.initData(f1, f2, f3);
},
initData: function (f1, f2, f3) {
    this._hp = f1;
    this._mp = f2;
    this._sp = f3;
},
unuse: function () {
    this._hp = 0;
    this._mp = 0;
    this._sp = 0;
    this.setVisible(false);
    this.removeFromParent(true);
},
reuse: function (f1, f2, f3) {
    this.initData(f1, f2, f3);
    this.setVisible(true);
}});

MySprite.create = function (f1, f2, f3) {
    return new MySprite(f1, f2, f3);
}

MySprite.reCreate = function (f1, f2, f3) {
    var pool = cc.pool;
    if (pool.hasObj(MySprite))
        return pool.getFromPool(MySprite, f1, f2, f3);
    return  MySprite.create(f1, f2, f3);
}

其实无非就是需要的时候加载一下,设置可见,不使用的时候设置不可见并从层中移除即可。官网的教程略有出错,这里稍作修改。我们的代码不用考虑到retain()release()等内存管理,原因是引擎已经帮助我们进行了管理。

往对象池添加对象

cc.pool.putInPool(object);
putInPool函数将调用object.unuse(),并将其放到cc.pool中以备后面使用。

在对象池中检索对象

当你想要从缓冲池中获取一个对象时,你可以通过给getFromPool方法传递类名和参数得到一个可用的对象或者空对象。在对象被返回之前,缓冲池将调用reuse函数,并用参数args来初始化该对象。

检查是否存在有效对象

var exist = cc.pool.hasObj(“MySprite”);

从cc.pool中删除对象

cc.pool.removeObj(object);

清空缓冲池

cc.pool.drainAllPools();

当切换游戏场景时,在旧场景中的对象可能将不再有用。可以清空对象池节约内存开支。CC.POOL的内部实现也很简单,其实就是将当前对象弹入数组中保存起来,需要的时候再进行抽取即可。

提高游戏可玩性

老实说,我平时并不怎么玩游戏。从以前做互相网站的角度来说,与用户的交互显得非常重要。这方面需要与美术、音乐做到完美配合。我喜欢做类似互动这一类的工作,我喜欢贴近去感受用户的需求。这次做这个手游,我也尽量把整个游戏做,其实市场上的游戏已经都做到了最基本的这一点。
游戏好不好玩,不仅需要创意,更要考虑到场景设置,道具安排,游戏的触控体验等等方面。
最后还要感谢一下yfwz学长能够屈尊跟我组队参加比赛,而且在开发游戏上也下了功夫,关键还能提供技术支持啊,赞!
大概就是这些了,做游戏真的是个体力活,尤其是对我这样一个有点强迫症的人来说,容不得一点不好啊。不过,过程倒也是蛮有趣的,至少也算是一次新的尝试吧。:P