Java "Breakout"克隆 : Suspending and resuming thread

标签 java multithreading

对于 CS 入门类(class),我正在尝试用 Java 克隆“Breakout”。游戏已经完成 99%,所以我想我应该添加一些额外内容。

我想添加的一件事是使用空格键暂停和恢复的功能。我添加了一个 boolean “isPaused”变量并在每次调用 game.resume() 和 game.suspend() 时更改它的值。然后,我使用 KeyAdapter 告诉程序在用户按下空格键时根据“isPaused”值恢复和暂停。这似乎大部分时间都有效,但偶尔需要单击两次空格键。我已经广泛查看了代码,但无法解决问题。它似乎通常在新级别开始时发生。因此,我将发布“Board.java”文件中的代码,其中包含游戏逻辑和手头的问题。谢谢!代码如下。

这个“Board”类处理所有游戏逻辑并在屏幕上显示项目。

//imports
import java.awt.*;
import javax.swing.*;
import java.util.Random;
import java.lang.Thread;
import javax.sound.sampled.*;
import java.io.*;
import java.awt.event.*;
import java.util.ArrayList;

//class definition
public class Board extends JPanel implements Runnable, Constants {
//variables
Paddle paddle;
Ball ball;
Brick[][] brick = new Brick[10][5];
int score = 0, lives = 5, bricksLeft = 50, waitTime = 3, xSpeed, withSound, level = 1;
String playerName;
Thread game;
String songFile = "music/Start.wav";
Color brickColor = new Color(0,0,255);
ArrayList<Item> items = new ArrayList<Item>();
boolean isPaused = true;

//constructor
public Board(int width, int height) {
    super.setSize(width, height);
    addKeyListener(new BoardListener());
    setFocusable(true);

    makeBricks();
    paddle = new Paddle(width/2, height-(height/10), width/7, height/50, Color.BLACK);
    ball = new Ball(BALL_X_START, BALL_Y_START, BALL_WIDTH, BALL_HEIGHT, Color.BLACK);

    //Get the player's name
    playerName = JOptionPane.showInputDialog(null, "Enter your name:", "Name", JOptionPane.INFORMATION_MESSAGE);
    if (playerName == null) {
        System.exit(0);
    }

    //Start Screen that displays information and asks if the user wants music or not
    String[] options = {"Yes", "No"};
    withSound = JOptionPane.showOptionDialog(null, "Brick Breaker, Version 1.0\nBy Ty-Lucas Kelley, for CSC 171 Fall 2013\nAll credit for the music goes to the SEGA Corporation.\n\n\nControls: Press spacebar to start, and use the arrow keys to move.\n\n\nWould you like to play with the music on?", "Introduction", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]);
    playMusic(songFile, withSound);

    game = new Thread(this);
    game.start();
    stop();
}

//fills the array of bricks
public void makeBricks() {
    for(int i = 0; i < 10; i++) {
        for(int j = 0; j < 5; j++) {
            Random rand = new Random();
            int itemType = rand.nextInt(3) + 1;
            int numLives = 3;
            brick[i][j] = new Brick(i * BRICK_WIDTH, (j * BRICK_HEIGHT) + (BRICK_HEIGHT / 2), BRICK_WIDTH - 5, BRICK_HEIGHT - 4, brickColor, numLives, itemType);
        }
    }
}

//starts the thread
public void start() {
    game.resume();
    isPaused = false;
}

//stops the thread
public void stop() {
    game.suspend();
    isPaused = true;
}

//ends the thread
public void destroy() {
    game.resume();
    isPaused = false;
    game.stop();
}

