How to create a “Game Option/Pause Screen” in Swing?


Background: Making a game in Swing. It is simple turn base game. Not a whole lot going on. Because of this I didn't think I would need to implement a Game Tick. Rather, my thought was when a component got changed or needed to be updated just simply revalidate/repaint that component on the fly rather than repainting the whole screen.

I have a GameJPanel which currently has all the components on it. This JPanel is the one that contains the components that get revalidated/repainted etc. I figured I could make JLayeredPane that holds GameJPanel and my OptionJPanel. Have a Button on GameJPanel that when pressed causes the OptionJPanel to show on top of it and having its JPanel 50% transparent (so it gives the affect it dims the GameJPanel).

However, once I did this what happened was that the GameJPanel started to replace OptionJPanel components (because of the events... etc; repainting of the components).

So currently I am at a loss on what to do. I'm thinking if I had some sort of game tick I wouldn't be having this issue, however, I am not 100% certain. I'm a little worried if I implemented a gametick that the events in game will cause the GameJPanel components to show through for half a second then get replaced. There are some events that cause components to repaint themselves without manually doing it (like quick example for JLabel setText();)

enter image description here As an example of what I'm trying to go for.

I have tried with a CardLayout but I couldn't figure out how to have the OptionJPanel be on top of GameJPanel while seeing GameJPanel in the background (I tried setting background color, setOpaque(false)..., tried to limit Option JPanel size but I think the CardLayout stretches it (not 100% sure)) all I got was a gray background when doing so.

I would prefer not to go the CardLayout route because in the future I also plan on placing components on top of the GameJPanel (like someone clicks a button, have another panel on a different layer have a component slide in or out etc). I use CardLayout a ton with my other components in GameJPanel to swap screens around, but haven't had the need to have the other components behind the one showing to show through.

Any ideas on how to go about this would be great or even example code that shows this.


Answers:


As noted above, you would use a JDialog, a component that is easy to make (similar to making a JFrame) and easy to place. Simply place it "relative-to" the JFrame, e.g.,

myDialog.setLocationRelativeTo(myJFrame);

... and it will automatically center itself on the JFrame. The tricky part is dimming the underlying JFrame, and for this you would need to use a JGlassPane added to the JFrame's rootpane, one set with a background color that uses an alpha composite value. The tricky part with this is to draw the darker background without causing side effects, and to do this, please read Rob Camick's (StackOverflow user camickr) excellent tutorial on drawing in Swing with alpha composites which you can find here: Java Tips Weblog: Backgrounds with Transparency

An example of such a program is shown here:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.*;

public class DialogEg {
    // path to example image used as "game" background
    private static final String IMG_PATH = "https://upload.wikimedia.org/"
            + "wikipedia/commons/7/76/Jump_%27n_Bump.png";

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui() {
        // get the "game" background image, or exit if fail
        BufferedImage img = null;
        try {
            URL imgUrl = new URL(IMG_PATH);
            img = ImageIO.read(imgUrl);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }

        // pass "game" image into main JPanel so that it will be drawn
        DeMainPanel mainPanel = new DeMainPanel(img);
        JFrame frame = new JFrame("Dialog Example");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(mainPanel); // add main JPanel to JFrame
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }
}

// main JPanel
@SuppressWarnings("serial")
class DeMainPanel extends JPanel {
    private BufferedImage img; // background image

    // JButton action that shows the JDialog and darkens the glasspane
    private PauseAction pauseAction = new PauseAction("Pause");

    public DeMainPanel(BufferedImage img) {
        super();
        this.img = img;
        add(new JButton(pauseAction));
    }

    // draw the "game" background image within the JPanel if not null
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (img != null) {
            g.drawImage(img, 0, 0, this);
        }
    }

    // size this JPanel to match the image's size
    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet() || img == null) {
            return super.getPreferredSize();
        }
        int width = img.getWidth();
        int height = img.getHeight();
        return new Dimension(width, height);
    }
}

// Action / ActionListener for JButton -- shows JDialog and darkens glasspane
@SuppressWarnings("serial")
class PauseAction extends AbstractAction {
    private static final int ALPHA = 175; // how much see-thru. 0 to 255
    private static final Color GP_BG = new Color(0, 0, 0, ALPHA);
    private DeDialogPanel deDialogPanel = new DeDialogPanel();  // jpanel shown in JDialog

    public PauseAction(String name) {
        super(name);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // comp is our JButton
        Component comp = (Component) e.getSource();
        if (comp == null) {
            return;
        }

        // create our glass pane
        JPanel glassPane = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                // magic to make it dark without side-effects
                g.setColor(getBackground());
                g.fillRect(0, 0, getWidth(), getHeight());
                super.paintComponent(g);
            }
        };
        // more magic below
        glassPane.setOpaque(false); 
        glassPane.setBackground(GP_BG);     

        // get the rootpane container, here the JFrame, that holds the JButton
        RootPaneContainer win = (RootPaneContainer) SwingUtilities.getWindowAncestor(comp);
        win.setGlassPane(glassPane);  // set the glass pane
        glassPane.setVisible(true);  // and show the glass pane

        // create a *modal* JDialog
        JDialog dialog = new JDialog((Window)win, "", ModalityType.APPLICATION_MODAL);
        dialog.getContentPane().add(deDialogPanel);  // add its JPanel to it
        dialog.setUndecorated(true); // give it no borders (if desired)
        dialog.pack(); // size it
        dialog.setLocationRelativeTo((Window) win); // ** Center it over the JFrame **
        dialog.setVisible(true);  // display it, pausing the GUI below it

        // at this point the dialog is no longer visible, so get rid of glass pane
        glassPane.setVisible(false);

    }
}

// JPanel shown in the modal JDialog above
@SuppressWarnings("serial")
class DeDialogPanel extends JPanel {
    private static final Color BG = new Color(123, 63, 0);

    public DeDialogPanel() {
        JLabel pausedLabel = new JLabel("PAUSED");
        pausedLabel.setForeground(Color.ORANGE);
        JPanel pausedPanel = new JPanel();
        pausedPanel.setOpaque(false);
        pausedPanel.add(pausedLabel);

        setBackground(BG);
        int eb = 15;
        setBorder(BorderFactory.createEmptyBorder(eb, eb, eb, eb));
        setLayout(new GridLayout(0, 1, 10, 10));
        add(pausedPanel);
        add(new JButton(new FooAction("RESUME")));
        add(new JButton(new FooAction("RESTART")));
        add(new JButton(new FooAction("EXIT TO MAP")));
    }

    // simple action -- all it does is to make the dialog no longer visible
    private class FooAction extends AbstractAction {
        public FooAction(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Component comp = (Component) e.getSource();
            Window win = SwingUtilities.getWindowAncestor(comp);
            win.dispose();  // here -- dispose of the JDialog
        }
    }
}

The GUI looks like this initially:

enter image description here

but then when the dialog shows and the glass pane is darkened, it looks like this:

enter image description here