JPanel, JFrame, MainMenu Problems

Ok, first, just to verify: This is not for Ludum Dare. I’m not quiteee experienced enough yet to do it but I am watching lots of the live streams for tips.

Anyways on to the question:
I have a VERY messy setup for a main menu, and (obviously) it isn’t working, and I really don’t know how to do it correctly.
Here are my classes:
MainMenu (main class) :

package Engine;

import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainMenu extends JFrame {

	public static String title = "TSA Droidz Defense";
	public static Dimension size = new Dimension(700, 550);
	public Screen screen;
	public static Point mse = new Point(0, 0);
	public static MainMenuPanel img;
	public static MainMenuHelp hlp;

	public MainMenu(int width, int height) {
		size = new Dimension(width, height);
		img = new MainMenuPanel(this);
		Image ii = new ImageIcon(MainMenu.class.getResource("imgs/icon.png")).getImage();
		setIconImage(ii);
		setTitle(title);
		setSize(size);
		setResizable(false);
		setLocationRelativeTo(null);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		init();
	}

	public void init() {
		setLayout(new GridLayout());
		addKeyListener(new KeyHandle());
		setVisible(true);
		setScreen(img);
	}

	public void setScreen(JPanel panel, JPanel remove) {
		remove(remove);
		add(panel);
	}

	public void setScreen(JPanel panel) {
		remove(img);
		add(panel);
	}
}

MainMenuPanel(the JPanel for the main menu):


package Engine;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;

import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class MainMenuPanel extends JPanel implements Runnable {

	public static String[] elements = { "New Game", "Load Save", "Help", "Quit" };
	public String[] items = { "New Game", "Load Save", "Help", "Quit" };
	public static Rectangle[] boxes = new Rectangle[elements.length];
	public int offS = 0;
	public MainMenu menu;
	public Thread thread;

	public MainMenuPanel(MainMenu menu) {
		this.setSize(menu.getSize());
		this.menu = menu;
		thread = new Thread(this);
		thread.start();
		menu.addMouseListener(new MenuInput());
		menu.addMouseMotionListener(new MenuInput());
		SoundEngine.playSound(Sounds.inGameMusic, true);
	}

	public void paint(Graphics g) {
		Font font = new Font("DialogInput", 1, 25);
		g.setColor(Color.DARK_GRAY);
		g.fillRect(0, 0, 800, 520);
		g.drawImage(createImageIcon("imgs/mainMenu.png").getImage(), 0, 0, 800, 520, null);
		g.setFont(font);
		g.setColor(Color.WHITE);
		for (int i = 0; i < elements.length; i++) {
			g.drawString(items[i], 390 - items[i].length() * 6, 200 + i * 40);
			boxes[i] = new Rectangle(390 - elements[i].length() * 6, 200 + i * 40 + 8, elements[i].length() * 25, 24);
			if (boxes[i].contains(MainMenu.mse)) {
				items[i] = "> " + elements[i] + " <";
			} else {
				items[i] = elements[i];
			}
		}
	}

	public void action(Point p) {
		for (int i = 0; i < boxes.length; i++) {
			if (boxes[i].contains(p)) {
				doAction(i);
			}
		}
	}

	protected ImageIcon createImageIcon(String path) {
		java.net.URL imgURL = getClass().getResource(path);
		if (imgURL != null) {
			return new ImageIcon(imgURL);
		} else {
			System.err.println("Couldn't find file: " + path);
			return null;
		}
	}

	public void doAction(int num) {
		if (num == 0) {
			new Frame(700, 550, true);
			menu.dispose();
		} else if (num == 1) {

		} else if (num == 2) {
			if (MainMenu.hlp == null) {
				MainMenu.hlp = new MainMenuHelp(menu);
			}
			menu.setScreen(MainMenu.hlp, MainMenu.img);
			for (int i = 0; i < boxes.length; i++) {
				boxes[i] = new Rectangle(0, 0, 0, 0);
			}
		} else if (num == 3) {
			menu.dispose();
			System.exit(0);
		}
	}

	@Override
	public void run() {
		while (true) {
			repaint();
		}
	}
}

MainMenuHelp (the help page which is just a slideshow of images of help):


package Engine;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;

