今天是开发飞行射击游戏第四天,NPC机制和攻击血量效果。
实现敌机阵列效果出现和玩家子弹击中敌机NPC 血量减少至0消失。
欢迎大家和我一起学习哦~ 下面是详细过程代码,还有遇到的错误以及改正方法。
所有被工厂管理的对象都要有vis属性,只要=false,就消失。
1)创建NPC类和NPCManager类
public game:MainGame;
public im:egret.Bitmap[] = []; //2张拼起来的图片所以用数组 //这里出现错误,原来写到
public vy:number; //速度
public vis:boolean; //工厂生存属性
public nm:NPCManager; //上级指针
public constructor(x:number,y:number,vy:number,nm:NPCManager) {
super();
this.nm = nm;
this.x = x ;this.y = y;
this.vy = vy;
//这个NPC是2张图片拼起来的所以第二张翻转正好拼成一个。为什么是半个图片,节省资源
this.im[0] = Main.createBitmapByName("npc5_png");
this.im[0].anchorOffsetX = this.im[0].width;
this.im[0].anchorOffsetY = this.im[0].height/2;
this.addChild(this.im[0]);
this.im[1] = Main.createBitmapByName("npc5_png");
this.im[1].anchorOffsetX = this.im[1].width;
this.im[1].anchorOffsetY = this.im[1].height/2;
this.im[1].scaleX = -1;
this.addChild(this.im[1]);
this.vis = true;
}
public update(){
this.y +=this.vy;
if(this.y > 1000){
this.vis = false;
}
}
出现的错误:
public im:egret.Bitmap[] = [];
原来写的public im:egret.Bitmap[]; 没有写=[ ]; 应该加强记忆
整理:完成一个NPC,但是游戏需要多个NPC,需要使用工厂管理,工厂需要create方法生成,同时,create方法生成的需要放到仓库中,再把仓库中的对象一个一个更新update。
只要是管理个数可变的对象,所有的工厂代码都是一样的,
2)NPCManager类
把ZDManager中代码全复制到NPCManager中名字相应修改就可以
public nm:Array; //Array动态数组(容器),通过添加,添加对象,移除对象。
public game:MainGame;
public constructor(game:MainGame) {
super();
this.game = game;
this.nm = new Array();
}
//每create一次就new一个
public create(x:number,y:number,v:number){
//生成子弹
let one = new NPC(x,y,v,this);
//添加到世界
this.addChild(one);
//放到仓库数组最后
this.nm.push(one);
}
//更新所有子弹,找到每一颗子弹,每颗更新
public update(){
//整个仓库长度 ,利用循环可以循环出所有子弹
for(let i = 0 ; i < this.nm.length ; i++){
//找到每颗子弹
let one = this.nm[i];
one.update();
//若子弹太多,仓库会满,所以子弹需要移除
//子弹出屏,vis == false。移除
if(one.vis == false){
//先从场景移除
this.removeChild(one);
//仓库移除
this.nm.splice(i ,1);
//移除一个对象,长度-1
i--;
}
}
}
在Maingame中申请,构造,更新,调用create方法。就可以显示
public nm:NPCManager;
this.nm = new NPCManager(this);
this.addChild(this.nm); //注意:nm要放在所有场景除背景外的最底层。
this.nm.update();
this.t--;
if(this.t <=0){
this.nm.create(Math.random()*480, -200,Math.random()*5 + 10);
this.t = Math.random()* 20 + 10;
}
多个NPC写多个NPC1,2,3类,都让它继承NPC类,而工厂管理时,管理NPC就可以,不用再管理单独的个体。这就是有限状态机的思想
父类与子类的继承关系,管理者管理父类,使用子类实现个体多样性的设计模式
将NPC类改为NPC0类
NPC类中申请
public vis:number;
public nm:NPCManager;
构造
this.nm = nm;
this.vis = true;
父类当中update方法,完成npc的更新,但是我是父类,不是具体的NPC, 具体的npc是子类,不同的npc有不同的路线,作为父类,应该怎么动?父类不知道子类怎么动,也不需要管理子类怎么动。
在父类中update方法,实际的方法由子类写,所有的子类都需要具备update方法,但是不知怎么写,就需要把这个方法变为抽象。
抽象就是方法不写。
public abstract update();
**抽象方法:**我自己这个类,我自己不知道这个方法干什么,但是继承我的子类必须要实现的方法
父类完成,继承子类必须实现的方法。
如果类中包含抽象方法那么一定是抽象类。abstract class NPC extends egret.Sprit{}
这时,NPC0类继承NPC类,将egret.sprite改为NPC 并且super(nm); 就可以
之后将NPC0中的重复NPC的属性删除,
在NPCManager类中,create方法添加id:number;
let one = null;
switch(id){
case 0 :
one = new NPC0(x,y,v,this);
break;
}
2.新建NPC1类,敌机飞下来转向回去
class NPC1 extends NPC{}
必须有update方法,否则NPC1会报错.因为NPC类是抽象方法,子类必须有.
需要实现敌机的飞下来转回去,需要多状态,申请t,m,vy:速度dy:停留位置。
public constructor(x:number,y:number,vy:number,dy:number,nm:NPCManager) {
super(nm);
this.x = x ;this.y = y;
this.vy = vy;
this.dy = dy;
this.m = 0 ;
this.t = 0;
this.im[0] = Main.createBitmapByName("npc6_png");
this.im[0].anchorOffsetX = this.im[0].width;
this.im[0].anchorOffsetY = this.im[0].height/2;
this.addChild(this.im[0]);
this.im[1] = Main.createBitmapByName("npc6_png");
this.im[1].anchorOffsetX = this.im[1].width;
this.im[1].anchorOffsetY = this.im[1].height/2;
this.im[1].scaleX = -1;
this.addChild(this.im[1]);
}
public update(){
switch(this.m){
//向下飞
case 0 :
//向下飞
this.y +=this.vy;
//飞到停留位置
if(this.y > this.dy){
this.y = this.dy;
this.t = 0 ;
//进入下一状态
this. m = 1 ;
}
break;
//停留
case 1 :
this.t++;
//图层转向
//10次 主循环的时间从0变到180度
if(this.x < 240) //屏幕左边逆时针转
this.rotation = -180 * this.t/10;
else //屏幕右边顺时针转
this.rotation = 180 * this.t/10;
if(this.t >=10){
this.t = 0;
this.m = 2 ;
}
break;
//向上飞
case 2 :
this.y -=this.vy;
//出屏,仓库清除
if(this.y < -100){
this.vis = false;
}
break;
}
}
1.NPC的生成模式 飞行射击游戏中常用的三种模式:
1)随机生成模式:一般就是随机生成,也有时根据游戏时间推进。时间越长或分数越高,生成NPC等级越高
2)时间队列生成:NPC按照固定队列,根据时间生成,到固定时间生成固定NPC,例如:30次主循环屏幕左生成3个NPC,50次主循环后屏幕右生成3个NPC。只要主循环到固定次数,就生成NPC。不管前面有没有NPC,有没有消失,都会出现。
3)阵列生成:一波一波出现,例如:左边3个NPC,若没有全打爆,则后面的不会出现。
1)随机生成
this.nm.create( Math.floor(Math.random()*2),
// Math.random()*480, -200,Math.random()*5 + 10 , 300);
// this.t = Math.random()* 20 + 10;
// }
2)时间队列过程:
将Maingame中update方法的create调用放在NPCManager当中,新建一个zl()方法,专用于create方法。并申请一个public t:number;//生成计时 并 this.t = 0 ;
在upda方法中调用this.zl();
//NPC阵列
public zl(){
this.t++;
switch(this.t){
//左侧3
case 10 :
this.create( 0 , 100 , -100 , 15 , 0);
this.create( 0 , 100 , -200 , 15 , 0);
this.create( 0 , 100 , -300 , 15 , 0);
break;
//右侧3
case 20 :
this.create( 0 , 300 , -100 , 15 , 0);
this.create( 0 , 300 , -200 , 15 , 0);
this.create( 0 , 300 , -300 , 15 , 0);
break;
//中间3个
case 30 :
this.create( 1 , 200 , -100 , 15 , 300);
this.create( 1 , 300 , -250 , 15 , 300);
this.create( 1 , 100 , -250 , 15 , 300);
break;
}
}
根据生成NPC的不同也就是Id的不同,0号只需要位置,1号需要停留坐标。因为在使用同一个方法,0号也得有dy,但是没作用,这种情况下怎么办?
若是JAVA或者C 可以用重载。但白鹭当中不存在,也就是同名方法只能写一个,不可能通过参数的不同实现不同的方法,在白鹭当中的解决方法是用缺省参数。
在create方法中id,x,y,v,是必填的,但dy可能会没有,当参数可有可无时,就做成缺省参数。
例:public create( id:number , x:number , y:number , v?:number , dy?:number ){}
在需要缺省参数的后面加上?
当方法当中出现缺省参数,那么缺省参数之后的所有参数都必须是缺省。
若不这样写,在传参数就会出现问题:create( 0 , 100 ,100,) id0 x 100 y 100 v和dy都没有值为空
若create (0, 100 ,100 ,20); 20会给v,dy是缺省。 缺省都不填那没关系,若填了会按顺序赋值,后面少几个就少几个缺省值。
缺省参数会按照顺序赋值。
2)阵列模式:只有第一波NPC全部打爆之后,第二波才会出现。跟时间没关系
生成的波数用cID; 申请 public cID:number; //当前生成的波数
那么怎样知道第一波NPC打完呢?
用仓库长度判断。
public zl(){
//仓库长度<=0 说明仓库是空的。
if(this.nm.length <= 0){
this.t++;
//每波NPC间隔10个主循环
if(this.t > 10)
{
switch(this.cID){
// 时间队列模式:根据时间生成 左侧3 case10,20,30
case 0 :
this.create( 0 , 100 , -100 , 15 );
this.create( 0 , 100 , -200 , 15 );
this.create( 0 , 100 , -300 , 15 );
break;
//右侧3
case 1 :
this.create( 0 , 300 , -100 , 15 );
this.create( 0 , 300 , -200 , 15 );
this.create( 0 , 300 , -300 , 15 );
break;
//中间3
case 2 :
this.create( 1 , 200 , -100 , 15 , 300);
this.create( 1 , 300 , -250 , 15 , 300);
this.create( 1 , 100 , -250 , 15 , 300);
break;
}
this.t = 0;
this.cID = 0;
}
}
1)NPC子弹体系:NPC发射子弹
2)玩家打爆NPC
1)NPC和玩家子弹不一样,方向不同,碰撞对象不同。玩家子弹检测NPC,NPC碰撞检测玩家。
NPC子弹速度慢,子弹大。 因为飞行射击游戏的核心是 玩家躲子弹。
创建NZD类 ,复制ZD类到NZD类。
public constructor(id:number,x:number, y:number,//子弹生成位置
v:number, n:number,//速度与角度
game:MainGame) {
super();
this.game = game;
this.id = id;
switch(this.id){
case 0 :
this.im = Main.createBitmapByName("nzd1_png");
this.addChild(this.im);
break;
case 1:
this.im = Main.createBitmapByName("nzd16_png");
this.addChild(this.im);
break;
}
NZDManager类:
public zm:Array<ZD>; //Array动态数组(容器),通过添加,添加对象,移除对象。
public game:MainGame;
public constructor(game:MainGame) {
super();
this.game = game;
this.zm = new Array();
}
//每create一次就new一个
public create(id:number,x:number,y:number,v:number,n:number,game:MainGame){
//生成子弹
let one = new ZD(id,x,y,v,n,game);
//添加到世界
this.addChild(one);
//放到仓库数组最后
this.zm.push(one);
}
//更新所有子弹,找到每一颗子弹,每颗更新
public update(){
//整个仓库长度 ,利用循环可以循环出所有子弹
for(let i = 0 ; i < this.zm.length ; i++){
//找到每颗子弹
let one = this.zm[i];
one.update();
//若子弹太多,仓库会满,所以子弹需要移除
//子弹出屏,vis == false。移除
if(one.vis == false){
//先从场景移除
this.removeChild(one);
//仓库移除
this.zm.splice(i ,1);
//移除一个对象,长度-1
i--;
}
}
}
在Maingame中申请,构造,更新
构造时//因为是躲NPC子弹,所以放在玩家上面,可以很容易被看到。
this.addChild(this.nzm);
this.addChild(this.player);
NPC子弹发射次数
放在状态里面,子弹会发射很多,每次主循环执行一次
如果放在状态改变里,子弹会发射一组
case 1 :
//间隔性发射子弹
if(this.t % 3 == 0){
this.nm.game.nzm.create(0,this.x ,this.y , 15,180,this.nm.game);
this.nm.game.nzm.create(0,this.x ,this.y , 15,160,this.nm.game);
this.nm.game.nzm.create(0,this.x ,this.y , 15,200,this.nm.game);
}
this.t++;
需要检测玩家子弹和NPC碰撞
玩家子弹ZD,和NPC0
一个提供碰撞检测isHit(),一个update方法调用isHit(),检测碰撞
NPC提供isHit(),子弹x,y检测isHit
在ZDManager类中,存放着所有玩家打出去的子弹,update方法中,for循环整个仓库中的子弹,同时把每颗子弹one执行更新,one就是遍历仓库的每一颗子弹,然后每颗子弹更新,再拿每颗子弹的坐标和所有 NPC碰撞检测是否发生碰撞。
在调用isHit方法时,父类需要有isHit方法,依然是抽象方法,所有的子类都继承这个方法。
在NPC父类中,添加isHit()方法
在NPC类添加 public abstract idHit(x:number,y:number):boolean;
父类中若没有这个方法,子类怎么写也不能用,所有的NPC对外以父类使用 。
在管理者当中,想使用的方法,属性,必须属于父类,只能看到父类方法。
父类有了isHit方法,子类也必须有,因为父类要用。
所有子类实现此方法
在ZDManager类的update方法的one.update()添加以下碰撞代码
//j是NPC game是上一级,nm:所有NPC(工厂) ,nm:仓库数组Array.length 仓库长度
for(let j = 0 ; j < this.game.nm.nm.length; j ++){
let npc = this.game.nm.nm[j];
if(npc.isHit(one.x,one.y) == true){
//NPC消失
npc.vis = false;
//子弹消失
one.vis = false;
//子弹和NPC碰撞结束,
break;
}
}
在NPC0,1,中添加isHit()方法
父类申请hp属性
子类this.hp = 10 ;赋值,不用再申请
npc.hp -= one.gj;
//NPC消失
if(npc.hp <= 0 ){
npc.vis = false;
}
如果是种类索引,状态机实现多样化,只需要多写一个属性就可以。
如果是以有限状态机实现,父类子类时,如果想增加属性或方法,就得在父类中增加属性或方法,同时,所有子类都得增加属性或方法。
至此,实现初步的玩家攻击NPC直至消失。 NPC hp=3攻击3次消失。完成上面图片显示的效果。
今天,完成了飞行射击 NPC机制阵列效果出现和玩家击中NPC消失的效果,
名头还会增加特效和子弹弹幕效果。当中出现了许多错误,自己也在总结和完善。
因为gif图片压缩的原因,可能有一些卡顿失真,但实际效果不会卡顿,请原谅。
至此,第四天的开发笔记已经完成,学习需要坚持,坚持到最后一定会有结果,每天写下笔记来记录自己的学习内容, 以后有需要也可以查看,大家可以一起学习。
想要我一起学习的可以关注我的公众号 知言不尽 找到我,交流学习,获取图片素材和源代码。