| 导语 微信小游戏都火成这样了,为什么不尝试一下? 我们的目标是使用Cocos Creator从零开始制作一个小游戏,并放到微信上玩。
上文链接:Cocos Creator制作一个微信小游戏(上)
在Script文件夹上右键-新建-JavaScript,创建一个名字为Menu的代码文件。
双击一下资源管理器中的Menu场景,再在层级管理器中单击一下Canvas节点,显示节点的属性检查器,然后把Script目录中的Menu代码文件搬去到属性检查器中,以实现代码和场景的绑定。
下图是绑定成功后的样子。
然后用你喜欢的代码编辑器打开编辑Menu代码文件。
创建的代码,会帮你做好一些结构,有一些是注释掉的,自己清理一下,保留onLoad方法,并在properties结构中添加startBtn属性,如下:
cc.Class({
extends: cc.Component,
properties: {
startBtn:{
default: null,
type: cc.Button
}
},
onLoad () {
//场景加载时执行
}
});
保存代码后回到Cocos Creator,应该会自动刷新一下,可以看到属性检查器中多了StartBtn一项。
在层级管理器中,找到startBtn,并拖动到上面箭头所指的位置,就实现了startBtn组件和代码中的startBtn属性的绑定,接下来可以在代码中调用这个按钮了。
继续编写代码,添加对startBtn的监听,和相应的处理方法,如下:
cc.Class({
extends: cc.Component,
properties: {
startBtn: {
default: null,
type: cc.Button
}
},
onLoad () {
//场景加载时执行
//侦听touchend事件来触发开始游戏方法。不能用click,否则在微信中无效。
this.startBtn.node.on('touchend', this.startGame.bind(this));
},
startGame: function () {
//调用系统方法加载Game场景
cc.director.loadScene('Game');
}
});
预览一下,点击开始游戏按钮时,会进入游戏界面,说明一切顺利。
添加一个Game代码文件,绑定到Game场景。并编辑代码为正面的样子:
cc.Class({
extends: cc.Component,
properties: {
sound1: {
default: null,
type: cc.Sprite
},
sound2: {
default: null,
type: cc.Sprite
},
help: {
default: null,
type: cc.Sprite
},
setting: {
default: null,
type: cc.Sprite
},
scoreLabel: { default: null, type: cc.Label
},
tipText: { default: null, type: cc.RichText
}
},
onLoad () {
},
update (dt) {
//每帧会执行的方法
},
});
然后返回Game场景,把各个需要在代码中调用的元素都拖过来绑定一遍:
现在我们需要把几个小球做成可以通过new方法创建实例的预制资源。
从资源管理器中把1号小球拖到任意一个场景中,再在层级管理器中把小球命名为Ball1,然后把Ball1拖回资源管理器中,再从层级管理器中把Ball1删除,即可实现把小球做成预制资源。
重复操作把所有小球都做成预制资源。
在Game.js代码的properties结构中,添加Ball1-Ball6 六个属性,然后返回Cocos Creator绑定。
接下来添加实现寻路的代码文件。直接把本项目中的AStar.js代码复制到你的代码目录中即可。这里不打算介绍AStar实现寻路的原理,网上有很多相关资料,可以自行搜索。现在只需要当成第三方库直接用即可。
Cocos Creator支持require方法,可以很容易实现模块化开发。
在Game.js顶部引入AStar,如图:
可以开始写主逻辑了,在Game.js的onLoad函数中写入游戏初始化代码,并创建几个待调用的方法,此步骤完整代码如下:
var {AStar, Grid, Node} = require("AStar");
cc.Class({
extends: cc.Component,
properties: {
Ball1: {
default: null,
type: cc.Prefab
},
Ball2: {
default: null,
type: cc.Prefab
},
Ball3: {
default: null,
type: cc.Prefab
},
Ball4: {
default: null,
type: cc.Prefab
},
Ball5: {
default: null,
type: cc.Prefab
},
Ball6: {
default: null,
type: cc.Prefab
},
gameContent: {
default: null,
type: cc.Node
},
sound1: {
default: null,
type: cc.Sprite
},
sound2: {
default: null,
type: cc.Sprite
},
help: {
default: null,
type: cc.Sprite
},
setting: {
default: null,
type: cc.Sprite
},
scoreLabel: {
default: null,
type: cc.Label
},
tipText: {
default: null,
type: cc.RichText
}
},
onLoad () {
//默认隐藏提示信息
this.tipText.node.active = false;
//行列数
this.row = 14;
this.col = 10;
this.maxNodes = this.row * this.col;
//格子尺寸
this.gridSize = 50;
//每次增加小球数
this.addBallsCount = 4;
//棋盘边距
this.paddingLeft = 20;
this.paddingTop = 150;
//棋盘在坐标系的起始位置(根据cocos 坐标系统推算)
this.posTop = 480 - this.paddingTop;
this.posLeft = -270 + this.paddingLeft;
//记录选中的格子
this.node1 = undefined;
this.node2 = undefined;
//是否正在移动小球
this.movingBall = false;
//游戏是否已经结束
this.gameOver = false;
//用于存放将要被清掉的小球的数组
this.toClearBallsArr = [];
//游戏得分
this.score = 0;
this.displayScore = 0;
this.updateScore();
//开始创建棋盘并添加小球
let maxCount = this.row * this.col;
let startCount = Math.round(maxCount * 0.1);
//创建一个随机棋盘,得到的是一个二维数组,结构如[[0,1,1,0],[0,1,0,0]...]
let startArr = this.getRandomArr(maxCount, startCount);
this.grid = new Grid(this.col, this.row);
this.aStar = new AStar();
this.mapArr = [];
this.nodesArr = [];
for (let i = 0; i < this.col; i++) {
let arr = [];
this.mapArr.push(arr);
for (let j = 0; j < this.row; j++) {
let value = startArr.pop();
let node;
if (value) {
let randomId = parseInt(cc.random0To1() * 6 + 1);
let Prefab = this['Ball' + randomId];
let newBall = cc.instantiate(Prefab);
newBall.type = randomId;
this.gameContent.addChild(newBall);
newBall.setPosition(cc.p(this.posLeft + 25 + i * 50, this.posTop - 25 - j * 50));
//newBall.scale = 0.3;
node = {ball: newBall, val: value, x: i, y: j}
arr.push(node);
this.grid.setWalkable(i, j, false);
} else {
this.grid.setWalkable(i, j, true);
node = {ball: undefined, val: value, x: i, y: j};
arr.push(node);
}
this.nodesArr.push(node);
}
}
this.node.on('touchend', this.onTouchEnd.bind(this));
this.sound1.node.on('touchend', function () {
//静音
}.bind(this));
this.sound2.node.on('touchend', function () {
//解除
}.bind(this));
let _this = this;
this.help.node.on('touchend', function () {
//显示帮助
}.bind(this));
this.setting.node.on('touchend', function () {
//显示设置面板
}.bind(this));
},
onTouchEnd: function (e) {
},
updateScore: function () {
this.scoreLabel.string = this.displayScore;
},
//创建一个包含指定数量真值的二维数组
getRandomArr: function (maxNum, trueNum) {
let arr = [];
for (let i = 0; i < maxNum; i++) {
arr.push(i > trueNum - 1 ? 0 : 1);
}
arr.sort(function () {
return cc.random0To1() > 0.5 ? 1 : -1;
})
return arr;
},
update (dt) {
//每帧会执行的方法
},
});
预览一下,应该能看到游戏界面初始化后的样子:
随机产生了14个不同状态的小球。
接着我们处理选择小球和移动小球。
onTouchEnd: function (e) {
if (this.movingBall) {
console.log('正在移动小球');
return;
}
if (this.gameOver) {
console.log('游戏已结束');
return;
}
//获取点击的位置,并根据点击位置获取到对应的格子。
let x = e.touch._point.x;
let y = e.touch._point.y;
x = x - this.paddingLeft;
y = this.node.height - this.paddingTop - y;
console.log(x, y);
let colId = Math.floor(x / this.gridSize);
let rowId = Math.floor(y / this.gridSize);
if (!(colId >= 0 && colId < this.col && rowId >= 0 && rowId < this.row)) {
console.log('没有选中格子');
return;
}
let clickNode = this.mapArr[colId][rowId];
//
if (!this.node1) {
//第一点击的是小球,只需要记录一下
if (clickNode.ball) {
//console.log('选中了小球:', rowId, colId);
this.node1 = this.mapArr[colId][rowId];
} else { //console.log('请选择一个小球');
this.AudioPlayer.playSound('warm');
}
} else {
if (clickNode.ball) {
//第二次点击的还是小球,也是记录一下,替代第一次点击的小球
//console.log('重新选中了小球:', rowId, colId);
this.node1 = this.mapArr[colId][rowId];
} else {
//第二次点击的如果是空格子,就开始处理移动小球逻辑了
//设置网格,开始寻路
this.node2 = this.mapArr[colId][rowId];
this.grid.setStartNode(this.node1.x, this.node1.y);
this.grid.setEndNode(this.node2.x, this.node2.y);
this.grid.setWalkable(this.node1.x, this.node1.y, true);
if (this.aStar.findPath(this.grid)) {
//找到了通路
let pathArr = this.aStar.get_path();
pathArr.shift();
let ball = this.mapArr[this.node1.x][this.node1.y].ball;
this.movingBall = true;
this.grid.setWalkable(this.node2.x, this.node2.y, false);
this.node2.ball = ball;
this.mapArr[this.node1.x][this.node1.y].ball = undefined;
let _this = this;
this.moveBall(ball, pathArr, function () {
_this.movingBall = false;
});
}
}
}
},
移动小球。
moveBall: function (ball, pathArr, onComplete) {
let node = pathArr.shift();
let newPosition = cc.p(this.posLeft + 25 + node.x * this.gridSize, this.posTop - 25 - node.y * this.gridSize);
ball.setPosition(newPosition);
let _this = this;
if (pathArr.length) {
setTimeout(() => {
_this.moveBall(ball, pathArr, onComplete)
}, 50);
} else {
onComplete();
}
},
至此,我们已经通过代码实现了开始菜单到游戏场景的跳转、游戏初始化、寻路和移动小球等功能,接下来的游戏逻辑,篇幅所限,接下来的游戏逻辑部分,不打算在这里详细讲了,有兴趣的可以直接下载源码自行研究,项目源码在文章后面。
增加一些动态效果,游戏应该会生动很多。
比如我们点击小球的时候,可以让小球处理一个循环放大缩小的动画效果。做法就是在update方法里检测当前选中小球,如果存在就循环改变它的scale。
update (dt) {
//选中的小球的动画
if (this.node1 && this.node1.ball) {
let ball = this.node1.ball;
let scale = ball.scale;
let scaleDirection = ball.scaleDirection || -1;
if (scaleDirection === -1) {
scale -= 0.02;
if (scale <= 0.7) {
ball.scaleDirection = 1;
}
} else {
scale += 0.02;
if (scale >= 1) {
ball.scaleDirection = -1;
}
}
ball.scale = scale;
}
}
其它动画,比如小球增加时、消除时、提示文本的向上飞行效果等,都可以这样做。
好玩的游戏是少不了音效的。我们来看看怎样给游戏添加背景音乐和音效。
我们需要一个可以全局调用的播放器,然后把需要播放的音频资源都绑定到这个播放器内,代码如下:
cc.Class({
extends: cc.Component,
properties: {
bgMusic: {
url: cc.AudioClip,
default: null
},
select: {
url: cc.AudioClip,
default: null
},
lose: {
url: cc.AudioClip,
default: null
},
clear: {
url: cc.AudioClip,
default: null
},
warm: {
url: cc.AudioClip,
default: null
},
move: {
url: cc.AudioClip,
default: null
},
gameOver: {
url: cc.AudioClip,
default: null
},
mute: false
},
onLoad () {
console.log('AudioClip onload');
},
playBgMusic: function () {
if (!this.mute && this.bgMusicChannel === undefined) {
this.bgMusicChannel = cc.audioEngine.play(this.bgMusic, true, 0.5);
}
},
switchMute: function () {
this.mute = !this.mute;
if (this.mute) {
this.stopBgMusic();
} else {
this.playBgMusic();
}
},
stopBgMusic: function () {
if (this.bgMusicChannel !== undefined) {
cc.audioEngine.stop(this.bgMusicChannel);
this.bgMusicChannel = undefined;
}
},
playSound: function (sound, vol) {
if (!this.mute) {
cc.audioEngine.play(this[sound], false, vol || 1);
}
}
});
然后让这个播放器可以全局调用。我的办法是在开始菜单场场景,创建一个空节点,并绑定播放器代码,然后注册到全局。
在Menu的onload方法里面,把播放器注册到全局:
cc.game.addPersistRootNode(this.AudioClip);
然后在需要调用播放器的场景,获取播放器并调用其中的方法:
//获取全局播放器
this.AudioPlayer = cc.find('AudioClip').getComponent("AudioClip");
//停止再开启背景音乐
this.AudioPlayer.stopBgMusic();
this.AudioPlayer.playBgMusic();
//根据静音状态设置声音按钮按钮
if (this.AudioPlayer.mute) {
this.sound1Sprite.node.active = false;
this.sound2Sprite.node.active = true;
} else {
this.sound1Sprite.node.active = true;
this.sound2Sprite.node.active = false;
}
加入帮助界面和游戏设置界面。
界面内逻辑就不说了,经过上面的介绍,已经变得很简单。
但需要注意的是,这类界面有可能用户永远不会点开,我们就没必要一开始就创建或绑定了。这时可以在项目下创建一个名称为resources(文件夹名称必须为resources)的目录,把需要动态加载的预制资源放到这里。需要使用的时候,可以这样加载并创建:
this.helpSprite.node.on('touchend', function () {
cc.loader.loadRes('Help', function (err, prefab) {
var help = cc.instantiate(prefab);
help.x = help.y = 0;
_this.container.addChild(help);
var hideHelp = function (e) {
help.off('touchend', hideHelp);
help.destroy();
hideHelp = undefined;
e.stopPropagation();
}
help.on('touchend', hideHelp);
})
}.bind(this));
cc.loader.loadRes会查找resources目录下的预制资源并加载。
iphonex和其他手机屏幕分辨率不一致,可能会导致界面和逻辑混乱。适配就是根据屏幕分辨率做一些界面或而已调整。
先完成适配的方法util,然后使用第五节中提到的办法把代码注册到全局可以调用,在每个场景加载的时候,去调整下尺寸就可以了。
比如在开始菜单中把GlobalData节点注册到全局,并注入resize方法:
cc.game.addPersistRootNode(this.GlobalData);
this.GlobalData.resize = function (container) {
let originHeight = 1170;
container.y = -(originHeight - cc.view.getVisibleSize().height) * 0.5;
}
this.GlobalData.resize(this.container);
在其他场景调用这个全局方法:
//根据屏幕高度调整全局显示内容的位置
this.GlobalData = cc.find('GlobalData');
this.GlobalData.resize(this.container);
//根据屏幕高度调整游戏内容的位置
this.contentOffsetHeight = 0;
if (cc.view.getVisibleSize().height > 1150) {
this.contentOffsetHeight = 105;
}
this.gameContent.y = this.gameContent.y - this.contentOffsetHeight;
首先安装好微信开发者工具,打开并登录好。
然后在cocos creator中找到偏好设置,设置好微信开发者工具的安装路径。
然后选择项目-构建发布,设置参数如下:
发布平台选择Wechat Game,然后点构建,构建完成后点运行,就会自动在微信开发者工具中打开并运行了。
如需要在手机微信上真机运行,只需要点击微信开发者工具中的预览按钮,代码上传完毕后就可以用自己的微信扫码体验了。只是自己体验的话是不需要专门注册小游戏账号的。
小游戏在没有发布之前,也只能自娱自乐了。实在想让别人体验一下,可以让他用微信登录一下开发者工具,再上传次一次代码,就可以用他的微信扫码体验了。
//振动15毫秒
window.wx && window.wx.vibrateShort();
附项目git地址:http://git.code.oa.com/jamesgan/WisdomBalls.git
--------------------------------------------------------------------------
原文作者:腾讯高级工程师甘远腾。
来源:腾讯内部KM论坛。
你也想成为腾讯工程师?
也想年终奖人手一部 Iphone X?
那就快加入NEXT学院吧!
NEXT学院课程「小游戏开发与实战」火热招生中!
感兴趣的同学赶紧点击原文了解详情吧~