初始需要定义的一些常量,和使用的库。
1 import acm.graphics.*;
2 import acm.program.*;
3 import acm.util.*;
4
5 import java.applet.*;
6 import java.awt.*;
7 import java.awt.event.*;
8
9 public class Breakout extends GraphicsProgram {
10
11 /** Width and height of application window in pixels */
12 public static final int APPLICATION_WIDTH = 400;
13 public static final int APPLICATION_HEIGHT = 600;
14
15 /** Dimensions of game board (usually the same) */
16 private static final int WIDTH = APPLICATION_WIDTH;
17 private static final int HEIGHT = APPLICATION_HEIGHT;
18
19 /** Dimensions of the paddle */
20 private static final int PADDLE_WIDTH = 60;
21 private static final int PADDLE_HEIGHT = 10;
22
23 /** Offset of the paddle up from the bottom */
24 private static final int PADDLE_Y_OFFSET = 30;
25
26 /** Number of bricks per row */
27 private static final int NBRICKS_PER_ROW = 10;
28
29 /** Number of rows of bricks */
30 private static final int NBRICK_ROWS = 10;
31
32 /** Separation between bricks */
33 private static final int BRICK_SEP = 4;
34
35 /** Width of a brick */
36 private static final int BRICK_WIDTH =
37 (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
38
39 /** Height of a brick */
40 private static final int BRICK_HEIGHT = 8;
41
42 /** Radius of the ball in pixels */
43 private static final int BALL_RADIUS = 10;
44
45 /** Offset of the top brick row from the top */
46 private static final int BRICK_Y_OFFSET = 70;
47
48 /** Number of turns */
49 private static final int NTURNS = 3;
第一步:创建游戏需要的砖块,砖块的行数和每行的数量都是确定的(NBRICK_ROWS,NBRICKS_PER_ROW),最上一排砖块距窗口顶部距离 (BRICK_Y_OFFSET)。然后是颜色的填充,利用行数判断所需的颜色。
1 for(int i = 0; i < NBRICK_ROWS; i++){
2 for(int j = 0; j < NBRICKS_PER_ROW; j++){
3 GRect brick = new GRect(0 + j * (BRICK_WIDTH + BRICK_SEP), BRICK_Y_OFFSET + i * (BRICK_HEIGHT + BRICK_SEP), BRICK_WIDTH, BRICK_HEIGHT);
4 brick.setFilled(true);
5 if(i < 2){
6 brick.setColor(Color.RED);
7 } else if(i < 4){
8 brick.setColor(Color.ORANGE);
9 } else if(i < 6){
10 brick.setColor(Color.YELLOW);
11 } else if(i < 8){
12 brick.setColor(Color.GREEN);
13 } else {
14 brick.setColor(Color.CYAN);
15 }
16 add(brick);
17 }
18 }
第二步:创建游戏所需的挡板
挡板的大小位置都是确定的;
1 private void createPaddle(){
2 /* HEIGHT 定义的是窗口高度所以要使用getHeight();*/
3 GRect paddle = new GRect((WIDTH - PADDLE_WIDTH) / 2, getHeight() - PADDLE_HEIGHT - PADDLE_Y_OFFSET, PADDLE_WIDTH, PADDLE_HEIGHT);
4 paddle.setFilled(true);
5 paddle.setColor(Color.BLACK);
6 add(paddle);
7
8 }
比较有挑戓性的部分是能够让挡板随着鼠标移劢。挡板只需要在x轴方向上移动。
加入鼠标侦听事件:addMouseListeners9();
和鼠标拖拽事件的代码:
1 /* 单击鼠标事件 */
2 public void mousePressed(MouseEvent e){
3 last = new GPoint(e.getPoint());
4 gobj = getElementAt(last);
5 }
6 /* 鼠标拖动事件 */
7 public void mouseDragged(MouseEvent e){
8 if(gobj != null){
9 gobj.move(e.getX() - last.getX(), 0);
10 last = new GPoint(e.getPoint());
11 }
12 }
这样就可以使用鼠标拖动挡板了。但是还需要求挡板能移动出游戏的边界,所以加入判断条件
(gobj.getX() > 0 || e.getX() - last.getX() > 0) && (gobj.getX() + gobj.getWidth() < getWidth() || e.getX() - last.getX() < 0)
当挡板移动到左边界时,鼠标需要向右移动才有效,当挡板移动到右边界时,鼠标需要向左移动才有效。
1 /* 鼠标拖动事件 */
2 public void mouseDragged(MouseEvent e){
3 if(gobj != null && (gobj.getX() > 0 || e.getX() - last.getX() > 0) && (gobj.getX() + gobj.getWidth() < getWidth() || e.getX() - last.getX() < 0)){
4 gobj.move(e.getX() - last.getX(), 0);
5 last = new GPoint(e.getPoint());
6 }
7 }
第三步:创建一个小球,使其在墙内反弹;
程序需要记录小球的速度。它由两个独立分量组成,你们可以按照下面的例子声明实例变量:
private double vx, vy;
速度分量表示在每个时间区间位置的变化量。一开始,小球向下运劢,初始速度vy可以设
为+3.0(在Java 中,y 值向屏幕往下增加)。如果每个回合小球的路线都相同,游戏会很无
聊,因此,vx 分量的值应该随机选取。
1. 声明一个实例变量rgen, 随机数生成器:
private RandomGenerator rgen = RandomGenerator.getInstance();
2. 初始化vx 变量:
vx = rgen.nextDouble(1.0, 3.0);
if (rgen.nextBoolean(0.5)) vx=-vx;
返段代码生成一个1.0到3.0间的双浮点型随机数赋给 vx,并按0.5 的概率将速度
取反。这种算法比调用
nextDouble(-3.0, +3.0)
好很多,后者可能会出现小球几乎垂直下落的情况,返对玩家来说太过于简单。
然后是让小球在墙壁之间来回弹跳,先忽略挡板和砖块的影响。
1 /* 创建一个反弹的小球 */
2 private void createBall(){
3 GOval ball = new GOval((getWidth() - 2 * BALL_RADIUS) / 2, (getHeight() - 2 * BALL_RADIUS) / 2, BALL_RADIUS, BALL_RADIUS);
4 ball.setFilled(true);
5 add(ball);
6 vy = 3.0;
7 vx = rgen.nextDouble(1.0, 3.0);
8 if(rgen.nextBoolean(0.5)) vx = -vx;
9 while(true){
10 ball.move(vx,vy);
11 pause(PAUSE_TIME);
12 if(ball.getX() < 0 || ball.getX() + 2 * BALL_RADIUS > getWidth()) vx = -vx;
13 if(ball.getY() < 0 || ball.getY() + 2 * BALL_RADIUS > getHeight()) vy = -vy;
14 }
15 }
第四步:碰撞检测
现在到了有趣的部分。为了使突破游戏更真实,需要判断小球是否和屏幕中其它物体产生了碰撞。
这里我为了让其他方法能获取ball和paddle的数据定义两个实例变量:
private GOval BALL;
private GRect PADDLE;
然后分别在createBall和createPaddle中加入 BALL = ball 和 PADDLE = paddle;
创建碰撞检测的方法
1 private GObject getCollidingObject(){ 2 /* 小球的正切正方形的四个顶点 */ 3 double x1 = BALL.getX(); 4 double y1 = BALL.getY(); 5 double x2 = BALL.getX() + 2 * BALL_RADIUS; 6 double y2 = BALL.getY(); 7 double x3 = BALL.getX() + 2 * BALL_RADIUS; 8 double y3 = BALL.getY() + 2 * BALL_RADIUS; 9 double x4 = BALL.getX(); 10 double y4 = BALL.getY() + 2 * BALL_RADIUS; 11 if(getElementAt(x1,y1) != null){ 12 return getElementAt(x1,y1); 13 } else if(getElementAt(x2,y2) != null){ 14 return getElementAt(x2,y2); 15 } else if(getElementAt(x3,y3) != null){ 16 return getElementAt(x3,y3); 17 } else if(getElementAt(x4,y4) != null){ 18 return getElementAt(x4,y4); 19 } else{ 20 return null; 21 } 22 }
是用小球的正切正方形的四个点进行判断十分碰到挡板或砖块;碰到挡板垂直反弹,碰到砖块,消除方块并反弹。
if(collider == PADDLE){
vy = -vy;
} else if (collider != null){
vy = -vy;
remove(collider);
n++;
}
第五步:尾声
1.加入游戏获胜和失败的判断条件;
2.加入获胜和失败的反馈;
3.加入回合数;
完整代码:
/* * File: Breakout.java * ------------------- * Name: * Section Leader: * * This file will eventually implement the game of Breakout. */ import acm.graphics.*; import acm.program.*; import acm.util.*; import java.applet.*; import java.awt.*; import java.awt.event.*; public class Breakout extends GraphicsProgram { /** Width and height of application window in pixels */ public static final int APPLICATION_WIDTH = 400; public static final int APPLICATION_HEIGHT = 600; /** Dimensions of game board (usually the same) */ private static final int WIDTH = APPLICATION_WIDTH; private static final int HEIGHT = APPLICATION_HEIGHT; /** Dimensions of the paddle */ private static final int PADDLE_WIDTH = 60; private static final int PADDLE_HEIGHT = 10; /** Offset of the paddle up from the bottom */ private static final int PADDLE_Y_OFFSET = 30; /** Number of bricks per row */ private static final int NBRICKS_PER_ROW = 10; /** Number of rows of bricks */ private static final int NBRICK_ROWS = 10; /** Separation between bricks */ private static final int BRICK_SEP = 4; /** Width of a brick */ private static final int BRICK_WIDTH = (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW; /** Height of a brick */ private static final int BRICK_HEIGHT = 8; /** Radius of the ball in pixels */ private static final int BALL_RADIUS = 10; /** Offset of the top brick row from the top */ private static final int BRICK_Y_OFFSET = 70; /** Number of turns */ private static final int NTURNS = 3; /* Method: run() */ /** Runs the Breakout program. */ public void run() { createAllBrick(NBRICK_ROWS, NBRICKS_PER_ROW); createPaddle(); int i = 0; while(i < NTURNS){ createBall(); i++; if (isWin == 1) break; } if(i == 3){ add(new GLabel("YOU LOSE", getWidth() / 2, getHeight() / 2)); } } /* * 根据提供的数据创建游戏需要的砖块; */ private void createAllBrick(int row, int rank){ for(int i = 0; i < row; i++){ for(int j = 0; j < rank; j++){ GRect brick = new GRect(0 + j * (BRICK_WIDTH + BRICK_SEP), BRICK_Y_OFFSET + i * (BRICK_HEIGHT + BRICK_SEP), BRICK_WIDTH, BRICK_HEIGHT); brick.setFilled(true); if(i < 2){ brick.setColor(Color.RED); } else if(i < 4){ brick.setColor(Color.ORANGE); } else if(i < 6){ brick.setColor(Color.YELLOW); } else if(i < 8){ brick.setColor(Color.GREEN); } else { brick.setColor(Color.CYAN); } add(brick); } } } /* * 创建一个可以用鼠标拖动的挡板; * 挡板不能被拖出屏幕边界; */ private void createPaddle(){ /* HEIGHT 定义的是窗口高度所以要使用getHeight();*/ GRect paddle = new GRect((WIDTH - PADDLE_WIDTH) / 2, getHeight() - PADDLE_HEIGHT - PADDLE_Y_OFFSET, PADDLE_WIDTH, PADDLE_HEIGHT); PADDLE = paddle; paddle.setFilled(true); paddle.setColor(Color.BLACK); add(paddle); addMouseListeners(); } /* 单击鼠标事件 */ public void mousePressed(MouseEvent e){ last = new GPoint(e.getPoint()); gobj = getElementAt(last); } /* 鼠标拖动事件 */ public void mouseDragged(MouseEvent e){ if(gobj != null && (gobj.getX() > 0 || e.getX() - last.getX() > 0) && (gobj.getX() + gobj.getWidth() < getWidth() || e.getX() - last.getX() < 0)){ gobj.move(e.getX() - last.getX(), 0); last = new GPoint(e.getPoint()); } } /* 创建一个反弹的小球 */ private void createBall(){ GOval ball= new GOval((getWidth() - 2 * BALL_RADIUS) / 2, (getHeight() - 2 * BALL_RADIUS) / 2, BALL_RADIUS, BALL_RADIUS); ball.setFilled(true); BALL = ball; add(ball); int n = 0; //记录消除的砖块数目; vy = 3.0; vx = rgen.nextDouble(1.0, 3.0); if(rgen.nextBoolean(0.5)) vx = -vx; while(true){ ball.move(vx,vy); GObject collider = getCollidingObject(); pause(PAUSE_TIME); if(collider == PADDLE){ vy = -vy; } else if (collider != null){ vy = -vy; remove(collider); n++; if (n == 100){ add(new GLabel("YOU WIN!", getWidth() /2, getHeight() / 2));//显示消息; remove(ball); isWin = 1; break; } } if(ball.getX() < 0 || ball.getX() + 2 * BALL_RADIUS > getWidth()) vx = -vx; if(ball.getY() < 0) vy = -vy; if(ball.getY() + 2 * BALL_RADIUS > getHeight()){ remove(ball); break; } } } private GObject getCollidingObject(){ /* 小球的正切正方形的四个顶点 */ double x1 = BALL.getX(); double y1 = BALL.getY(); double x2 = BALL.getX() + 2 * BALL_RADIUS; double y2 = BALL.getY(); double x3 = BALL.getX() + 2 * BALL_RADIUS; double y3 = BALL.getY() + 2 * BALL_RADIUS; double x4 = BALL.getX(); double y4 = BALL.getY() + 2 * BALL_RADIUS; if(getElementAt(x1,y1) != null){ return getElementAt(x1,y1); } else if(getElementAt(x2,y2) != null){ return getElementAt(x2,y2); } else if(getElementAt(x3,y3) != null){ return getElementAt(x3,y3); } else if(getElementAt(x4,y4) != null){ return getElementAt(x4,y4); } else{ return null; } } /* 创建一个随机数生成器 */ private RandomGenerator rgen = RandomGenerator.getInstance(); /* 小球移动的实例变量 */ private double vx, vy; /* 小球移动的暂停时间 */ private int PAUSE_TIME = 20; private GOval BALL; private GRect PADDLE; private int isWin = 0;/* 是否获胜 */ private GObject gobj; /* The object being dragged */ private GPoint last; /* The last mouse position */ }
刚开始学,还有很多地方实现的不完善;


