首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >从零开始构建一个突破游戏

从零开始构建一个突破游戏
EN

Code Review用户
提问于 2012-01-16 15:08:35
回答 4查看 18.3K关注 0票数 4

我是个初学者,目前正在学习教Java的斯坦福大学免费CS106A课程e。第二次分配让您从零开始使用ACM程序和图形包构建突破。

因为这是一门在线课程,而且我没有任何人可以复习我的代码,所以我想我可能会问你我犯了什么错误。我肯定有很多。

我很难弄清楚该把这些行为放在哪里。我有一个Ball类,我想在其中放置碰撞检测和反应,但最后不得不将它放在主程序中,因为ACM不允许我从球内检查球周围的帆布。

我还可以从任何地方获得主程序的实例,只有它的类,这不是我所需要的。对怎么做有什么想法吗?其他例子也是问题。例如,有什么方法可以让我不把Paddle's实例传递给构造函数就可以在Ball中得到它?有没有方法调用它?

我不知道该在哪里使用私有/公共和静态。我觉得我的很多用途都是变通的。

你怎么会以不同的方式建造这个呢?我应该停止犯什么大错误?组织得很好吗?

主程序:

代码语言:javascript
复制
import acm.graphics.*; 
import acm.program.*; 
import acm.util.*; 
import java.applet.*; 
import java.awt.*; 
import java.awt.event.*; 

