1 package org.xn.chapter11.practice; 2 3 /** 4 * 课后习题2:做一个弹球游戏,在书中程序的基础上将所有的组件换成图片显得更美观和实用 5 * 程序分解: 6 * 1、图形界面: 7 * 球桌、弹球、球杆、障碍物 8 * 2、动画核心: 9 * 定时器,每隔100ms绘制一次图形 10 * JPanel组件,这里要使用JPanel而不是Canvas,因为使用Canvas会产生闪烁 11 * 键盘监听类,用于左右键来控制球杆的运动 12 * JPanel组件的监听类,用于检测小球是否碰壁或者是否游戏结束 13 * 3、扩展 14 * 自己新增了一个计数器,一个玩家可以有3条命,用完了游戏结束 15 * 4、已知bug: 16 * a、在某些地方会出现无法反弹的问题 17 * b、每损失一条命,小球的速度会越来越快 18 * 19 * 5、联系方式: 20 * QQ:1037784758 21 * 22 * 6、最后一次修改日期: 23 * 2015.7.20 24 * */ 25 26 import java.awt.Color; 27 import java.awt.Dimension; 28 import java.awt.Font; 29 import java.awt.Graphics; 30 import java.awt.event.ActionEvent; 31 import java.awt.event.ActionListener; 32 import java.awt.event.KeyAdapter; 33 import java.awt.event.KeyEvent; 34 import java.awt.event.WindowAdapter; 35 import java.awt.event.WindowEvent; 36 import java.awt.image.BufferedImage; 37 import java.io.File; 38 import java.util.Random; 39 40 import javax.imageio.ImageIO; 41 import javax.swing.JFrame; 42 import javax.swing.JPanel; 43 import javax.swing.Timer; 44 45 public class PinBallImage { 46 //定义球桌的尺寸 47 private final int TABLE_WIDTH = 400;//桌面宽度 48 private final int TABLE_HEIGHT = 600;//桌面高度 49 //定义球拍的尺寸(此处是图片的尺寸),是为了后面的判断小球是否出界时使用 50 private final int RACKET_WIDTH = 200; 51 private final int RACKET_HEIGHT = 30; 52 //定义小球的尺寸(此处是图片的尺寸),是为了后面的判断小球是否出界时使用 53 private final int BALL_SIZE = 32; 54 //定义障碍物的尺寸(此处是图片的尺寸),是为了后面的判断小球是否碰壁时使用 55 private final int BLOCK_WIDTH = 50; 56 private final int BLOCK_HEIGHT = 30; 57 //定义1个障碍物的位置,这里我们将障碍物固定不动,时间有限没有定义更多的障碍物 58 private final int blockX = 200; 59 private final int blockY = 200; 60 //定义一个剩余生命值图片的位置 61 private final int lifeX = 50; 62 private final int lifeY = 50; 63 //定义剩余的生命值,初始值为3 64 private int lifeNum = 3; 65 66 //定义一个随机数 67 Random rand = new Random(); 68 //定义小球的x轴和y轴的速度,其中y轴速度值是固定的, 69 //x轴的速度值是在y轴的基础之上乘以一个随机的比率得来,这个比率必须是在正负之间 70 //小球会固定一个方向弹走, 71 private double xyRate = rand.nextDouble()-0.5; 72 private int speedY = 10; 73 private int speedX = (int)(speedY*xyRate*2); 74 //定义小球和球拍的位置为随机 75 private int ballX = rand.nextInt(200)+20;//小球的x坐标(20~~220) 76 private int ballY = rand.nextInt(10)+20;//小球的y坐标(20~~30) 77 private int racketX = rand.nextInt(200);//代表球拍的水平位置 78 private int RACKET_Y = 500;//球拍的垂直位置是不变的 79 80 //--------------------------------------------------------- 81 /** 82 * 为游戏增加如下的新特性:将小球、球拍、球桌由图形换成位图 83 * 并在球桌上增加了障碍物,同时还新增了一个显示剩余生命值的区域,默认生命值为3个球 84 * */ 85 private BufferedImage table; 86 private BufferedImage ball; 87 private BufferedImage stick; 88 private BufferedImage block; 89 private BufferedImage life; 90 //------------------------------------------------------------------ 91 92 private JFrame jf = new JFrame("弹球游戏"); 93 private MyCanvas tableArea = new MyCanvas(); 94 Timer t ; 95 private boolean isLose = false;//标记位判断是否出界 96 97 public void init()throws Exception{ 98 // 定义图片的位置,使用ImageIO从磁盘中读取位图文件 99 table = ImageIO.read(new File("x:\\gamesImage\\table.jpg"));100 ball = ImageIO.read(new File("x:\\gamesImage\\ball.png"));101 stick = ImageIO.read(new File("x:\\gamesImage\\stick.jpg"));102 block = ImageIO.read(new File("x:\\gamesImage\\block.jpg"));103 life = ImageIO.read(new File("x:\\gamesImage\\life.png"));104 105 //定义键盘的监听器,每按一次左(右)键,向左(右)移动10px106 KeyAdapter key = new KeyAdapter(){107 public void keyPressed(KeyEvent e){108 if(e.getKeyCode()==KeyEvent.VK_LEFT){109 if(racketX>0){110 racketX -= 10;111 }112 }113 if(e.getKeyCode()==KeyEvent.VK_RIGHT){114 if(racketX=blockY&&ballY<=(blockY+BLOCK_HEIGHT)&&ballX<=blockX&&ballX>=blockX-BALL_SIZE)||129 (ballY>=blockY&&ballY<=(blockY+BLOCK_HEIGHT)&&ballX<=(blockX+BLOCK_WIDTH)&&ballX>=(TABLE_WIDTH-(blockX+BLOCK_WIDTH)))||130 ballX>=TABLE_WIDTH-BALL_SIZE){131 System.out.println("水平方向碰壁"+",坐标:x="+ballX+" ,y="+ballY+ballY+",速度:x="+speedX+",y="+speedY);132 speedX = -speedX;133 }134 //检查上下方向上是否碰壁和小球是否出界,但是要先检查出界,再检查碰壁135 if(ballY >= RACKET_Y - BALL_SIZE &&136 (ballX < racketX || ballX > racketX + RACKET_WIDTH)){137 if(!isLose&&lifeNum>1){138 again();139 }else{140 isLose = true;//小球出界,游戏结束141 t.stop();//关闭计时器142 tableArea.repaint();143 }144 }else if(ballY<=0||//这里对障碍物垂直方向的碰撞的判断,也分为障碍物上面和障碍物下面145 (ballY>=RACKET_Y-BALL_SIZE&&racketX<=ballX&&ballX<=racketX+RACKET_WIDTH)||146 (ballX>=blockX&&ballX<=(blockX+BLOCK_WIDTH)&&ballY>=blockY-BALL_SIZE&&ballY<=blockY)||147 (ballX>=blockX&&ballX<=(blockX+BLOCK_WIDTH)&&ballY<=blockY+BALL_SIZE&&ballY>=blockY)){148 System.out.println("垂直方向碰壁"+",坐标:x="+ballX+" ,y="+ballY+",速度:x="+speedX+",y="+speedY);149 speedY = -speedY;150 }151 //小球的x轴和y轴的坐标增加或减少来实现移动152 ballY += speedY;153 ballX += speedX;154 tableArea.repaint();155 }156 };157 158 jf.addWindowListener(new WindowAdapter(){159 public void windowClosing(WindowEvent e){160 System.exit(0);161 }162 });163 //设置画布的固定尺寸164 tableArea.setPreferredSize(new Dimension(TABLE_WIDTH,TABLE_HEIGHT));165 tableArea.addKeyListener(key);166 167 t = new Timer(100,go);//定义计时器168 t.start();//打开计时器169 jf.addKeyListener(key);170 jf.add(tableArea);171 jf.setLocation(50,50);172 jf.setResizable(false);173 jf.pack();174 jf.setVisible(true);175 }176 177 class MyCanvas extends JPanel{178 private static final long serialVersionUID = 1L;179 180 //覆写Canvas的paint方法,实现绘图181 public void paint(Graphics g){182 if(isLose){183 g.setColor(new Color(255,0,0));184 g.setFont(new Font("Times",100,50));185 g.drawString("游戏已结束!", 50, 400);186 }else{187 g.drawImage(table,0,0,null);188 g.drawImage(ball,ballX,ballY,null);189 g.drawImage(stick,racketX,RACKET_Y,null);190 g.drawImage(block,blockX,blockY,null);191 g.drawImage(life,lifeX,lifeY,null);192 g.setColor(new Color(255,0,0));193 g.setFont(new Font("Times",100,18));194 g.drawString("X"+lifeNum, 65, 65);195 }196 }197 }198 199 //再来一次游戏200 public void again() {201 //定义小球和球拍的位置为随机202 ballX = rand.nextInt(200)+20;//小球的x坐标(20~~220)203 ballY = rand.nextInt(10)+20;//小球的y坐标(20~~30)204 racketX = rand.nextInt(200);//代表球拍的水平位置205 RACKET_Y = 500;//球拍的垂直位置是不变的206 speedY = 10;//将速度恢复初始值207 speedX = (int)(speedY*xyRate*2);208 try {209 init();210 } catch (Exception e) {211 e.printStackTrace();212 }213 --lifeNum;214 }215 216 //构造方法217 public PinBallImage(){}218 219 public static void main(String[] args) throws Exception{220 new PinBallImage().init();221 }222 }