如何从启动器 运行 Java Swing 游戏的动画线程?

How to run the animation thread for a Java Swing game from a launcher?

我对线程有点陌生,所以请多多包涵。所有相关的 类 都将位于文本下方的一处,以便于参考。

背景故事:

我按照本教程创建了一个简单的类似乒乓球的游戏:http://www.edu4java.com/en/game/game1.html

一切都很完美,然后我进行了修改以更好地理解它是如何工作的。在教程中,有一个主要方法可以连续播放动画。根据教程作者的说法,Thread.sleep(10)“...告诉处理器正在 运行 的线程必须休眠 10 毫秒,这允许处理器执行其他线程,特别是 AWT-EventQueue调用 paint 方法的线程。”

现在,我的问题是:

(只是为了好玩和练习Java,)我制作了一个"launcher",用于我制作的各种小程序和游戏。我还没有让乒乓球游戏在发射器内运行。如果 pong 框架内没有 main 方法,动画永远不会 运行s。我在下面的代码中留下了 main 方法,这样它就可以工作了。我将如何从 main 以外的地方启动动画?

代码如下:

框架和主要方法:

package pongGame;

import javax.swing.*;

public class PongMainGUI extends JFrame
{
    private static final int WINDOW_WIDTH = 500;
    private static final int WINDOW_HEIGHT = 800;

    private static AnimationPanel panel;

    public PongMainGUI()
    {
        //This line sets the title, and, since it calls the super constructor, it calls setTitle().
        super("Pong!");

        panel = new AnimationPanel(this);

        //This method simply makes the screen appear in the center of whatever size screen you are using.
        setLocationRelativeTo(null);

        setSize(WINDOW_WIDTH,WINDOW_HEIGHT);

        add(panel);

        setVisible(true);
    }

    public static void main(String args[]) throws InterruptedException
    {
        new PongMainGUI();
        while(true)
        {
            System.out.println("PongMainGUI");
            panel.repaint();
            panel.move();
            Thread.sleep(10);
        }
    }
}

动画面板:

package pongGame;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;

@SuppressWarnings("serial")
public class AnimationPanel extends JPanel
{   
    PongMainGUI frame;
    Ball ballClass;
    Racquet racquetClass;
    boolean bool = false;

    public AnimationPanel(PongMainGUI frame)
    {
        this.frame = frame;
        addMouseListener(new MouseListener()
        {
            @Override
            public void mouseClicked(MouseEvent arg0) 
            {

            }
            @Override
            public void mouseEntered(MouseEvent arg0) 
            {

            }
            @Override
            public void mouseExited(MouseEvent arg0) 
            {

            }
            @Override
            public void mousePressed(MouseEvent arg0) 
            {

            }
            @Override
            public void mouseReleased(MouseEvent arg0) 
            {

            }
        });
        addMouseMotionListener(new MouseMotionListener()
        {
            @Override
            public void mouseDragged(MouseEvent e) 
            {

            }
            @Override
            public void mouseMoved(MouseEvent e) 
            {

            }
        });
        addKeyListener(new KeyListener()
        {
            @Override
            public void keyPressed(KeyEvent e) 
            {
                racquetClass.keyPressed(e);
            }
            @Override
            public void keyReleased(KeyEvent e) 
            {
                racquetClass.keyReleased(e);
            }
            @Override
            public void keyTyped(KeyEvent e) 
            {

            }
        });

        //This is needed to ensure that the keyboard will register properly and receive focus.
        setFocusable(true);
        ballClass = new Ball(this);
        racquetClass = new Racquet(this);
    }

    public void move()
    {
        //ballClass.moveBall();
        racquetClass.moveRacquet();
    }

    @Override
    public void paint(Graphics g) 
    {
        System.out.println("AnimationPanel paint method");
        //This method clears the panel so it appears as if the circle is moving.
        super.paint(g);

        //Better version of Graphics.
        Graphics2D g2d = (Graphics2D) g;

        //This method turns antialiasing on, which cleans up the corners.
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        ballClass.paint(g2d);
        racquetClass.paint(g2d);
    }
    public void gameOver()
    {
        System.out.println("Game over method");
        JOptionPane.showMessageDialog(null, "Game Over", "Game Over", JOptionPane.YES_NO_OPTION);
        System.exit(ABORT);
    }

}

球"sprite":

