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 //定义键盘的监听器,每按一次左(右)键,向左(右)移动10px 106 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<TABLE_WIDTH-RACKET_WIDTH){ 115 racketX += 10; 116 } 117 } 118 } 119 }; 120 121 //实现监听事件,每次触发该事件,用来检查小球是否碰壁,如果碰壁则将小球队的速度求反。 122 ActionListener go = new ActionListener(){ 123 public void actionPerformed(ActionEvent e){ 124 //由于增加了障碍物,判断的复制程度又提高了,这里新增对碰撞障碍物的判断 125 //障碍物的判断又分为障碍物左边和右边两种情况,要区别对待 126 //检查左右方向上是否碰壁 127 if(ballX<=0|| 128 (ballY>=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 }
时间: 2024-11-07 21:13:04