在之前完成开始界面和刀光的制作后,接下来完成水果的分开、掉落部分。另外,在完成一个炸弹的火星特效,完成后大概像这个样子:
切开水果效果:
炸弹的火星效果:
对于水果分开的话,在已有的素材中已经有切割完成的了,因此,只需要将切割后的两部分水果,制成预制体Prefab就行了,同时需要去调整好它们的锚点,以方便旋转的效果更好一些。
可以先让完整sandia的锚点处于正中位置,然后拖动切开后的两部分sandia1和sandia2,让它们拼起来并且与sandia重合,同时让这三个prefab的锚点重合,并且position也相等,这样后面写代码的时候就方便一点。
新建脚本fruit.js,用它来作为水果类:
主要是声明一些需要用到的属性,同时在最后保存了一个游戏主体start的引用。(类似于官方的“摘星星”例子)
//fruit.js
cc.Class({
extends: cc.Component,
properties: {
fruit_type: null, //水果种类
position_x: null, //水果生成位置
position_y: null, //
//fruit_per_roate: 0, //水果自转角度
fruit_all: { // 完整水果的prefab
default: null,
type: cc.Prefab
},
fruit_apart_1: { // 水果切开后的部分1
default: null,
type: cc.Prefab
},
fruit_apart_2: { // 切开后的部分2
default: null,
type: cc.Prefab
},
fruit_all_curent_angle: 0, // 完整水果实时角度
fruit_all_per_roate_angle: 0, //完整水果自转角度
fruit_all_x_speed: 0, //完整水果x和y速度
fruit_all_y_speed: 0,
fruit_all_y_acc: 0.15, //完整水果y轴掉落加速度
fruit_apart_x_speed: 0, //部分水果...
fruit_apart_y_speed: 0,
fruit_apart_y_acc: 0.17,
fruit_apart_roate_angle: 150,
isBroken: false, //是否完整
isActivate: true,
isAvailable: true,
game_manager: null, //这里保存了游戏主体脚本start的一个引用
},
//...
});
水果切开的逻辑,实际上就是移除原先的完整水果,在同一位置生成切开后的两部分水果,并且在旋转的同时完成一个下落的过程。
//fruit.js
//当水果被切开时
apart(){
this.fruit_apart_1.x = this.fruit_all.x;
this.fruit_apart_1.y = this.fruit_all.y;
this.fruit_apart_2.x = this.fruit_all.x;
this.fruit_apart_2.y = this.fruit_all.y;
this.fruit_apart_1.angle = this.fruit_all.angle;
this.fruit_apart_2.angle = this.fruit_all.angle;
let direction = Math.sign((this.fruit_all_curent_angle - 90) * (270 - this.fruit_all_curent_angle%360)+1e-10);
this.fruit_apart_x_speed = (1.5 * Math.random() + 3.5) * direction;
this.fruit_apart_roate_angle = (150 * Math.random() + 50) * direction;
this.fruit_all.parent.removeChild(this.fruit_all);
this.fruit_all = null;
// this.game_manager.node.removeChild(this.fruit_all);
this.fruit_apart_1.runAction(cc.rotateBy(1,this.fruit_apart_roate_angle));
this.fruit_apart_2.runAction(cc.rotateBy(1,-this.fruit_apart_roate_angle));
},
这里面主要是一些简单的数学计算,首先要让切开水果的两部分的位置和角度和原先完整水果一样,接着,根据具体角度,设置这两部分水果在x轴上的运动方向(当然也可以不管角度,就直接将它们按固定的方向飞)。并且使用runAction来完成它们各自的旋转。
最后,在update里面,对它们的x和y方向的位置做更新就可以了。x轴做匀速运动,y轴做向下加速运动。
//fruit.js
update(dt){
this.fruit_all.x += this.fruit_all_x_speed;
this.fruit_all_y_speed -= this.fruit_all_y_acc;
this.fruit_all.y += this.fruit_all_y_speed;
}
这样便完成水果的分开、下落部分了。接下来只需要添加判定水果是否被切到的逻辑就行了,简单的做法可以是对完整水果prefab添加一个碰撞组件,并且在start.js中的移动监听函数TouchMove中,判断当前坐标是否碰到水果就OK了。同样的,水果的生成也可以通过对象池来实现(可利用上面fruit.js中的isActivate和isAvailable)。
最后是炸弹火星的特效制作。之前没发现炸弹的素材是没有这种火光特效的,因此为了好看点需要自己做一下。它看起来像是那种粒子特效,或者也可以用图片调整大小来做。不过没接触过感觉有点麻烦,我这里就直接用CocosCreator的Graphics组件来代码实现吧。
事实上,可以观察到每一片“火焰”都是长这样的。
看起来像是叶子的那种性状,实际上,它可以通过两段贝塞尔曲线连接起来来完成。有关贝塞尔曲线这里就不多做介绍了,像是js、css也都有实现的方法。在cocos中,可以通过以下方式来画贝塞尔曲线。
首先在层级管理器canvas下新建一个节点paint,并给它添加组件Graphics,然后拖动到资源管理器中做成prefab,然后给start.js中新加一个属性,并拖动paint到脚本组件上完成引用。然后可以通过以下函数来绘制两段贝塞尔曲线。
//start.js
properties:{
//...
paint:{
default: null,
type: cc.Prefab
},
//...
},
drawLine(start_point,end_point,control_point_1,control_point_2){
var g = this.abc.getComponent(cc.Graphics);
g.moveTo(start_point[0],start_point[1]);
g.quadraticCurveTo(control_point_1[0],control_point_1[1],end_point[0],end_point[1]);
g.quadraticCurveTo(control_point_2[0],control_point_2[1],start_point[0],start_point[1]);
g.stroke();
g.fillColor = new cc.Color().fromHEX("f0ef9c");
g.fill();
}
接下来就是如何确定好起点startpoint、终点endpoint和两个控制点controlpoint。
这里同样把火星写成一个类BoomFlame,可以简单的写在start.js最下面(注意别写在cc.Class里面)就行了。
//start.js
cc.Class({
//...
});
//炸弹火星类
function BoomFlame(id,life,center,angle,length,create_time,paint){
this.id = id;
this.create_time = create_time; //创建时间
this.life = life; //持续时间
this.center = center; //中心点位置
this.angle = angle; //运动的方向
this.length = length; //运动的距离
this.radius = 15; //火焰的“半长轴”
this.elapse = 0; //已经过的时间
this.paint = paint; //画笔(前面的带有graphics组件的prefab)
this.active = true; //用于对象池维护
BoomFlame.prototype.set_properties = function(id,life,center,angle,length,create_time,paint){
this.id = id;
this.create_time = create_time;
this.life = life;
this.center = center;
this.angle = angle;
this.length = length;
this.radius = 15;
this.elapse = 0;
this.paint = paint;
this.active = true;
}
//确定起始点和控制点
BoomFlame.prototype.update_boom_flame = function(draw){
if(this.life - this.elapse <= 0){
this.remove_boom_flame();
return ;
}
this.paint.getComponent(cc.Graphics).clear();
let ratio_elapse_life = this.elapse / this.life; //比例
let angle = this.angle;
let center = this.center;
let len = this.length;
let r = this.radius
center = [ Math.trunc(center[0] + len * ratio_elapse_life * Math.cos(angle)) , Math.trunc( center[1] + len * ratio_elapse_life * Math.sin(angle) ) ];
let start_point = [ Math.trunc( center[0] - r * (1-ratio_elapse_life) * Math.cos(angle) ) , Math.trunc( center[1] - r * (1-ratio_elapse_life) * Math.sin(angle) ) ];
let end_point = [ Math.trunc( center[0] + r * (1-ratio_elapse_life) * Math.cos(angle) ) , Math.trunc( center[1] + r * (1-ratio_elapse_life) * Math.sin(angle) ) ];
let control_point_1 = [ Math.trunc( center[0] + r * (1-ratio_elapse_life) * Math.cos(angle + 0.5*Math.PI) * 0.3 ) , Math.trunc( center[1] + r * (1-ratio_elapse_life) * Math.sin(angle + 0.5*Math.PI) * 0.3 ) ];
let control_point_2 = [ Math.trunc( center[0] + r * (1-ratio_elapse_life) * Math.cos(angle - 0.5*Math.PI) * 0.3 ) , Math.trunc( center[1] + r * (1-ratio_elapse_life) * Math.sin(angle - 0.5*Math.PI) * 0.3 ) ];
// cc.log('draw');
// cc.log(this.paint);
draw(this.paint.getComponent(cc.Graphics),start_point,end_point,control_point_1,control_point_2);
}
BoomFlame.prototype.remove_boom_flame = function(){
this.paint.getComponent(cc.Graphics).clear();
this.paint.destroy();
// cc.log('remove');
this.active = true;
this.id = null;
this.create_time = null;
this.life = null;
this.center = null;
this.angle = null;
this.length = null;
this.radius = null;
this.elapse = null;
this.paint = null;
}
}
其中的update_boom_flame则是主要代码,用来计算出贝塞尔曲线的起始点和控制点,从而完成火焰在某一方向上的运动效果。其中的ratio_elapse_life主要是为了让其有那种 随着时间逐渐变小消失的效果。对于起始点和控制点的计算可以从下面的图看出来:
只要确定好中心点center后,就可以根据角度计算出控制点和起始点了,最后再通过上面的ration_elapse_life控制整体的大小缩放。
通过cocos的计时器schedule就可以实现这种无限发射的效果了。
相关代码如下:
//start.js
properties:{
//...
paint:{
default: null,
type: cc.Prefab
},
gid: 0,
pooled_boom_flame: [],
num_pooled_boom_flame: 20,
global_timer: 0,
},
drawLine(g,start_point,end_point,control_point_1,control_point_2){
g.moveTo(start_point[0],start_point[1]);
g.quadraticCurveTo(control_point_1[0],control_point_1[1],end_point[0],end_point[1]);
g.quadraticCurveTo(control_point_2[0],control_point_2[1],start_point[0],start_point[1]);
g.stroke();
g.fillColor = new cc.Color().fromHEX("f0ef9c");
g.fill();
},
onload(){
//...
// pooled_boom_flame是存放BoomFlame的对象池
for(let i=0;i
基本完成了一大部分了,最后只剩下游戏中水果出现控制,游戏得分控制,以及一些转场的逻辑等等。游戏中水果是从下面往上抛然后下落的,实现起来也和前面的方法差不多,至于炸弹由于有火星的原因,可以在生成的同时给它绑定一个计时器就行。