游戏状态管理
游戏状态管理
1 游戏状态管理
1.1 游戏状态
如果游戏一直是游戏运行状态,显然是不合理的,正常的逻辑是:
- 游戏启动以后进入准备好状态
- 当点击鼠标以后,游戏开始运行
- 鼠标离开游戏范围时后,进入暂停
- 如果英雄机的生命被消耗干净,则游戏结束
1.2 鼠标单击事件
鼠标点击事件处理:
- 声明鼠标事件处理类(鼠标事件监听器)
- 创建鼠标事件处理类的对象,并且注册到面板上
- 当有鼠标事件发生时候,Java API会自动调用事件处理方法
案例:
鼠标事件处理类(鼠标事件监听器)
package demo01;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class MouseAction extends MouseAdapter{
@Override
public void mouseClicked(MouseEvent e){
System.out.println("鼠标点击了");
}
}
重构World的action方法,创建监听器对象并且注册到面板
public void action(){//启动方法
Timer timer=new Timer();
LoopTask task=new LoopTask();//定时器任务
timer.schedule(task,1000,1000/100);//规定计划每间隔多少时间执行
//将鼠标事件处理对象(鼠标监听器对象)挂载到当前面板上
MouseAction action=new MouseAction();
this.addMouseListener(action);
this.addMouseMotionListener(new MouseAdapter(){
@Override
public void mouseMoved(MouseEvent e){
//鼠标事件发生时候,e对象中包含着鼠标相关的数据
//如x,y
int x=e.getX();//获得发生鼠标事件时候,鼠标x坐标
int y=e.getY();//获得发生鼠标事件时候,鼠标y坐标
//System.out.println("鼠标移动ok:"+x+","+y);
hero.move(x,y);// 把英雄机移动动xy的位置
}
});
}
2 READY状态
利用鼠标事件控制变量state的状态。
游戏启动以后立刻进入READY状态,READY状态时候游戏效果实现步骤:
-
在World 类上声明游戏的4种状态,并且定义变量存储当前状态,默认值上READY
-
单击鼠标时切换到运行状态,监听鼠标事件,在点击鼠标时候,如果为READY状态,就切换到RUNNING状态,修改鼠标移动事件,READY状态时候,游戏不移动
-
修改定时任务LoopTask,处理READY状态时候各种对象状态
- 飞机不能出场,飞机也无需移动,子弹无需移动,不需要检测碰撞
- 为了增强视觉效果,可以让天空移动。
-
绘制开始图片,修改paint方法,在READY状态时候绘制开始图片
2.1 定义四种状态
重构World类:
public class World extends JPanel{
public static final int READY=0;//准备状态
public static final int RUNNING=1;//运行
public static final int PAUSE=2;//暂停
public static final int GAME_OVER=3;//结束
/*
*当前状态 ,初始值是READY
*/
private int state=READY;//把准备状态设置为state
2.2 重构鼠标事件
重构World类Action方法中的鼠标事件,RUNNING状态下才能移动英雄机,添加鼠标点击事件,点击发生时候将READY状态切换到RUNNING状态:
public void action(){//启动方法
Timer timer=new Timer();
LoopTask task=new LoopTask();//定时器任务
timer.schedule(task,1000,1000/100);//规定计划每间隔多少时间执行
//将鼠标事件处理对象(鼠标监听器对象)挂载到当前面板上
this.addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e){
//鼠标点击执行的方法
if(state==READY){//如果状态为准备状态
state=RUNNING;//把状态设置为运行
}
}
});
this.addMouseMotionListener(new MouseAdapter(){
@Override
public void mouseMoved(MouseEvent e){
if(state==RUNNING){//如果准备状态切换为运行状态。缩进括起来以后为只有运行为RUNNING才运行
//鼠标事件发生时候,e对象中包含着鼠标相关的数据
//如x,y
int x=e.getX();//获得发生鼠标事件时候,鼠标x坐标
int y=e.getY();//获得发生鼠标事件时候,鼠标y坐标
//System.out.println("鼠标移动ok:"+x+","+y);
hero.move(x,y);// 把英雄机移动动xy的位置
}
}
});
}
2.3 控制物体移动
重构World类,更改定时任务LoopTask类,设定只有在运行时候才能开火,创建飞机,移动物体,碰撞检查,清理无用飞机:
private class LoopTask extends TimerTask{//需要隐藏的属性和方法定义为private
public void run() {
index++;
if(state==RUNNING) {
fireAction();
createPlane();//如果还想创建飞机直接调用方法
objectMove();
hitDetection();
clean();//勿忘调用
runAway();
}
sky.move();//不进行move方法。paint方法重写。进行单独处理将无法呈现连续的天空(sky)
//调用重写绘制方法,这个方法会自动执行paint
repaint();
}
}
2.4 绘制开始图片
重构World类中的paint方法在READY时候绘制开始图片,原理是在原有内容上面覆盖了一个半透明的图片:
public void paint(Graphics g) {
sky.paint(g);
hero.paint(g);
for(int i=0; i<bullets.length; i++) {
//i=0 1 2 3 4 5a
bullets[i].paint(g);
}
//调用每个飞机的多态方法,实现多态的绘制
for(int i=0; i<planes.length; i++) {
planes[i].paint(g);
}
g.setColor(Color.white);//画白色
g.drawString("SCORE:"+score,20,40);//分数在框内20,40位置显示
g.drawString("LIFE:"+life,20,60);//血量在框内20,60位置显示
if(state==READY) {//当前状态为准备状态
Images.start.paintIcon(this, g, 0, 0);//飞机大战图像
}
}
2.5清除测试数据
清除World类构造器中的测试数据,保留天空和英雄,planes和bullets数组内容清除掉,但是数组要保持0长度的数组,表示天空中没有任何物体,否则会因为planes或者bullets为null造成空指针异常:
public World(){
//创建空数组,表示开始时候天空中没有物体
//物体出场时候会自动扩容
planes new FlyigObject[0];
bullets=new Bullets[0];
sky=new Sky();
hero =new Hero(148,380);
}
3 RUNNING与PAUSE
运行状态RUNNING与暂停状态PAUSE处理逻辑不难,在开发调试游戏过程中始终是运行状态下调试的,而暂停状态就是让一切物体不能运动,处理方案如下:
-
更新定时器任务,仅在运行状态时候执行(已实现)
- 可以创建新飞机
- 可以射击新子弹
- 可以移动每个物体
- 可以检测是否发生碰撞
- 清理无用的飞机子弹
-
运行时候,如果鼠标离开了当前面板区域,就进入暂停状态
-
暂停期间如果鼠标进入到当前面板区域就切换成运行状态
-
运行状态时候绘制暂停界面图片
3.1 鼠标进入,鼠标离开事件
public void action(){//启动方法
Timer timer=new Timer();
LoopTask task=new LoopTask();//定时器任务
timer.schedule(task,1000,1000/100);//规定计划每间隔多少时间执行
//将鼠标事件处理对象(鼠标监听器对象)挂载到当前面板上
this.addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e){
//鼠标点击执行的方法
if(state==READY){//如果状态为准备状态
state=RUNNING;//把状态设置为运行
}
}
@Override
public void mouseEntered(MouseEvent e){
System.out.println("鼠标进入");
}
@Override
public void mouseExited(MouseEvent e){
System.out.println("鼠标离开");
}
});
this.addMouseMotionListener(new MouseAdapter(){
@Override
public void mouseMoved(MouseEvent e){
if(state==RUNNING){//如果准备状态切换为运行状态。缩进括起来以后为只有运行为RUNNING才运行
//鼠标事件发生时候,e对象中包含着鼠标相关的数据
//如x,y
int x=e.getX();//获得发生鼠标事件时候,鼠标x坐标
int y=e.getY();//获得发生鼠标事件时候,鼠标y坐标
//System.out.println("鼠标移动ok:"+x+","+y);
hero.move(x,y);// 把英雄机移动动xy的位置
}
}
});
}
3.2 切换运行/暂停状态
重构World类中action方法中的鼠标事件,增加鼠标进入事件和离开事件:
public void action(){//启动方法
Timer timer=new Timer();
LoopTask task=new LoopTaskk();//定时器任务
timer.schedule(task,1000,1000/100);//规定计划每间隔多少时间执行
//将鼠标事件处理对象(鼠标监听器对象)挂载到当前面板上
this.addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e){
//鼠标点击执行的方法
if(state==READY){//如果状态为准备状态
state=RUNNING;//把状态设置为运行
}
}
@Override
public void mouseEntered(MouseEvent e){
System.out.println("鼠标进入");
if(state==PAUSE){
state=RUNNING;
}
}
@Override
public void mouseExited(MouseEvent e){
System.out.println("鼠标离开");
if(state==RUNNING){
state=PAUSE;
}
}
});
this.addMouseMotionListener(new MouseAdapter(){
@Override
public void mouseMoved(MouseEvent e){
if(state==RUNNING){//如果准备状态切换为运行状态。缩进括起来以后为只有运行为RUNNING才运行
//鼠标事件发生时候,e对象中包含着鼠标相关的数据
//如x,y
int x=e.getX();//获得发生鼠标事件时候,鼠标x坐标
int y=e.getY();//获得发生鼠标事件时候,鼠标y坐标
//System.out.println("鼠标移动ok:"+x+","+y);
hero.move(x,y);// 把英雄机移动动xy的位置
}
}
});
}
3.3 显示暂停图片
在游戏界面上覆盖一个半透明的暂停图片,感知目前的游戏状态:
public void paint(Graphics g) {
sky.paint(g);
hero.paint(g);
for(int i=0; i<bullets.length; i++) {
//i=0 1 2 3 4 5a
bullets[i].paint(g);
}
//调用每个飞机的多态方法,实现多态的绘制
for(int i=0; i<planes.length; i++) {
planes[i].paint(g);
}
g.setColor(Color.white);//画白色
g.drawString("SCORE:"+score,20,40);//分数在框内20,40位置显示
g.drawString("LIFE:"+life,20,60);//血量在框内20,60位置显示
if(state==READY) {//当前状态为准备状态
Images.start.paintIcon(this, g, 0, 0);//飞机大战图像
}
if(state==PAUSE){
Images.pause.paintIcon(this, g, 0, 0);//暂停图像
}
}
4 GAME_OVER
游戏结束:如果英雄的生命数量为0,不能再创建英雄了则游戏结束:
- 当英雄和某个飞机相撞时,飞机被撞死,英雄机也死亡
- 英雄在死亡状态下播放死亡动画,播放完毕后进入僵尸状态
- 如果英雄是僵尸状态,生命值减1,创建新英雄,如果没有生命可以减少了,则游戏结束
- 只有英雄活着才能射击
- 游戏结束状态时候点击鼠标进入READY状态
- 游戏结束状态时候绘制游戏结束图片
4.1 检测英雄是否发生碰撞
在World中创建runAway(逃跑)方法,用于检测英雄逃跑过程是否与飞机发生碰撞。
算法规则:
1.如果英雄是活着的时候,才能检测英雄是否与其他飞机发生了碰撞,如果碰撞发生,则英雄死掉,被撞飞机也要死掉,英雄死掉以后会播放死亡动画,动画播放完毕进入僵尸状态
如果英雄当前状态是动画播放完毕以后的僵尸状态,就创建一架新飞机,并且减少英雄机的数量
在定时循环中调用runAway方法
代码:
/*
* 检查英雄躲避飞机期间,是否发生了与飞机碰撞
*/
private void runAway() {
if(hero.isLiving()) {
for(int i=0; i<planes.length; i++) {
if(!planes[i].isLiving()) {
continue;
}
if(hero.duang(planes[i])) {
hero.goDead();
planes[i].goDead();
break;
}
}
}else if(hero.isZombie()) {//如果飞机是僵尸状态
if(life>0) {//如果生命大于0
hero=new Hero(138,380);//那么再创建一架新飞机
//保险:清理当前的全部飞机,避免与新英雄机相撞
for(int i=0; i<planes.length; i++) {
planes[i].goDead();
}
life--;//生命减少一条
}else {
state=GAME_OVER;//生命耗尽,游戏结束
}
}
}
在定时任务中调用这个runAway方法:
private class LoopTask extends TimerTask{//需要隐藏的属性和方法定义为private
public void run() {
index++;
if(state==RUNNING){
fireAction();
createPlane();//如果还想创建飞机直接调用方法
//调用重写绘制方法,这个方法会自动执行paint
objectMove();
hitDetection();
runAway();
clean();//勿忘调用
}
//调用重写绘制方法,这个方法会自动执行paint
sky.move();//不进行move方法。paint方法重写。进行单独处理将无法呈现连续的天空(sky)
repaint();
}
}
4.2 重构射击方法
只能英雄活着的时候才能射击(自动):
private void fireAction(){
if(! hero.isLiving()){
return;
}
if(index % 15==0){
//在定时任务中执行英雄开火方法
Bullet[] bubu=hero.openFire();
//子弹数组扩容
int len=bullets.length;
Bullet[] arr=Arrays.copyOf(bullets,len+bubu.length);
//将子弹添加到新数组最后位置
System.arraycopy(bubu, 0, arr, len, bubu.length);
//替换原数组
bullets=arr;
}
}
4.3 鼠标事件处理
重构鼠标事件处理方法:
- 重构鼠标单击事件:游戏结束状态时候点击鼠标回到READY状态,此时需要清理全部变量的值,将数据恢复到开始游戏的状态
- 重构鼠标移动事件:只有英雄活着的时候才能移动英雄的位置:
public void action(){//启动方法
Timer timer=new Timer();
LoopTask task=new LoopTask();//定时器任务
timer.schedule(task,1000,1000/100);//规定计划每间隔多少时间执行
//将鼠标事件处理对象(鼠标监听器对象)挂载到当前面板上
this.addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e){
//鼠标点击执行的方法
if(state==READY){//如果状态为准备状态
state=RUNNING;//把状态设置为运行
}else if(state==GAME_OVER){
score=0;
life=3;
planes=new FlyingObject[0];
bullets=new Bullet[0];
hero=new Hero(148,380);
state=READY;
}
}
@Override
public void mouseEntered(MouseEvent e){
System.out.println("鼠标进入");
if(state==PAUSE){
state=RUNNING;
}
}
@Override
public void mouseExited(MouseEvent e){
System.out.println("鼠标离开");
if(state==RUNNING){
state=PAUSE;
}
}
});
this.addMouseMotionListener(new MouseAdapter(){
@Override
public void mouseMoved(MouseEvent e){
if(state==RUNNING){//如果准备状态切换为运行状态。缩进括起来以后为只有运行为RUNNING才运行
//鼠标事件发生时候,e对象中包含着鼠标相关的数据
//如x,y
int x=e.getX();//获得发生鼠标事件时候,鼠标x坐标
int y=e.getY();//获得发生鼠标事件时候,鼠标y坐标
//System.out.println("鼠标移动ok:"+x+","+y);
if(hero.isLiving()){
hero.move(x,y);// 把英雄机移动动xy的位置
}
}
}
});
}
4.4 绘制GAME_OVER图片
游戏结束状态时候显示“游戏结束”界面:
public void paint(Graphics g) {
sky.paint(g);
hero.paint(g);
for(int i=0; i<bullets.length; i++) {
//i=0 1 2 3 4 5a
bullets[i].paint(g);
}
//调用每个飞机的多态方法,实现多态的绘制
for(int i=0; i<planes.length; i++) {
planes[i].paint(g);
}
g.setColor(Color.white);//画白色
g.drawString("SCORE:"+score,20,40);//分数在框内20,40位置显示
g.drawString("LIFE:"+life,20,60);//血量在框内20,60位置显示
if(state==READY) {//当前状态为准备状态
Images.start.paintIcon(this, g, 0, 0);//飞机大战图像
}
if(state==PAUSE){
Images.pause.paintIcon(this, g, 0, 0);//暂停图像
}
if(state==GAME_OVER){
Images.gameover.paintIcon(this, g, 0, 0);//结束图像
}
}
飞机大战内存管理
- 方法区:在内存中分配一个静态区域,分配以后不回收,全部的类加载到方法区,加载以后长期占用空间,空间就不回收了
- 栈:内存中存储方法中局部变量数据的临时区域,方法定义的局部变量,以及方法参数都分配在栈中,this也是局部变量,也分配在栈中,一旦方法执行结束,方法占用的区域就会释放,释放的栈空间可以重复利用
- 堆:内存中用于存储对象(属性)的区域,Java的对象全部分配在堆中,分配对象时候按照类中定义实例对象分配对象存储空间,对象在new运算时候创建的,直到没有引用对象了,Java垃圾回收器就会回收对象,对象回收以后内存空间可以被重新利用。
完整飞机大战代码
重构的细节:
- 反转爆炸动画加载顺序,目的是反转播放顺序
- 重构部分if语句为switch case
- 爆炸动画居中绘制
- 加快爆炸动画播放速度
- 重构World类的构造器,抽取了初始化算法,与鼠标点击事件复用
1 Images 图片素材管理类
package demo1;
import javax.swing.ImageIcon;
public class Images {//管理图片
public static ImageIcon[] airplane;
public static ImageIcon[] bigplane;
public static ImageIcon[] bee;
public static ImageIcon bullet;
public static ImageIcon[] hero;
public static ImageIcon[] bom;
public static ImageIcon sky;
public static ImageIcon start;
public static ImageIcon pause;
public static ImageIcon gameover;
static {//先创建数组,然后对数组初始化
airplane=new ImageIcon[2];
airplane[0]=new ImageIcon("images/airplane0.png");
airplane[1]=new ImageIcon("images/airplane1.png");
bigplane=new ImageIcon[2];
bigplane[0]=new ImageIcon("images/bigairplane0.png");
bigplane[1]=new ImageIcon("images/bigairplane1.png");
bee=new ImageIcon[2];
bee[0]=new ImageIcon("images/bee0.png");
bee[1]=new ImageIcon("images/bee1.png");
bullet=new ImageIcon("images/bullet.png");
hero=new ImageIcon[2];
hero[0]=new ImageIcon("images/hero0.png");
hero[1]=new ImageIcon("images/hero1.png");
bom=new ImageIcon[4];
bom[0]=new ImageIcon("images/bom1.png");
bom[1]=new ImageIcon("images/bom2.png");
bom[2]=new ImageIcon("images/bom3.png");
bom[3]=new ImageIcon("images/bom4.png");
sky=new ImageIcon("images/background.png");
start=new ImageIcon("images/start.png");
pause=new ImageIcon("images/pause.png");
gameover=new ImageIcon("images/gameover.png");
}
public static void main(String[] args) {
System.out.println(airplane[0].getImageLoadStatus());//8
System.out.println(airplane[1].getImageLoadStatus());
System.out.println(bigplane[0].getImageLoadStatus());
System.out.println(bigplane[1].getImageLoadStatus());
System.out.println(bee[0].getImageLoadStatus());
System.out.println(bee[1].getImageLoadStatus());
System.out.println(bullet.getImageLoadStatus());
System.out.println(hero[0].getImageLoadStatus());
System.out.println(hero[1].getImageLoadStatus());
System.out.println(bom[0].getImageLoadStatus());
System.out.println(bom[1].getImageLoadStatus());
System.out.println(bom[2].getImageLoadStatus());
System.out.println(bom[3].getImageLoadStatus());
System.out.println(sky.getImageLoadStatus());
System.out.println(start.getImageLoadStatus());
System.out.println(pause.getImageLoadStatus());
System.out.println(gameover.getImageLoadStatus());
}
}
2 FlyingObject 飞行物类
package demo1;
import java.awt.Graphics;
import javax.swing.ImageIcon;
/*
* 父类中定义从子类抽取的属性和方法
* 这种抽取方式称为“泛化”
*/
public abstract class FlyingObject {
public static final int LIVING=1;
public static final int DEAD=0;
public static final int ZOMBIE=-1;//定义活着,死亡,僵尸
protected int state=LIVING;//定义开始飞机都为活着
protected int life=1;//定义生命值都为一条命
protected double x;//需要留给子类的属性和方法定义为保护的protected
protected double y;
protected double width;
protected double height;
protected double step;
protected ImageIcon image;//所有属性
/**
* 当前对象动画帧,如果没有动画帧如;子弹,天空,则此属性保持null
*/
protected ImageIcon[] images;
/**
* 爆炸动画帧,如果没有保持null
*/
protected ImageIcon[] bom;
/**
* 动画帧播放计数器,%数组长度得到播放动画帧的位置
*/
protected int index=0;
/*
* 无参数构造器,减少子类的编译错误
*/
public FlyingObject() {
}
public FlyingObject(double x, double y, ImageIcon image, ImageIcon[] Images, ImageIcon[] bom) {
//当前图片 全部动画帧 爆炸图片
this.x=x;
this.y=y;
this.image=image;
this.images=Images;
this.bom=bom;
width=image.getIconWidth();
height=image.getIconHeight();
}
public abstract void move();
/*
* 被打击方法,没打击溢出加少名
* 如果打击成功,减命则返回true,否则false
*/
public boolean goDead() {
if(state==LIVING) {//如果当前状态为活着
life=0;//把生命值设成0
state=DEAD;//把当前状态设为死亡的
return true;//死亡返回true
}else {
return false;//没死亡返回false
}
}
public boolean hit(){
if(life>0){
life--;
if(life==0){
state=DEAD;
}
return true;
}
return false;
}
/*
* 动画帧的播放方法
*/
public void nextImage() {
switch(state) {//设置当前状态
case LIVING://第一种情况,活着,注意符号
if(images==null) {//没有动画帧时候,不播放动画帧图片
return;
}
//System.out.println(index +","+(index % images.length));//打桩
image=images[index++/30 % images.length];//除以30等于调慢30倍
break;//switch语句要写break
case DEAD://另一种情况 死了,注意符号
int index=i++/10;
if(bom==null) {//如果没有这个照片,不做处理
return;
}
if(index==bom.length) {//照片如果播放到头,成僵尸状态
state=ZOMBIE;
return;
}
image=bom[index];//不是僵尸状态,正常播放取照片交给当前照片
}
}
public void paint(Graphics g) {
nextImage();//换照片,换动画帧,然后再(在)绘制
//居中绘制照片,主要是绘制爆炸效果居中
int x1=(int)(x+(width-image.getIconWidth())/2);
int y1=(int)(y+(height-image.getIconHeight())/2);
image.paintIcon(null, g, (int)x, (int)y);
}
@Override
public String toString() {
String className=getClass().getName();
return className+" [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
}
/*
* 碰撞检测方法
* 1,在父类型定义的duang方法可以被任何子类继承,所有子类都获得碰撞检测功能
* 2.将方法中的数据类型定义为FluingObject就可以处理各种多态的参数
*/
public boolean duang(FlyingObject bu) {
FlyingObject p=this;
//计算小飞机的内切圆数据
double r1=Math.min(p.width, p.height)/2;
double x1=p.x+p.width/2;
double y1=p.y+p.height/2;
//计算子弹的内切圆数据
double r2=Math.min(bu.width, bu.height)/2;
double x2=bu.x+bu.width/2;
double y2=bu.y+bu.height/2;
//利用勾股定理计算圆心距离
double a=y2-y1;
double b=x2-x1;
double c=Math.sqrt(a*a + b*b);
//如果圆心距离小于半径和就表示两个圆相交 就是发生了碰撞
//System.out.println(c+","+(r1+r2));
return c<r1+r2;
}
public boolean isLiving() {
return state==LIVING;//是不是活的,状态等于活着
}
public boolean isDead() {//是不是死的,状态等于死亡
return state==DEAD;
}
public boolean isZombie() {//是不是僵尸,状态等于僵尸
return state==ZOMBIE;
}
public boolean outOfBounds() {
return(y<-height-50)||(y>700+50);
}
}
3 Sky天空
package demo1;
import java.awt.Graphics;
public class Sky extends FlyingObject{
private double y0;//需要隐藏的属性和方法定义为私有的private
public Sky() {
super(0,0,Images.sky,null,null);
step =0.8;
y0=-height;
}
public void move() {
y+=step;
y0+=step;//(两个坐标都在移动)
if(y>=height) {
System.out.println("第一个照片返回:y="+y);
y=-height;
}
if(y0>=height) {
System.out.println("第二个照片返回:y0="+y0);
y0=-height;
System.out.println("y="+y+" y0="+y0+" width="+width+" height="+height);
}
}
public void paint(Graphics g) {//改写父类中的方法
image.paintIcon(null, g, (int)x, (int)y);
image.paintIcon(null, g, (int)x, (int)y0);//对天空做修改,对两张照片进行处理
}
}
4 Bullet子弹
package demo1;
public class Bullet extends FlyingObject{
public Bullet(double x, double y) {
super(x,y,Images.bullet,null,null);
this.step =4;
}
/*
* 重写继承与超类的move方法,作用就是修改了超类move的行为
* 超类是向下移动,修改为向上移动
*/
public void move() {
y -=step;
}
}
5 Hero 英雄
package demo01;
public class Hero extends FlyingObject{
public Hero(double x, double y) {
super(x,y,Images.hero[0],Images.hero,Images.bom);
}
/*
* 重写move方法,空方法,目的是不动
* 修改超类中规定的向下飞,改成不动
*/
public void move() {
}
/*
*将鼠标机移动到鼠标位置X,Y
*@param x鼠标位置x
*@param y鼠标位置y
*/
public void move(int x, int y) {
this.x=x-width/2;//偏移,飞机随着鼠标移动
this.y=y-height/2;//偏移,飞机随着鼠标移动
}
/*
* 开火方法
*/
public Bullet fire() {
double x=this.x+width/2-5;//当前英雄机的位置加上英雄一半减5
double y=this.y-20;//y减去高度,假设为20,利用飞机的xy算出子弹的xy
Bullet bullet=new Bullet(x,y);
return bullet;
}
private int doubleFire=0;
public void doubleFire() {
doubleFire=20;//表示双枪次数为20
}
public Bullet[] openFire(){
if(doubleFire>0) {//双枪状态大于0,处于双枪状态
doubleFire--;//表示次数减1
double x=this.x+width/2-5;//当前英雄机的位置加上英雄一半减5
double y=this.y-20;//y减去高度,假设为20,利用飞机的xy算出子弹的xy
Bullet b1=new Bullet(x+15,y);//创建子弹+15
Bullet b2=new Bullet(x-15,y);//创建子弹-15
return new Bullet[] {b1,b2};//创建数组封装返回,不能省略new Bullet
}else {
Bullet bullet=fire();//调用fire方法
return new Bullet[] {bullet};//回到单枪
}
}
}
6 Plane 飞机类
package demo1;
import java.util.Random;
import javax.swing.ImageIcon;
public abstract class Plane extends FlyingObject{
public Plane() {
}
/*
* 根据位置初始化对象数据
*/
public Plane(double x, double y, ImageIcon image, ImageIcon[] Images, ImageIcon[] bom) {
super(x, y, image, Images, bom);
}
/*
* 利用算法实现飞机从屏幕上方出场
*/
public Plane(ImageIcon image, ImageIcon[] Images, ImageIcon[] bom) {
Random random=new Random();
this.image=image;
width=image.getIconWidth();
height=image.getIconHeight();
x=random.nextInt(400-(int)width);
y=-height;
this.images=Images;
this.bom=bom;
}
public void move() {
y +=step;
}
}
7 Enemy敌人接口
package demo1;
/*
*敌人接口
*/
public interface Enemy {
/**
* 获取当前敌人被打掉以后的得分
* @return
*/
int getScore();
}
8 Award 奖励接口
package demo1;
public interface Award {
/*
* 奖品
*/
int DOUBLE_FIRE=1;
/*
* 命
*/
int LIFE=2;
/*
* 获取奖品
* @return//返回值是DOUBLE_FIRE 之一
*/
int getAward();
}
9 Airplane 小飞机
package demo1;
public class Airplane extends Plane implements Enemy{
/*
* 飞机从屏幕上方随机位置出场
*/
public Airplane() {//添加重载构造器
super(Images.airplane[0],Images.airplane,Images.bom);
step=Math.random()*4+1.5;
}
/*
* 从自定位置出场
*/
public Airplane(double x, double y, double step) {
super(x,y,Images.airplane[0],Images.airplane,Images.bom);
this.step=step;
}
@Override
public int getScore() {
return 10;
}
}
10 Bigplane 大飞机
package demo1;
public class Bigplane extends Plane implements Enemy{
public Bigplane() {
super(Images.bigplane[0],Images.bigplane,Images.bom);
step=Math.random()*3+0.5;
life=5;//生命值为5
}
public Bigplane(double x, double y, double step) {
super(x,y,Images.bigplane[0],Images.bigplane,Images.bom);
this.step = step;
life=5;//生命值为5
}
@Override
public int getScore() {
return 100;
}
}
11 蜜蜂(奖品)
package demo1;
public class Bee extends Plane implements Award{
private int direction;//给蜜蜂一个方向
public Bee() {
super(Images.bee[0],Images.bee,Images.bom);
step=Math.random()*5+2;
direction=Math.random()>0.5?1:-1;//用随机数初始化,大于0.5的时候1,否则为负1
}
public Bee(double x, double y, double step) {
super(x,y,Images.bee[0],Images.bee,Images.bom);
this.step=step;
direction=Math.random()>0.5?1:-1;
}
/*
* 重写父类型move方法修改为斜向飞行
*/
public void move() {
//调用父类型方法,复用父类型定义的算法
super.move();//向下飞行y++
x+=direction;
if(x<0) {//如果蜜蜂碰到了左边缘
direction=1;//往反方向飞,向右飞
}else if(x+width>400) {//x+蜜蜂的宽度大于屏幕的宽度
direction=-1;//从右向左飞
}
}
@Override
public int getAward() {
return Math.random()>0.5?DOUBLE_FIRE:LIFE;
}
}
12 World 世界
package demo1;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class World extends JPanel{
public static final int READY=0;//准备状态
public static final int RUNNING=1;//运行
public static final int PAUSE=2;//暂停
public static final int GAME_OVER=3;//结束
/*
*当前状态
*/
private int state=READY;//把准备状态设置为state
private FlyingObject[] planes;//所有的可以被打掉的战机
private Bullet[] bullets;
private Sky sky;
private Hero hero;
private int index=0;//计数器
private int life=3;//英雄生命值3条命
private int score=0;
/*
* 利用构造器初始化世界中每个物体
*/
//public void World(){
// init();
//}
//private void init(){}
public World(){
life=3;
score=0;
planes=new FlyingObject[0];//创建数组对象!
bullets =new Bullet[0];
sky=new Sky();
hero=new Hero(138,380);
}
public void paint(Graphics g) {
sky.paint(g);
hero.paint(g);
for(int i=0; i<bullets.length; i++) {
//i=0 1 2 3 4 5
bullets[i].paint(g);
}
//调用每个飞机的多态方法,实现多态的绘制
for(int i=0; i<planes.length; i++) {
planes[i].paint(g);
}
g.setColor(Color.white);//画白色
g.drawString("SCORE:"+score,20,40);//分数在框内20,40位置显示
g.drawString("LIFE:"+life,20,60);//血量在框内20,60位置显示
if(state==READY) {//当前状态为准备状态
Images.start.paintIcon(this, g, 0, 0);//飞机大战图像
}
if(state==PAUSE) {
Images.pause.paintIcon(this, g, 0, 0);//暂停图像
}
if(state==GAME_OVER) {
Images.gameover.paintIcon(this, g, 0, 0);//结束图像
}
}
public static void main(String[] args) {
JFrame frame=new JFrame();
World world=new World();
frame.add(world);
frame.setSize(400,700);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
//调用action方法启动定时器
world.action();
}
//private int index=0;//计数器
//index=0 1 2 3 4 5 6 7 8 9 ....
// * *
// index%4=0 缩小飞机出场的频率
public void createPlane() {//创建一架飞机。进入到场地中
if(index % 16==0) {
Random random=new Random();
int n=random.nextInt(10);//0-9
Plane plane;//Java不允许一个变量里面重复定义//也可以更改为FlyingObject
switch(n) {
case 8:
case 7:
plane=new Bigplane();
break;
case 9:
plane=new Bee();
break;
default:
plane=new Airplane();
}
planes=Arrays.copyOf(planes, planes.length+1);//数组容量扩展+1
//将新飞机添加到新数组到最后位置
planes[planes.length-1]=plane;
}
}
/*
* 添加内部类,实现定时计划任务
* 为何使用内部类实现定时任务
* 1,隐藏定时任务到world类中
* 2,可以访问外部类中的数据,飞机,子弹等
*/
private class LoopTask extends TimerTask{//需要隐藏的属性和方法定义为private
public void run() {
index++;
if(state==RUNNING) {
fireAction();
createPlane();//如果还想创建飞机直接调用方法
objectMove();
hitDetection();
clean();//勿忘调用
runAway();
}
sky.move();//不进行move方法。paint方法重写。进行单独处理将无法呈现连续的天空(sky)
//调用重写绘制方法,这个方法会自动执行paint
repaint();
}
}
private void objectMove() {//把内部类移动至当前的外部类
for(int i=0; i<planes.length; i++) {//执行飞机移动方法;是多态的移动方法,每个飞机都不同
if(planes[i].isLiving()) {//如果活着就动,死了便不动了
planes[i].move();//利用for循环简化各项飞行代码
}
}
for(int i=0; i<bullets.length; i++) {
if(bullets[i].isLiving()) {//如果活着就动,死了便不动了
bullets[i].move();
}
}
}
public void action() {//启动方法
Timer timer=new Timer();
LoopTask task=new LoopTask();//定时器任务
timer.schedule(task, 100,1000/100);//规定计划每间隔多少时间执行
this .addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
//鼠标点击执行的方法
if(state==READY) {//如果状态为准备状态
state=RUNNING;//把状态设置为运行
}else if(state==GAME_OVER) {
score=0;
life=3;
planes=new FlyingObject[0];
bullets=new Bullet[0];
hero=new Hero(138,380);
state=READY;
}
}
@Override
public void mouseEntered(MouseEvent e) {//鼠标进入
System.out.println("鼠标进入");
if(state==PAUSE) {//如果当前为暂停状态
state=RUNNING;//那么改为运行
}
}
@Override
public void mouseExited(MouseEvent e) {//鼠标退出
System.out.println("鼠标离开了");
if(state==RUNNING) {//如果当前为运行状态
state=PAUSE;//那么改为暂停
}
}
});
this.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if(state==RUNNING) {//如果准备状态切换为运行状态。缩进括起来以后为只有运行为RUNNING才运行
//鼠标事件发生时候,e对象中包含鼠标相关的数据
//如;x.y等
int x=e.getX();//获取发生鼠标事件时候,鼠标x坐标
int y=e.getY();//获取发生鼠标事件时候。鼠标y坐标
//System.out.println("鼠标移动:"+x+","+y);
if(hero.isLiving()) {
hero.move(x,y);// 把英雄机移动动xy的位置
}
}
}
});
}
//private int score=0;
/**
* 分数统计方法
* @param obj是被统计的对象
*/
public void scores(FlyingObject obj) {//对飞行物进行统计
if(obj.isDead()) {//检测一下飞机是不是死飞机
if(obj instanceof Enemy) {//检测一下obj是不是敌机
Enemy enemy=(Enemy)obj;//如果是就进行转换
score +=enemy.getScore();//对分数进行统计
System.out.println("score:"+score);//控制台输出一下分数
}
/*
* 处理奖励规则
*/
if(obj instanceof Award) {//如果被打掉的飞机是奖品类型
Award award=(Award)obj;//转换为奖品类型
int type=award.getAward();//拿到奖品
if(type==Award.DOUBLE_FIRE) {//如果奖品为双枪
hero.doubleFire();//把英雄设为双枪
}else if(type==Award.LIFE){//如果奖品为生命
life++;//生命加1
}
}
}
}
public void hitDetection() {
//拿到每个子弹
for(int i=0; i<bullets.length; i++) {
if(!bullets[i].isLiving()) {
continue;
}
for(int j=0; j<planes.length; j++) {
if(!planes[j].isLiving()) {
continue;
}
if(planes[j].duang(bullets[i])) {
//System.out.println(planes[j]+"撞到:"+bullets[i]);
bullets[i].goDead();//子弹去死
planes[j].hit();//飞机被打一下
scores(planes[j]);//飞机打掉了检测一下
}
}
}
}
private void fireAction() {
if(!hero.isLiving()) {//如果不是活着状态不执行下一步
return;
}
if(index % 15==0) {
//在定时任务中执行英雄开火方法
//Bullet bullet=hero.fire();
Bullet[]bubu=hero.openFire();
int len=bullets.length;
//子弹数组扩容
//bullets=Arrays.copyOf(bullets, bullets.length+1);
Bullet[]arr=Arrays.copyOf(bullets, len+bubu.length);
//将子弹添加到新数组到最后位置
//bullets[bullets.length-1]=bullet;
System.arraycopy(bubu,0,arr,len,bubu.length);
bullets=arr;
}
}
public void clean() {
FlyingObject[] living=new FlyingObject[planes.length];
int index=0;
for(int i=0; i<planes.length; i++) {
if(planes[i].isZombie()||planes[i].outOfBounds()) {//死亡并且解决内存泄漏走下一步
continue;
}
living[index++]=planes[i];
}
planes=Arrays.copyOf(living, index);
Bullet[]arr=new Bullet[bullets.length];
index=0;
for(int i=0; i<bullets.length; i++) {
if(bullets[i].isDead()||bullets[i].outOfBounds()) {//死亡并且解决内存泄漏走下一步
continue;
}
arr[index++]=bullets[i];
}
bullets=Arrays.copyOf(arr,index);
//System.out.println(planes.length+","+bullets.length);
}
public void testClean() {//clean方法
System.out.println(planes.length);
planes[0].state=FlyingObject.ZOMBIE;
planes[3].state=FlyingObject.ZOMBIE;
clean();//调用方法
System.out.println(planes.length);
}
/*
* 检查英雄躲避飞机期间,是否发生了与飞机碰撞
*/
private void runAway() {
if(hero.isLiving()) {
for(int i=0; i<planes.length; i++) {
if(!planes[i].isLiving()) {
continue;
}
if(hero.duang(planes[i])) {
hero.goDead();
planes[i].goDead();
break;
}
}
}else if(hero.isZombie()) {//如果飞机是僵尸状态
if(life>0) {//如果生命大于0
hero=new Hero(138,380);//那么再创建一架新飞机
for(int i=0; i<planes.length; i++) {
planes[i].goDead();
}
life--;//生命减少一条
}else {
state=GAME_OVER;//生命耗尽,游戏结束
}
}
}
}