public class Breakout extends GraphicsProgram { 

public void init() { 
    setBricks();
    add(paddle);
    add(ball,WIDTH/2,500);
    lifeBar.setColor(Color.WHITE);
    add(lifeBar,20,60);
    addMouseListeners();
    setBackground(Color.BLACK);
}

public void run() { 
    while(gameOver != true) {
        pause(20);
        checkCollision(ball);
        ball.move();

        if (prize != null) {
            prize.drop();
            if (prize.leftScreen()) remove(prize);
        }
    }
    addMouseListeners();
} 

private void setBricks() {
    for (int row = 0; row < NBRICK_ROWS; row++) {
        Color rowColor = checkColor(row);   
        for (int col = 0; col < NBRICKS_PER_ROW; col++) {
            add(new Brick(col,row,rowColor));
        }
    }
}

public void mouseClicked(MouseEvent e) {
    removeBrick(getElementAt(e.getX(),e.getY()));
}

public void mouseMoved(MouseEvent e) {
    paddle.followMouse(e.getX());
}


private Color checkColor(int row) {
    switch (row) {
        case 0:
        case 1: return Color.RED;
        case 2:
        case 3: return Color.ORANGE;
        case 4:
        case 5: return Color.YELLOW;
        case 6:
        case 7: return Color.GREEN;
        case 8:
        case 9: return Color.CYAN;
    }
    return Color.CYAN;
}


private void removeBrick(GObject brick) {
    /* add test to see if removing brick is possible */
    if (brick != null && brick instanceof Brick) {
        remove(brick);
        if (((Brick) brick).isSpecial()) callPrize(brick.getX(),brick.getY());
        if (Brick.decreaseBricks()) callGameOver(true);
    }
}

private void removeLife() {
    lives--;
    lifeBar.setLabel("Lives: " + lives);
    if (lives <= 0) callGameOver(false);
}

public void callPrize(double x, double y) {
    prize = new Prize(x,y,Color.MAGENTA, paddle);
    add(prize);
}

public void callGameOver(boolean won) {
    GRect gameOverScreen = new GRect(0,0,WIDTH,HEIGHT);
    gameOverScreen.setFilled(true);
    gameOverScreen.sendToFront();
    GLabel gameOverLabel = new GLabel("");
    if (won == true) {
        gameOverScreen.setColor(Color.WHITE);
        gameOverLabel.setColor(Color.BLUE);
        gameOverLabel.setLabel("WOO YOU WON!!!");
        if (lives == 3) gameOverLabel.setLabel("HOLY SHIT PERFECT SCORE!!!");
    } else {
        gameOverScreen.setColor(Color.RED);
        gameOverLabel.setColor(Color.WHITE);
        gameOverLabel.setLabel("HAH WHAT A LOSER");
    }
    add(gameOverScreen);
    add(gameOverLabel,WIDTH/2-gameOverLabel.getWidth()/2,HEIGHT/2+gameOverLabel.getAscent()/2);

    gameOver = true;
}

private void checkCollision(Ball ball) {
        double r = ball.getRadius();
        ball.move();
        double x = ball.getX();
        double y = ball.getY(); 
        ball.moveBack();
        GObject collider = null;
        int dir = 0;

        // Check walls + keep within bounds (keep from getting stuck)
        if (x-r <= 0) {
            ball.setLocation(r,y);
            ball.changeDirection(1);
        } else if (x+r >= WIDTH) {
            ball.setLocation(WIDTH-r,y);
            ball.changeDirection(1);
        }

        if (y-r <= 0) {
            ball.setLocation(x,r);
            ball.changeDirection(2);
        // less life
        } else if (y+r >= HEIGHT) {
            removeLife();
            ball.restart();
        }

        //Check elements

        //Check right
        if (getElementAt(x+r,y) != null && getElementAt(x+r,y) != ball && !(getElementAt(x+r,y) instanceof GLabel) && !(getElementAt(x+r,y) instanceof Prize)) {
            ball.changeDirection(1);
            collider = getElementAt(x+r,y);
        } 
        //Check left
        if (getElementAt(x-r,y) != null && getElementAt(x-r,y) != ball && !(getElementAt(x-r,y) instanceof GLabel) && !(getElementAt(x-r,y) instanceof Prize)) {
            ball.changeDirection(1);
            collider = getElementAt(x-r,y);
        } 
        //Check bottom
        if (getElementAt(x,y+r) != null && getElementAt(x,y+r) != ball && !(getElementAt(x,y+r) instanceof GLabel) && !(getElementAt(x,y+r) instanceof Prize)) {
            ball.changeDirection(2);
            collider = getElementAt(x,y+r);
        } 
        //Check top
        if (getElementAt(x,y-r) != null && getElementAt(x,y-r) != ball && !(getElementAt(x,y-r) instanceof GLabel) && !(getElementAt(x,y-r) instanceof Prize)) {
            ball.changeDirection(2);
            collider = getElementAt(x,y-r); 
        } 

        ball.changeDirection(dir);
        if (collider instanceof Brick) removeBrick(collider);
        if (collider == paddle) {
            ball.setDirection(paddle.collide(x,r));
        }
}


/* Init things */
private Paddle paddle = new Paddle();       
private Ball ball = new Ball(BALL_RADIUS);

/** 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) */ 
    public static final int WIDTH = APPLICATION_WIDTH; 
    public static final int HEIGHT = APPLICATION_HEIGHT; 

 /** Offset of the paddle up from the bottom */ 
    public static final int PADDLE_Y_OFFSET = 30; 

 /** Number of bricks per row */ 
    public static final int NBRICKS_PER_ROW = 10;

 /** Number of rows of bricks */ 
    private static final int NBRICK_ROWS = 10;

 /** Radius of the ball in pixels */ 
    private static final int BALL_RADIUS = 10;

 /** Offset of the top brick row from the top */ 
    public static final int BRICK_Y_OFFSET = 70;

 /** Number of turns */ 
    private static final int NTURNS = 3;


/** Points */
    private int lives = NTURNS;  

/** Game is Over */
    private boolean gameOver = false;  

/* Prize on Board */
    Prize prize = null;


    private GLabel lifeBar = new GLabel("Lives: " + lives);
 }

Ball

代码语言:javascript
复制
import acm.graphics.*; 
import acm.util.*; 
import java.awt.*; 

