Egret实战开发笔记,飞行射击游戏(六)

今天是开发飞行射击游戏第五天,玩家多类型子弹及状态和关卡模式。

简介

实现玩家特殊子弹,激光,追踪导弹。
玩家多状态 下方飞出 正常游戏 胜利等待 胜利飞出 以及
玩家保护 和 关卡切换

实现效果

本来想录视频转GIF的,但是gif文件过大,超过5M又上传不了,而且压缩后失帧严重,仅截取了一部分转为gif, 请大家原谅。

在这里插入图片描述在这里插入图片描述

一.激光

ZD类中
正常的子弹按照自己的轨迹运动,激光每次跟着玩家运动。激光的轨迹不是沿着自己的轨迹,而是在玩家移动的基础上有一个偏移坐标。即子弹跟着玩家移动。
需要一个属性:dx,dy

public dx:number;
public dy:number;			//玩家坐标基础上的偏移值
public static fi:number = 0 ;	//激光的动画帧

//激光类型

case 10:
				//用动画帧构造图片
				this.im = Main.createBitmapByName("pzd4"+ZD.fi+"_png");
				//每发射一颗子弹 就加一张
				ZD.fi++;		
				if(ZD.fi >= 10) ZD.fi = 0;
				this.addChild(this.im);
				//每次主循环发射一颗,每颗间距离是80
				this.vy = -80;  //-80为向上
				this.vx = 0 ;
				this.dx = this.dy = 0 ;
				this.n = 0;
				this.gj = 10;
			break;

更新update():
//特殊的更新需要放在前面,进去直接更新,更新完return跳出结束更新

if(this.id == 10){
	//移动的是它和飞机之间的差值
	this.dx +=this.vx;
	this.dy +=this.vy;

	//偏移值:基础值+偏移
	//激光的坐标是在玩家坐标基础上加上改变值,移动的是改变值。这样就实现激光跟随玩家移动
	this.x = this.game.player.x + this.dx;
	this.y = this.game.player.y + this.dy;
	//出屏检测 
	if(this.y < -100 ){
		this.vis = false;
	}
	return;
}

什么叫偏移?
对激光来说移动的不是自己的坐标,而是移动和飞机之间的距离,是偏移值,而不是实际坐标

二.跟踪子弹的实现

实现效果
在这里插入图片描述

跟朝向型子弹不同。朝向子弹是NPC发的,发射时已经算好了子弹位置和玩家的位置,直接朝向玩家发射,子弹是直线型。

跟踪子弹是玩家发射的,找到NPC,根据NPC角度和坐标和自身的角度算出一个玩家该朝向的角度,而算出角度后,根据目前的角度和该朝向角度的差值进行移动。比如玩家是90度,实际要0度才能达到,并不是直接从90度变为0度,而是90.80,70,。。一个弧形,依次递减到需要朝向的角度。

1)NPC的选取:
需要选择屏幕当中的一个NPC,如果有就跟踪,如果没有就是竖直向上.

在NPCManager中

	//获取NPC
public getNPC():NPC{
	//仓库长度>0说明有NPC,如果没有跳出
	if(this.nm.length > 0 ){
		//有npc 随机取一个npc。
		let npc = this.nm[Math.floor(Math.random()* this.nm.length)]
		//判断npc是否在屏幕内,满足条件在屏幕内
		if(npc.x > 0 && npc.x < 480 && npc.y > 0 && npc.y < 800){
			return npc;
		}
	}
	//没找到 为空
	return null;
}

2)计算打到NPC的角度
3)更新角度的改变打到NPC

申请
public npc:NPC; //追踪的目标

//跟踪导弹
		case 20:
			this.im = Main.createBitmapByName("pzd1_3_png");
			this.npc = null;	
		break;

更新update()

//特殊的更新需要放在前面,进去直接更新,更新完return跳出结束更新

