java基础 小游戏 飞机大战(中)

    科技2025-11-06  24

    文章目录

    7 键盘控制原理8 面向对象重构飞机类的键盘控制9 炮弹类基本设计10 使用数组产生多发炮弹11 双缓冲解决闪烁12 矩形检测原理13 炮弹和飞机的碰撞检测

    7 键盘控制原理

    键盘和程序交互时,每次按下键,松开键都会触发相应的键盘事件,事件的信息被封装到了KeyEvent对象中

    为了识别按下的是哪个键,系统对所有按键都做了编号,每个按键都对应相应的数字,如回车对应10,空格对应32等,这些编号都可以通过KeyEvent对象来查询

    //定义键盘监听的内部类 class KeyMonitor extends KeyAdapter{ @Override public void keyPressed(KeyEvent e) { System.out.println("按下" + e.getKeyCode()); } @Override public void keyReleased(KeyEvent e) { System.out.println("松开" + e.getKeyCode()); } } //初始化窗口 public void launchFrame() { this.setTitle("coisini"); //设置窗口名字 this.setSize(500, 500); //设置窗口大小 this.setLocation(300,300); //设置窗口显示位置 this.setVisible(true); //窗口可视化 //实际关闭窗口 this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); new PaintThread().start(); //启动重画窗口的线程 addKeyListener(new KeyMonitor()); //给窗口增加键盘的监听 } public static void main(String[] args) { //主方法创建窗口入口 MyGameFrame f = new MyGameFrame(); //由MyGameFrame类新建对象 f.launchFrame(); //调用lanuchFrame()方法 } }

    分别按下上下左右,记录按下键位和松下键位的编号数字

    8 面向对象重构飞机类的键盘控制

    理解了键盘的控制原理,增加了键盘监听后,在Plane类中增添两种控制方法,按下键位时和松开键位时对plane的移动控制

    package cn.coisini.game; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyEvent; public class Plane extends GameObject { int speed = 3; boolean left,right,up,down; //按下键位时重画位置 public void drawSelf(Graphics g) { g.drawImage(img, (int)x, (int)y, null); if(left) { x -= speed; } if(right) { x += speed; } if(up) { y -= speed; } if(down) { y += speed; } } public Plane(Image img, double x, double y) { this.img = img; this.x = x; this.y = y; } //按下某个键增加相应的方向 public void addDirection(KeyEvent e) { switch(e.getKeyCode()) { case KeyEvent.VK_LEFT: left = true; break; case KeyEvent.VK_RIGHT: right = true; break; case KeyEvent.VK_UP: up = true; break; case KeyEvent.VK_DOWN: down = true; break; } } //更改键位后,取消原来的方向 public void minusDirection(KeyEvent e) { switch(e.getKeyCode()) { case KeyEvent.VK_LEFT: left = false; break; case KeyEvent.VK_RIGHT: right = false; break; case KeyEvent.VK_UP: up = false; break; case KeyEvent.VK_DOWN: down = false; break; } } }

    9 炮弹类基本设计

    炮弹类用黄色实心椭圆实现,不用再加载新的图片,我们的逻辑是在窗口固定位置(200,200)处生成炮弹,炮弹的方向是随机的,并且遇到边界会反弹

    由于窗口界面大小是确定的,为了避免更改后产生的问题,创建一个常量类Constant来存储不变的数值:

    package cn.coisini.game; public class Constant { public static final int GAME_WIDTH = 500; public static final int GAME_HEIGHT = 500; }

    炮弹类:

    package cn.coisini.game; import java.awt.Color; import java.awt.Graphics; public class Shell extends GameObject { double degree; public Shell() { x = 200; y = 200; width = 10; height = 10; speed = 3; //角度设置为0-360之间的随机数 degree = Math.random()*Math.PI*2; } public void draw(Graphics g) { Color c = g.getColor(); g.setColor(Color.YELLOW); g.fillOval((int)x, (int)y, width, height); //炮弹沿着任意角度去飞 x += speed*Math.cos(degree); y += speed*Math.sin(degree); //设置炮弹反弹 if(x<0 || x>Constant.GAME_WIDTH - width) { degree = Math.PI - degree; //碰到左右边界,则做纵坐标反转 } if(y<30 || y>Constant.GAME_HEIGHT - height) { degree = -degree; //碰到右边界,则做横坐标反转 } g.setColor(c); } }

    完成这些后,在MyGameFrame中new一个shell,并调用Shell的draw方法

    10 使用数组产生多发炮弹

    完成Shell类后,现在开始产生多发炮弹,使用数组的知识,一开始先创建一个shells的数组,再对这50个元素初始化(即创建50个对象存入),再使用draw方法循环画出这50个炮弹

    /** * @author coisini1999 * @飞机游戏的主窗口 */ package cn.coisini.game; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JFrame; //导入JFrame包 public class MyGameFrame extends JFrame { Image bg = GameUtil.getImage("images/bg.jpg"); //加载图片对象,指定路径 Image planeImg = GameUtil.getImage("images/plane.png"); //加载图片对象,指定路径 Plane plane = new Plane(planeImg, 250 ,250); //创建飞机对象 Shell[] shells = new Shell[50]; //创建炮弹数组 public void paint(Graphics g) { //自动被调用,g 相当于一支笔 g.drawImage(bg, 0, 0, null); plane.drawSelf(g); //画飞机 //画50个炮弹 for(int i=0; i < shells.length; i++) { shells[i].draw(g); } } //帮助我们反复重画方法 class PaintThread extends Thread { //内部类,使用此类的所有属性和方法 Thread是线程 @Override public void run() { while(true) { //System.out.println("窗口重画了一次"); repaint(); //重画 try { Thread.sleep(40); //40ms } catch (InterruptedException e) { e.printStackTrace(); } } } } //定义键盘监听的内部类 class KeyMonitor extends KeyAdapter{ @Override public void keyPressed(KeyEvent e) { plane.addDirection(e); } @Override public void keyReleased(KeyEvent e) { plane.minusDirection(e); } } //初始化窗口 public void launchFrame() { this.setTitle("coisini"); //设置窗口名字 this.setSize(Constant.GAME_WIDTH, Constant.GAME_HEIGHT); //设置窗口大小 this.setLocation(300,300); //设置窗口显示位置 this.setVisible(true); //窗口可视化 //实际关闭窗口 this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); new PaintThread().start(); //启动重画窗口的线程 addKeyListener(new KeyMonitor()); //给窗口增加键盘的监听 //初始化50个炮弹 for(int i=0; i < shells.length; i++) { shells[i] = new Shell(); } } public static void main(String[] args) { //主方法创建窗口入口 MyGameFrame f = new MyGameFrame(); //由MyGameFrame类新建对象 f.launchFrame(); //调用lanuchFrame()方法 } }

    11 双缓冲解决闪烁

    再完成上面工作后,使用JFrame包,屏幕有略微的闪烁,使用普通的Frame包,闪烁的更为严重,影响用户体验,为了解决闪烁问题,采用Frame加双缓冲来解决优于JFrame

    双缓冲机制原理如下: 1,在内存中创建与画布一样的缓冲区 2,在缓冲区画图 3,将缓冲区位图拷贝到当前画布上 4,释放内存缓冲区

    双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象拷贝到屏幕上,能大大加快绘图的速度

    //双缓冲机制 private Image offScreenImage = null; public void update(Graphics g) { if(offScreenImage == null) { offScreenImage = this.createImage(Constant.GAME_WIDTH, Constant.GAME_HEIGHT); } Graphics gOFF = offScreenImage.getGraphics(); paint(gOFF); g.drawImage(offScreenImage, 0, 0, null); }

    12 矩形检测原理

    在窗口中的所有物体(飞机,炮弹等)其实都是矩形,我们需要检测游戏中,飞机是否碰撞到了炮弹,即矩形间有没有相交,相交就可以认为游戏失败

    java的API中,提供了Rectangle类来表示矩形相关信息,并且提供了intersects()方法,直接判断矩形是否相交

    在GameObject类中,之前定义过一个方法:

    //返回物体所在的矩形,便于后续的碰撞检测 public Rectangle getRect() { return new Rectangle((int)x, (int)y, width, height); }

    通过返回物体所在的矩形,来判断是否发生碰撞

    13 炮弹和飞机的碰撞检测

    对飞机类增加新的属性来判断飞机是否死亡,以及在MyGameFrame中进行飞机的碰撞检测和反馈

    Plane类新增属性及新的画飞机方法:

    int speed = 3; boolean left,right,up,down; boolean live = true; //创建飞机是否死亡的属性 public void drawSelf(Graphics g) { //如果飞机没死,则再画飞机,死亡则不画 if(live) { g.drawImage(img, (int)x, (int)y, null); if(left) { x -= speed; } if(right) { x += speed; } if(up) { y -= speed; } if(down) { y += speed; } } }

    在MyGameFrame中每次画一个炮弹后进行矩形相交检测,反馈飞机是否死亡,从而决定画不画飞机:

    public void paint(Graphics g) { //自动被调用,g 相当于一支笔 g.drawImage(bg, 0, 0, null); plane.drawSelf(g); //画飞机 //画50个炮弹 for(int i=0; i < shells.length; i++) { shells[i].draw(g); //每画出一个炮弹进行炮弹和飞机的矩形相交检测 boolean peng = shells[i].getRect().intersects(plane.getRect()); //碰撞后令飞机死亡 if(peng) { plane.live = false; } } }
    Processed: 0.010, SQL: 8