微信曾经火爆过一款叫《围住神经猫》的小游戏。说起来它的玩法很简单,用最少的步数把一神经兮兮的猫围死。 有兴趣的童鞋可以在这篇教程里,学一学如何用Cocos2d-JS来实现一个神经猫这样的游戏。 让我们先看下游戏最后完成了的效果图:

p1

p2

p3

你可能注意到了,神经猫换成了可爱的小羊驼:)

在线游戏地址:http://app9.download.anzhuoshangdian.com/xyt/?from=singlemessage&isappinstalled=0

游戏分析

三个界面基本上就是整个游戏的全部内容:

  1. 左边的是主界面,展示游戏名称以及主角,让玩家对游戏的整体画风有个大概的印象。
  2. 中间的是游戏界面,点击空格防止橙色六边形砖块来围堵小羊驼。
  3. 右边的是游戏成功或失败的界面。

整个游戏的主逻辑都在游戏界面中完成。

玩法是这样:

  1. 游戏初始化开始,小羊驼始终是站在地图中间,在地图的其他区域随机生产一些位置随机的砖块。
  2. 玩家点击一个空白区域,放置一个砖块来围堵羊驼。
  3. 羊驼AI寻路移动一步。
  4. 循环2和3,直到羊驼被围堵在一个圈里面(游戏成功),或羊驼到达地图边界(游戏失败)

整个游戏的思路理清楚了,接下来我们开始进入编码阶段。

开发环境与新建项目

本教程开发基于当前最新的Download v3.0RC1.

下载引擎并解压到磁盘的某个目录。

打开控制台,输入下面的命令来新建项目。

1
2
3
4
$cd cocos2d-js-v3.0-rc1/tools/cocos2d-console/bin
$./cocosnew-l js --no-native
$cd MyJSGame/
$../cocos run -p web


主界面实现

游戏的入口代码在main.js中,用编辑器打开并修改为下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cc.game.onStart = function(){
    // 1.
    cc.view.adjustViewPort(true);
 
    // 2.
    if(cc.sys.isMobile)
        cc.view.setDesignResolutionSize(320,500,cc.ResolutionPolicy.FIXED_WIDTH);
    elsecc.view.setDesignResolutionSize(320,480,cc.ResolutionPolicy.SHOW_ALL);
    cc.view.resizeWithBrowserSize(true);
 
    // 3.
    cc.LoaderScene.preload(resources, function () {
        // 4.
        gameScene =newGameScene();
        cc.director.runScene(gameScene);
    },this);
};
 
cc.game.run();

关键点解析如下:

  1. 设置浏览器meta来适配屏幕,引擎内部会根据屏幕大小来设置meta的viewport值,会达到更好的屏幕适配效果。
  2. 针对手机浏览器和PC浏览器启用不同的分辨率适配策略。
  3. 预加载图片声音等资源。 cc.LoaderScene.preload会生成一个“加载中 x%”的界面,等待资源加载结束后,调用第二个参数传入的匿名函数。 对于基于html的游戏,页面是放在服务器端供浏览器下载的,为了获得流畅的用户体验,cc.LoaderScene.preload让浏览器先把远程服务器的资源缓存到本地。需要预加载的资源定义在src/Resources.js文件中。
  4. 启动游戏的第一个场景。

主界面的由两个层实现,

  1. GameLayer层,游戏主逻辑层,在未初始化地图矩阵时,它只显示背景地图。
  2. StartUI层,显示logo图片和开始游戏按钮。

GameScene的初始化代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var GameScene = cc.Scene.extend({
    onEnter : function () {
        this._super();
 
        var bg =newcc.Sprite(res.bg);
        bg.attr({
            anchorX : 0.5,
            anchorY : 0.5,
            x : cc.winSize.width/2,
            y : cc.winSize.height/2
        });
        this.addChild(bg);
 
        layers.game =newGameLayer();
        this.addChild(layers.game);
 
        layers.startUI =newStartUI();
        this.addChild(layers.startUI);
 
        layers.winUI =newResultUI(true);
        layers.loseUI =newResultUI(false);
        layers.shareUI =newShareUI();
    }
});

由引擎提供的cc.Scene.extend方法,让js能实现高级面向对象语言的继承特性。 onEnter方法是场景初始化完成即将展示的消息回调,在onEnter中必须调用this._super();来确保Scene被正确的初始化。

整个游戏的设计只有一个scene,界面之间的切换由layer来实现,这可能不是一个最优的设计,但也提供另一种思路。 为了用layer来实现切换,全局变量layers存储了各层的一个实例。

GameLayer我们在下一章节中详细讲解。

StartUI的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var StartUI = cc.Layer.extend({
    ctor : function () {
        this._super();
 
        var start =newcc.Sprite(res.start);
        start.x = cc.winSize.width/2;
        start.y = cc.winSize.height/2 + 20;
        this.addChild(start);
    },
    onEnter : function () {
        this._super();
 
        cc.eventManager.addListener({
            event: cc.EventListener.TOUCH_ALL_AT_ONCE,
            onTouchesEnded: function (touches, event) {
                var touch = touches[0];
                var pos = touch.getLocation();
                if(pos.y < cc.winSize.height/3) {
                    layers.game.initGame();
                    layers.startUI.removeFromParent();
                }
            }
        },this);
    }
});

cc.Layer.extend作用同cc.Scene.extend一样,只不过是一个扩展Scene,一个扩展Layer。ctor是Cocos2d-JS中的构造函数,在ctor中必须调用this._super();以确保正确的初始化。

在onEnter中,我们为StartUI层绑定事件监听,判断触摸点的位置坐标来触发scene切换。

细心的读者可能要问,为什么不用Menu控件? 当前的Cocos2d-JS版本已实现模块化,可以选择只加载游戏中用到的模块,已减少最终打包size。 为了不加入Menu模块,这里使用了最简单的触摸点坐标判断来实现通用的事情。

游戏界面的实现

橙色块的初始化

游戏地图区域是由9*9的六边形方块组成的,首先用InActive的图片初始化一边矩阵。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var ox = x = y = 0, odd =false, block, tex =this.batch.texture;
for(var r = 0; r < ROW; r++) {
    y = BLOCK_YREGION * r;
    ox = odd * OFFSET_ODD;
    for(var c = 0; c < COL; c++) {
        x = ox + BLOCK_XREGION * c;
        block =newcc.Sprite(tex, BLOCK2_RECT);
        block.attr({
            anchorX : 0,
            anchorY : 0,
            x : x,
            y : y,
            width : BLOCK_W,
            height : BLOCK_H
        });
        this.batch.addChild(block);
    }
    odd = !odd;
}

每次循环odd改变,已实现上下错位的排布。 attr是Node基类的新方法,可以方便的一次性设置多个属性。

橙色方块的初始化是由initGame函数来完成。 先来看initGame的实现:

1
2
3
4
5
6
7
8
9
10
11
0