前两天在Cocos官方公众号上学习了「大掌教」的Cocos Creator 2.x Camera教程,总算是对摄像机组件有了一个初步的认识。乘热打铁,Shawn即刻就使用Camera摄像机练习了一个飞机游戏的,目前主要实现3个功能:
- 无限滚动背景
- 控制飞机移动
- 子弹发射
我们现看下面游戏视频:
1. 无限滚动背景
滚动背景我们当然是使用最新的摄像机来实现,我这里做了一个卷轴摄像机组件ScrollCamera,我们现来看一下组件暴露的属性:
ScrollCamera组件很像真实世界中的摄像机的推进器,Speed是推进速度,LoopGrounds是一个节点数组,他们是一组可首尾衔接的精灵节点,看下图:
我们再看一下ScrollCamera组件的代码:
cc.Class({
editor: {
requireComponent: cc.Camera, //前置要求摄像机组件
},
extends: cc.Component,
properties: {
speed: 300, //滚动速度
loopGrounds: [cc.Node], //循环节点
},
start () {
//获取节点上的摄像机组件
this.camera = this.getComponent(cc.Camera);
},
/**
*每帧更新函数
*1. 更新摄像机位置
*2. 检查循环节点,设置新位置
**/
update(dt) {
//获取当前节点
let current = this.loopGrounds[0];
//计算当前节点在摄像机中的位置
let pt = this.camera.getWorldToCameraPoint(current.position);
//当前节点超出摄像机范围(摄像机可视范围就是屏幕大小)
if (pt.y <= -cc.winSize.height) {
//取最后一个地图节点
let last = this.loopGrounds[this.loopGrounds.length - 1];
//将当前节点从数组中移除
this.loopGrounds.shift();
//将当前节点放到数组最后
this.loopGrounds.push(current);
//将当前节点位置移动到最顶部位置
current.y = last.y + (last.height + current.height) / 2;
}
//更新摄像机节点位置
this.node.y += dt * this.speed;
}
});
推动摄像机的代码很简单,看update函数中的最后一行:
this.node.y += dt * this.speed;
update中前面的几行代码是在做loopGrounds节点的检查和位置更新,每一行都注释,这里就不再过多赘述了。
将这个组件直接拖动到场景编辑器或层级管理器,设置background节点为background分组:
同时设置ScrollCamera的cullingMask属性只勾选background,看下图:
通过上面的设置和ScrollCamera的十几代码,无限滚动背景就搞定了。
2. 控制飞机移动
不知道大家还记得公众号之前的一篇文章《Cocos Creator基础教程(11)—可拖拽组件》,我直接将Dragable.js组件脚本拿过来,挂载到飞机节点上就OK了,代码也很简单:
/**
* 可拖动组件
*/
cc.Class({
extends: cc.Component,
onLoad() {
//注册TOUCH_MOVE事件
this.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
cc.log('onload');
},
_onTouchMove(touchEvent) {
//let location = touchEvent.getLocation();
//this.node.position = this.node.parent.convertToNodeSpaceAR(location);
//获取触摸移动增量
let delta = touchEvent.getDelta();
//当前节点位置+增量,更新节点位置
this.node.position = delta.add(this.node.position);
}
});
_onTouchMove函数稍微调整了一下,之前使用方法的是当前节点设置为触摸点位置,需要将全局坐标转换为当前节点的父节点坐标(设置一个节点的位置,是设置它在其父节点中的位置),拖动时节点总是保持在移动点的中心,特别是在第一次拖动节点时会有一个跳跃感,不够平滑。
我这里简单改进了一下,通过获取移动增量再加上当前节点位置,可以拖动节点的任意位置,不会出现突然将节点拉动到手指中心的突兀感。
Shawn在做这个飞机游戏过程中也尝试了一下消灭病毒当下这个火热的游戏,他的整个屏幕任意位置都可以控制飞机的移动,它是怎么做的呢,大家先可以思考一下?
我们这里再修改一下Dragable组件,增加一个target节点属性,将它从飞机节点上移到外层foreground节点,看下图:
触摸事件发生在foreground节点上,但移动的是target属性所指向的节点,我们看下代码:
/**
* 可拖动组件
*/
cc.Class({
extends: cc.Component,
properties: {
target: cc.Node,
},
...
_onTouchMove(touchEvent) {
//获取触摸移动增量
let delta = touchEvent.getDelta();
//如果this.target未设置,使用移动当前节点(兼容之前的用法)
let node = this.target || this.node;
//当前节点位置+增量,更新节点位置
node.position = delta.add(node.position);
}
});
代码就增加了一个target节点的定义,在TouchMove事件中检查this.target存在就用它,不存在默认移动当前节点,这样可以兼容曾经该组件的地方,不用做修改。
3. 子弹发射
飞机游戏的一个亮点就是子弹发射的华丽视觉效果,Shawn这里在网上找了些子弹特效图片,然后编辑了一个子弹Bullet的预制体,使用到之前文章《Cocos Creator基础教程(12)—精灵变身》中的SpriteEx.js组件,在上面配置了几张子弹图片,使用index属性可以方便切换子弹的表现效果,看下图:
Bullet子弹只是表现效果,要让子弹运动起来,我这里编写了一个LineEmmiter.js(线性发射器)的脚本,将它挂载到飞机节点上,用它来实例化Bullet预制体并让它动起来,先看一下LineEmmiter组件的属性:
之前的文章中提到过:组件为节点赋予能力,飞机节点上有一个Sprite可显示图片纹理,我们再挂上LineEmmiter组件,让它具有发射子弹的能力。
发射器的主要属性是子弹预制体、发射频率、子弹飞行速度,OffsetX属性要特别一点,它可以控制子弹与飞机的偏移位置,以实现同时发射多行子弹的效果,看下图:
我们再看下发射器的组件代码:
cc.Class({
extends: cc.Component,
properties: {
prefab: cc.Prefab,
rate: 1, //发射间隔
speed: 1000, //移动速度
offsetX: 0,
},
start() {
this.schedule(this._emmitNode, this.rate);
},
_emmitNode() {
//实例化节点,设置位置&父节
let node = cc.instantiate(this.prefab);
node.position = this.node.position;
node.x += this.offsetX;
node.parent = this.node.parent;
//计算子弹需要飞行的距离,飞行时间 = 距离 / 速度
let distance = ((cc.winSize.height / 2) - this.node.y);
let duration = distance / this.speed;
//使用moveBy动作,完成后删除子弹节点
let moveBy = cc.moveBy(duration, cc.v2(0, distance));
let removeSelf = cc.removeSelf();
let sequence = cc.sequence(moveBy, removeSelf);
node.runAction(sequence);
}
});
发射器代码也很简单:
1. 实例化子弹节点
2. 让子弹飞起来
我们这里子弹是垂直飞行的,直接使moveBy动作就可以完成,子弹从当前飞机节点出发直到屏幕顶部结束,这是它飞行的距离根据公式:距离/速度=时间,计算每颗子弹的飞行时间,保证飞机在不同位置,所有子弹都是按同样的速度飞行。
4. 小结
本次教程我们实现了一个最小飞机游戏的简单原型,我们的核心地图滚动与子弹发射代码只有70多行,有没有觉得使用Cocos Creator开发游戏飞一般的简单呢…
不过还有很多欠缺的地方,比如:限制飞机不要跑出屏幕之外、子弹应该使用内存池进行优化,在功能上还缺少敌机生成、少子弹碰撞、得分计算等等,这些内容我们留到下次继续。