public class Ball extends GCompound {

public Ball(double radius) {
    r = radius;
    GOval ball = new GOval(-r,-r,r*2,r*2);
    ball.setFilled(true);
    ball.setColor(new Color(200,200,200));


    GOval light = new GOval(-r+3,-r+3,r*2-6,r-2);

    light.setFilled(true);
    light.setColor(new Color(255,255,255,150));
    add(ball);
    add(light);

    direction = rgen.nextDouble(220,320);
    direction = 290;
}

public void move() {
    this.movePolar(velocity,direction);
}

public void moveBack() {
    this.movePolar(velocity, (direction+180)%360);
}

public void restart() {
    this.setVisible(false);
    pause(100);
    this.setVisible(true);
    pause(100);
    this.setVisible(false);
    pause(100);
    this.setLocation(Breakout.WIDTH/2,Breakout.HEIGHT/2);
    this.setVisible(true);
    direction = rgen.nextDouble(220,320);
}

public double getRadius() {
    return r;
}

public void changeDirection(int xory) {
    if (xory == 1) direction -= (direction-270)*2;
    if (xory == 2) direction -= (direction-180)*2;
}

public double getDirection() {
    return direction;
}

public static void setVelocity(double v) {
    velocity = v;
}

public void setDirection(double d) {
    direction = d;
}

private double r;
private double direction;


private static final double STARTING_VELOCITY = 5.0;
private static double velocity = STARTING_VELOCITY;

private RandomGenerator rgen = RandomGenerator.getInstance();
}

Brick

代码语言:javascript
复制
import acm.graphics.*; 
import acm.util.*;  
import java.awt.*; 

public class Brick extends GCompound {

public Brick(int colNum, int rowNum, Color color) {

    if (rgen.nextBoolean(0.01)) {
        color = Color.MAGENTA;
        special = true;
    }
    GRect fill = new GRect(0,0,BRICK_WIDTH, BRICK_HEIGHT);
    fill.setFilled(true);
    fill.setColor(color);
    GRect light = new GRect(0,0,BRICK_WIDTH,BRICK_HEIGHT/2);
    light.setFilled(true);
    light.setColor(new Color(255,255,255,120+10*rowNum));
    if (special == true) light.setColor(new Color(255,255,255,120));



    add(fill);
    add(light);

    // Find and set location
    double x = START_X + colNum * (BRICK_WIDTH + BRICK_SEP);
    double y = START_Y + rowNum * (BRICK_HEIGHT + BRICK_SEP);
    setLocation(x,y);

    // Add to bricks remaining;
    bricksRemaining++;
}

public static int getBricksRemaining() {
    return bricksRemaining;
}

/* decreases bricks remaining and checks for end */
public static boolean decreaseBricks() {
    bricksRemaining--;
    if (bricksRemaining < 75) Ball.setVelocity(7); //Set the velocity higher if
    if (bricksRemaining < 50) Ball.setVelocity(9); 
    if (bricksRemaining < 25) Ball.setVelocity(11); 
    return (bricksRemaining > 0) ? false : true;
}

public boolean isSpecial() {
    return special;
}

 /** Separation between bricks */ 
    public static final int BRICK_SEP = 4;

 /** Width of a brick */ 
    public static final int BRICK_WIDTH = 
    (Breakout.WIDTH - (Breakout.NBRICKS_PER_ROW - 1) * BRICK_SEP) / Breakout.NBRICKS_PER_ROW; 

 /** Height of a brick */ 
    public static final int BRICK_HEIGHT = 12;


/** Starting point X and Y values for first brick */
    private static final double START_X = (Breakout.WIDTH - ((Breakout.NBRICKS_PER_ROW * BRICK_WIDTH) + (Breakout.NBRICKS_PER_ROW - 1) * BRICK_SEP)) / 2 ;      
    private static final int START_Y = Breakout.BRICK_Y_OFFSET;


    private boolean special = false;

    private static int bricksRemaining = 0;

    private RandomGenerator rgen = RandomGenerator.getInstance();
}

Paddle

代码语言:javascript
复制
import acm.graphics.*; 
import java.awt.*; 

