转载:http://mp.weixin.qq.com/s?__biz=MjM5OTM0MzIwMQ==&mid=200936074&idx=1&sn=60f7b81bf02304f5d899ebc4e5a16d09&key=1c5732370ff09d2c4b0b306c6c0a6d485c79ee942543fc2cd1c333ab8ea849bc0668298f76b1f49e73dcf5729c5149f1&ascene=7&uin=ODM1MzAzNjIw&pass_ticket=7UjkHno%2BADDc1GHvMN5xDkyfHhfR9NF70ECdnXit6aFjgEqQI2DFlpue2oel41N3

前言

一夜之间,微信上一款叫《围住神经猫》的小游戏火了。它的玩法很简单,用最少的步数把一只神经兮兮的猫围死。 7月22号上线以来,3天、500万用户和1亿访问,想必各位程序猿都按耐不住了,想实现自己的神经猫游戏。

在这篇教程里,我会教大家如何用Cocos2d-JS来实现一个类似神经猫这样的游戏。让我们先看下游戏最后完成了的效果图:

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

在线游戏地址:(请点击下方“阅读原文”)

游戏分析

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

1.左边的是主界面,展示游戏名称以及主角,让玩家对游戏的整体画风有个大概的印象。

2.中间的是游戏界面,点击空格防止橙色六边形砖块来围堵小羊驼。

3.右边的是游戏成功或失败的界面。

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

玩法是这样:

1.游戏初始化开始,小羊驼始终是站在地图中间,在地图的其他区域随机生产一些位置随机的砖块。

2.玩家点击一个空白区域,放置一个砖块来围堵羊驼。

3.羊驼AI寻路移动一步。

4.循环2和3,直到羊驼被围堵在一个圈里面(游戏成功),或羊驼到达地图边界(游戏失败)

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

开发环境与新建项目

本教程开发基于当前最新的Cocos2d-JS 3.0.

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

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

1
2
3
4
$cd cocos2d-js-v3.0-rc1/tools/cocos2d-console/bin
$./cocos new -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);
else cc.view.setDesignResolutionSize(320,480,cc.ResolutionPolicy.SHOW_ALL);
cc.view.resizeWithBrowserSize(true);
// 3.
cc.LoaderScene.preload(resources, function () {
// 4.
gameScene = new GameScene();
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 = new cc.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 = new GameLayer();
this.addChild(layers.game);
layers.startUI = new StartUI();
this.addChild(layers.startUI);
layers.winUI = new ResultUI(true);
layers.loseUI = new ResultUI(false);
layers.shareUI = new ShareUI();
}
});

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

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

StartUI的实现如下:

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 = new cc.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 = new cc.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
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
initGame : function() {
if (this.inited) return;
this.player_c = this.player_r = 4;
this.step = 0;
// 1.
for (var i = 0, l = this.active_nodes.length; i < l; i++) {
this.active_nodes[i].removeFromParent();
}
this.active_nodes = [];
for (var r = 0; r < ROW; r++) {
for (var c = 0; c < COL; c++) {
this.active_blocks[r][c] = false;
}
}
// 2.
this.randomBlocks();
// 3.
this.player.attr({
anchorX : 0.5,
anchorY : 0,
x : OFFSET_X + BLOCK_XREGION * this.player_c + BLOCK_W/2,
y : OFFSET_Y + BLOCK_YREGION * this.player_r - 5
});
this.player.stopAllActions();
this.player.runAction(this.moving_action);
this.inited = true;
},

要点解析如下:

1.为了方便逻辑处理,这里用了active_nodes和active_blocks来记录被激活的方块。在初始化矩阵前,需要清理上一次游戏已生成的橙色方块。active_nodes存储精灵实例,active_blocks记录精灵的矩阵坐标。

2.randomBlocks函数生成随机橙色砖块。首先产生一个7-20的随机数,也就是确定橙色块的数量。然后循环确定每一个块的位置坐标,当然位置坐标也是随机确定的。

3.复位小羊驼的位置以及动画。

响应触摸事件

按照我们之前的分析,游戏界面初始化完成后,需要等待用户指令才能进行下一步的游戏。

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1.
cc.eventManager.addListener({
// 2.
event: cc.EventListener.TOUCH_ALL_AT_ONCE,
// 3.
onTouchesBegan: function (touches, event) {
var touch = touches[0];
var pos = touch.getLocation();
var target = event.getCurrentTarget();
if (!target.inited) return;
pos.y -= OFFSET_Y;
var r = Math.floor(pos.y / BLOCK_YREGION);
pos.x -= OFFSET_X + (r%2==1) * OFFSET_ODD;
var c = Math.floor(pos.x / BLOCK_XREGION);
if (c >= 0 && r >= 0 && c < COL && r < ROW) {
if (target.activateBlock(r, c)) {
target.step ++;
target.movePlayer();
}
}
}
}, this);

