Java实现飞机大战(详细思路与过程,含源代码) 您所在的位置:网站首页 飞机类手机游戏视频软件 Java实现飞机大战(详细思路与过程,含源代码)

Java实现飞机大战(详细思路与过程,含源代码)

2024-07-11 05:08| 来源: 网络整理| 查看: 265

目录 演示构思详细分析GameFrame背景动画菜单选项添加(键盘)监听器 GamePanel动态游戏显示区(双缓冲) Plane(myplane,enemyplane,bossplane)BreakCollisionBulletDialogSoundMain方法 总结:

演示

飞机大战

源代码下载: https://github.com/Fattybenny/javaswingproject/tree/main/java%E9%A3%9E%E6%9C%BA%E5%A4%A7%E6%88%98.

构思

首先,要把整体的游戏框架和内容构思出来(根据预先构思游戏里存在的组件内容和游戏功能抽象出指定类)。以我的小游戏为例:

1.主界面框架类:GameFrame(extends JFrame)            显示开始界面

2.弹出界面类:Dialog (extends JDialog)             弹出设置界面(声音开关)、弹出游戏成功、失败界面 JDialog 窗体的功能是从一个窗体中弹出另一个窗体,就像是在使用 IE 浏览器时弹出的确定对话框,JDialog 窗体与 JFrame 窗体形式基本相同,设置窗体的特性时调用的方法名称都基本相同,如设置窗体大小、窗体关闭状态等

3.游戏面板类:GamePanel (extends JPanel)            真正显示飞机大战动态游戏画面,并且还添加了按钮JButton用于控制游戏开始暂停。

4.玩家飞机类:MyPlane            移动玩家飞机、画玩家飞机等其他与玩家飞机相关的方法

5.敌机类:EnemyPlane            移动敌机、画敌机

6.BOSS飞机类:BossPlane            移动BOSS飞机、画BOSS飞机

7.子弹类(也可以分三个类:玩家飞机子弹、敌机子弹、BOSS子弹)            移动子弹、绘制子弹

8.碰撞类:Collision            检测各种碰撞情况

9.爆炸类:Break            绘制飞机爆炸图片

10.声音类:Sound            控制声音的播放与暂停

11.主类:Main             开启程序

详细分析 GameFrame

在这里插入图片描述

背景动画

动态的图像(视频)原理:视频由一张张静态的图片快速变换形成,连续的图像变化每秒超过24帧(frame)画面以上时,根据视觉暂留原理,人眼无法辨别单幅的静态画面;看上去是平滑连续的视觉效果。

在java程序中,如果通过人手动点击一次换一次图片,那么要想实现肉眼看见的视频效果需要我们一秒钟至少点击24次,这是非常困难的。而我们希望的是通过一次点击就可以产生动画效果,让其自动每隔一段极短的时间就换一次图片。于是可以采用多线程的方法来实现。由于动画效果是在当前的GameFrame类中实现的,可以直接定义一个内部类继承Thread,当然也可以新建一个class文件定义。

获取包中的图片:

Image img; img=Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("/Capture/飞机.mp4_"+i+".png")); //获得资源的URL:this.getClass().getResource(String name) // 单斜杠 /开头表示从根目录开始 private class setBackground2 extends Thread { Image img; Graphics mg; @Override public void run() { while(true) { for(int i=0;i Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } 菜单选项

设置不同的JLabel标签添加到界面的相应位置。

一般容器都有默认布局方式,但是有时候需要精确指定各个组建的大小和位置,就需要用到空布局。 首先利用setLayout(null) 语句将容器的布局设置为null布局(空布局) 再调用组件的setBounds(int x, int y, int width,int height) 方法设置组件在容器中的大小和位置,单位均为像素。

通过JLabel组件添加一张图片:

ImageIcon background = new ImageIcon(this.getClass().getResource("/images/mainback.png")); back = new JLabel(background); back.setBounds(0,700,1800, 300); this.getContentPane().add(back); //设置标签 label01 = new JLabel("开始游戏"); label01.setFont(new Font("acefont-family", Font.BOLD, 50)); label01.setForeground(Color.blue);//设置字体背景颜色 label01.setBounds(820, 740, 400, 120);//起点宽高 label02 = new JLabel("选择飞机"); label02.setFont(new Font("acefont-family", Font.BOLD, 50)); label02.setBounds(820, 830, 400, 120); label03 = new JLabel(icon); label03.setBounds(600, 740, 250, 120); label04 = new JLabel(icon); label04.setBounds(600, 830, 250, 120); label04.setVisible(false); ... ...