public class Paddle extends GCompound {

public Paddle() {
    fill = new GRect(0,0, PADDLE_STARTING_WIDTH, PADDLE_HEIGHT);
    fill.setFilled(true);
    fill.setColor(Color.GRAY);
    light = new GRect(0,PADDLE_HEIGHT/2, PADDLE_STARTING_WIDTH, PADDLE_HEIGHT/2);
    light.setFilled(true);
    light.setColor(new Color(0,0,0,50));
    add(fill);
    add(light);
    this.setLocation((Breakout.WIDTH - PADDLE_STARTING_WIDTH) / 2, PADDLE_Y);
}

public void followMouse(double mouseX) {
    if (mouseX < paddleWidth/2) {
        this.setLocation(0, PADDLE_Y);
    } else if (mouseX > Breakout.WIDTH - paddleWidth/2) {
        this.setLocation(Breakout.WIDTH - paddleWidth,PADDLE_Y);
    } else {
        this.move(mouseX-paddleWidth/2 - this.getX(), 0);
    }
}

public double collide(double ballX, double r) {
    double paddleX = this.getX();
    setColor(Color.RED);
    double hitSpot = ballX - paddleX;
    double maxPaddle = paddleWidth + r;
    double minPaddle = -r;
    double paddleRange = maxPaddle - minPaddle;
    double minAngle = 160;
    double maxAngle = 20;
    double angleRange = maxAngle - minAngle;
    double newDirection = ((hitSpot * angleRange) / paddleRange) + minAngle;


    return newDirection;
}

private void widen() {
    paddleWidth += 30;
    this.move(-15, 0);
    fill.setSize(paddleWidth, PADDLE_HEIGHT);
    light.setSize(paddleWidth, PADDLE_HEIGHT / 2);
}

public void checkForPrize(Prize prize, double x, double y) {
    if ((y >= PADDLE_Y) && (y <= PADDLE_Y + PADDLE_HEIGHT) && x > this.getX() && x < this.getX() + paddleWidth) {
        prize.pickUp();
        this.widen();
    }
}


 /** Dimensions of the paddle */ 
private static final int PADDLE_STARTING_WIDTH = 60; 
private static final int PADDLE_HEIGHT = 15;

private GRect fill;
private GRect light;
private int paddleWidth = PADDLE_STARTING_WIDTH;
 /** Dimensions of the paddle */ 
public static final int PADDLE_Y = Breakout.HEIGHT - Breakout.PADDLE_Y_OFFSET;
}

Prize

代码语言:javascript
复制
import acm.graphics.*; 
import java.awt.*; 


public class Prize extends GOval {

public Prize(double x, double y, Color color, Paddle pad) {
    super(x + Brick.BRICK_HEIGHT/2, y + Brick.BRICK_WIDTH/2, PRIZE_RADIUS * 2, PRIZE_RADIUS * 2);
    setFilled(true);
    setColor(color);

    paddle = pad;
}

public void drop() {
    this.move(0, PRIZE_SPEED);
    paddle.checkForPrize(this,this.getX()+PRIZE_RADIUS,this.getY()+PRIZE_RADIUS*2);
}

public boolean leftScreen() {
    if (this.getY() - PRIZE_RADIUS > Breakout.HEIGHT || pickedUp == true) {
        return true;
    } else {
        return false;
    }   
}

public void pickUp() {
    pickedUp = true;
}

private boolean pickedUp = false;

private Paddle paddle;

public static final int PRIZE_RADIUS = 5;
public static final int PRIZE_SPEED = 10;
}
EN

回答 4

Code Review用户

发布于 2012-01-16 16:19:15

我没有尝试进入高水平的设计,你的游戏,但以下是一些细节,你可能想要修复。

  • if (won == true)可以写成if (won)
  • 只能使用三元运算符一次使用setLabel:gameOverLabel.setLabel((lives == 3)?"HOLY SHIT PERFECT SCORE!!!":"WOO YOU WON!!!");
  • 我想检查元素的代码可以分解。
  • 使用instanceof通常是一件坏事。
  • 您可能希望将神奇的数字存储在常量变量中,以便更容易理解。
  • 在Ball构造函数中初始化方向的方式很奇怪(改变方向的方式也很尴尬)
  • decreaseBricks()方法中可能有一个问题:您可能希望按另一个顺序进行测试,并以其他顺序分隔,您可以简单地使用return (bricksRemaining > 0) (可能返回bricksRemaining会工作,但我只是不记得它在Java中是如何工作的)
  • leftScreen()方法可以是return <your big expression>;