//runs the game
public void run() {
    xSpeed = 1;
    while(true) {
        int x1 = ball.getX();
        int y1 = ball.getY();

        //Makes sure speed doesnt get too fast/slow
        if (Math.abs(xSpeed) > 1) {
            if (xSpeed > 1) {
                xSpeed--;
            }
            if (xSpeed < 1) {
                xSpeed++;
            }
        }

        checkPaddle(x1, y1);
        checkWall(x1, y1);
        checkBricks(x1, y1);
        checkLives();
        checkIfOut(y1);
        ball.move();
        dropItems();
        checkItemList();
        repaint();

        try {
            game.sleep(waitTime);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
    }
}

public void addItem(Item i) {
    items.add(i);
}

public void dropItems() {
    for (int i = 0; i < items.size(); i++) {
        Item tempItem = items.get(i);
        tempItem.drop();
        items.set(i, tempItem);
    }
}

public void checkItemList() {
    for (int i = 0; i < items.size(); i++) {
        Item tempItem = items.get(i);
        if (paddle.caughtItem(tempItem)) {
            items.remove(i);
        }
        else if (tempItem.getY() > WINDOW_HEIGHT) {
            items.remove(i);
        }
    }
}

public void checkLives() {
    if (bricksLeft == 0) {
        ball.reset();
        bricksLeft = 50;
        makeBricks();
        lives++;
        level++;
        repaint();
        stop();
    }
    if (lives == 0) {
        repaint();
        stop();
    }
}

public void checkPaddle(int x1, int y1) {
    if (paddle.hitLeft(x1, y1)) {
        ball.setYDir(-1);
        xSpeed = -1;
        ball.setXDir(xSpeed);
    }
    else if (paddle.hitRight(x1, y1)) {
        ball.setYDir(-1);
        xSpeed = 1;
        ball.setXDir(xSpeed);
    }

    if (paddle.getX() <= 0) {
        paddle.setX(0);
    }
    if (paddle.getX() + paddle.getWidth() >= getWidth()) {
        paddle.setX(getWidth() - paddle.getWidth());
    }
}

public void checkWall(int x1, int y1) {
    if (x1 >= getWidth() - ball.getWidth()) {
        xSpeed = -Math.abs(xSpeed);
        ball.setXDir(xSpeed);
    }
    if (x1 <= 0) {
        xSpeed = Math.abs(xSpeed);
        ball.setXDir(xSpeed);
    }
    if (y1 <= 0) {
        ball.setYDir(1);
    }
    if (y1 >= getHeight()) {
        ball.setYDir(-1);
    }
}

public void checkBricks(int x1, int y1) {
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 5; j++) {
            if (brick[i][j].hitBottom(x1, y1)) {
                ball.setYDir(1);
                if (brick[i][j].isDestroyed()) {
                    bricksLeft--;
                    score += 50;
                    addItem(brick[i][j].item);
                }
            }
            if (brick[i][j].hitLeft(x1, y1)) {
                xSpeed = -xSpeed;
                ball.setXDir(xSpeed);
                if (brick[i][j].isDestroyed()) {
                    bricksLeft--;
                    score += 50;
                    addItem(brick[i][j].item);
                }
            }
            if (brick[i][j].hitRight(x1, y1)) {
                xSpeed = -xSpeed;
                ball.setXDir(xSpeed);
                if (brick[i][j].isDestroyed()) {
                    bricksLeft--;
                    score += 50;
                    addItem(brick[i][j].item);
                }
            }
            if (brick[i][j].hitTop(x1, y1)) {
                ball.setYDir(-1);
                if (brick[i][j].isDestroyed()) {
                    bricksLeft--;
                    score += 50;
                    addItem(brick[i][j].item);
                }
            }
        }
    }
}

public void checkIfOut(int y1) {
    if (y1 > PADDLE_Y_START) {
        lives--;
        score -= 100;
        ball.reset();
        repaint();
        stop();
    }
}

//plays music throughout game if user wants to
public void playMusic(String song, int yesNo) {
    if (yesNo == 1) {
        return;
    }
    else if (yesNo == -1) {
        System.exit(0);
    }
    try {
        AudioInputStream audio = AudioSystem.getAudioInputStream(new File(song).getAbsoluteFile());
        Clip clip = AudioSystem.getClip();
        clip.open(audio);
        clip.loop(Clip.LOOP_CONTINUOUSLY); 
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//fills the board
@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    paddle.draw(g);
    ball.draw(g);

    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 5; j++) {
            brick[i][j].draw(g);
        }
    }
    g.setColor(Color.BLACK);
    g.drawString("Lives: " + lives, 10, getHeight() - (getHeight()/10));
    g.drawString("Score: " + score, 10, getHeight() - (2*(getHeight()/10)) + 25);
    g.drawString("Level: " + level, 10, getHeight() - (3*(getHeight()/10)) + 50);
    g.drawString("Player: " + playerName, 10, getHeight() - (4*(getHeight()/10)) + 75);

    for (Item i: items) {
        i.draw(g);
    }

    if (lives == 0) {
        int heightBorder = getHeight()/10;
        int widthBorder = getWidth()/10;
        g.setColor(Color.BLACK);
        g.fillRect(widthBorder, heightBorder, getWidth() - (2 * widthBorder), getHeight() - (2 * heightBorder ));
        g.setColor(Color.WHITE);
        g.drawString("Game Over! Click the Spacebar twice to start over.", getWidth()/5, getHeight()/2);
    }
}

