问题
After the dialog of my game finishes, a scary pop-up is suppose to show with a screeching noise. When I click the button (BPanel), the picture appears "corrupted" until the sound file finishes playing. After the scream finishes, the picture pops out.
Is it possible to simply synchronize the two together? Note that the problem is happening in the Woc class where Scaryface.png and the Sound class is used.
Main method:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Game {
public static void main(String args[]) {
Woc WineOrCheese = new Woc("Wine or Cheese");
WineOrCheese.applyBackground("TitleBackground.png");
WineOrCheese.applyButton("Play.png", 250, 200, 400, 200);
}
}
Woc is the JFrame
import static java.lang.System.out;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class Woc extends JFrame {
private JFrame window;
private Woc.BPanel background;
private BufferedImage backgroundImg;
final int HEIGHT = 600;
final int WIDTH = 900;
private BufferedImage scaryImg;
public Woc(String text) {
/*********************************
* Sets up window. *
********************************/
window = new JFrame(text);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setLayout(null);
window.setVisible(true);
window.setSize(WIDTH, HEIGHT);
window.setLocationRelativeTo(null);
}
public void applyBackground(String ImgName) {
try {
backgroundImg = ImageIO.read(getClass().getResource(ImgName));
} catch (IOException e) {
out.println("No image detected");
}
background = new Woc.BPanel(backgroundImg, 0, 0, WIDTH, HEIGHT);
window.add(background);
background.setBounds(0, 0, WIDTH, HEIGHT);
}
public void applyButton(String ImgName, int x1, int y1, int width,
int height) {
BufferedImage buttonImg = null;
try {
buttonImg = ImageIO.read(getClass().getResource(ImgName));
} catch (IOException e) {
}
Woc.BPanel button = new Woc.BPanel(buttonImg, x1, y1, width, height);
window.add(button);
button.setBounds(0, 0, WIDTH, HEIGHT);
button.addMouseListener(new Clicker());
}
public static void play(String filename) {
try {
Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(new File(filename)));
clip.start();
} catch (Exception exc) {
exc.printStackTrace(System.out);
}
}
private class BPanel extends JPanel {
public BufferedImage img;
public int x1, y1, width, height;
public BPanel(BufferedImage img, int x1, int y1, int width, int height) {
super();
this.img = img;
this.x1 = x1;
this.y1 = y1;
this.width = width;
this.height = height;
// this.setOpaque(false);
this.setBackground(new Color(0, 0, 0, 0));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, x1, y1, width, height, null);
}
}
private class Clicker implements MouseListener {
@Override
public void mouseClicked(MouseEvent arg0) {
//Dialog of game is here in the form of JOptionPane.
applyBackground("Scaryface.png");
for (int k = 0; k < 10; k++) {
for (int z = 0; z < 10; z++) {
out.println(".");
}
}
Sound scary = null;
try {
scary = new Sound("scary.wav", window);
} catch (Exception e) {
}
}
@Override
public void mouseEntered(MouseEvent arg0) {
}
@Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void mousePressed(MouseEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void mouseReleased(MouseEvent arg0) {
// TODO Auto-generated method stub
}
}
Sound class:
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.*;
public class Sound {
public Sound(String s, JFrame win) throws InterruptedException {
Clip play = null;
try {
File in = new File(s);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(in);
play = AudioSystem.getClip();
play.open(audioInputStream);
FloatControl volume = (FloatControl) play.getControl(FloatControl.Type.MASTER_GAIN);
volume.setValue(1.0f); // Reduce volume by 10 decibels.
play.start();
// Loop until the Clip is not longer running.
// We loop this way to allow the line to fill, otherwise isRunning will
// return false
do {
Thread.sleep(10);
} while (play.isRunning());
play.drain();
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
ex.printStackTrace();
} finally {
try {
play.close();
} catch (Exception exp) {
}
}
}
}
By the way, are there any tips of how to make my game much easier to write? Any methods or classes that can improve and ease the pain I have to go through to? (Okay fine not real pain but it's still painful)
回答1:
do {
Thread.sleep(10);
} while (play.isRunning());
and
play.drain();
Are blocking the Event Dispatching Thread with the aforementioned code which is preventing the UI from been updated. See Concurrency in Swing for more details
Avoid using null
layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
You shouldn't be using a MouseListener
with buttons, but instead a ActionListener
, there is more then one way to trigger a button. See How to Use Buttons, Check Boxes, and Radio Buttons and How to Write an Action Listeners for more details
If you want to know when the audio has finished, you should be using a LineListener
, for example. With this, you can choose what to do when the audio has completed. Personally, I would pass the an instance of LineListener
to your Sound
class, as your Sound
class shouldn't care about anything other then playing the sound
Without the while loop, the sound is unable to play. How do I resolve this issue because this was the only way I was able to play the sound
Don't close the Clip
until after the audio has completed, for this I would use (another) LineListener
.
The following example basically takes your Sound
class and applies a LineListener
to it. One is used to notified the interested party (the one who started the sound) about line events and one is used to monitor the clip internally and close it when it stops.
The example use a ReentrantLock
and Condition
to stop the code execution until the clip has completed, in this example, this will stop the JVM from terminating, but you don't need, I just used to provide a basic demonstration
import java.io.File;
import java.io.IOException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
public class TestAudio {
public static void main(String[] args) {
ReentrantLock lockWait = new ReentrantLock();
Condition conWait = lockWait.newCondition();
try {
new Sound("...", new LineListener() {
@Override
public void update(LineEvent event) {
if (event.getType().equals(LineEvent.Type.STOP)) {
System.out.println("Line has stopped");
lockWait.lock();
try {
conWait.signal();
} finally {
lockWait.unlock();
}
}
}
});
System.out.println("Waiting for audio to finish");
lockWait.lock();
try {
conWait.await();
} finally {
lockWait.unlock();
}
System.out.println("Audio has finished");
} catch (InterruptedException | LineUnavailableException | IOException | UnsupportedAudioFileException exp) {
exp.printStackTrace();
}
}
public static class Sound {
private Clip play;
public Sound(String s, LineListener listener) throws InterruptedException, LineUnavailableException, IOException, UnsupportedAudioFileException {
play = null;
File in = new File(s);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(in);
play = AudioSystem.getClip();
play.addLineListener(new LineListener() {
@Override
public void update(LineEvent event) {
if (event.getType().equals(LineEvent.Type.STOP)) {
System.out.println("Audio stopped, closing clip");
play.close();
}
}
});
play.addLineListener(listener);
play.open(audioInputStream);
FloatControl volume = (FloatControl) play.getControl(FloatControl.Type.MASTER_GAIN);
volume.setValue(1.0f); // Reduce volume by 10 decibels.
play.start();
}
}
}
For a more complex example, using Swing and Clip
, have a look at Playing multiple sound clips using Clip objects
来源:https://stackoverflow.com/questions/34758718/panel-fails-to-show-up-concurrently-with-sound-file-in-java-swing