问题与解决: 1.先添加的组件会覆盖影响到后添加的组件。 例如这里有三个JLabel组件,其中一个是带有背景图片的,其他两个是带有文字的,一定要最后添加带有背景图片的,否则无法将文字显示在图片上。 在这里插入图片描述 2.注意画笔绘制图片的覆盖问题: 后面经常要用到drawimage方法,要注意,画笔后画的内容会覆盖先画的内容;画笔画的内容会覆盖类似JLabel这种组件,无论组件先添加还是后添加(解决办法:在组件添加到相应的容器之后再设置坐标位置(setbounds),或者如上文所说的通过按正确顺序添加JLabel组件的方法来达到设置背景图片的功能,避免了用drawimage)。

添加(键盘)监听器 public void keyadapter() { this.requestFocusInWindow(); this.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); //监听向上或向下按 if(key == KeyEvent.VK_DOWN || key == KeyEvent.VK_UP) { label03.setVisible(!label03.isVisible()); label04.setVisible(!label04.isVisible()); if(label03.isVisible()) { label01.setForeground(Color.blue); label02.setForeground(Color.black); } else { label01.setForeground(Color.black); label02.setForeground(Color.blue); } } if(key == KeyEvent.VK_ENTER && label03.isVisible()) { //添加游戏面板JPanel add(...); //并移除之前添加的JLabel组件 remove(label02); remove(label03); remove(label04); remove(back); ... ... } ... ... } }); }

问题与解决 焦点的获取 在使用键盘监听器的时候,一定要让监听对象获取焦点,如果焦点不在监听对象上,那么键盘输入的内容就无法被监听到。 使用requestFocusInWindow()方法

注意在监听器方法中的this关键字 在添加监听器时有两种不同的方法: 1.this.addKeyListener(listener); listener是用户定义的监听器类,这个类implements相应接口。 2.直接在当前类定义监听器;