public String playerInfo() {
    return rank(score) + "." + " Name: " + playerName + ", Score: " + score;
}

public int rank(int score) {
    //check to see where this player falls on the list of saved games by reading from file
    return 0;
}

public void saveGame() {
    if (rank(score) >= 10) {
        return;
    }
    //save this game to HighScores.txt
}

public void printScores() {
    //print to paintComponent method. replace current 'game over' string
}

//Private class that handles gameplay and controls
private class BoardListener extends KeyAdapter {
    public void keyPressed(KeyEvent ke) {
        int key = ke.getKeyCode();
        if (key == KeyEvent.VK_SPACE) {
            if (lives > 0) {
                if (!isPaused) {
                    stop();
                }
                else {
                    start();
                }
            }
            else {
                paddle.setWidth(getWidth()/7);
                lives = 5;
                score = 0;
                bricksLeft = 50;
                level = 1;
                makeBricks();
                isPaused = true;
                for (int i = 0; i < 10; i++) {
                    for (int j = 0; j < 5; j++) {
                        brick[i][j].setDestroyed(false);
                    }
                }
            }
        }
        if (key == KeyEvent.VK_LEFT) {
            paddle.setX(paddle.getX() - 50);
        }
        if (key == KeyEvent.VK_RIGHT) {
            paddle.setX(paddle.getX() + 50);
        }
    }
    public void keyReleased(KeyEvent ke) {
        int key = ke.getKeyCode();
        if (key == KeyEvent.VK_LEFT) {
            paddle.setX(paddle.getX());
        }
        if (key == KeyEvent.VK_RIGHT) {
            paddle.setX(paddle.getX());
        }
    }
}

最佳答案

您的问题很可能是某种并发问题。您可能希望将变量从 boolean 切换为 AtomicBoolean 并在 suspend 中使用 synchronized (game) code>resume 方法。

此外,您不应使用 Thread.suspendThread.resume 方法。阅读他们的 JavaDoc 以获取更多信息。

像这样:

...
AtomicBoolean isPaused = new AtomicBoolean(false);
...

private void gamePause() {
   synchronized(game) {
      game.isPaused.set(true);
      game.notify();
   }
}

private void gameContinue() {
   synchronized(game) {
      game.isPaused.set(false);
      game.notify();
   }
}
...

然后在你处理循环的地方:

...
public void run() {
  xSpeed = 1;
  while(true) {
    synchronized(game) {
      while(game.isPaused().get()) {
         try {
            Thread.sleep(1000);
         } catch (InterruptedException iex) {
            // This most likely means your JVM stops. Maybe log the Exception.
            game.destroy();
            return;
         }
      }
      int x1 = ball.getX();
      int y1 = ball.getY();
      ...
      }
    }
  }
}

还有在 checkLives 方法中。 (据我所知,checkLives 仅从运行中调用,如果是这种情况,它已经在 synchronized(game) block 中。如果不是,则必须添加 synchronized 也围绕 stop() 进行。

public void checkLives() {
    if (bricksLeft == 0) {
        ...
        if(!game.isPaused().get())
           stop();
    }
    if (lives == 0) {
        repaint();
        if(!game.isPaused().get())
           stop();
    }
}

问题是 checkLives() 调用 stop() 触发 isPaused 被翻转。如果同时 KeyListener 被激活,它会探测 isPaused,认为游戏已暂停并继续,因此您必须再次按空格键才能继续。

关于Java "Breakout"克隆 : Suspending and resuming thread,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20172562/

相关文章:

java - 如何在 Java 的步骤定义中使用 Cucumber 数据表中的变量名称?

java - 如何使用 JPA 查询更新单个表单

c++ - 在 C++ 中多线程模拟

c++ - 在类函数中将参数传递给 _beginthread

java - 获取文件修改时间

java - 如何用 Java 重新启动我的服务器

java - 从文件中读取并显示随机行

java - 在java中发送电子邮件而不阻塞主代码流的最佳方法是什么?

java - 图表上的 setFont 问题 (JFreeChart)

multithreading - HTML5 Web Worker 是针对一个页面还是跨页面