if(this.id == 20){
	let bn = 0 ;		//目标角度 向上
  	 if(this.npc == null){
		   this.npc = this.game.nm.getNPC();
	   }else{
		   bn = Math.atan2(this.npc.x -this.x,this.y-this.npc.y);
		   //这个角度是弧度制,转换成角度值
		   bn = bn * 180/ Math.PI;
	   }

//N角度限制在 -180°~180°

while(this.n <= -180)
	this.n += 360;
while(this.n > 180)
	this.n -= 360;
//若角度差值小于5度,两个角度变为等值
if(Math.abs(this.n - bn) < ZD.VN){
	this.n = bn;

}else{
	//
	if(this.n < bn){
		if(this.n < bn - 180)
			this.n -= ZD.VN;
		else
			this.n += ZD.VN;
	}else{
		if(this.n > bn + 180)
			this.n += ZD.VN;
		else 
			this.n -= ZD.VN;
	 }
}	
//sin用的是弧度制 n是角度制,所以 n* Math.PI / 180 转换为弧度制。
//重新计算速度与角度
this.vx = this.v * Math.sin(this.n * Math.PI / 180);
this.vy= -this.v * Math.cos(this.n * Math.PI / 180);
this.im.rotation = this.n;

this.x +=this.vx;
this.y +=this.vy;

//出屏检测

if(this.x < -100 || this.x > 580 || this.y < -100 || this.y > 900){
		this.vis = false;
}
return;
}

注意:反三角函数求出的角度是 -180°180°,不是0°360°

将n角度从0°~360°转换为 -180°~ 180°

while(this.n <= -180)
	this.n += 360;
while(this.n > 180)
	this.n -=360;

在Player类中 fire方法
//追踪导弹

this.t++;
if(this.t >= 10){
	 this.game.zm.create(20,this.x,this.y,15,135,this.game);
	 this.game.zm.create(20,this.x,this.y,15,-135,this.game);
	 this.game.zm.create(20,this.x,this.y,15,45,this.game);
	 this.game.zm.create(20,this.x,this.y,15,-45,this.game);
	 this.t = 0 ;
}

53

三.玩家多状态

0 下方飞出
1 正常游戏
10 胜利等待
11 胜利飞出
在Player的更新update方法中

switch(this.m){
			case 0 :
				this.y -=this.v;
				if(this.y <= 400){
					this.m =1;
					this.t = 0 ;
				}
			break;
			case 1 :
				this.fire();
				this.movePlayer();
				break;
			case 10:
				this.t++;
				if(this.t >= 20){
					this.t = 0;
					 this.m = 11;
				}
			break;
			case 11:
				this.y -=this.vy;
				this.vy +=3;
				if(this.y < -200){
					//游戏胜利的切换
				}
			break;
		}

整合movePlayer方法
将update方法中这段代码变为movePlayer方法

public movePlayer(){

		if(this.isDown == true ){
			let a = this.ny - this.oy;
			let b = this.nx - this.ox;
			let c = Math.sqrt(a*a + b*b);
		
		if( c > this.v){
			this.vx = this.v*b/c;
			this.vy = this.v*a/c;
			this.ox += this.vx;
			this.oy += this.vy;
		}else{
			this.vx = b;
			this.vy = a;
			this.ox = this.nx;
			this.oy = this.ny;
		}
		//飞机跟着速度一起移动
		 this.x +=this.vx ;
		 this.y +=this.vy ;
		 //边界检测
		 if(this.x < 0)
			 this.x = 0;
		 else if(this.x > 480)
		 	this.x = 480;
		if(this.y < 0)
			this.y = 0;
		else if(this.y > 800)
			this.y = 800;
		}
		else{
			this.vx = 0;
		}


		if(this.vx < 0 ){
			//向左飞:
			if(this.fi > -2)
				this.fi --;
			}else if(this.vx > 0)
			{
				if(this.fi < 2)
				this.fi++;
			}
			else{
				this.fi = 0 ;
			}
			this.resetFI();
	}

在update方法中增加状态机

四.玩家的死亡处理

玩家中增加public isHit(x:number , y:number):boolean{}

public isHit(x:number , y:number):boolean{
	// this.x this.y是圆心, 60是半径  勾股定理 ,进入圆算碰撞。
	//两点间距离公式。横坐标差的平方+纵坐标差的平方再开方 
	if(this.bhT > 0 ){
		if((this.x -x )*(this.x -x ) + (this.y -y )*(this.y - y ) < 60*60){
			return true;
		}
		return false;
	}
	if(this.m !=1)
		return false;
	if(Math.abs(this.x - x) < 20 && Math.abs(this.y -y )<20){
		//玩家死亡
		return true;	
	}
		return false;
}

增加

public dead(){
		for(let i = 0 ; i < 10 ; i++){		//环数
			let dn =Math.random()*Math.PI * 2;
			for(let j = 0 ; j < 15 ; j ++) 		//每个环爆炸个数   
			{
				this.game.tm.create(0,
				this.x + (i+1)*30* Math.sin(dn+Math.PI*2*j/15),
				this.y + (i+1)*30* Math.cos(dn+Math.PI*2*j/15),
				i,Math.random() * 10 +5,this.game);
			}
		}
		this.x = 240;
		this.y = 1000;
		this.m = 0 ;
		this.t = 0;		 
	}

