Ожидание и уведомление в контексте звука игры

#java #multithreading #audio

#java #многопоточность #Аудио

Вопрос:

Я создал простую игру snake, в которой я реализовал звуковые эффекты и немного фоновой музыки.

Я создал класс «SoundFX» для обработки звуков, два экземпляра SoundFX воспроизводят GameOver и appleGet FX, но класс SoundFX также расширяет поток, чтобы зацикливать фоновую музыку во время воспроизведения игры.

Я хочу, чтобы эта музыка останавливалась, когда игрок нажимает «game over», а затем снова запускал игру, как только игрок нажимал клавишу пробела.

Я пытаюсь использовать ожидание и уведомление, чтобы избежать ожидания ожидания фоновой музыки. поток.

Однако я не могу понять, как это сделать, не оставляя мой фоновый музыкальный поток в состоянии ожидания или не нажимая исключение «Поток не является текущим владельцем».

Я совсем новичок в потоках, и, несмотря на учебные пособия YouTube и чтение в «Head First Java», я не могу понять, как применить это к этому контексту.

 public class SoundFX extends Thread{

private final String soundName;
private volatile boolean isOn;
private volatile boolean isFailedGame = false;

public SoundFX(String soundName) {
    this.soundName = soundName;
}
public SoundFX(String soundName, boolean isOn) {
    this.soundName = soundName;
    this.isOn = isOn;
}

public void setOn(boolean on) {
    isOn = on;
}

public void setFailedGame(boolean failed){
    isFailedGame = failed;
}

public void playSound(String soundName){
    File soundFile = new File(soundName);
    try{
        Clip clip = AudioSystem.getClip();
        clip.open(AudioSystem.getAudioInputStream(soundFile));
        clip.start();
    } catch (Exception e){
        e.printStackTrace();
    }
}

@Override
public synchronized void run() {
    File soundFile = new File(soundName);
    try{
        Clip clip = AudioSystem.getClip();
        clip.open(AudioSystem.getAudioInputStream(soundFile));
        while (Thread.currentThread().isAlive()) {
            if (!isOn) {
                clip.stop();
                if(isFailedGame) {
                    clip.setFramePosition(0);
                }
                //Thread.onSpinWait();
                this.wait();
            }
            clip.loop(Clip.LOOP_CONTINUOUSLY);
        }
    } catch (Exception e){
        e.printStackTrace();
    }
}
  

}

 public class Gameplay extends JPanel implements KeyListener, ActionListener {

//region SoundFX
private String appleGetSound = "/Users/Niklas/Downloads/JavaFontysICT-master/2D-Snake-AwaisMirzaTutorial/AppleGet.wav";
private String failSound = "/Users/Niklas/Downloads/JavaFontysICT-master/2D-Snake-AwaisMirzaTutorial/SnakeFail.wav";
private String backgroundSong = "/Users/Niklas/Downloads/JavaFontysICT-master/2D-Snake-AwaisMirzaTutorial/SnakeSongLoop.wav";
SoundFX appleGetSoundFX = new SoundFX(appleGetSound);
SoundFX failedSoundFX = new SoundFX(failSound);
SoundFX backgroundSongLoop = new SoundFX(backgroundSong, false);
//endregion

private boolean paused = false;
private boolean failed = false;


public Gameplay() {
    addKeyListener(this);
    setFocusable(true);
    setFocusTraversalKeysEnabled(false);


    //the speed of the snake
    timer = new Timer(clockSpeed, this);
    backgroundSongLoop.start();
    timer.start();
}

public synchronized void paint(Graphics g){

    //region Default position
    if(moves==0){
        backgroundSongLoop.setOn(true);
        snakeXLength[2] = 50;
        snakeXLength[1] = 75;
        snakeXLength[0] = 100;

        snakeYLength[2] = 100;
        snakeYLength[1] = 100;
        snakeYLength[0] = 100;
        failed=false;
    }
    //endregion


    //region Snake-Snake Collision
    for (int b=1; b<lengthOfSnake; b  ){
        if(snakeXLength[b]==snakeXLength[0] amp;amp; snakeYLength[b]==snakeYLength[0]){
            backgroundSongLoop.setOn(false);
            backgroundSongLoop.setFailedGame(true);

            System.out.println(Thread.currentThread());

            failedSoundFX.playSound(failSound);
            failed=true;
            timer.stop();

            //region High score manager
            if (score>highscores[2] amp;amp; score<highscores[1]){
                highscores[2]=score;
            }
            if (score>highscores[1] amp;amp; score<highscores[0]){
                highscores[2]=highscores[1];
                highscores[1]=score;
            }
            if (score > highscores[0]){
                highscores[2]=highscores[1];
                highscores[1]=highscores[0];
                highscores[0] = score;
            }
            //endregion

            g.setColor(Color.BLACK);
            g.fillRect(25, 75, 850, 575);

            g.setColor(Color.WHITE);
            g.setFont(new Font("arial", Font.BOLD, 50));
            g.drawString("GAME OVER", 300, 300);
            g.setFont(new Font("arial", Font.BOLD, 20));
            g.drawString("Press [Space Bar] to play again", 300, 340);
            g.drawString("HIGHSCORES:", 380, 380);
            g.drawString("#1: "   highscores[0], 380, 400);
            if (highscores[1]>0){
                g.drawString("#2: "   highscores[1], 380, 420);
            }
            if (highscores[2]>0) {
                g.drawString("#3: "   highscores[2], 380, 440);
            }
        }
    }
    //endregion

    g.dispose();

}


@Override
public void keyPressed(KeyEvent e) {
    
    //region restart function
    if (failed){
        if (e.getKeyCode()==KeyEvent.VK_SPACE) {
            backgroundSongLoop.setOn(true);
            backgroundSongLoop.setFailedGame(false);
            score = 0;
            lengthOfSnake = 3;
            moves = 0;
            repaint();
            timer.start();
        }
    }
    //endregion

    // region pause function
    if (e.getKeyCode()==KeyEvent.VK_ESCAPE amp;amp; !paused amp;amp; !failed){
        backgroundSongLoop.setOn(false);
        paused=true;
    } else if (e.getKeyCode()==KeyEvent.VK_ESCAPE amp;amp; paused amp;amp; !failed){
        paused=false;
        backgroundSongLoop.setOn(true);
        timer.start();
    }
    //endregion
}
  

}

Ответ №1:

Я рекомендую удалить Thread расширение из SoundFX . Просто создайте Clip переменную экземпляра в своем SoundFX классе. Создайте отдельные методы для инициализации (конструктор отлично подходит для этого), воспроизведения и остановки воспроизведения клипа. Метод воспроизведения (для непрерывного цикла) будет следующим:

 public void play() {
    clip.setFramePosition(0);
    clip.loop(Clip.LOOP_CONTINUOUSLY);
}
  

Этот setFramePosition(0) метод не является строго необходимым. Это сделано для того, чтобы гарантировать, что если вы остановите свой клип и захотите возобновить воспроизведение, воспроизведение начнется с начала звука.

Когда вы создаете экземпляр своего clip в конструкторе, я рекомендую использовать строку, которая позволяет вам получить a URL с помощью getResource метода, и использовать URL для создания вашего AudioInputStream . Это избавит вас от множества головных болей, когда придет время запускать вашу программу.

Нет необходимости совмещать открытие звукового файла с его запуском. На самом Clip деле он был разработан для инициализации, открытия и хранения в памяти до момента, когда потребуется воспроизведение. Он также был разработан для остановки, изменения положения и перезапуска. Если аудиофайл слишком велик для хранения в памяти, SourceDataLine обычно предпочтительнее a Clip .