今天是开发飞行射击游戏第五天,爆炸特效体系与NPC子弹弹幕。
简介
实现爆炸特效体系与NPC子弹弹幕。
飞机爆炸也是一个类,爆炸也是个数可变的,也需要特效管理者类。
实现效果
本来想路视频转GIF的,但是gif文件过大,超过5M又上传不了,而且压缩后失帧严重,仅截取了一部分转为gif, 请大家原谅。
代码及过程
一、特效体系:爆炸
爆炸是一个动画帧,播放完就可以,因为比较简单, 用一个类+id 种类索引通过状态机的形式区分多种不同的爆炸。
创建特效类TX和特效管理者类TXManager类
1)TX类
public id:number; //代表种类
public vis:boolean; //被工厂管理的都需要vis
public fi:number; //代表动画帧播放第几帧
public t:number; //延时计时器 记录延时如:3次主循环出现,
public m:number; //状态
public l:number; //特效播放的总时长
public tm:TXManager; //上级指针
构造:
update(){}
动画延时播放:当create时,它可能不存在,如果有延时,比如延时5,visible可见性是false,但是vis是true。
如果t是0,就不用延时,一种是倒计时状态,没有出现。另一种是播放状态
m 0是倒计时 1是播放。
在构造最后
if(this.t > 0){
//虽然在仓库,但是看不到
//0 是倒计时, 1是播放
this.visible = false;
this.m = 0;
}else{
this.visible = true;
this.m = 1 ;
}
TX类
```c
class TX extends egret.Sprite{
public im:egret.Bitmap;
public id:number; //代表种类
public vis:boolean; //被工厂管理的都需要vis
public fi:number; //代表动画帧播放第几帧
public t:number; //延时计时器 记录延时如:3次主循环出现,
public m:number; //状态
public l:number; //特效播放的总时长
public tm:TXManager; //上级指针
public constructor(id:number,x:number, y:number,
t:number,l:number,tm:TXManager) {
super();
this.id = id;
this.x = x ;
this.y = y;
this.t = t;
this.l = l;
this.m = 0;
this. fi = 0 ;
this.vis = true; //若它是false 工厂内对象消失
this.tm = tm;
this.im = Main.createBitmapByName("tx1_1_png");
this.anchorOffsetX = this.im.width/2;
this.anchorOffsetY = this.im.height/2;
this.addChild(this.im);
if(this.t > 0){
//虽然在仓库,但是看不到
//0 是倒计时, 1是播放
this.visible = false;
this.m = 0;
}else{
this.visible = true;
this.m = 1 ;
}
}
public update(){
switch(this.m ){
//
case 0:
//倒计时
this.t-- ;
if(this.t <=0 ){
//出现爆炸
this.visible = true;
this.m = 1; //播放
}
break;
case 1 :
//动画播放
this.fi++;
//
if(this.fi >= this.l){
this.vis =false; //工厂里对象销毁
}else{ //没有播放完,动画帧切换
//动画切换公式 fi从0开始,到9,
//10是动画的张数有几张动画就*几。 fi是动画帧,l是动画长度,
this.im.texture = RES.getRes("tx1_"+Math.floor(this.fi*10/this.l + 1)+"_png");
}
}
}
}
``
TXManager类
class TXManager extends egret.Sprite{
public tm:Array<TX>; //Array动态数组(容器),通过添加,添加对象,移除对象。
public game:MainGame;
public constructor(game:MainGame) {
super();
this.game = game;
this.tm = new Array();
}
//每create一次就new一个
public create(id:number,x:number,y:number,t:number,l:number,game:MainGame){
//生成子弹
let one = new TX(id,x,y,t,l,this);
//添加到世界
this.addChild(one);
//放到仓库数组最后
this.tm.push(one);
}
//更新所有子弹,找到每一颗子弹,每颗更新
public update(){
//整个仓库长度 ,利用循环可 以循环出所有子弹
for(let i = 0 ; i < this.tm.length ; i++){
//找到每颗子弹
let one = this.tm[i];
one.update();
//若子弹太多,仓库会满,所以子弹需要移除
//子弹出屏,vis == false。移除
if(one.vis == false){
//先从场景移除
this.removeChild(one);
//仓库移除
this.tm.splice(i ,1);
//移除一个对象,长度-1
i--;
}
}
}
}
在Maingame中申请public tm:TXManager
构造:this.tm = tm; this.addChild(this.tm);
更新 this.tm.update();
在ZDManager类中
if(npc.hp <= 0 ){
for(let k = 0 ; k < 10 ; k ++){ //-50 到 +50
this.game.tm.create( 0, npc.x + Math.random()*100 -50 ,
npc.y + Math.random()*100 -50 ,
Math.floor(Math.random() * 5), 10,this.game);
}
npc.vis = false;
}
实现爆炸效果
在上面的设置中,所有的NPC爆炸都是一个样子,如果出现不同的NPC爆炸怎么办?
有限状态机实现多样化都是在子类中,case。
如果对象和对象当中,想出现不同的现象,需要不同的代码。
需要给每一个子类,每一个特殊的NPC写相应的爆炸,
二.爆炸效果多样性
子类实现不同的dead()方法,
在NPC父类中 添加:public abstract dead();
实现每个NPC不同的死亡效果,在每个子类中都添加。
在NPC0,1中添加
public dead(){
for(let k = 0 ; k < 10 ; k ++){
this.nm.game.tm.create( 0, this.x + Math.random()*100 - 50 ,
this.y + Math.random()*100 - 50 ,
Math.floor(Math.random() * 5), 10,this.nm.game);
}
}
之后再ZDManager中调用dead方法
父类当中有抽象方法,但是实际执行每个子类的代码。
三.实现NPC的特殊弹幕
boss也是NPC,也得接受NPCManager的管理
创建BOSS0类
class BOSS0 extends NPC{
public im:egret.Bitmap;
public m:number;
public t:number;
public constructor(x:number,y:number,nm:NPCManager) {
super(nm);
this.x = x ;this.y = y;
this.im = Main.createBitmapByName("boss50_png");
this.im.anchorOffsetX = this.im.width/2;
this.im.anchorOffsetY = this.im.height/2;
this.addChild(this.im);
this.m = this.t = 0;
this.hp = 1000;
}
public update(){
}
public isHit(x:number,y:number):boolean{
return false;
}
public dead(){
}
}
添加BOSS更新方法
switch(this.m){
//boss停的位置
case 0:
this.y+=10;
if(this.y >= 150){
this.t = 20 ;
this.m = 1;
}
break;
//等待状态
case 1:
this.t--;
if(this.t <=0){
this.m = 10;
this.t = 0 ;
}
break;
//发射子弹
case 10:
this.t++;
//每隔3次主循环发射一颗
if(this.t % 3 == 0 ){
for(let i = 0 ; i < 5 ; i++){
//5串子弹
//160+i*10 间隔10度发一颗
this.nm.game.nzm.create(1, this.x, this.y, 10 ,160+i*10,this.nm.game );
}
}
if(this.t >= 20){
this.t = 10;
this.m = 1;
}
break;
}
①一串子弹
②实现环形的子弹
//发射子弹
case 10:
this.t++;
//每隔3次主循环发射一颗
if(this.t % 3 == 0 ){
for(let i = 0 ; i < 36 ; i++){
//5穿子弹
//160+i*10 间隔10度发一颗
this.nm.game.nzm.create(1, this.x, this.y, 10 ,160+i*10,this.nm.game );
}
}
if(this.t >= 20){
this.t = Math.random()* 20 + 10;
this.m = 1;
}
break;
③漩涡形子弹
case 11:
this.t++;
//随着t的++,发射子弹的角度不断变化
this.nm.game.nzm.create(1, this.x, this.y, 10 ,180+this.t*10,this.nm.game ); //逆时针转
this.nm.game.nzm.create(1, this.x, this.y, 10 ,180 - this.t*10,this.nm.game ); //顺时针转
//子弹发射跟t有关联 间隔10度发一颗
if(this.t >= 36){
this.t = Math.random()* 20 + 10;
this.m = 1;
}
break;
④矩阵型子弹
case 12:
this.t++;
//每10次主循环中15,16,17,18,19发射子弹 5颗
if(this.t % 20 > 14){
for(let i = 0 ; i < 5 ; i++){
//间隔20度打一排,从130度开始打5颗,
//每十次主循环是一波,this.t/10,前十次结果为10,11,12,13结果为1,取整*20就是每波的距离
this.nm.game.nzm.create(1,this.x,this.y,10,Math.floor(this.t/20)*20 +130+i*5,this.nm.game );
//135度左右打5颗,
}
}
if(this.t >= 100){
this.t = Math.random() * 20 + 10;
this.m =1 ;
}
break;
⑤鞭形子弹
后发先至效果
//鞭形子弹
case 13:
this.t++;
//速度随着时间增加,产生后发先至效果
this.nm.game.nzm.create( 1, this.x - 50, this.y, 6 + this.t *2 ,190 - this.t,this.nm.game );
this.nm.game.nzm.create( 1, this.x+50, this.y, 6 + this.t *2 ,170 + this.t,this.nm.game );
if(this.t >= 10){
this.t = Math.random() * 20 + 10;
this.m =1 ;
}
break;
⑥朝向性子弹
NPC朝向玩家发射子弹并不是跟踪形子弹
朝向性子弹由子弹发射位置和玩家位置决定
过程:
NZDManager将角度参数设为缺省值 n?:numbe。 若不赋值,值为空。
原理:
0角度向上顺时针转过n角度,求角度n = arctan( px-x /y -py)
代码为:
在NZDManager中
//如果n角度是空的,就是没有赋值
if(!n){
n = Math.atan2(this.game.player.x - x,y - this.game.player.y);
//注意:三角函数算出的都是弧度制,还需要弧度制转换角度制
n = n * 180/Math.PI;
}
BOSS0中
//朝向型子弹
case 14 :
this.t++;
//打5团,每10次主循环打一组,
if(this.t % 10 > 4){
this.nm.game.nzm.create( 1, this.x - 50, this.y, 15);
}
if(this.t >= 50){
this.t = Math.random() * 20 + 10;
this.m =1 ;
}
break;
实现随机类型子弹
在等待状态case1中 this.m = Math.floor(Math.random() * 5 ) + 10; //随机类型子弹
四.不规则图形的碰撞
白鹭引擎中有像素级的碰撞检测,但是在游戏中,子弹与BOSS的碰撞,假如一颗子弹100*100像素,一颗子弹就检测了10000次,50颗子弹就是50万次,游戏会非常卡。那么不开启像素级碰撞,不规则图形的碰撞怎么解决呢?
图片宽 146 高 166 各中心点 73,83像素
if(Math.abs(x - this.x ) < 45 &&
Math.abs(y - this.y )< 82){
return true;
}
不规则图形拆成规则图形。
//宽73,高36
if(Math.abs(x - this.x) < 73 &&
Math.abs(y -(this.y + 44)) < 36){
return true;
}
要用矩形中心检测,横坐标在中心,纵坐标在选框中,实际比y多44,
至此,第五天的开发笔记已经完成,学习需要坚持,坚持到最后一定会有结果,每天写下笔记来记录自己的学习内容, 以后有需要也可以查看,大家可以一起学习。
想要我一起学习的可以关注我的公众号 知言不尽 找到我,交流学习,获取图片素材和源代码。