项目文档: http://docs.cocos.com/creator/manual/zh/getting-started/quick-start.html
最近心血来潮,想写个微信小游戏,查阅相关资料后准备从cocos框架入手。下面要分析的游戏项目的所有资源都由从cocos官方提供。当然官方文档已经足够详尽了,我这里只是进行更进一步的解构并适当扩展游戏内容。
http://fbdemos.leanapp.cn/star-catcher/
从这个游戏项目来看,cocos构造游戏的核心就是在于层级管理器(node tree)。所有的编辑操作都是为了构造游戏的node tree而服务的。关于资源管理器以及属性检查器等界面编辑操作可以查阅官方项目文档,本文不再赘述。
拿当前项目的node tree来说:
Canvas作为根拥有四个子结点。其中结点Player以及Score作为游戏的动态元素添加了用户脚本组件。
API文档: http://docs.cocos.com/creator/api/zh/
cc.Class({
//inheritance
extends: cc.Component,
properties: {
//...
maxStarDuration: 0,
starPrefab: {
default: null,
type: cc.Prefab
},
//...
},
// use this for initialization
onLoad: function () {
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
},
});
上面代码中的properties里示例了两种属性声明方式。一是简单声明定义default,二是完整声明,可以配置default, type, visible, displayname等属性。所有组件属性都会映射到绑定该组件的结点的属性检查器面板上,开发者可以直接在编辑器上编辑属性,非常方便。
setJumpAction: function () {
// 跳跃上升
var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
// 下落
var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
// 不断重复
return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
},
onLoad: function () {
// 初始化跳跃动作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
},
function | params | addition |
---|---|---|
cc.moveBy | (duration, Vec2) | 在指定时间内移动到指定坐标 |
cc.p | (x, y) | 指定x,y创建坐标 |
cc.sequence | (FiniteTimeAction, FiniteTimeAction) | 顺序执行动作 |
cc.repeatForever | (FiniteTimeAction) | 永远地重复一个动作 |
this.node.runAction | (Action) | 为当前结点添加动作 |
setInputControl: function () {
var self = this;
// 添加键盘事件监听
// 有按键按下时,判断是否是我们指定的方向控制键,并设置向对应方向加速
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, function (event){
switch(event.keyCode) {
case cc.KEY.a:
self.accLeft = true;
break;
case cc.KEY.d:
self.accRight = true;
break;
}
});
// 松开按键时,停止向该方向的加速
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, function (event){
switch(event.keyCode) {
case cc.KEY.a:
self.accLeft = false;
break;
case cc.KEY.d:
self.accRight = false;
break;
}
});
},
function | params | addition |
---|---|---|
cc.systemEvent.on | (type, callback) | 添加系统监听事件 |
根据键盘控制力的方向,分别用accLeft、accRight进行标识。
onLoad: function () {
// 初始化跳跃动作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
// 加速度方向开关
this.accLeft = false;
this.accRight = false;
// 主角当前水平方向速度
this.xSpeed = 0;
// 初始化键盘输入监听
this.setInputControl();
},
update: function (dt) {
// 根据当前加速度方向每帧更新速度
if (this.accLeft) {
this.xSpeed -= this.accel * dt;
} else if (this.accRight) {
this.xSpeed += this.accel * dt;
}
// 限制主角的速度不能超过最大值
if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) {
// if speed reach limit, use max speed with current direction
this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
}
// 根据当前速度更新主角的位置
this.node.x += this.xSpeed * dt;
},
根据力的方向实时改变速度,进行实时更新this.node.x。
properties: {
//...
// 跳跃音效资源
jumpAudio: {
default: null,
url: cc.AudioClip
},
},
setJumpAction: function () {
// 跳跃上升
var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
// 下落
var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
// 添加一个回调函数,用于在动作结束时调用我们定义的其他方法
var callback = cc.callFunc(this.playJumpSound, this);
// 不断重复,而且每次完成落地动作后调用回调来播放声音
return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
},
playJumpSound: function () {
// 调用声音引擎播放声音
cc.audioEngine.playEffect(this.jumpAudio, false);
},
function | params | addition |
---|---|---|
cc.callFunc | (callback, instance) | 回调返回ActionInstant |
cc.sequence | (action, …, action) | 顺序执行动作 |
cc.audioEngine.playEffect | (sound, boolean) | 播放声音,boolean指定循环与否 |
Game.js
properties: {
// 这个属性引用了星星预制资源
starPrefab: {
default: null,
type: cc.Prefab
},
// 星星产生后消失时间的随机范围
maxStarDuration: 0,
minStarDuration: 0,
// 地面节点,用于确定星星生成的高度
ground: {
default: null,
type: cc.Node
},
}
对于需要重复生成的node,需要将其保存为Pretab(预设)资源。
Game.js
spawnNewStar: function() {
// 使用给定的模板在场景中生成一个新节点
var newStar = cc.instantiate(this.starPrefab);
// 将新增的节点添加到 Canvas 节点下面
this.node.addChild(newStar);
// 为星星设置一个随机位置
newStar.setPosition(this.getNewStarPosition());
// 将 Game 组件的实例传入星星组件
newStar.getComponent('Star').game = this;
// 重置计时器,根据消失时间范围随机取一个值
this.starDuration = this.minStarDuration + cc.random0To1() * (this.maxStarDuration - this.minStarDuration);
this.timer = 0;
},
function | params | addition |
---|---|---|
cc.instantiate | (Pretab | Node | Obj) | 克隆指定的任意类型的对象,或者从 Prefab 实例化出新节点 |
this.node.addChild | (node) | 给当前结点添加子结点 |
newStar.setPosition | (Vec2) | 为星星指定一个随机位置 |
newStar.getComponent | (scriptName) | 获取节点上指定类型的组件的实例 |
Game.js
getNewStarPosition: function () {
var randX = 0;
// 根据地平面位置和主角跳跃高度,随机得到一个星星的 y 坐标
var randY = this.groundY + cc.random0To1() * this.player.getComponent('Player').jumpHeight + 50;
// 根据屏幕宽度,随机得到一个星星 x 坐标
var maxX = this.node.width/2;
randX = cc.randomMinus1To1() * maxX;
// 返回星星坐标
return cc.p(randX, randY);
},
onLoad: function () {
// 获取地平面的 y 轴坐标
this.groundY = this.ground.y + this.ground.height/2;
// 初始化计时器
this.timer = 0;
this.starDuration = 0;
// 生成一个新的星星
this.spawnNewStar();
// 初始化计分
this.score = 0;
},
function | params | addition |
---|---|---|
cc.random0To1 | \ | 随机返回0~1的浮点数 |
cc.randomMinus1To1 | \ | 随机返回-1~1的浮点数 |
this.player.getComponent | (scriptName) | 获取子结点player结点指定的组件实例 |
Star.js
properties: {
// 星星和主角之间的距离小于这个数值时,就会完成收集
pickRadius: 0,
// 暂存 Game 对象的引用
game: {
default: null,
serializable: false //不序列化(保存)该属性
}
},
getPlayerDistance: function () {
// 根据 player 节点位置判断距离
var playerPos = this.game.player.getPosition();
// 根据两点位置计算两点之间距离
var dist = cc.pDistance(this.node.position, playerPos);
return dist;
},
onPicked: function() {
// 当星星被收集时,调用 Game 脚本中的接口,生成一个新的星星
this.game.spawnNewStar();
// 调用 Game 脚本的得分方法
this.game.gainScore();
// 然后销毁当前星星节点
this.node.destroy();
},
// update (dt) {},
update: function (dt) {
// 每帧判断和主角之间的距离是否小于收集距离
if (this.getPlayerDistance() < this.pickRadius) {
// 调用收集行为
this.onPicked();
return;
}
// 根据 Game 脚本中的计时器更新星星的透明度
var opacityRatio = 1 - this.game.timer/this.game.starDuration;
var minOpacity = 50;
this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity));
},
function | params | addition |
---|---|---|
this.game.player.getPosition | \ | 返回结点game.player的位置 |
cc.pDistance | (position, position) | 返回两点之间的直线距离 |
this.node.destroy | \ | 销毁当前结点 |
参考资料: http://www.cocoachina.com/bbs/3g/read.php?tid=461222
游戏结束后切换scene,并将前一个scene的分数显示到新的scene。这个涉及到scene之间的数据传递,是一个小小的难点。方式有很多,下面用设置常驻结点方式实现。此外,还需要设置一个重新游戏的按钮,点击切换回原来的场景。
scene切换的时候前一个scene的所有结点都会被销毁,所谓常驻结点就不会随着scene切换而销毁的结点。需要注意的是,常驻结点必须与scene的canvas平级结点。因此这里将Score结点的提升一层。
切换scence:
cc.director.loadScene('gameover', function () {});
可以看到,Score结点并没有被销毁:
在新的scene中获取常驻结点。首先得挑选一个结点作为绑定结点“召唤”常驻结点:
onLoad: function () {
var score = cc.director.getScene().getChildByName('Score');
this.node.getChildByName('Hint').getComponent(cc.Label).string = score.getComponent(cc.Label).string;
score.destroy();
},
这里直接挑选根结点,添加脚本组件,获取常驻结点。这里因为只是将常驻结点作为一种传递分数的载体,故传值后就销毁了。
下面实现点击按钮的返回场景功能。按钮的点击事件实际上通过属性面板就可以实现绑定了,在这之前得先写好按钮的用户组件脚本,在里面写好点击事件:
replay: function () {
cc.director.loadScene('game');
},