1.cc.eventManager.addListener加入新的事件监听。

2.设置事件监听模式为TOUCH_ALL_AT_ONCE。

3.重写onTouchesBegan方法,判断触摸点的坐标,确定是哪个块被点击,并做响应的处理。 activateBlock方法在对应的矩阵位置加入橙色块,并更新状态数组。然后调用movePlayer移动小羊驼。

羊驼的移动

整个逻辑的关键是AI.js中的getDistance函数,

getDistance有6个参数:

1.羊驼所在行号

2.羊驼所在列号

3.前进方向,l_choices、r_choices、t_choices或b_choices

4.激活块的记录数组

5.辅助记录表,记录在寻路算法中某个节点是不是已经被访问过。

6.最短路径

返回值有三种情况:

1.羊驼到达地图边界,返回羊驼坐标和最短路径0

2.羊驼还在地图中,返回羊驼的下一个坐标值和最短路径cost

3.-1表示羊驼被圈住了,但可能可以移动。

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
26
var getDistance = function (r, c, dir_choices, activate_blocs, passed, cost) {
passed[r][c] = true;
if (r <= 0 || r >= ROW_MINUS_1 || c <= 0 || c >= COL_MINUS_1) {
return [r, c, cost];
}
var odd = (r % 2 == 1) ? 1 : 0;
var choices = dir_choices[odd];
var nextr, nextc, result;
for (var i = 0, l = choices.length; i < l; i++) {
nextr = r + choices[i][0];
nextc = c + choices[i][4];
if (!activate_blocs[nextr][nextc] && !passed[nextr][nextc]) {
cost ++;
result = getDistance(nextr, nextc, dir_choices, activate_blocs, passed, cost);
if (result != -1) {
result[0] = nextr;
result[1] = nextc;
return result;
}
}
}
return -1;
};

在羊驼移动函数movePlayer中,首先通过getDistance来判断上下左右4个方向,来寻找最佳移动方向。根据getDistance的返回结果做相应的逻辑处理。

游戏结束界面

游戏结束的两种情况,玩家胜利或失败。

在ResultUI的构造函数中,加入参数win,用来标识是否胜利。而胜利和失败仅仅是显示文字的区别,下方的两个按钮均一样。

在ctor中,根据是否胜利加载不同的图片来显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ctor : function (win) {
this._super();
this.win = win;
if (win) {
this.winPanel = new cc.Sprite(res.succeed);
this.winPanel.x = cc.winSize.width/2;
this.winPanel.anchorY = 0.2;
this.winPanel.y = cc.winSize.height/2;
this.addChild(this.winPanel);
}
else {
this.losePanel = new cc.Sprite(res.failed);
this.losePanel.x = cc.winSize.width/2;
this.losePanel.anchorY = 0.2;
this.losePanel.y = cc.winSize.height/2;
this.addChild(this.losePanel);
}
}

在onEnter中,根据是否胜利加载不同的文字描述:

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
if (this.win) {
this.winPanel.removeAllChildren();
var w = this.winPanel.width, h = this.winPanel.height;
var label = new cc.LabelTTF("继续刷屏!\n"+step+"步推倒我的小羊驼\n打败"+percent+"%朋友圈的人!\n你能超过我吗?", "宋体", 20);
label.x = w/2;
label.y = h/4;
label.textAlign = cc.LabelTTF.TEXT_ALIGNMENT_CENTER;
//label.boundingWidth = w;
label.width = w;
label.color = cc.color(0, 0, 0);
this.winPanel.addChild(label);
}
else {
this.losePanel.removeAllChildren();
var w = this.losePanel.width, h = this.losePanel.height;
label = new cc.LabelTTF("我滴小羊驼呀它又跑掉了\nT_T 快帮我抓回来!", "宋体", 20);
label.x = w/2;
label.y = h/4+5;
label.textAlign = cc.LabelTTF.TEXT_ALIGNMENT_CENTER;
//label.boundingWidth = w;
label.width = w;
label.color = cc.color(0, 0, 0);
this.losePanel.addChild(label, 10);
}

"通知好友"按钮加载shareUI层,这个层其实是一个帮助指导界面,指示用户点击微信右上角的分享按钮进行分享。

1
2
gameScene.addChild(layers.shareUI, 100);
target.win ? share(1, step, percent) : share(2);

"再来一次"实现很简单,调用initGame重新初始化矩阵,并移除ResultUI层。

layers.game.initGame();
target.win ? layers.winUI.removeFromParent() : layers.loseUI.removeFromParent();

在游戏结束界面我们加入了分享按钮。现在我们就来实现分享界面。

分享指导界面