import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class MainMenuHelp extends JPanel implements Runnable {

	public static String[] elements = { "Next", "Main Menu", "Previous" };
	public String[] items = { "Next", "Main Menu", "Previous" };
	public static Rectangle[] boxes = new Rectangle[elements.length];
	public Image[] slides = new Image[6];
	public int offS = 0;
	public MainMenu menu;
	public Thread thread;
	public int imageIterator = 0;

	public MainMenuHelp(MainMenu menu) {
		this.setSize(menu.getSize());
		this.menu = menu;
		thread = new Thread(this);
		thread.start();
		menu.addMouseListener(new MenuInput());
		menu.addMouseMotionListener(new MenuInput());
		slides[0] = createImageIcon("imgs/sliden1.png").getImage();
		slides[1] = createImageIcon("imgs/sliden2.png").getImage();
		slides[2] = createImageIcon("imgs/sliden3.png").getImage();
		slides[3] = createImageIcon("imgs/sliden4.png").getImage();
		slides[4] = createImageIcon("imgs/sliden5.png").getImage();
		slides[5] = createImageIcon("imgs/sliden6.png").getImage();
	}

	public void paint(Graphics g) {
		Font font = new Font("DialogInput", 1, 25);
		g.setColor(Color.DARK_GRAY);
		g.fillRect(0, 0, 800, 520);
		g.drawImage(slides[imageIterator], 0, 0, 800, 460, null);
		System.out.println(imageIterator);
		g.setFont(font);
		g.setColor(Color.WHITE);

		g.drawString(items[0], 25 - items[0].length() * 2, 479);
		boxes[0] = new Rectangle(25 - items[0].length() * 2, 479, elements[0].length() * 25, 24);

		g.drawString(items[1], 340 - items[1].length() * 2, 479);
		boxes[1] = new Rectangle(340 - items[1].length() * 2, 479, elements[1].length() * 25, 24);

		g.drawString(items[2], 680 - items[2].length() * 2, 479);
		boxes[2] = new Rectangle(680 - items[2].length() * 2, 479, elements[2].length() * 25, 24);

		for (int i = 0; i < boxes.length; i++) {
			if (boxes[i].contains(MainMenu.mse)) {
				items[i] = "> " + elements[i] + " <";
			} else {
				items[i] = elements[i];
			}
		}

	}

	public void action(Point p) {
		for (int i = 0; i < boxes.length; i++) {
			if (boxes[i].contains(p)) {
				doAction(i);
			}
		}
	}

	public void doAction(int num) {
		if (num == 0 && imageIterator != 5) {
			imageIterator++;
		} else if (num == 2 && imageIterator > 0) {
			imageIterator--;
		} else if (num == 1) {
			menu.setScreen(menu.img, this);
		}
	}

	protected ImageIcon createImageIcon(String path) {
		java.net.URL imgURL = Frame.class.getResource(path);
		if (imgURL != null) {
			return new ImageIcon(imgURL);
		} else {
			System.err.println("Couldn't find file: " + path);
			return null;
		}
	}

	public void run() {
		while (true) {
			repaint();
		}
	}
}

as well as the input class(may not be important, but I’ll include it anyway):


package Engine;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

public class MenuInput implements MouseMotionListener, MouseListener {

	@Override
	public void mouseEntered(MouseEvent e) {

	}

	@Override
	public void mousePressed(MouseEvent e) {

	}

	@Override
	public void mouseDragged(MouseEvent e) {
		MainMenu.mse = e.getPoint();
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		MainMenu.mse = e.getPoint();
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		MainMenu.img.action(e.getPoint());
		if (MainMenu.hlp != null) {
			MainMenu.hlp.action(e.getPoint());
		}
	}

	@Override
	public void mouseExited(MouseEvent e) {

	}

	@Override
	public void mouseReleased(MouseEvent e) {

	}
}

basically, my problem is that, if I go into the help menu, go back to the main menu, and click new game, it opens up TWO new games instead of one. Again, this is probably the messiest setup of this in the world, so it could be a number of things, I was thinking maybe the Threads colliding or multiple objects that get messed up because of static or something else. I am about to rip out my hair here so help is very much appreciated.
-cMp

Your code is messy and hard to read. I would suggest learning some basic patterns like command design and learning a bit more about objects (i.e. using multiple objects in an ArrayList to define buttons for your menu). You should also avoid “magic numbers” – this is where you have code like this:

boxes[i] = new Rectangle(390 - elements[i].length() * 6, 200 + i * 40 + 8, elements[i].length() * 25, 24);

This makes the code very difficult to read, difficult to debug, prone to errors, difficult to adapt to changes, etc. Whereas instead you could have code that scales and reads beautifully, with little extra effort.

Another problem is that your game loop is not a game loop; it’s just an infinite loop that repaints the frame. I’m not sure how well this is performing on your end but it will probably just lead to further problems down the road.

Ultimately if you are aiming to make a game I would recommend LibGDX. I recommend this to all beginners because (a) it’s easier to use than Swing/Java2D for a game, (b) it allows you to write a game without re-inventing the wheel, © it includes tons of documentation and examples often written by very skilled programmers (even full open-source games), (d) it will always out-perform Java2D for sprite rendering, and (e) it can distribute to Android, iOS and HTML5.

To show you how easy a menu would look in LibGDX, using a slightly more thought out design:

The code is flexible and it should be straight-forward to add or remove buttons to your menu. The result looks something like this:

Of course, you can use images instead of the default UI skin, and re-position them to your liking (using alignment, padding, or what have you).

As I said, I know it’s extremely messy. The numbers, they aren’t random, I made them according to the size of the screen. Thank you very much for the link though, I will learn about enums, which I haven’t ever taken a look at before.

“Magic numbers” is a term describing an anti-pattern.

Generally code like this works better:

final float BUTTONS_Y_OFF = 50;

...
    ... drawRect( x, y + BUTTONS_Y_OFF, ...)

A better alternative would be to use resolution-independent layouts of some sort, even something simple like centering it to the frame or anchoring it 5 pixels from the bottom right corner.

Th game loop was, as you said, a very messy one however this is only because it was used as a test. The actual game loop starts when the game itself gets going. Is this a problem and shsould I run everything on one loop?

You should never use an infinite loop. Generally game loops look like this:

create();

while (running) {
   ... render() ...
}

dispose();

At any time, running can be set to false which terminates the game loop.

Did you look into LibGDX? The example I posted is a fraction of the size of your code and should perform many times faster than anything Java2D is capable of.

Yes, I decided a long while back that I was going to learn libGDX once I was done with this game. I am very happy with this, as I am almost done with the game by now! So here I come libGDX! :smiley: In the meantime, I will certainly look at these examples!