票数 4
EN

Code Review用户

发布于 2012-01-17 02:08:36

General

如果没有多余的"this."s,您的代码将更加可读性。

主程序

代码语言:javascript
复制
private void checkCollision(Ball ball) {
        double r = ball.getRadius();
//// If you moved the ball BEFORE checkCollision, you wouldn't need
//// to move it here, and move it back only to move it again, AFTER.
        ball.move();
        double x = ball.getX();
        double y = ball.getY(); 

//// There's no point in finishing this function after removeLife,
//// so test for it up-front and get it out of the way.

        // less life
        if (y+r >= HEIGHT) {
            removeLife();
            ball.restart();
            return;
        }

        ball.moveBack();
        GObject collider = null;
        int dir = 0;

        // Check walls + keep within bounds (keep from getting stuck)
        if (x-r <= 0) {
            ball.setLocation(r,y);
            ball.changeDirection(1);
        } else if (x+r >= WIDTH) {
            ball.setLocation(WIDTH-r,y);
            ball.changeDirection(1);
        }

        if (y-r <= 0) {
            ball.setLocation(x,r);
            ball.changeDirection(2);
        }

        //Check elements

如果建立了“区域”(y值范围),那么这个“检查元素”部分可能要快得多,在该区域中,您希望与砖块或桨碰撞,并且只测试球在其区域内时是否与这些东西发生碰撞。

为每个给定的参数对调用一次getElementAt,并重用结果,执行起来更快,更容易阅读。

你不会同时碰到两块砖的情况,这些砖是并排的,自上而下的,或者是触角的。您只处理检测到的第二次碰撞。但是,如果您修复了这个问题,请不要忘记,同一块砖也可能在多个方向上匹配。

你只需测试一个或两个方向的砖(S),球目前正每个方向一个+/-x (除非方向是90)和+/-y。

由于砖块没有移动,根据最初放置砖块的位置,编写测试与之碰撞的砖块的功能可能更容易。那么,将你的砖块放在一个二维数组中会有帮助。然后,您可以单独处理桨,而忘记了GLabels。

无论是分区还是特殊外壳,砖块碰撞都免除了检查(对撞机安装的砖块)的需要,这是很好避免的。

向上移动时,不需要检查是否有划桨。

球类

在这里和这里调用时,使用consts或枚举,而不是1或2。

代码语言:javascript
复制
public void changeDirection(int xory) {
    if (xory == 1) direction -= (direction-270)*2;
    if (xory == 2) direction -= (direction-180)*2;
}

正如已经评论过的,这些都是奇怪的方向表达式。它们在技术上对x轴反射和y轴反射是正确的,但却是一种非常迂回的表达方式:

代码语言:javascript
复制
direction = ((xory==1) ?  180 : 0) - direction;

砖类

从未打过电话:

代码语言:javascript
复制
public static int getBricksRemaining() {
    return bricksRemaining;
}

bricksRemaining,decreaseBricks,et.阿尔。应该由主要的突破类来处理,这样砖块就不需要依靠球了。

桨类

代码语言:javascript
复制
//// These should be static consts.
    double minAngle = 160;
    double maxAngle = 20;
    double angleRange = maxAngle - minAngle;



public void checkForPrize(Prize prize, double x, double y) {
    if ((y >= PADDLE_Y) && (y <= PADDLE_Y + PADDLE_HEIGHT) && x > getX() && x < getX() + paddleWidth) {
        prize.pickUp();
//// Moving the next statement into Prize::pickUp makes the purpose of the Prize 
//// more self-evident within its class and would allow different prizes to have
//// different effects.
        widen();
    }
}
票数 3
EN

Code Review用户

发布于 2012-01-17 10:12:45

把Ball类中的changeDirection分解为两种方法,并简化每一种方法中的数学。有点像

代码语言:javascript
复制
public void bounceVertical(){
    direction = 540 - direction;
}

public void bounceHorizontal(){
    direction = 360 - direction;
}

再加上其他答卷人提出的好建议。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/7865

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档