I am making a game in which a player attempts to navigate a maze, but I have run into two bugs that have absolutely stumped me. My first problem is with using setContentPane(). Whenever I set it to my intended background, it covers up everything else on the screen. I have tried relocating it to every place I thought it could be, but it still covered it up. My second question is about my JButton. Whenever I click it, it changes to the playing speed as intended, but I can't move afterwards. It works when I use the method in my Keylistener, and they have the same code inside of them. I have included all code that I thought was necessary, but if you need any more, let me know. The full source for this project can be found Here.
package game;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
public class Game extends JPanel implements ActionListener, KeyListener {
private boolean upPressed = false;
private boolean downPressed = false;
private boolean rightPressed = false;
private boolean leftPressed = false;
private int tileSize = 200;
private int playerSize = tileSize / 4;
private int playerSpeed = playerSize / 10;
private int mazeX = 51;
private int mazeY = 51;
private int[][] maze = new int[mazeX][mazeY];
private int[][] initX = new int[mazeX][mazeY];
private int[][] initY = new int[mazeX][mazeY];
private boolean[][] visited = new boolean[mazeX][mazeY];
private int currentCellX = 0;
private int currentCellY = 0;
private int deltaX = 0;
private int deltaY = 0;
JComponent background = new JLabel(new ImageIcon(Main.class.getResource("/assets/ui/mainMenu.png")));
JButton adventure = new JButton();
JButton freePlay = new JButton();
JButton exit = new JButton();
JButton pause = new JButton();
JButton resume = new JButton();
private String screen = "menu";
public Main main;
public Game(Main main) {
this.main = main;
setFocusable(true);
addKeyListener(this);
addButtons();
setUpInitialCoordinates();
Timer timer = new Timer(1000 / 120, this);
timer.start();
}
private void tick() {
switch (screen) {
case "playing" :
background.setVisible(false);
if (upPressed) {
deltaY += playerSpeed;
} else if (downPressed) {
deltaY -= playerSpeed;
}
if (rightPressed) {
deltaX -= playerSpeed;
} else if (leftPressed) {
deltaX += playerSpeed;
}
break;
case "paused":
background.setVisible(false);
adventure.setVisible(false);
freePlay.setVisible(false);
pause.setVisible(false);
exit.setVisible(true);
break;
case "menu":
main.setContentPane(new JLabel(new ImageIcon(Main.class.getResource("/assets/ui/mainMenu.png"))));
adventure.setVisible(true);
freePlay.setVisible(true);
pause.setVisible(false);
exit.setVisible(true);
break;
}
repaint();
}
private void addButtons() {
setLayout(null);
add(freePlay);
freePlay.setBounds(10, 100, 180, 80);
freePlay.setSize(180, 80);
freePlay.setAction(new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
downPressed = false;
rightPressed = false;
leftPressed = false;
generateMaze();
screen = "playing";
}
});
freePlay.setIcon(new ImageIcon(Main.class.getResource("/assets/ui/buttons/freePlayUnselected.png")));
freePlay.setRolloverIcon(new ImageIcon(Main.class.getResource("/assets/ui/buttons/freePlaySelected.png")));
}
private void setUpInitialCoordinates() {
}
private void generateMaze() {
}
private boolean checkForUnvisitedCells() {
}
private boolean checkForUnvisitedAdjacentCells() {
}
#Override
public void actionPerformed(ActionEvent e) {
tick();
}
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void paintComponent(Graphics g) {
}
}
setContentPane does just that, it replaces what ever was previously been used as the content with the new component.
Take a closer look at How to Use Root Panes for more details.
I'd suggest that a better solution would be to use a CardLayout, but in this case, a better solution would be to just change the icon property of the current content pane...
background.setIcon(new ImageIcon(Main.class.getResource("/assets/ui/mainMenu.png")));
and simply make the background the frame's content pane...
JLabel background = new JLabel(new ImageIcon(Main.class.getResource("/assets/ui/mainMenu.png")));
//....
public Game(Main main) {
this.main = main;
setContentPane(background);
//...
Then never change the content pane...
You could then use a CardLayout to change what appears on the content pane
My second question is about my JButton. Whenever I click it, it changes to the playing speed as intended, but I can't move afterwards. It works when I use the method in my Keylistener
Welcome to the wonderful world of why you shouldn't use KeyListener, the button has stolen key board focus and the KeyListener will no longer respond to key board input because it relies on the component it is registered to be focusable AND focused. Use key bindings instead. How to Use Key Bindings
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
My main problem is I am confused on where to implement the listener classes so that whenever an action is made (whether it be a key press or mouse click) the applet is updated. I realize my compiling issue is from my CanvasPanel method in my CanvasPanel class and not having arguments in my actionPerformed method. However at this point I'm not sure how these listeners should be implemented correctly. Have tried looking over different questions already posted, so sorry if it is a duplicate.
Here is my code:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class WholePanel extends JPanel
{
private Color foregroundColor, backgroundColor;
private int currentDiameter, x1, y1;
private CanvasPanel canvas;
private JPanel buttonPanel;
private JRadioButton filledRadio, unfilledRadio;
private JRadioButton redRadio, greenRadio;
private boolean fill;
private Graphics myCircle;
public WholePanel()
{
backgroundColor = Color.CYAN;
foregroundColor = Color.RED;
currentDiameter = 100;
x1 = 200; y1 = 100;
unfilledRadio = new JRadioButton("Unfilled", true);
filledRadio = new JRadioButton("Filled", false);
redRadio = new JRadioButton("Red", true);
greenRadio = new JRadioButton("Green", false);
buttonPanel = new JPanel();
buttonPanel.add(unfilledRadio);
buttonPanel.add(filledRadio);
buttonPanel.add(redRadio);
buttonPanel.add(greenRadio);
canvas = new CanvasPanel();
JSplitPane sPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, buttonPanel, canvas);
setLayout(new BorderLayout());
add(sPane, BorderLayout.CENTER);
}
private class ColorListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
if (redRadio.isSelected()) {
greenRadio.setSelected(false);
backgroundColor = Color.RED;
}
else if (greenRadio.isSelected()) {
redRadio.setSelected(false);
backgroundColor = Color.GREEN;
}
// ...extra else/if statements
}
} // end of ColorListener
private class FillListener implements ActionListener
{
private boolean fill;
public void actionPerformed(ActionEvent event)
{
if (filledRadio.isSelected()) {
unfilledRadio.setSelected(false);
fill = true;
paintComponent(myCircle);
}
else if (unfilledRadio.isSelected()) {
filledRadio.setSelected(false);
fill = false;
paintComponent(myCircle);
}
}
}
private class CanvasPanel extends JPanel
{
public CanvasPanel( )
{
addKeyListener(new DirectionListener());
addMouseListener(new PointListener());
setBackground(backgroundColor);
//This method needs to be called for this panel to listen to keys
//When panel listens to other things, and go back to listen
//to keys, this method needs to be called again.
ColorListener.actionPerformed();
FillListener.actionPerformed();
requestFocus();
}
public void paintComponent(Graphics page)
{
super.paintComponent(page);
setBackground(backgroundColor);
page.setColor(foregroundColor);
page.drawOval(x1, y1, currentDiameter, currentDiameter);
if (fill == true) {
page.fillOval(x1, y1, currentDiameter, currentDiameter);
}
}
/** This method is overriden to enable keyboard focus */
public boolean isFocusable()
{
return true;
}
private class DirectionListener implements KeyListener
{
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
public void keyPressed(KeyEvent e)
{
currentDiameter = 100;
x1 = 200; y1 = 100;
int keyCode = e.getKeyCode();
// switch statement here
}
}
} // end of DirectionListener
public class PointListener implements MouseListener
{
public void mousePressed (MouseEvent event)
{
canvas.requestFocus();
}
public void mouseClicked (MouseEvent event) {}
public void mouseReleased (MouseEvent event) {}
public void mouseEntered (MouseEvent event) {}
public void mouseExited (MouseEvent event) {}
} // end of PointListener
} // end of Canvas Panel Class
} // end of Whole Panel Class
Some major problems in that code:
You're calling paintComponent directly, something you should never do. Instead change a state field of your class, call repaint()and then have paintComponent use the state field to decide what it should paint.
Same for using a Graphics field -- don't. Instead only use the Graphics object given to your paintComponent method by the JVM. The Swing Graphics tutorial will explain this.
You're trying to call your listener call back methods directly, which is the exact opposite of how listeners are supposed to work and negates the benefits of using listeners. Instead ADD your listeners to the components that will use them -- including adding the ActionListeners to the buttons that need them, the KeyListeners to the components that need them, MouseListeners... etc...
For instance, let's look at a much simpler example, one that uses two JRadioButtons and that's it:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class PartialPanel extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int CIRC_W = 200;
private int circleX = 300;
private int circleY = 200;
private Color circleColor = null;
private ButtonGroup buttonGroup = new ButtonGroup();
private JRadioButton blueButton = new JRadioButton("Blue");
private JRadioButton redButton = new JRadioButton("Red");
public PartialPanel() {
ColorListener colorListener = new ColorListener();
blueButton.addActionListener(colorListener);
redButton.addActionListener(colorListener);
buttonGroup.add(blueButton);
buttonGroup.add(redButton);
JPanel buttonPanel = new JPanel();
buttonPanel.add(blueButton);
buttonPanel.add(redButton);
add(buttonPanel);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (circleColor != null) {
g.setColor(circleColor);
g.fillOval(circleX, circleY, CIRC_W, CIRC_W);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
} else {
return new Dimension(PREF_W, PREF_H);
}
}
private class ColorListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == blueButton) {
circleColor = Color.BLUE;
} else if (e.getSource() == redButton) {
circleColor = Color.RED;
}
repaint();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
PartialPanel mainPanel = new PartialPanel();
JFrame frame = new JFrame("PartialPanel");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Here we add a ColorListener to the JRadioButtons. In the listener, we change the state of the class's circColor field, and then call repaint(). The paintComponent method then uses circColor's value to decide what color to use when drawing a circle.
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();
}
}
}
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.
stackoverflow community!
I am making a small GUI based game in JAVA.
I am having a few troubles right now.
To give you basic understanding of my program,
I have a window that shows menu(JPanel) first. When I click on "start game" button. It proceeds to another JPanel on which I can play game.
![menu_window][1]
I have a timer working on a small bullet that is moving periodically. Every time the timer works, the Panel repaints the bullet. I can see the repaint method works but it doesn't remove the previous trace.
![enter image description here][2]
This is my main class
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main {
private final int window_x_size = 500;
private final int window_y_size = 500;
private JFrame frame;
private JPanel Menu;
private Game myGame = new Game();//extends from JPanel
private JButton startButton = new JButton("Game Start");
private JButton exitButton = new JButton("Game Exit");
private JButton showRank = new JButton("Rank");
private JButton OneOnOne = new JButton("One on One");
private ActionListener MyButtonListener = new MyButtonListener();
public class MyButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == startButton) {
frame.remove(Menu);
frame.setContentPane(myGame);
frame.validate();
frame.repaint(); // prefer to write this always.
} else if (source == exitButton) {
System.exit(1);
} else if (source == showRank) {
} else {// one on one
}
}
}
public Main() {
frame = new JFrame();
frame.setBackground(Color.white);
frame.setSize(window_x_size, window_y_size);
frame.setTitle("My First GUI Game");
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
Menu = new JPanel();
startButton.addActionListener(MyButtonListener);
exitButton.addActionListener(MyButtonListener);
showRank.addActionListener(MyButtonListener);
OneOnOne.addActionListener(MyButtonListener);
Menu.setLayout(new GridLayout(4, 1));
Menu.add(startButton);
Menu.add(OneOnOne);
Menu.add(showRank);
Menu.add(exitButton);
frame.setContentPane(Menu);
frame.setVisible(true);
}
public static void main(String[] args) {
/*
* This is the most important part of your GUI app, never forget
* to schedule a job for your event dispatcher thread :
* by calling the function, method or constructor, responsible
* for creating and displaying your GUI.
*/
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new Main();
}
});
}
}
This is my Game class
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Game extends JPanel {
private Random rnd = new Random();
private ActionListener MyBulletListener = new BulletListener();
public KeyListener myKeyListen = new MyKeyListener();
Timer bullet_timer;
private boolean IsExplosion = false;
private ImageIcon spacecraftImg = new ImageIcon("spacecraft.png");
private Rectangle spacecraftBox = new Rectangle(5, 10,
spacecraftImg.getIconWidth(), spacecraftImg.getIconHeight());
private ImageIcon explosionImg = new ImageIcon("explosion.png");
private ImageIcon bulletImg = new ImageIcon("bullet.png");
private Rectangle bulletBox = new Rectangle(70, 200,
bulletImg.getIconWidth(), bulletImg.getIconHeight());
private MySound explosion_sound = new MySound();
public Game() {
addKeyListener(myKeyListen);
bullet_timer = new Timer(500, MyBulletListener);
bullet_timer.start();
}
public class MyKeyListener implements KeyListener {
private int x;
private int y;
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
int keyCode = e.getKeyCode();
if (keyCode == 37) {
x = -10;
y = 0;
} else if (keyCode == 38) {
x = 0;
y = -10;
} else if (keyCode == 39) {
x = 10;
y = 0;
} else {
x = 0;
y = 10;
}
if (spacecraftBox.x + x < 0
|| spacecraftBox.x + x > 500
|| spacecraftBox.y + y < 0
|| spacecraftBox.y + y > 500) {
} else {
move_plane(x, y);
repaint();
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
public class BulletListener implements ActionListener {
private int x;
private int y;
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
Object source = e.getSource();
if (source == bullet_timer) {
int dir = rnd.nextInt(4);
if (dir == 0) {// move left
x = -10;
y = 0;
} else if (dir == 1) {// move right
x = 10;
y = 0;
} else if (dir == 2) {// move up
x = 0;
y = -10;
} else {// move down
x = 0;
y = 10;
}
if (bulletBox.x + x < 0
|| bulletBox.x + x > 500
|| bulletBox.y + y < 0
|| bulletBox.y + y > 500) {
} else {
move_bullets(x, y);
repaint();
}
}
}
}
#Override
public void paint(Graphics g) {
g.drawImage(bulletImg.getImage(), (int) bulletBox.getX(),
(int) bulletBox.getY(), null);
g.drawImage(spacecraftImg.getImage(), (int) spacecraftBox.getX(),
(int) spacecraftBox.getY(), null);
if (IsExplosion) {
g.drawImage(explosionImg.getImage(), (int) bulletBox.getX(),
(int) bulletBox.getY(), null);
MySound.play();
IsExplosion = false;
}
}
public void move_plane(int x, int y) {
spacecraftBox.translate(x, y);
if (spacecraftBox.intersects(bulletBox)) {
IsExplosion = true;
}
}
public void move_bullets(int x, int y) {
bulletBox.translate(x, y);
if (spacecraftBox.intersects(bulletBox)) {
IsExplosion = true;
}
}
}
Plus I added KeyListener but it doesn't work. I have no idea how to fix it out.
I tried googling about the issue. I tried the game panel focusable in main class but it didn't work.
I would really appreciate your help.
Best Regards,
Dongseop
This is almost always due to your painting method not calling the super's painting method to clean up dirty pixels. Since you're overriding paint, you would need to call super.paint(g); as the first method call in your paint override.
Other issues:
Next I'm going to suggest that you not override paint but rather paintComponent(Graphics g), as this will give your animation automatic double buffering and make the animation smoother. Again, call super.paintComponent(g); in your method override.
The easiest way to swap views is to use a CardLayout.
Better to use KeyBindings rather than a KeyListener as this will help you get around the KeyListener's focus issue, the issue which is probably preventing your KeyListener from working. It also allows you to use reusable Actions. Please search this site on KeyListeners to see what I mean and to see examples of Key Bindings (some by me). Also Google Java Key Bindings Tutorial to see the official tutorial on how to use these.
I've been working on an "elevator simulator" where I have to animate "elevators". I was able to create different elevator objects, which I did by having each Elevator Object have the parameters of width, height, and coordinates. I then stored them all into an array and used a for loop to draw them into my frame using JPanel's PaintComponent method.
Can you guys suggest a way or give me advice to animate them separately from each other, like say move them up and down independently? I was able to make it move when I only had ONE elevator, but when I tried to apply it to multiple elevators, it did not work.
My previous attempt involved an ActionListener (that listens to a button press to "start" the animation) that simply changes the x and y coordinates of the SINGLE elevator. So How do I go and do that with several elevators (the number of elevators is arbitrary to the user). Do I have to make as many ActionListeners as there are elevators, so it can listen to them independently?
Here's the ActionListener that worked when there's only ONE elevator. It only moves up so far.
private ActionListener timerActionUp = new ActionListener()
{
private int iterator = 0;
private int top = 0;
public void actionPerformed(ActionEvent e)
{
if(top<floorQueue.length){
if(iterator<floorQueue[top]){ //floorQueue is so that the elevator knows where to stop
if(movefloorup<VERTICALFLOORDISTANCE*6){ //this is when the elevator reaches the "top" floor that can fit into the screen, and moves to the next column representing the floors further up
repaint();
movefloorup = movefloorup + VERTICALFLOORDISTANCE;
System.out.println(movefloorup);
iterator++;
}
else{
//timer.stop();
repaint();
switchmarker = 1; //makes elevator moves to the next column
movefloorup = 0;
iterator++;
}
}
else
{
System.out.println("Picking up passengers...");
elevatorCapacity = elevatorCapacity + peopleQueue[floorQueue[top]];
System.out.println("Passengers in elevator: "+elevatorCapacity);
peopleQueue[floorQueue[top]] = 0; //Updates the number of people in the elevator
top++;
if(elevatorCapacity >= 5)
{
System.out.println("WARNING! ELEVATOR FULL!");
elevfull = 1;
}
//timer.stop();
}
}
else
{
System.out.println("Done picking up passengers.");
timer.stop();
}
}
};
Thank you very much!
"Do I have to make as many ActionListeners as there are elevators, so it can listen to them independently?"
No, that would require multiple Timers. Avoid doing this whenever you can.
"Can you guys suggest a way or give me advice to animate them separately from each other, like say move them up and down independently?"
What you should do is try and implement the business logic in methods within your Elevator class and just call the those methods while looping through all the Elevators in your array.
Two make the Elevators to appear to move independently, you can have flags, say in your move method, like
public void move() {
if (move) {
// do something
}
}
What ever is your reason for making the elevator move, that will be the reason the raise the flag. And vice versa. Maybe something like if (onFloor) { elevator.move = false }, maybe for a duration of 20 timer "iterations", and keep a count in the elevator class, that will reset back to 0 when the count hits 20, then move will be back at true.
Here's an example you can play with. I was working on it for about 20 minutes then gave up. The logic is a bit off, but it basically points out the ideas i mentioned. Maybe you'll have better luck with it. You can also see a good working example of moving different object here
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ElevatorAnimate extends JPanel {
private static final int D_W = 300;
private static final int FLOORS = 6;
private static final int FLOOR_HEIGHT = 100;
private static final int BUILDING_HEIGHT = FLOORS * FLOOR_HEIGHT;
private static final int D_H = BUILDING_HEIGHT;
private static final int BUILDING_BASE = BUILDING_HEIGHT;
private static final int ELEVATOR_HEIGHT = 60;
private static final int ELEVATOR_WIDTH = 30;
private final List<Elevator> elevators;
public ElevatorAnimate() {
elevators = createElevators();
Timer timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (Elevator el : elevators) {
el.move();
}
repaint();
}
});
timer.start();
}
private List<Elevator> createElevators() {
List<Elevator> list = new ArrayList<>();
list.add(new Elevator(6, 30));
list.add(new Elevator(4, 90));
list.add(new Elevator(2, 150));
return list;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawFloors(g);
for (Elevator el : elevators) {
el.drawElevator(g);
}
}
private void drawFloors(Graphics g) {
for (int i = 1; i <= FLOORS; i++) {
g.drawLine(0, FLOOR_HEIGHT * i, D_W, FLOOR_HEIGHT * i);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public class Elevator {
int floor, x, y;
boolean move = false;
boolean up = true;
int stopCount = 0;
public Elevator(int floor, int x) {
this.floor = floor;
y = BUILDING_HEIGHT - (floor * FLOOR_HEIGHT);
this.x = x;
}
public void drawElevator(Graphics g) {
g.fillRect(x, y, ELEVATOR_WIDTH, ELEVATOR_HEIGHT);
}
public void move() {
if (y <= 0) {
up = false;
} else if (y >= BUILDING_BASE + ELEVATOR_HEIGHT) {
up = true;
}
if (isOnFloor()) {
move = false;
}
if (move) {
if (up) {
y -= 2;
} else {
y += 2;
}
} else {
if (stopCount >= 20) {
move = true;
stopCount = 0;
} else {
stopCount++;
}
}
}
private boolean isOnFloor() {
return y / FLOOR_HEIGHT == 100;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new ElevatorAnimate());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Starting from this related Subway simulation, the following variation adds two independent panels, each of which contains its own view and control panel.
// Common initialization for either JApplet or JFrame
private static void initContainer(Container container) {
container.add(createPanel(), BorderLayout.NORTH);
container.add(createPanel(), BorderLayout.SOUTH);
}
private static JPanel createPanel() {
JPanel panel = new JPanel(new BorderLayout());
ButtonPanel control = new ButtonPanel();
SubwayPanel subway = new SubwayPanel(control);
panel.add(subway, BorderLayout.NORTH);
panel.add(control, BorderLayout.SOUTH);
subway.beginOperation();
return panel;
}
I have many planes(threads) that move in window, and I want switch the ImageIcon according to the direction of the plane.
For example: if a plane goes to the right, the imageIcon of the plane is right and then plane goes to the left, exchange the imageIcon for the plane is left.
How can I do that in method paintComponent?
Sorry for my bad english.
If you're talking about swapping the ImageIcon displayed by a JLabel, then you should not switch ImageIcons in paintComponent but rather should do this in the non-paintComponent region of code, perhaps in a Swing Timer. Even if you're not talking about a JLabel, the paintComponent method should not be used for changing the state of an object.
Your question however leaves too much unsaid to allow us to be able to answer it completely and well. Consider telling and showing more.
If you are looking for a logic thingy, then a small example is here, though you might have to modify it for your needs.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
public class FlyingAeroplane
{
private Animation animation;
private Timer timer;
private ActionListener timerAction = new ActionListener()
{
#Override
public void actionPerformed(ActionEvent ae)
{
animation.setValues();
}
};
private void displayGUI()
{
JFrame frame = new JFrame("Aeroplane Flying");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
animation = new Animation();
frame.setContentPane(animation);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
timer = new Timer(100, timerAction);
timer.start();
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new FlyingAeroplane().displayGUI();
}
});
}
}
class Animation extends JPanel
{
private final int HEIGHT = 150;
private final int WIDTH = 200;
private int x;
private int y;
private ImageIcon image;
private boolean flag;
private Random random;
public Animation()
{
x = 0;
y = 0;
image = new ImageIcon(getClass().getResource("/image/aeroplaneright.jpeg"));
flag = true;
random = new Random();
}
public void setValues()
{
x = getXOfImage();
y = random.nextInt(70);
repaint();
}
private int getXOfImage()
{
if (flag)
{
if ((x + image.getIconWidth()) == WIDTH)
{
flag = false;
x--;
return x;
}
x++;
image = new ImageIcon(getClass().getResource("/image/aeroplaneright.jpeg"));
}
else if (!flag)
{
if (x == 0)
{
flag = true;
x++;
return x;
}
x--;
image = new ImageIcon(getClass().getResource("/image/aeroplaneleft.jpeg"));
}
return x;
}
#Override
public Dimension getPreferredSize()
{
return (new Dimension(WIDTH, HEIGHT));
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image.getImage(), x, y, this);
}
}
IMAGES USED :
On setting the direction you should set the image icon too, or have a getImageIcon(direction).
In the paintComponent no heavy logic should happen; it should be as fast as possible. You have no (total) control when and how often paintComponent is called.