this.addKeyListener(new KeyAdapter() { //KeyAdapter系统提供的抽象类,他承接了KeyListener接口 //public abstract class KeyAdapter implements KeyListener @Override public void keyPressed(KeyEvent e) { //this.add()这里的this指的不是当前文件类, //如果想调用当前文件类的方法,直接写方法名就行了, //因为这里的this指的是KeyAdapter的对象 ...} @Override public void keyReleased(KeyEvent e) { ...} ... } GamePanel

按钮区和分数区都分别添加相应的JButton按钮,并添加ActionListener监听器即可 在这里插入图片描述 在这里插入图片描述

动态游戏显示区(双缓冲)

还是和GameFrame中的背景动画类似,都采用多线程方法。

private class MapPanel extends Canvas implements Runnable

在这里插入图片描述 问题与解决: 1.Canvas使用 Canvas是AWT组件,JPanel是Swing组件,Swing组件是以AWT组件为基础的,从理论上来说,Canvas要比JPanel更轻量些.如果canvas能满足需求,就用canvas.Canvas 组件表示屏幕上一个空白矩形区域,应用程序可以在该区域内绘图,或者可以从该区域捕获用户的输入事件。 不能直接使用该类,需要继承Canvas并重写其paint方法. repaint paint update 三个方法的调用顺序: repaint->update->paint paint源码:

public void paint(Graphics g) { g.clearRect(0, 0, width, height);//清除界面 }

update源码:

public void update(Graphics g) { g.clearRect(0, 0, width, height);//清除界面 paint(g); }

由此看来我们可以选择性的重写update或者paint方法来满足程序绘画需要,如果不需要清除界面就去除 g.clearRect(0, 0, width, height)方法,在本例中就需要执行这一步,因为每次重绘图片如果都执行一遍清除界面操作就会出现闪烁现象。(如果是用户自己定义的绘制方法,不需要用到paint方法进行重绘,则不用重写这些方法,需要用到双缓冲技术,如下所示)

除此以外解决图片闪烁现象,还要用到双缓冲技术: 先在内存中预先分配一定大小的图片缓冲区,在将所有绘图方法绘制到缓冲区之后,再最后将图片缓冲区的内容绘制出来;

//创建图片缓冲区 BufferedImage iBuffer=new BufferedImage(1600, 1000, BufferedImage.TYPE_INT_RGB); //在缓冲区内部绘图 Graphics gBuffer = iBuffer.getGraphics();//获得缓冲区画笔 gBuffer.drawImage(bg2, 0, bg2_y, 1600, 1000, this); gBuffer.drawImage(planePic[planeID], myPlane_x, myPlane_y, PLANE_SIZE, PLANE_SIZE, null); ... ... //缓冲区内部内容绘制完成,将缓冲区整体绘制 Graphics canvasg=this.getGraphics(); canvasg.drawImage(iBuffer, 0, 0, null);//把缓冲图像载入屏 Plane(myplane,enemyplane,bossplane)

这三个不同的飞机类内容基本相同:

class Plane { 初始化飞机的图片、坐标等 如果是一组图片则用Image[]数组存储 { planeimg=Toolkit.getDefaultToolkit().getImage(getClass().getResource()); } 绘制飞机方法: { 先判断飞机是否还存活 存活: g.drawImage(planeimg,x, y, null); 死亡: 调用爆炸类里的绘制爆炸图片方法(下文所示) } 移动飞机方法: { 修改飞机图片的x,y坐标 } } Break class Break { 初始化爆炸图片 { plane_b = new Image[6]; for(int i = 0; i g.drawImage(plane_b[i/5], x, y, EnemyPlane.ENEMY_SIZE, EnemyPlane.ENEMY_SIZE, null); g.drawImage(plane_b[i/5], x, y, MyPlane.PLANE_SIZE, MyPlane.PLANE_SIZE, null); ... } } Collision

两张图片(飞机和飞机,飞机和子弹)是否相碰,需要判断的是矩形图片是否有重叠部分。 以飞机与飞机相碰为例: 按照x,y坐标的大小不同,总共有2*2四种情况:

在这里插入图片描述

//玩家飞机与敌机碰撞 void plane_enemy(MyPlane m, EnemyPlane e) { if(m.getX_Y().getX() >= e.getX_Y().getX()-MyPlane.PLANE_SIZE && m.getX_Y().getX() = e.getX_Y().getY()-MyPlane.PLANE_SIZE && m.getX_Y().getY() m.stayed = false; GamePanel.live = 0; } else GamePanel.live -= 50; } } Bullet class Bullet { 初始化子弹图片 { bullet = Toolkit.getDefaultToolkit().getImage(getClass().getResource()); } 绘制子弹 { g.drawImage(bullet, bullet_x, bullet_y, BULLET_WIDTH,BULLET_HEIGHT, null); } 移动子弹 ... }

存储子弹:

private ArrayList mybulletarray;//玩家飞机子弹数组 private ArrayList enemybulletarray;//敌机子弹数组 private ArrayList bossbulletarray;//boss子弹数组 Dialog

弹出对话框和JFrame类似,往里面添加各种组件和监听器即可。

public class Dialog extends JDialog { public Dialog(JFrame j, int i) { super(j, true); setLayout(null); setResizable(false); if(i == 1) showFail(j);//显示挑战失败 else if(i == 2) showSuccess(j);//显示挑战成功对话 else showSetting(j);//显示设置对话 setVisible(true); } ... //游戏失败 private void showFail(JFrame j) { setTitle("提示"); setBounds(800, 400, 500, 300); jl01 = new JLabel("挑战失败"); jl01.setFont(new Font("acefont-family", Font.BOLD, 50)); jl01.setForeground(Color.blue); jl01.setBounds(65, 40, 400, 50); add(jl01); jl02 = new JLabel("分数" + GamePanel.sum); jl02.setFont(new Font("acefont-family", Font.BOLD, 30)); jl02.setForeground(Color.RED); jl02.setBounds(65, 120, 400, 50); add(jl02); } .... } Sound public class Sound { private Clip clip; static boolean[] b = new boolean[]{true, true, true, true};//控制声音播放 //按键音 //打开声音文件的方法。 public Sound(String path){ AudioInputStream audio; try { URL url = this.getClass().getResource(path); audio = AudioSystem.getAudioInputStream(url); clip = AudioSystem.getClip(); clip.open(audio); }catch (Exception e) { e.printStackTrace(); } } /** * 停止播放 */ void stop() { clip.stop();//暂停音频播放 } /** * 开始播放 */ void start() { clip.start();//播放音频 } /** * 回放背景音乐设置 */ void loop() { clip.loop(20);//回放 } }

想要在某个界面或时刻实现播放声音,直接new一个对象,并传入文件地址就行。 如下Main方法所示:

Main方法 public class Main { static GameFrame f1; static Sound sound; public static void main(String[] args) { f1=new GameFrame(); f1.setDefaultCloseOperation(3); f1.setVisible(true); f1.setResizable(false); if(sound==null) { sound=new Sound("/sounds/mainback.wav"); sound.start(); sound.loop(); } } } 总结:

除了上文中所解决的问题,还有一点就是,对于多线程到底该什么时候去创建?或者说多线程该设置在什么位置?(以下是我自己的理解)

直观的感受就是多线程是在自动持续执行时引入的,对于飞机大战来说,动画界面一旦开始执行(在满足条件的情况下),会一直执行直到满足结束条件就退出,很明显,背景、玩家飞机、敌机、boss飞机等等这些图片都是在自动不断刷新,刚开始会想到让这些自动执行的类都实现多线程(extends Thread),要完成刷新的同步,只需要统一好阻塞时间就行了,但是这样实现会发现代码开销比较大,且不便于统一管理。所以可以将这些类统一用一个Thread类实现同步的控制(只需要在这个Thread类中调用各个类中的方法即可)。 在本例中就是用GamePanel(可以直接让GamePanel extends Thread 或者创建一个内部Thread类)来控制背景、飞机等各种图片的绘制和图片移动。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有