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 }