@飞机大战总结
飞机大战
JavaAWT实现“飞机大战”
具体实现效果图:
主要需要的类:(解释取自Java JDK 中文文档.)
1.Frame -java.awt.Frame 是带有标题和边框 的顶层窗口
2.Graphics-java.awt.Graphics是所有图形上下文的抽象基类,允许应用程序在组件以及闭屏图像上进行绘制,通俗的说,就是Graphics类提供一个画笔
4.Image-java.awt.Image 是以特定平台的方式获取图形
5.BufferedImage-java.awt.image.BufferedImage 是Image的子类,描述具有可访问图像数据缓冲区的Image
6.ImageIO-javax.imageio.ImageIO 该类包含一些用来查找 ImageReader 和 ImageWriter 以及执行简单编码和解码的静态便捷方法
7.WindowAdapter-java.awt.event.WindowAdapter 是抽象类,此类中的方法为空,此类存在的目的是方便创建侦听器对象
8.WindowEvent-java.awt.event.WindowEvent 是窗口状态改变的低级别事件,比如打开,关闭,激活,停用等
9.KeyAdapter-java.awt.event.KeyAdapter 抽象类,目的是方便创建侦听键盘对象,KeyAdapter 对象实现 KeyListener 接口
10.KeyEvent-java.awt.event.KeyEvent 表示组件中发生键击的事件,当按下、释放或键入某个键时,组件对象(如文本字段)将生成此低级别事件。该事件被传递给每一个 KeyListener 或 KeyAdapter 对象,这些对象使用组件的 addKeyListener 方法注册,以接收此类事件,发生事件时,所有此类侦听器对象都将获得此 KeyEvent
11.URL-java.net.URL 代表一个统一资源定位符,它是指向互联网“资源”的指针,资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,比如对数据库或者搜索引擎的查询
12.IOException-java.io.IOException 当发生某种 I/O 异常时,抛出此异常
13.Thread -java.lang.Thread 线程 是程序中的执行线程,Java 虚拟机允许应用程序并发地运行多个执行线程
代码实现
模拟飞机大战,首先我们需要两个类,飞机类plane和子弹类Bullet,封装了各自对象的属性和方法 ,分析飞机属性方法,向上延伸出一个父类PictureBase,以便于创建不同的飞机。常量类Constant来存放游戏的数据,便于代码管理,工具类GameTools在本项目中存放调取图片的方法,爆炸类Blast,显示当飞机和子弹碰撞的时候。主界面MyPlaneWarFrame
1.父类PictureBase
public class PictureBase
{
Image img;//图片
int x,y,wide,high;//图片的起始位置,宽高
int speed;//物体速度
public PictureBase(){}//构造器初始化
public PictureBase(int x, int y, int wide, int high, int speed,Image img)//飞机
{
this.x = x;
this.y = y;
this.wide = wide;
this.high = high;
this.speed = speed;
this.img=img;
}
//有绘画功能,传入参数理解为传入了一个画笔
public void paintMyself(Graphics g)
{
g.drawImage(img,x,y,wide,high,null);
}
//所有的物体都是矩形,当你获得对应的矩形的时候,我们就可以做一些相关的判断的操作
public Rectangle getRec()
{
return new Rectangle((int)x,(int)y,wide,high);
}
}
2.飞机类Plane
作为一个飞机首先得有个形状,这里用一张图片实现,设置它的长宽,起始坐标,飞机要上下左右移动,其实就是操作它的坐标,用布尔值判断飞机飞向哪个方向,然后对坐标值进行增减,以及布尔值判断飞机生死
飞机类所要实现的方法,第一,需要在我们的窗口将它自己展现出来;第二,它移动时对坐标相应的操作;第三,我们需要实现的效果是按下键盘的上下左右键,飞机对应的上下左右移动,松开键盘,飞机就停止移动;第四,为了判断飞机的生死需要返回可容纳飞机的最小矩形,这里就是容纳飞机的最小矩形如果和子弹最小矩形有相交,则飞机死亡
飞机继承自父类的属性:长宽,起始坐标,速度,图片
飞机继承自父类的方法:getRec();
飞机独特的属性有:1.是否存活 2.飞行方向,
飞机的独特方法是:1.通过键入上下左右,可以改变飞机方向的boolean值, 2.飞机具有独特的飞行方法(复写drawMyself方法)。
public class Plane extends PictureBase
{
boolean live=true;//飞机是否活着
boolean up, down, left , right;//飞机飞行方向
public Plane(int x,int y,int wide,int high,int speed,Image img)
{
super(x,y,wide,high,speed,img);
}
public void drawMyself(Graphics e)
{
if(live)
{
super.paintMyself(e);
if (left)
x -= speed;
if (right)
x += speed;
if (up)
y -= speed;
if (down)
y += speed;
}
}
public void addDistance(KeyEvent e)
{
switch(e.getKeyCode())
{
case KeyEvent.VK_UP:
up=true;
break;
case KeyEvent.VK_DOWN:
down=true;
break;
case KeyEvent.VK_RIGHT:
right=true;
break;
case KeyEvent.VK_LEFT:
left=true;
}
}
public void reduceDistance(KeyEvent e)
{
switch(e.getKeyCode())
{
case KeyEvent.VK_UP:
up=false;
break;
case KeyEvent.VK_DOWN:
down=false;
break;
case KeyEvent.VK_RIGHT:
right=false;
break;
case KeyEvent.VK_LEFT:
left=false;
}
}
}
3.子弹类Bullet
类似于飞机类,子弹类也需要坐标控制它的飞行,子弹大小,由于子弹是无规则的在窗口随意运动,所以需要设置子弹的角度,子弹类要实现的方法比较简单,第一,画无规则从窗口中心散出的子弹;第二,返回最小矩形
子弹继承自父类的属性:长宽,起始坐标,速度,图片
子弹继承自父类的方法:无
子弹独特的属性有:方向
子弹的独特方法是:复写drawMyself方法
public class Bullet extends PictureBase
{
double degree;//炮弹具有角度
public Bullet()
{
x=200;
y=200;
wide=5;
high=5;
degree=Math.random()*Math.PI*2;//返回0~1中的随机数
speed=10;
}
public void drawMyself(Graphics e)
{
Color c=e.getColor();
e.setColor(Color.yellow);
e.fillOval(x,y,wide,high);
e.setColor(c);
x+=speed*Math.cos(degree);
y+=speed*Math.sin(degree);
if(y>Constant.Game_HEIGHT-this.high||y<40)
{
degree=-degree;
}
if(x> Constant.Game_Windth-this.wide||x<0)
{
degree=Math.PI-degree;
}
}
}
4.常量类
封装了一些常量,便于修改,在飞机类和子弹类中也使用了该类
public class Constant
{
//游戏窗口的宽度
public static final int Game_Windth=500;
//游戏窗口的高度
public static final int Game_HEIGHT=500;
}
5.工具类
该工具类只封装了加载图片这个方法,为便于调用方法设置为静态
public class GameTools
{
//构造器私有化,防止别人创建本类对象
private GameTools(){}
public static Image getImage(String path)
{
BufferedImage img=null;
URL u=GameTools.class.getClassLoader().getResource(path);
try
{
img= ImageIO.read(u);
}
catch(IOException e)
{
e.printStackTrace();
}
return img;
}
}
6.爆炸类
爆炸也需要爆炸地点即坐标,用一系列图片来实现爆炸,所以也需要设置图片的长度和宽度,以及存放这些图片的数组,使用static数组存放图片图片,使用静态代码块初始化,还需要设置一个int变量来计数,确保你一系类图片加载结束以后就不再爆炸,不会炸个没完没了
也要有一个drawMyself的方法,将爆炸的图片“画”到主界面上
public class Blast
{
double x,y;//爆炸的坐标,爆炸物的宽高
Blast(){}
Blast(double x,double y)
{
this.x=x;
this.y=y;
}
static Image[]blast=new Image[16];
int count=1;
static
{
for(int i=0;i<16;i++)
{
blast[i]=GameTools.getImage("photo/blastPhoto/e"+(i+1)+".gif");
}
}
public void drawMyself(Graphics e)
{
if(count<16)
{
e.drawImage(blast[count++],(int)x,(int)y,null);
}
}
}
到这一步,准备工作就做完了,接下来就要把创建窗口类来实现这个游戏,窗口类需要继承Frame类,这里多加了一部,把经常使用的关于窗口的方法包装成一个独立的MyFrame类,它继承Frame类,然后让游戏窗口类直接继承MyFrame类
7.游戏主界面类
首先要创建游戏的界面
1.Frame类
a.窗口的加载,那么就得先聊聊Frame类
Frame 是带有标题和边框的顶层窗口。窗体的默认布局为 BorderLayout,
Window 对象是一个没有边界和菜单栏的顶层窗口,Windows类提供了方法来设置窗口的大小,位置等信息
在我们加载这个窗口的时候就需要设置这些值,设置好以后就可以运行出一个空白的带框窗口了
this.setTitle("飞机大战");
this.setBounds(100,100,500,500);//界面的初始化位置,宽高
this.setVisible(true);
b.定义一个内部线程类,因为我们的飞机和子弹都是时刻在运动的,所以就需要不断的创建新的线程,使飞机和子弹动起来
原理通俗来讲:就是将一帧一帧的图片通过不断的刷新界面,从而拼接成一个完整的动画,而刷新就是通过创建一个线程去刷新。通过线程中的while循环,不断的调用repaint方法,
class myThread extends Thread
{
public void run()
{
while(true)
{
repaint();//内部类可以直接调用外部类的成员。
try {
Thread.sleep(50); //1s=1000ms,1s画20次(20*50=1000),一秒画20次
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
repaint方法
repaint会尽快的去调用paint方法
c…AWT存在闪烁问题,这里使用双缓冲技术消除闪烁,在spring中无需设置,会自动消除闪烁
/**
* 解决屏幕闪烁问题,增加双缓冲技术*/
private Image offScreenImage=null;
public void update(Graphics g)
{
if(offScreenImage==null)
offScreenImage=this.createImage(Constant.Game_Windth, Constant.Game_HEIGHT);//游戏窗口的宽高
Graphics gOff=offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage,0,0,null);
}
2.游戏主界面类
往窗口里画东西,飞机,子弹,右上角的计时器
画飞机的首要条件就是飞机还是存活的,直接调用飞机对象的draw方法,
画子弹的时候,因为子弹数目比较多,所以这里需要用到集合的知识,把子弹放在一个List中(这里没有使用泛型,一般建议使用),每创建一个子弹,都要检测它是否与飞机碰撞,若是,则飞机死亡爆炸
画计时器,通过Date类不断创建新的时间对象,startTime在上文中加载窗口方法中就已经计算出确切的时间值了
游戏结束时,显示游戏时间以及Gameover,GameOver比较简单,就是在窗口固定坐标输出一行字
a.创建构造方法,通过建立对象自动调用构造方法启动窗口事件。
public void MyPlaneWraFrame()
{
init();//启动窗口事件
}
b.创建窗口事件
添加关闭窗口事件,启动键盘监听,创建炮弹。启动重画线程(只要创建了对象,重画线程就开始被调用–>paint方法就开始被调用。)
//窗口事件
public void init()
{
this.setTitle("飞机大战");
this.setBounds(100,100,500,500);//界面的初始化位置,高宽
this.setLayout(new FlowLayout());
this.addWindowListener(new WindowAdapter()//关闭窗口事件
{
@Override
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
new myThread().start();//启动重画线程
this.addKeyListener(new keymonitor());//启动键盘监听
//创建炮弹
for(int i=0;i<BulletNum;i++)
{
Bullet bullet=new Bullet();
al.add(bullet);
}
this.setVisible(true);
}
c.给界面中“画”背景,飞机,炮弹,
public void paint(Graphics g)
{
g.drawImage(bgImg,0,0,500,500,null);//画背景
p1.drawMyself(g);//画飞机
System.out.println("容器目前的大小:"+al.size());
drawTime(g);//画时间
for(int i=0;i<BulletNum;i++)//画炮弹
{
Bullet b=(Bullet)al.get(i);
b.drawMyself(g);
//碰撞测试
boolean boom=b.getRec().intersects(p1.getRec());
if(boom)
{
GameOver=true;
button();
p1.live=false;
if(blast==null)
{
blast=new Blast(p1.x,p1.y);
}
blast.drawMyself(g);
}
}
}
d.给界面中添加计时功能
public void drawTime(Graphics g)
{
Color c=g.getColor();
Font f=g.getFont();
g.setColor(Color.green);
if(p1.live)
{
period=(System.currentTimeMillis()-start.getTime())/1000;
g.drawString("坚持:"+period,30,50);
}
else
{
if(end==null)
{
end=new Date();
period=(end.getTime()-start.getTime())/1000;
}
g.setColor(Color.ORANGE);
g.setFont(new Font("微软雅黑",Font.BOLD,30)); //设置字体
g.drawString("最终时间为:"+period+"s",150,150);
g.setFont(new Font("微软雅黑",Font.BOLD,60)); //设置字体
g.drawString("GameOver",90,250);
}
g.setColor(c);
g.setFont(f);
}
e.创建内部类,监听键盘
//监听键盘
class keymonitor extends KeyAdapter
{
@Override
public void keyPressed(KeyEvent e)
{
p1.addDistance(e);
}
@Override
public void keyReleased(KeyEvent e)
{
p1.reduceDistance(e);
}
}
f.如果飞机坠毁,重新开始游戏
public void button()//如果飞机坠毁,那么启动按钮
{
if(GameOver)
{
if(button==null)
{
button = new Button("PlayNewGame");
this.add(button);
button.setBackground(Color.RED);
button.setForeground(Color.orange);
init();
//创建按钮监听
button.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
if(e.getClickCount()==2)
{
MyPlaneWarFrame myPlaneWarFrame= new MyPlaneWarFrame();
myPlaneWarFrame.MyPlaneWraFrame();
}
}
});
}
}
}
以上就是所有的分析代码,下面是整个界面类的完整代码
public class MyPlaneWarFrame extends Frame
{
Image planeImg=GameTools.getImage("photo/planePhoto/plane.png");
Image bgImg=GameTools.getImage("photo/planePhoto/bg.jpg");
Plane p1=new Plane(400,400,20,20,6,planeImg);
Blast blast;//创建爆炸对象
Date start=new Date();//游戏开始的时间
Date end;//游戏结束的时间
long period=0;//玩了多久
int BulletNum=10;
ArrayList<Bullet>al=new ArrayList<Bullet>(BulletNum);
private Button button;
boolean GameOver=false;
public void MyPlaneWraFrame()
{
init();
}
//窗口事件
public void init()
{
this.setTitle("飞机大战");
this.setBounds(100,100,500,500);//界面的初始化位置,高宽
this.setLayout(new FlowLayout());
this.addWindowListener(new WindowAdapter()//关闭窗口事件
{
@Override
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
new myThread().start();//启动重画线程
this.addKeyListener(new keymonitor());//启动键盘监听
//创建炮弹
for(int i=0;i<BulletNum;i++)
{
Bullet bullet=new Bullet();
al.add(bullet);
}
this.setVisible(true);
}
public void button()//如果飞机坠毁,那么启动按钮
{
if(GameOver)
{
if(button==null)
{
button = new Button("PlayNewGame");
this.add(button);
button.setBackground(Color.RED);
button.setForeground(Color.orange);
init();
//创建按钮监听
button.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
if(e.getClickCount()==2)
{
MyPlaneWarFrame myPlaneWarFrame= new MyPlaneWarFrame();
myPlaneWarFrame.MyPlaneWraFrame();
}
}
});
}
}
}
public void paint(Graphics g)
{
g.drawImage(bgImg,0,0,500,500,null);//画背景
p1.drawMyself(g);//画飞机
System.out.println("容器目前的大小:"+al.size());
drawTime(g);//画时间
for(int i=0;i<BulletNum;i++)//画炮弹
{
Bullet b=(Bullet)al.get(i);
b.drawMyself(g);
//碰撞测试
boolean boom=b.getRec().intersects(p1.getRec());
if(boom)
{
GameOver=true;
button();
p1.live=false;
if(blast==null)
{
blast=new Blast(p1.x,p1.y);
}
blast.drawMyself(g);
}
}
}
public void drawTime(Graphics g)
{
Color c=g.getColor();
Font f=g.getFont();
g.setColor(Color.green);
if(p1.live)
{
period=(System.currentTimeMillis()-start.getTime())/1000;
g.drawString("坚持:"+period,30,50);
}
else
{
if(end==null)
{
end=new Date();
period=(end.getTime()-start.getTime())/1000;
}
g.setColor(Color.ORANGE);
g.setFont(new Font("微软雅黑",Font.BOLD,30)); //设置字体
g.drawString("最终时间为:"+period+"s",150,150);
g.setFont(new Font("微软雅黑",Font.BOLD,60)); //设置字体
g.drawString("GameOver",90,250);
}
g.setColor(c);
g.setFont(f);
}
class myThread extends Thread
{
public void run()
{
while(true)
{
repaint();//内部类可以直接调用外部类的成员。
try {
Thread.sleep(50); //1s=1000ms,1s画20次(20*50=1000),一秒画20次
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
/**
* 解决屏幕闪烁问题,增加双缓冲技术*/
private Image offScreenImage=null;
public void update(Graphics g)
{
if(offScreenImage==null)
offScreenImage=this.createImage(Constant.Game_Windth, Constant.Game_HEIGHT);//游戏窗口的宽高
Graphics gOff=offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage,0,0,null);
}
//监听键盘
class keymonitor extends KeyAdapter
{
@Override
public void keyPressed(KeyEvent e)
{
p1.addDistance(e);
}
@Override
public void keyReleased(KeyEvent e)
{
p1.reduceDistance(e);
}
}
public static void main(String[] args)
{
MyPlaneWarFrame myPlaneWarFrame= new MyPlaneWarFrame();
myPlaneWarFrame.MyPlaneWraFrame();
}
}