package pongGame;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Ball 
{
    int xCoordinate = 0;
    int yCoordinate = 0;

    //1 = right movement, -1 = left
    int xDirection = 1;
    int yDirection = 1;
    private final static byte ballWidth = 30;
    private final static byte ballHeight = 30;

    private AnimationPanel panel;

    public Ball(AnimationPanel panel)
    {
        this.panel = panel;
    }

    public void paint(Graphics2D g2d)
    {
        //This creates the actual circle with a specified width and height.
        //Because super.paint(g) is called at the start, a new circle is created each time.
        g2d.fillOval(xCoordinate, yCoordinate, ballWidth, ballHeight);

        System.out.println("Ball paint method");

        moveBall();
    }
        //What this method does is add 1 to the x and y coordinates each time it's called.  However, getWidth() and getHeight() are used to determine the current panel size, not the frame size.
        //Then, whatever the width and/or height is is subtracted so the circle does not completely disappear from view.
        public void moveBall() 
        {
            if (xCoordinate + xDirection < 0)
            {
                xDirection = 1;
            }
            else if (xCoordinate + xDirection > panel.getWidth() - ballWidth)
            {
                xDirection = -1;
            }

            if (yCoordinate + yDirection < 0)
            {
                yDirection = 1;
            }
            else if (yCoordinate + yDirection > panel.getHeight() - ballHeight)
            {
                System.out.println("Ball moveBall method");
                panel.gameOver();
            }

            if (collision() == true)
            {
                yDirection = -1;
                yCoordinate = panel.racquetClass.getPaddleHeight() - ballHeight;
            }
            xCoordinate = xCoordinate + xDirection;
            yCoordinate = yCoordinate + yDirection;
        }
        public Rectangle getBounds() 
        {
            return new Rectangle(xCoordinate, yCoordinate, ballWidth, ballHeight);
        }
        private boolean collision() 
        {
            return panel.racquetClass.getBounds().intersects(getBounds());
        }

}

最后,球拍 "sprite":

package pongGame;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

public class Racquet 
{
    private AnimationPanel panel;

    private int xCoordinate = 0;

    //0 = no movement, 1 is right, -1 is left.
    private byte direction = 0;

    //All of the following values are in pixels.
    private final static byte PADDLE_OFFSET = 100;
    private final static byte PADDLE_WIDTH = 120;
    private final static byte PADDLE_HEIGHT = 10;

    public Racquet(AnimationPanel panel) 
    {
        this.panel = panel;
    }
    public void moveRacquet() 
    {
        if (xCoordinate + direction > 0 && xCoordinate + direction < panel.getWidth()-60)
            xCoordinate = xCoordinate + direction;
    }

    public void paint(Graphics2D g) 
    {
        g.fillRect(xCoordinate, getPaddleHeight(), PADDLE_WIDTH, PADDLE_HEIGHT);
        //move();
    }

    public void keyReleased(KeyEvent e) 
    {
        direction = 0;
    }

    public void keyPressed(KeyEvent e) 
    {
        if (e.getKeyCode() == KeyEvent.VK_LEFT)
            direction = -1;
        if (e.getKeyCode() == KeyEvent.VK_RIGHT)
            direction = 1;
    }

    public Rectangle getBounds() 
    {
        return new Rectangle(xCoordinate, getPaddleHeight(), PADDLE_WIDTH, PADDLE_HEIGHT);
    }
    public int getPaddleHeight()
    {
        return panel.getHeight() - PADDLE_OFFSET;
    }
}

这可能有帮助也可能没有帮助,但这是我想用来打开游戏的启动器代码:

这是"main menu":

package GUI;

import javax.swing.*;

import painter.MainPainterGUI;

import java.awt.*;
import java.awt.event.*;

/**
 * This class serves to create the launcher gui for the program.
 * It extends JFrame.
 * @author Jackson Murrell
 */
@SuppressWarnings("serial")
public class LauncherGUI extends JFrame implements ActionListener
{
    //A couple constants that are used for sizing things.
    private final short WINDOW_HEIGHT = 225;
    private final short WINDOW_WIDTH = 550;
    private final byte BLANK_SPACE = 25;

    //Panels to use for adding in components.
    JPanel textPanel, buttonPanel, mainPanel;

    //Buttons for user input and selection.
    JButton calculator, colorChooser, timer, exit, primeNumberTester, game, painter;

    //A text label that will be used for giving the user
    //instructions on the program.
    JLabel textLabel;