分享界面由分享图标和分享说明组成。这和前面的layer创建一样。很简单,唯一的区别是,分享界面是cc.LayerColor(cc.LayerColor支持设置层的颜色)的子类。下面是实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ctor: function () {
this._super(cc.color(0, 0, 0, 188), cc.winSize.width, cc.winSize.height);
var arrow = new cc.Sprite(res.arrow);
arrow.anchorX = 1;
arrow.anchorY = 1;
arrow.x = cc.winSize.width - 15;
arrow.y = cc.winSize.height - 5;
this.addChild(arrow);
var label = new cc.LabelTTF("请点击右上角的菜单按钮\n再点\"分享到朋友圈\"\n让好友们挑战你的分数!", "宋体", 20, cc.size(cc.winSize.width*0.7, 250), cc.TEXT_ALIGNMENT_CENTER);
label.x = cc.winSize.width/2;
label.y = cc.winSize.height - 100;
label.anchorY = 1;
this.addChild(label);
},

加入touch事件用于移除分享界面:

1
2
3
4
5
6
7
8
9
onEnter: function () {
this._super();
cc.eventManager.addListener({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
onTouchBegan: function (touch, event) {
layers.shareUI.removeFromParent();
}
}, this);
}

微信分享

我们需要的功能:

1
2
3
4
- 分享到微信朋友圈
- 分享给微信好友
- 分享到腾讯微博
- 关注指定用户

实现方式

本功能已经有大神提供了完整的库,地址是:https://github.com/zxlie/WeixinApi ,以下我们做一个简单的使用分析。

注:除特殊说明外,本小节实现均在文件 WeixinApi.js中。

现在我们实现的分享有,发给指定朋友,分享到朋友圈,分享到腾讯微博。对于不同的分享方式,实现方式大同小异,我们主要以分享到朋友圈为例。

分享数据

我们分享的时候需要的数据有:appid,图片,链接,标题,文字内容,例如:

对应在代码中就需要以下数据:

1
2
3
4
5
"appid":theData.appId ? theData.appId : '',
"img_url":theData.imgUrl,
"link":theData.link,
"desc":theData.desc,
"title":theData.title, // 注意这里要分享出去的内容是desc

数据来源

为了得到数据,我们需要在GameScene.js中实现ResultUI的时候,将以上数据生成出来。比如胜利时,我们需要显示:

var label = new cc.LabelTTF("继续刷屏!\n"+step+"步推倒我的小羊驼\n打败"+percent+"%朋友圈的人!\n你能超过我吗?", "宋体", 20);

完成数据后,我们需要判断胜利或失败,并传回ui中显示:

1
target.win ? share(1, step, percent) : share(2);

分享回调

为了监测分享的状态,无论分享成功与否我们回调都会上报状态,以便程序处理,我们需要的状态有:

·用户取消分享

·分享失败

·分享成功所以我们需要以下实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function (resp) {
switch (resp.err_msg) {
// share_timeline:cancel 用户取消
case 'share_timeline:cancel':
callbacks.cancel && callbacks.cancel(resp);
break;
// share_timeline:fail 发送失败
case 'share_timeline:fail':
callbacks.fail && callbacks.fail(resp);
break;
// share_timeline:confirm 发送成功
case 'share_timeline:confirm':
case 'share_timeline:ok':
callbacks.confirm && callbacks.confirm(resp);
break;
}
// 无论成功失败都会执行的回调
callbacks.all && callbacks.all(resp);
}

WeixinJSBridge

在微信上,通过公众平台推送给用户的文章,是在微信内部直接打开的,用的无外乎就是一个UIWebView控件(IOS上,Android上也差不多)。但特殊的是,微信官方在这里面加了一个默认的Js API--WeixinJSBridge,通过它,能直接在该页面上做分享操作。以下代码,拿去玩吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
WeixinJSBridge.on('menu:share:timeline', function (argv) {
if (callbacks.async && callbacks.ready) {
window["_wx_loadedCb_"] = callbacks.dataLoaded || new Function();
if(window["_wx_loadedCb_"].toString().indexOf("_wx_loadedCb_") > 0) {
window["_wx_loadedCb_"] = new Function();
}
callbacks.dataLoaded = function (newData) {
window["_wx_loadedCb_"](newData);
shareTimeline(newData);
};
// 然后就绪
callbacks.ready && callbacks.ready(argv);
} else {
// 就绪状态
callbacks.ready && callbacks.ready(argv);
shareTimeline(data);
}
});

最后,赶紧写点诱惑的东东,让用户分享出去吧,这是微信病毒传播的乐趣!