五.增加玩家保护

申请public bh:egret.Bitmap; // 保护罩图片
public bhT:number; //保护罩倒计时 持续3秒
构造 this.bh.anchorOffsetX = this.bh.width/2;
this.bh.anchorOffsetY =this.bh.height/2;
this.addChild(this.bh);
this.bh.scaleX = this.bh.scaleY = 0.5;
this.bhT = 60; //60次主循环是3秒

更新;
//保护罩时间>0。减到0 消失,图片可见性为false

if(this.bhT > 0 ){
	this.bhT--;
	if(this.bhT <=0){
		this.bh.visible = false;
	}
}

在NZDManager类中的更新方法中修改

if(this.game.player.isHit(one.x , one.y) ==true ){

				one.vis = false;
				if(this.game.player.m == 1 && this.game.player.bhT <= 0 ){
					this.game.player.dead();
				} 
			}

在玩家死亡方法最后添加
this.bhT = 60;
this.bh.visible = true;

六.玩家过关 与 关卡切换

BOSS0中添加死亡爆炸方法

public dead(){
			for(let i = 0 ; i < 10 ; i++){		//环数
			let dn =Math.random()*Math.PI * 2;
			for(let j = 0 ; j < 15 ; j ++) 		//每个环爆炸个数   
			{
				this.nm.game.tm.create(0,
				this.x + (i+1)*30* Math.sin(dn+Math.PI*2*j/15),
				this.y + (i+1)*30* Math.cos(dn+Math.PI*2*j/15),
				i,Math.random() * 10 +5,this.nm.game);
			}
		} 
		this.nm.game.player.win();
	}

在Player中添加win方法
//通过关卡

public win(){
	 this.t = 0 ; 
	 this.m = 10;		//胜利 玩家飞出屏幕
}

2)关卡切换
Maingame中申请 public level:number; //判断当前关卡序号
构造:this.level = 0;

NPCManager中,不同的关卡生成不同的阵列。
对生成的switch进行状态机嵌套

在Maingame中添加
reset方法

public reset(level:number)
    {
        this.level = level;
        this.player.reset();
        this.bg.reset();
        this.nm.reset();
        
    }

BG类中

public reset(){
	switch(this.game.level){
		case 0:
			for(let i = 0 ; i < 2; i ++){
			this.bg[i].texture = RES.getRes("bg11_jpg");
			}
		break;
		case 1:
			for(let i = 0 ; i < 2; i ++){
			this.bg[i].texture = RES.getRes("bg31_jpg");
			}
		break;
	}
}

NPCManager类中

//仓库中所有东西 移除

public reset(){
		//整个仓库长度 ,利用循环可以循环出所有子弹
		for(let i = 0 ; i < this.nm.length ; i++){
			//找到每颗子弹
			let one = this.nm[i];
				this.removeChild(one);
				this.nm.splice(i ,1);
				i--;
		
		}

		this.t =0;
		this.cID = 0;
		}

Player类中

public dead(){
		for(let i = 0 ; i < 10 ; i++){		//环数
			let dn =Math.random()*Math.PI * 2;
			for(let j = 0 ; j < 15 ; j ++) 		//每个环爆炸个数   
			{
				this.game.tm.create(0,
				this.x + (i+1)*30* Math.sin(dn+Math.PI*2*j/15),
				this.y + (i+1)*30* Math.cos(dn+Math.PI*2*j/15),
				i,Math.random() * 10 +5,this.game);
			}
		}
		this.x = 240;
		this.y = 700;
		this.m = 0 ;
		this.t = 0;		 
		this.bhT = 60;
		this.bh.visible = true;
	}
public reset(){
		this.isDown = false;
		this.x = 240;this.y = 1000;
		this.m = this.t = 0;
		this.bhT = 60;			//60次主循环是3秒
	}

至此,第五天的开发笔记已经完成,学习需要坚持,坚持到最后一定会有结果,每天写下笔记来记录自己的学习内容, 以后有需要也可以查看,大家可以一起学习。

想要我一起学习的可以关注我的公众号 知言不尽 找到我,交流学习,获取图片素材和源代码。

发布了7 篇原创文章 · 获赞 117 · 访问量 6806

猜你喜欢

转载自blog.csdn.net/qq_39207481/article/details/105799648
今日推荐