2

I am a bit new to threading, so bear with me. All relevant classes will be below the text in one place for easier reference.

Backstory:

I created a simple pong-like game following this tutorial: http://www.edu4java.com/en/game/game1.html

Everything worked perfectly, then I made modifications to better understand how it all works. In the tutorial, there is a main method from which the animations are played continuously. According to the tutorial author, Thread.sleep(10) "...tells the processor that the thread which is being run must sleep for 10 ms, which allows the processor to execute other threads and in particular the AWT-EventQueue thread which calls the paint method."

Now, my question is this:

(Just for fun and to practice Java,) I have created a "launcher" for all the various small programs and games I make. I have yet to get the pong game to work inside the launcher. Without a main method inside the pong frame, the animation never runs. I left the main method in in the code below, so that it works. How would I go about launching the animation from somewhere other than main?

Here's the code:

The Frame and main method:

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);
        }
    }
}

The Animation Panel:

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);
    }

}

The Ball "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());
        }

}

And finally, the Racquet "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;
    }
}

This may or may not help, but this is the code for the launcher I wanted to use to open the game:

This is the "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();
        }
    }
}

Here's the actual game launcher:

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();
        }

    }
}
Barett
  • 5,226
  • 6
  • 46
  • 53
Rezkin
  • 279
  • 1
  • 5
  • 16
  • 1
    For better help sooner, post an [MCVE](http://stackoverflow.com/help/mcve) (Minimal Complete Verifiable Example) or [SSCCE](http://www.sscce.org/) (Short, Self Contained, Correct Example). – Andrew Thompson Jun 26 '15 at 17:44
  • 1
    @AndrewThompson Thanks, I'll keep that in mind for future posts :) – Rezkin Jun 26 '15 at 20:09

1 Answers1

5

mainis a static method like others, so you can call it from your launcher:

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

However, note that, in order to avoid lots of trouble, Swing components must be created from the Event Dispatch Thread, as shown in this example. So you should wrap the content of your main method inside a Runnable and launch it with SwingUtilities.invokeLater().

However (again), by doing so, your Thread.sleep(10) will run on the EDT, blocking the GUI responsiveness again. Fortunately, Swing thought of that problem and created a utility called javax.swing.Timer that runs tasks periodically on the EDT (without blocking it):

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();
      }
   });
}

This main() method will run safely in standalone, or from your launcher.

Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
Eric Leibenguth
  • 3,895
  • 1
  • 18
  • 40
  • 3
    Close but not quite -- that needs to be a **Swing** Timer, not a java.util.Timer. Otherwise you're guaranteeing to call `panel.move() off of the EDT. – Hovercraft Full Of Eels Jun 26 '15 at 16:57
  • 1
    @EricLeibenguth So, since my launcher is started from a main method, it's possible to have another one which runs the animation part? It wasn't terribly relevant, but I have a separate "Main" class which simply creates an instance of LauncherGUI. I thought it was a nono to have two mains. – Rezkin Jun 26 '15 at 20:10
  • 1
    @EricLeibenguth I just had a chance to test this, and wow, this works fantastically. I wish I could upvote your answer more, but I cannot. Thanks a lot, i was getting so frustrated trying to figure this out on my own. :D – Rezkin Jun 26 '15 at 20:16
  • 1
    You can have as many `main` methods as you want. The only thing special about them is that they can (but not necessarily will) work as "entry points" in your program. – Eric Leibenguth Jun 26 '15 at 20:18