今天是开发飞行射击游戏第五天,玩家多类型子弹及状态和关卡模式。
简介
实现玩家特殊子弹,激光,追踪导弹。
玩家多状态 下方飞出 正常游戏 胜利等待 胜利飞出 以及
玩家保护 和 关卡切换
实现效果
本来想录视频转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秒
}
至此,第五天的开发笔记已经完成,学习需要坚持,坚持到最后一定会有结果,每天写下笔记来记录自己的学习内容, 以后有需要也可以查看,大家可以一起学习。
想要我一起学习的可以关注我的公众号 知言不尽 找到我,交流学习,获取图片素材和源代码。