aircraft-war(二)
双弹道子弹
双弹道子弹和弹道子弹差别并不大,实现起来也不难。先来找张图分析一下:
Hero的锚点处于中心位置,两颗子弹分别位于左右两边1/3处,也就是说子弹的分为位于Hero原点的X轴{left: -width/3, right: width/3}
位置。剩下的和单发子弹没有区别。
脚本修改如下:
bulletGroup
cc.Class({
extends: cc.Component,
properties: {
bullet: cc.Prefab,
hero: cc.Node,
rate: cc.Integer,
bulletCount: {
default: 10,
type: cc.Integer
},
bulletBlue: cc.Prefab,
},
onLoad: function () {
// this.genBulletPool();
this.genBulletBluePool();
this.schedule(function () {
this.startShoot(this.bulletBluePool, [this.hero.width / 3, -this.hero.width / 3])
}.bind(this), this.rate);
},
genBulletPool: function () {
this.bulletPool = new cc.NodePool();
for (let i = 0; i < this.bulletCount; ++i) {
let newBullet = cc.instantiate(this.bullet); // 创建节点
this.bulletPool.put(newBullet); // 通过 putInPool 接口放入对象池
}
},
genBulletBluePool: function () {
this.bulletBluePool = new cc.NodePool();
for (let i = 0; i < this.bulletCount; ++i) {
let newBullet = cc.instantiate(this.bulletBlue); // 创建节点
this.bulletBluePool.put(newBullet); // 通过 putInPool 接口放入对象池
}
},
//获取子弹位置
getBulletPosition: function(x){
let heroP = this.hero.getPosition();
let newV2_x = heroP.x + x;
let newV2_y = heroP.y;
return cc.p(newV2_x, newV2_y);
},
startShoot: function (pool, posOfHero) {
let newNode = null;
if (pool.size() > 0) {
for(var i = 0; i < posOfHero.length; i++) {
newNode = pool.get();
this.node.addChild(newNode);
let p = this.getBulletPosition(posOfHero[i]);
newNode.setPosition(p);
newNode.getComponent('bullet').bulletGroup = this;
}
}
},
//销毁子弹
destroyBullet: function (bullet) {
}
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
bullet:
cc.Class({
extends: cc.Component,
properties: {
speed: cc.Integer,
},
// use this for initialization
onLoad: function () {
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
this.node.y += dt * this.speed;
if (this.node.y > this.node.parent.height){
this.bulletGroup.bulletBluePool.put(this.node);
}
},
});
好了,目前两种类型的子弹已经实现了功能,但这只是第一步而已。Cocos Creator提供的属性检查器并没有被充分的用到,而且从“前端工程师”的角度来讲,还远远未到组件化。
所以接下来要做的,是进行“解耦”和“封装”。(感谢提供源代码的A123asdo11大神,他的代码写的非常棒,很值得学习,这是项目源码)
把都需要用到的抽象出来
先来回顾一下之前的代码,发现两种类型的子弹,可以把属性的设置也抽象出来充分利用属性检查器。两种类型的子弹,位置、Prefab,持续时间,对象池所需数量都不一样,所以把这些通过继承的方式作为类抽离出来。
先按照之前写脚本的逻辑顺序,首先把生成节点、生成对象池的方法抽离出来,接着是发射那块创建节点的方法,还有子弹销毁节点,这都是很多地方需要重复用到的,都需要抽象出来。
首先要创建一个用来放这些抽象函数的地方,然后还需要一个全局变量,作为传递的媒介。看这里
在资源选择器中创建global
脚本:
// declare global variable "D"
window.D = {
// singletons
common: null, //公共方法
commonConstant: null, //定义的一些常量
};
接着按照之前的开发逻辑流程,来改造bulletGroup
脚本:
// 子弹生成的位置
const bulletPosition = cc.Class({
name: 'bulletPosition',
properties: {
positionX: {
default: '',
tooltip: '子弹相对Hero的位置'
}
}
});
// 无限时长子弹
const infiniteBullet = cc.Class({
name: 'infiniteBullet',
properties: {
name: '',
rate: 0,
poolAmount: 0,
prefab: cc.Prefab,
position: {
default: [],
type: bulletPosition,
tooltip: '子弹位置'
}
}
});
// 有限时长子弹
const finiteBullet = cc.Class({
extends: infiniteBullet,
name: 'finiteBullet',
properties: {
duration: 0,
}
});
// 有限时长子弹
cc.Class({
extends: cc.Component,
properties:() => ({
infiniteBullet: {
default: null,
type: infiniteBullet,
tooltip: '无限子弹'
},
finiteBullet: {
default: [],
type: finiteBullet,
tooltip: '有限子弹'
},
hero: cc.Node,
}),
onLoad: function () {
// 初始化对象池
D.common.initNodePool(this, this.infiniteBullet);
D.common.batchInitNodePool(this, this.finiteBullet);
this.startAction();
},
// 发射子弹,定时器
startAction: function () {
this.startShoot = function () {
this.genNewBullet(this.infiniteBullet);
}.bind(this);
// 定时器 发射子弹的就是创建子弹对象
this.schedule(this.startShoot, this.infiniteBullet.rate);
},
// 生成子弹
genNewBullet: function (bulletInfo) {
let poolName = bulletInfo.name + 'Pool';
for(let i = 0; i < bulletInfo.position.length; i++) {
let newNode = D.common.genNewNode(this[poolName], bulletInfo.prefab, this.node);
let pos = this.getBulletPosition(bulletInfo.position[i].positionX);
newNode.setPosition(pos);
// 这是个很基础的知识点,需要小心!
// newNode.getComponent('bullet') 找到的是bullet脚本组件
// 这里将this传进去,是为了下面bullet中调用destroyBullet方法时,this对象不变
// 如果:newNode.getComponent('bullet').died = this.destroyBullet;
// 那么只是将这个方法传给了bullet,当在bullet中使用该函数,函数的当前上下文就变了
// 可以试试在 destroyBullet 中打印一下 this 看看当前的上下文
newNode.getComponent('bullet').bulletGroup = this;
}
},
//获取子弹位置
getBulletPosition: function(positionStr){
let heroP = this.hero.getPosition();
let newV2_x = heroP.x + eval(positionStr);
let newV2_y = heroP.y;
return cc.p(newV2_x, newV2_y);
},
//销毁子弹
destroyBullet: function (node) {
// bullet中是由bulletGroup调用,所以当前this为bulletGroup
D.common.putBackPool(this, node);
}
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
下面来展开这部分的代码:
首先需要看一下CCClass的进阶参考,了解一下属性参数和继承,然后先别急往下看,因为这里的代码变动比较大,涉及的知识与变动也比较多。
接着看一下那个存放公共函数的脚本,在资源选择器中创建common
脚本:
cc.Class({
extends: cc.Component,
properties: {
},
// use this for initialization
onLoad: function () {
D.common = this;
},
// 批处理对象池
batchInitNodePool: function (that, objArray) {
for(let i=0; i< objArray.length; i++) {
let objInfo = objArray[i];
this.initNodePool(that, objInfo);
}
},
// 初始化对象池
initNodePool: function (that, objInfo) {
let name = objInfo.name;
let poolName = name + 'Pool';
that[poolName] = new cc.NodePool();
// 创建对象,并放入池中
for (let i = 0; i < objInfo.poolAmount; i++) {
let newNode = cc.instantiate(objInfo.prefab);
that[poolName].put(newNode);
}
},
// 生成节点
genNewNode: function (pool, prefab, nodeParent) {
let newNode = null;
if (pool.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象
newNode = pool.get();
} else { // 如果没有空闲对象,也就是对象池中备用对象不够时,就用 cc.instantiate 重新创建
newNode = cc.instantiate(prefab);
}
nodeParent.addChild(newNode);
return newNode;
},
// 销毁节点
putBackPool: function (that, node) {
let poolName = node.name + "Pool";
that[poolName].put(node);
}
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
Prefab的名称要通过属性检查器修改:
这部分代码需要搞懂,慢一点没什么关系。这部分的源码放在这里了