用Cocos2d-JS制作《你是我的小羊驼》相关推荐

  1. 使用 ale.js 制作一个小而美的表格编辑器(2)

    今天来教大家如何使用 ale.js 制作一个小而美的表格编辑器,首先先上 gif: 是不是还是有一点非常 cool 的感觉的?那么我们现在开始吧! 这是我们这篇文章结束后完成的效果(如果想继续完成请访 ...

  2. 使用 ale.js 制作一个小而美的表格编辑器(3)

    今天来教大家如何使用 ale.js 制作一个小而美的表格编辑器,首先先上 gif: 是不是还是有一点非常 cool 的感觉的?那么我们现在开始吧! 这是我们这篇文章结束后完成的效果(如果想继续完成请访 ...

  3. (译)如何使用cocos2d来制作一个打地鼠的游戏:第一部分

    免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播.同时,转载时不要移除本申明.如产生任何纠纷,均与本博客所有人.发表该翻译稿之人无任何关系.谢谢合作 ...

  4. 纯JS制作的窗户雨滴效果

    今天本站推荐的代码是用JS制作的窗户雨滴效果,绚丽的效果不亚于FLASH,由于不知出处在哪,总而言之, 在此感谢作者的慷慨分享. function demo() { var engine = new ...

  5. 使用 FlipClock.js 制作精美的时钟、定时器和倒计时功能

    FlipClock.js 被创建出来是因为其他的解决方案不够抽象,不能够在不重写的代码的情况下提供了深层次的自定义.有些库的参数过多,而另外一些则是脚本中的硬编码太多,不够灵活. 在参考许多现有的解决 ...

  6. php简单网页制作代码,用HTML和CSS以及JS制作简单的网页菜单界面的代码

    这篇文章主要介绍了使用HTML+CSS+JS制作简单的网页菜单界面,这个ABROAD项目所使用的JavaScript部分代码非常简单,需要的朋友可以参考下 写ABROAD项目用到了标签这个东东,其实标 ...

  7. 用JS制作一个信息管理平台完整版

      前  言 JRedu 在之前的文章中,介绍了如何用JS制作一个实用的信息管理平台. 但是那样的平台功能过于简陋了,我们今天来继续完善一下. 首先我们回顾一下之前的内容.   1.JSON的基础知识 ...

  8. html5 注册协议弹出层,js制作带有遮罩弹出层实现登录注册表单特效代码分享

    本文实例讲述了js制作带有遮罩弹出层实现登录注册表单代码特效代码.分享给大家供大家参考.具体如下: 运行效果图:                     ----------------------查 ...

  9. 使用 ale.js 制作一个小而美的表格编辑器(1)

    今天来教大家如何使用 ale.js 制作一个小而美的表格编辑器,首先先上 gif: 是不是还是有一点非常 cool 的感觉的?那么我们现在开始吧! 这是我们这篇文章结束后完成的效果(如果想继续完成请访 ...

  10. html生成小窗口,用JS制作9种弹出小窗口(HTML)

    进入许多网站时,有弹出式小窗口,它们五花八门,使我们捉摸不透下面就来介绍用JS制作9种制作弹出小窗口: 1.最基本的弹出窗口代码 其实代码非常简单: window.open ("page.h ...

最新文章

  1. 2022华为首个「天才少年」,是从绩点1.8逆袭的复旦博士
  2. Vue 脚手架||Vue 脚手架的基本用法
  3. Ubuntu 18.0安装教程
  4. OpenCASCADE:拓扑 API之偏移、拔模、管道和演变形状
  5. boost::leaf::result用法的测试程序
  6. ZOJ 3430 Detect the Virus 【AC自动机+解码】
  7. 什么就像谈恋爱一样?
  8. 卡巴绿杀6 By Moshow魔手
  9. .Net中加密解密相关知识
  10. c# dialogresult 选择文件_C#控件美化之路(12):自定义消息弹出框
  11. 推荐系统系列 - 引导 - 5类系统推荐算法,非常好使,非常全
  12. oracle连续周数,oracle周数计算
  13. bilibili弹幕游戏
  14. java 拼音_GitHub - promeG/TinyPinyin: 适用于Java和Android的快速、低内存占用的汉字转拼音库。...
  15. 浅谈跨站脚本攻击与防御
  16. 四, Scala 伴生对象, 特质
  17. fckeditor的皮肤系列:皮肤更换
  18. 曾经拥有VS天长地久
  19. 高项考试-信息系统服务管理
  20. 【厚积薄发系列】Python项目总结1—后端常驻程序的基本要求

热门文章

  1. 2018 蜜拓蜜春雷行动 万场地推引爆同城
  2. glibc 知:手册12:输入/输出流
  3. 手机pdf阅读器之轻快PDF阅读器
  4. MyDocument.exe病毒免疫方法
  5. ---中南海香烟---系列[男人应该抽的烟]
  6. AndroidVideoCache优化
  7. 安卓udp编辑软件_IOS安卓7000+全球电视节目分享
  8. 智慧职教云计算机文化基础,2020智慧职教云课堂计算机文化基础答案最新最全单元测试答案...
  9. MIDI文件深入剖析
  10. group by rollup 和grouping的使用实例