    //A constructor to create the GUI components when an object of this class is created.
    public LauncherGUI()
    {
        //This call's the parent method's (JFrame) setTitle method.
        super("Omni-program");

        //These methods set various options for the JFrame.
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
        setLocationRelativeTo(null);

        textPanel = new JPanel();
        buttonPanel = new JPanel();
        mainPanel = new JPanel();

        calculator = new JButton("Calculator");
        colorChooser = new JButton("Color Chooser");
        timer = new JButton("Timer");
        primeNumberTester = new JButton("Prime Number Tester");
        game = new JButton("Games");
        exit = new JButton("Exit Launcher and Programs");
        painter = new JButton("Painter");

        calculator.addActionListener(this);
        colorChooser.addActionListener(this);
        timer.addActionListener(this);
        exit.addActionListener(this);
        primeNumberTester.addActionListener(this);
        game.addActionListener(this);
        painter.addActionListener(this);

        textLabel = new JLabel("Welcome to the launcher!  Click the button for the mini-program you would like to run.", 0);

        textPanel.add(Box.createVerticalStrut(BLANK_SPACE));
        textPanel.add(textLabel);

        buttonPanel.add(calculator);
        buttonPanel.add(colorChooser);
        buttonPanel.add(timer);
        buttonPanel.add(primeNumberTester);
        buttonPanel.add(game);
        buttonPanel.add(painter);
        buttonPanel.add(exit);

        mainPanel.setLayout(new GridLayout(2,1));

        mainPanel.add(textPanel);
        mainPanel.add(buttonPanel);
        //mainPanel.add(Box.createVerticalStrut(BLANK_SPACE));

        add(mainPanel);

        //pack();

        //Having this line at the end instead of the top ensures that once everything is added it is all set to be visible.
        setVisible(true);
    }
    //This method is required since ActionListener is implemented.
    //It will be used to process user input.
    @Override
    public void actionPerformed(ActionEvent e) 
    {
        if (e.getSource() == calculator)
        {
            new CalculatorGUI();
            dispose();
        }
        else if (e.getSource() == colorChooser)
        {
            new ColorChooserGUI();
            dispose();
        }
        else if(e.getSource() == timer)
        {
            new TimerGUI();
            dispose();
        }
        else if (e.getSource() == primeNumberTester)
        {
            new PrimeNumberTesterGUI();
            dispose();
        }
        else if(e.getSource() == exit)
        {
            System.exit(0);
        }
        else if(e.getSource() == painter)
        {
            new MainPainterGUI();
            dispose();
        }
        else if(e.getSource() == game)
        {
            new GameLauncherGUI();
            dispose();
        }
    }
}

这是实际的游戏启动器:

package GUI;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

import pongGame.PongMainGUI;

public class GameLauncherGUI extends JFrame implements ActionListener
{
    //A couple constants that are used for sizing things.
    private final short WINDOW_HEIGHT = 225;
    private final short WINDOW_WIDTH = 550;

    private JButton adventureGame, pong, back;

    private JLabel label;

    private JPanel mainPanel, buttonPanel, textPanel;

    public GameLauncherGUI()
    {
        //This call's the parent method's (JFrame) setTitle method.
        super("Omni-program");

        //These methods set various options for the JFrame.
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
        setLocationRelativeTo(null);

        adventureGame = new JButton("Adventure Game (Broken)");
        adventureGame.addActionListener(this);

        pong = new JButton("Pong");
        pong.addActionListener(this);

        back = new JButton("Back");
        back.addActionListener(this);

        label = new JLabel("Click the button below for the game you wish to play,\nor click back to go to the previous screen.");

        mainPanel = new JPanel();
        buttonPanel = new JPanel();
        textPanel = new JPanel();

        textPanel.add(label);

        buttonPanel.add(adventureGame);
        buttonPanel.add(pong);
        buttonPanel.add(back);

        mainPanel.add(textPanel);
        mainPanel.add(buttonPanel);

        add(mainPanel);

        //Having this line at the end instead of the top ensures that once everything is added it is all set to be visible.
        setVisible(true);
    }
    @Override
    public void actionPerformed(ActionEvent e) 
    {
        if(e.getSource() == back)
        {
            new LauncherGUI();
            dispose();
        }
        else if(e.getSource() == pong)
        {
            new PongMainGUI();
            dispose();
        }

    }
}

main和其他方法一样是静态方法,因此您可以从启动器中调用它:

PongMainGUI.main(null); // launch the pong game

但是请注意,为了避免很多麻烦,Swing组件必须从Event Dispatch Thread创建,如this example所示。因此,您应该将 main 方法的内容包装在 Runnable 中,然后使用 SwingUtilities.invokeLater().

启动它

但是(再次),通过这样做,您的 Thread.sleep(10) 将在 EDT 上 运行,再次阻止 GUI 响应。幸运的是,Swing 想到了这个问题并创建了一个名为 javax.swing.Timer 的实用程序,它 运行s 在 EDT 上定期执行任务(不会阻塞它):

public static void main(String args[])
{
   SwingUtilities.invokeLater(new Runnable(){
      public void run(){
         new PongMainGUI();

         Timer timer = new Timer(10, new ActionListener(){
            public void actionPerformed(ActionEvent e){
               System.out.println("PongMainGUI");
               panel.repaint();
               panel.move();
            }
         });
         timer.start();
      }
   });
}

main() 方法将 运行 安全地独立运行,或从您的启动器。