Hi I'm building a game which includes 3 JPanels on a JFrame, a Startscreen, a DrawingPanel and a GameOver screen. If I just create the DrawingPanel and tell the GameController class to begin updating it works fine, but if I create a StartScreen with a button to start the game, then when I press the start game button the game window does not display, although the game code runs.
EDIT:
I have created a new program which mimics the creation of the JPanels, but excludes all of the game code to make it a bit simpler to follow. Below I have included all the relevant classes:
This class creates a JFrame and two JPanels. It also runs the code that updates the game state and tells the DrawingPanel to repaint.
public class TestController{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private final int FRAME_WIDTH = (int)screenSize.getWidth();
private final int FRAME_HEIGHT = (int)screenSize.getHeight();
public boolean gameStarted = false;
private JFrame gameWindow;
private TestDrawingPanel myDrawingPanel;
private TestStartGame startGame;
int counter;
Set<Rectangle> rects;
//creates a JFrame and all the JPanels
public TestController(String title)
{
gameWindow = new JFrame(title);
gameWindow.setSize(FRAME_WIDTH, FRAME_HEIGHT);
gameWindow.setVisible(true);
gameWindow.setResizable(false);
gameWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
startGame = new TestStartGame(getAvailableWidth(), getAvailableHeight());
startGame.addAL(new StartButton());
myDrawingPanel = new TestDrawingPanel(getAvailableWidth(), getAvailableHeight());
gameWindow.add(startGame);
gameWindow.add(myDrawingPanel);
myDrawingPanel.setVisible(false);
myDrawingPanel.setEnabled(false);
rects = new HashSet();
}
private int getAvailableWidth()
{
return gameWindow.getWidth() - gameWindow.getInsets().left - gameWindow.getInsets().right;
}
private int getAvailableHeight()
{
return gameWindow.getHeight() - gameWindow.getInsets().top - gameWindow.getInsets().bottom;
}
//starts the game running
public void startTheGame()
{
myDrawingPanel.setEnabled(true);
startGame.setVisible(false);
startGame.setEnabled(false);
myDrawingPanel.setVisible(true);
gameStarted = true;
update();
}
public boolean getGameStarted()
{
return gameStarted;
}
//loop that runs the game code
public void update()
{
counter = 0;
while(gameStarted)
{
updatePictureState();
myDrawingPanel.draw(rects);
myDrawingPanel.repaint();
}
}
//updates the game state
public void updatePictureState()
{
rects.clear();
for (int i = counter + 10; i < counter + 100; i = i + 10)
{
rects.add(new Rectangle(i,i,10,10));
}
}
//an action listener to be added to the start screen
private class StartButton implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
Object buttonPressed = e.getSource();
if(buttonPressed.equals(TestStartGame.start))
{
startTheGame();
}
}
}
}
This class is an extended JPanel with a single button to start the game:
public class TestStartGame extends JPanel{
private final JPanel buttons;
public static JButton start;
//creates a JPanel with a single button to start the game
public TestStartGame(int width, int height)
{
setSize(width, height);
setLayout(new GridLayout(2,1));
setBackground(Color.GREEN);
buttons = new JPanel();
buttons.setSize(width, height / 2);
buttons.setBackground(Color.red);
start = new JButton("Start");
buttons.add(start);
add(buttons, BorderLayout.SOUTH);
}
//adds an action listener to the button
public void addAL(ActionListener al)
{
start.addActionListener(al);
}
}
This class is an extended JPanel and acts as the main screen for the game, being updated to with each cycle of the game to display it's current state:
public class TestDrawingPanel extends JPanel{
Set<Rectangle> drawSet;
//creates the drawing panel and sets the size and background
public TestDrawingPanel(int width, int height)
{
setSize(width, height);
this.setBackground(Color.CYAN);
drawSet = new HashSet();
}
public void draw(Set<Rectangle> platforms)
{
drawSet.clear();
drawSet = platforms;
}
//draws the game window
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("works here");
g.setColor(Color.red);
for (Rectangle r : drawSet)
{
g.fillRect((int)r.getX(), (int)r.getY(), (int)r.getWidth(), (int)r.getHeight());
}
}
}
If I just add the TestDrawingPanel, it displays fine, but if I start with a TestStartScreen then when I click the start game button the TestStartScreen does not disappear and the TestDrawingPanel never displays. Interestingly, if I have both screens but do not call the update method is TestController then the start game button works correctly and the TestDrawingPanel displays, although obviously nothing happens as the update method is where the game state is changed.
I have discovered the problem is that if the TestDrawingPanel is not the only JPanel created then the call to repaint it fails.
Here:
Thread.sleep(20);
You are most likely sleeping on the Event Dispatcher Thread. That will freeze your whole application. You have to step back and look into invokeLater to ensure "correct" threading within your UI.
Problem solved courtesy of #Andrew-Thompson:
"while(gameStarted) .. No, no a thousand times no. An infinite loop will likely freeze the GUI. Use a Swing based Timer to call those code statements in the loop, in the actionPerformed method of an ActionListener"
Related
I am trying to make a little program that includes changing the color of a Panel time-based.
Right now I am just trying to do that part without the rest. So I just wrote a little Interface with only one panel and I want to change the color within a loop multiple times.
The problem is, even though the thread pauses for the correct amount of time, the color of the Panel doesn't change correctly. It changes just sometimes in the loop not every time.
my Interface Class:
import javax.swing.*;
import java.awt.*;
//creates the Interface
public class Interface extends JFrame {
private JPanel frame1;
public Interface (String titel) {
super(titel);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame1 = new JPanel();
this.frame1.setPreferredSize(new Dimension (200, 200));
setLayout(new FlowLayout());
add(frame1);
this.setVisible(true);
}
public JPanel getFrame1() {
return frame1;
}
}
my Pause Class:
import java.util.TimerTask;
//supposed to pause the thread by #pause amount of milliseconds
public class Pause extends TimerTask {
private int pause;
public Pause(int pause){
this.pause = pause;
}
#Override
public void run() {
System.out.println("Timer"+ pause+" task started at:"+System.currentTimeMillis());
pause();
System.out.println("Timer task"+ pause+" ended at:"+System.currentTimeMillis());
}
public void pause() {
try {
Thread.sleep(this.pause);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
my Blink Class
import javax.swing.*;
import java.awt.*;
public class Blink {
private JPanel frame1;
public Blink(Interface anInterface){
this.frame1 = anInterface.getFrame1();
}
// blink should change the color of the JPanel inside my Frame.
// Its supposed to change to red for 200 ms
// and then to white again for 1000 ms.
// this should be repeated 10 times.
public void blink() {
Pause pause1 = new Pause(200);
Pause pause2 = new Pause(1000);
pause2.run();
int i = 1;
while(i <= 10){
i++;
frame1.setBackground(Color.red);
frame1.repaint();
pause1.run();
frame1.setBackground(Color.white);
frame1.repaint();
pause2.run();
}
}
public static void main ( String[] args ) {
Interface anInterface = new Interface("Title");
anInterface.setVisible(true);
Blink blink = new Blink(anInterface);
blink.blink();
}
}
According to Concurrency to Swing you cannot simply Thread.sleep the Thread where the GUI runs because it will freeze it, hence events cannot take place. Instead, for any kind of animation or long-heavy task (consider Thread.sleep as one), Swing Timers and Swing Workers should be used. In your case, a javax.swing.Timer fits better.
One example of its usage:
public class Blink {
private JPanel frame1;
private int pause1TimesRan;
private int pause2TimesRan;
private Timer pauser1, pauser2;
public Blink(Interface anInterface) {
this.frame1 = anInterface.getFrame1();
//Create pauser 1 with delay 200ms
pauser1 = new Timer(200, e -> {
if (pause1TimesRan == 10) {
pauser1.stop();
return;
}
Color color = randomColor();
frame1.setBackground(color);
System.out.println("Pauser #1 changed background to: " + color);
pause1TimesRan++;
});
//Create pauser 2 with delay 1000ms
pauser2 = new Timer(1000, e -> {
if (pause2TimesRan == 10) {
pauser2.stop();
return;
}
Color color = randomColor();
frame1.setBackground(color);
System.out.println("Pauser #2 changed background to: " + color);
pause2TimesRan++;
});
}
private static Color randomColor() {
return new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255));
}
public void blink() {
pauser1.start();
pauser2.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Interface anInterface = new Interface("Title");
anInterface.setVisible(true);
Blink blink = new Blink(anInterface);
blink.blink();
});
}
static class Interface extends JFrame {
private JPanel frame1;
public Interface(String titel) {
super(titel);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame1 = new JPanel();
this.frame1.setPreferredSize(new Dimension(200, 200));
setLayout(new FlowLayout());
add(frame1);
this.setVisible(true);
}
public JPanel getFrame1() {
return frame1;
}
}
}
One off-topic advice is to name your methods (and variables) properly. You called the method getFrame1(), but it is actually a JPanel and not a JFrame. So, a better name could be getPanel(). Also, about the SwingUtilities.invokeLater part, read What does SwingUtilities.invokeLater does.
i've got a problem with my program. I'm finishing to develop Pacman for my exam but I've a trouble when I change Panel from "menu" to "game" Panel.
Precisely in the main frame I've added two Panel : the "menu" panel and the "game " one. If I don't add the menu panel , the game (and above all, the listener) works; but if I also add the menu panel, when I press the "start game" button in it , the "setVisible" property of menu will sets up to false and the new thread will starts. In this case the problem is that the listener doesn't work .
public static void main(String[] args) {
boolean gameOver = false;
MainFrame mf = new MainFrame(FRAME_WIDTH, FRAME_HEIGHT, TITLE);
// board is a logic map; gb is the graphic map of the game
Board board = new Board();
GraphicBoard gb = new GraphicBoard(mf, board);
gb.setLocation(150, 0);
Background background = new Background(mf.getWidth(), mf.getHeight(), BACKGROUND_COLOR);
background.add(gb);
PacmanThread thread = new PacmanThread(gb, gameOver);
Menu menu = new Menu(mf.getWidth(), mf.getHeight(), thread);
mf.add(menu);
mf.add(background);
mf.setResizable(false);
mf.setVisible(true);
}
In menu.java is present a subclass, so
public class Menu extends JPanel{
public Menu(int pWidth, int pHeight, PacmanThread pThread) {
this.setLayout(null);
this.setSize(pWidth, pHeight);
this.thread = pThread;
initComponent();
}
private class StartGame implements ActionListener {
private Menu menu;
public StartGame(Menu pMenu) {
this.menu = pMenu;
}
#Override
public void actionPerformed(ActionEvent e) { //<--- is the problem here? what I should add?
menu.setVisible(false);
thread.start();
}
}
}
Finally tha actionListener is present in a class called Pacman.java
I am working to develop a GUI in which I paint some 2D shapes repeatedly on different locations. Currently I am having a method createGUI() that creates the basic layout and the panels and then I call the constructor for the content_panel to create 2D shapes in the content_panel.
But, I want to use another method to create the shapes in the main JPanel. Is there a way in Java, so that I can have two method calls in main. First method createGUI() creates the GUI including JFrames and JPanel. While the second method createShapes() creates shapes in the one specific JPanel - content_panel. I would like to call this createShapes() method repeatedly and pass different arguments to see shapes at different locations.
Please let me know if you need some more info or the question is unclear. Thanks
Code:
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class Board{
public static void main(String[] args)
{
createGUI();
drawShapes();
}
//This method creates the basic GUI
private static void createGUI()
{
//Creating the JFrame main window
JFrame mainFrame = new JFrame();
mainFrame.setSize(800, 500);
mainFrame.setTitle("Particle Filter");
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setLocation(100, 100);
mainFrame.getContentPane().setLayout(new BoxLayout(mainFrame.getContentPane(), BoxLayout.X_AXIS));
//creates two panels content and sidebar. Sidebar has null layout
JPanel content = new JPanel();
content.setPreferredSize(new Dimension(700,500));
content.setBackground(Color.LIGHT_GRAY);
mainFrame.getContentPane().add(content);
JPanel sidebar = new JPanel();
sidebar.setBackground(Color.LIGHT_GRAY);
sidebar.setPreferredSize(new Dimension(100,500));
mainFrame.getContentPane().add(sidebar);
sidebar.setLayout(null);
//creates three buttons in sidebar
JButton start_button = new JButton("START");
start_button.setBounds(10, 75, 77, 23);
sidebar.add(start_button);
JButton stop_button = new JButton("STOP");
stop_button.setBounds(10, 109, 77, 23);
sidebar.add(stop_button);
JButton reset_button = new JButton("RESET");
reset_button.setBounds(10, 381, 77, 23);
sidebar.add(reset_button);
//calls the content_Walls class and sends the number of ovals to be generated
int n=1000; // n denotes the number of ovals
content.add( new Content_Walls(n));
mainFrame.setVisible(true);
}
private static void drawShapes()
{
}
}
class Content_Walls extends JPanel
{
ArrayList<Integer> list;
Content_Walls(int n)
{
setPreferredSize(new Dimension(680,450));
setBackground(Color.WHITE);
list = new ArrayList<Integer>(Collections.nCopies(n, 0));
}
public void paintComponent(Graphics g)
{
int x=0,y=0;
super.paintComponent(g);
createObstacles(g,150,225,100,40);
createObstacles(g,500,300,40,100);
for(int i=0;i<list.size();i++)
{
x=randomInteger(11,670); // bounds of x between which the particles should be generated
y=randomInteger(11,440); // bounds of y between which the particles should be generated
int radius = 4;
x=x-(radius/2);
y=y-(radius/2);
g.fillOval(x, y, radius, radius);
}
private void createObstacles(Graphics g, int x, int y, int width, int height)
{
g.setColor(Color.BLACK);
g.fillRect(x, y, width, height);
}
private static int randomInteger(int min, int max)
{
Random rand = new Random();
int randomNum = rand.nextInt((max - min) + 1) + min;
return randomNum;
}
}
There are all sorts of problems with your code.
You're creating Swing components on the main thread instead of the Event Dispatch Thread. Search for help on this.
Have Board subclass JFrame and do the GUI initialization in the constructor or an instance method instead of a static method.
Make drawShapes() an instance method.
When you create the JPanel, store its reference in an instance variable (e.g., myPanel). This will be easier to do and will be a lot less messy if you fix #2.
If you do #2 and #3, just pass the reference to the drawShapes() method.
drawShapes()might not even be needed, if all the logic is in the paintComponent() method. Call myPanel.repaint() to invoke the paintComponent() method.
You should use event dispatching to let different JPanels component to act on the event.
e.g. You could attach a custom event with an event type and jpanel id; and then fire the event from the main. The panel listening to the event do something based on the logic.
Each JPanel listening to the event will intercept the event and if the jpanelid in the event matches to its own jpanel id, it will draw the shape.
I hope you get a pointer.
Here is a sample code, which I have not tested, but it shows how could you use event dispatching/listening to communicate between GUI components.
Define a ChangeEventListener interface
public interface ChangeEventListener {
public void stateChanged(ChangeEvent e);
}
And define an Event
public class ChangeEvent {
private Object source;
private int jPanelId;
public ChangeEvent(Object source, int jPanelId) {
this.source = source;
this.jPanelId= jPanelId;
}
public Object getSource() {
return source;
}
public int getJPanelId() {
return jPanelId;
}
}
Define your panel like
public class ShapePanel extends JPanel {
private int jPanelId;
private ChangeEventListener changeEventListener;
public void ShapePanel(int jPanelId){
this.jPanelId = jPanelId;
}
/*
.............
.............. Other code
.................
*/
public void addChangeEventListener(ChangeEventListener changeEventListener) {
this.changeEventListener = changeEventListener;
}
public int getJPanelId(){
return jPanelId;
}
public getChangeEventListener(){
return changeEventListener;
}
}
Your main should contain something like;
// Craete different Jpanel
JPanel squareShapePanel = new ShapePanel(1);
JPanel roundShapePanel = new ShapePanel(2);
JPanel triangleShapePanel = new ShapePanel(3);
// Attach event listener with each one like
squareShapePanel.addChangeEventListener(new ChangeEventListener() {
#Override
public void stateChanged(ChangeEvent e) {
if(e.getJPanelId() == squareShapePanel.getJPanelId()){
// Createshape method can be available inside JPanel code
// something like squareShapePanel.craeteShape();
// All in one it is a method which could do something for you on the event.
// Assuming that it is available in current class
createShape("square");
}
});
/*
Similarly attach eventlistener with each panels.
*/
// to draw the square shape, Fire change event
ChangeEvent event = new ChangeEvent(new String("Main"),1);
squareShapePanel.getChangeEventListener().stateChanged(event);
Hope this helps.
Check out Custom Painting Approaches for the two common ways to do custom painting:
Keep the Shapes you want to paint in an List and then just paint all the Shapes in the List
Use a BufferedImage and just draw the Shapes onto the BufferedImage.
Both examples contain an addRectangle(...) method that allows you to dynamically add a Rectangle to be painted.
i'm trying to make it display one JPanel and remove the other depended on which timer is off in java. here is the snip it of code. mainMenu and pongField are the JPanels and this is inside a timer that acts every millisecond;
if (SwingUtilities.getAncestorOfClass(JFrame.class, pongField) != null){
if (!pongField.getTimer().isRunning())
{
mainMenu = new MainMenu(screenX, screenY, myColor, background, contentPane);
contentPane.remove(pongField);
}}
if (SwingUtilities.getAncestorOfClass(JFrame.class, mainMenu) != null){
if (!mainMenu.getTimer().isRunning())
{
switch(mainMenu.getButton())
{
case 1:
pongField = new PongField(screenX, screenY, 0, moderator, player1UpControl, player2UpControl, player1DownControl, player2DownControl, myColor, background, contentPane, speed);
mainMenu.setButton(0);
contentPane.remove(mainMenu);
break;
case 2:
break;
case 3:
break;
case 4:
break;
}
}}
let me be a little more specific.
I am trying to make Pong, and one JPanel is the actual game while the other is the main menu. if the game ends the timer stops and if i press a button on the main Menu the timer stops and set getButton() to a number depending on which is pressed. for example if button single player is pressed it creates the game and gets rid of the current Jpanel. but if the game ends it get rid of the current JPanel and displays the main menu. Also each JPanel when created set it's own bounds, background color, and adds itself to the JFrame.
The problem is when i run it and the game ends it doesn't change back unless i minimize it and bring it back up but then the button won't work. here is the code for MainMenu. PongField's code is too long but i think i summed up what it did good enough but i can post it too if you want.
public class MainMenu extends JPanel
{
private static final long serialVersionUID = 1L;
//JButtons
private JButton singleJButton, multiJButton, settingsJButton, exitJButton;
//timer if game is still on
private Timer runningTimer;
//JLabel for title
private JLabel titleJLabel;
//screen size
int screenX, screenY;
//color of text and background
private Color myColor, background;
//Container of the JFrame
private Container contentPane;
//which button was clicked
private int buttonPressed;
public MainMenu(int x, int y, Color myC, Color backg, Container contentP)
{
super();
//Initialize all given variables
screenX = x;
screenY = y;
myColor = myC;
background = backg;
contentPane = contentP;
setBounds(0,0,screenX,screenY);
setBackground(background);
contentPane.add(this);
setFocusable(true);
requestFocusInWindow();
singleJButton = new JButton();
singleJButton.setBounds(100, 100, 100, 50);
singleJButton.setText("Single Player");
singleJButton.setFocusable(false);
contentPane.add(singleJButton);
singleJButton.addActionListener(new ActionListener()
{public void actionPerformed( ActionEvent event )
{singleJButtonAction(event);}});
runningTimer = new Timer( 30, new ActionListener()
{public void actionPerformed( ActionEvent event )
{}});
runningTimer.start();
}
public Timer getTimer()
{
return runningTimer;
}
public int getButton()
{
return buttonPressed;
}
public void setButton(int val)
{
buttonPressed = val;
}
private void singleJButtonAction(ActionEvent e)
{
runningTimer.stop();
buttonPressed = 1;
}
//make this panel have focus
public void focus()
{
requestFocusInWindow();
}
What, is it not working? I'm just guessing because you don't say.
The first thing I see if you're creating and removing panes dynamically. It would be more efficient to just hide them. Of course, I don't know what your layout looks like. The other option is to just construct them on startup with all your other GUI stuff and go ahead and add and remove them, but don't recreate them based on the timer. Just show them and hide them as needed. Like this:
mainMenu.setVisible( true );
mainMenu.setVisible( false );
Every millisecond if also pretty fast. I look at ratcheting down, to say one second or so. Of course, it depends on what you're doing.
HTH
I still have problem understanding what your question is?
One thing that seems to be missing in the code is where you add the mainMenu and pongField you create. You remove them from the contentPane by contentPane.remove(pongField); and contentPane.remove(mainMenu); is that your problem?
I'm working on a minesweeper game and I want to make the bomb (or in this case a panda image I created) to show up under the game space when it's pressed. At this point I just want to make it show up under every single space, I know how to do the randomizing of where it shows up after that, the problem is making it show up.
Right now the parts of my code that apply to this topic are in 2 different classes:
1st class
public class MSBoard extends JPanel implements ActionListener
{
int x = 8;
int y = 8;
public GridLayout gl = new GridLayout(x,y,0,0);
public MSBoxes boxarray[][] = new MSBoxes[x][y];
MSBoard()
{
super();
setLayout(gl);
for(int i=0;i<x;i++)
for (int j=0;j<y;j++)
{
boxarray[i][j] = new MSBoxes();
add(boxarray[i][j]);
}
}
public void actionPerformed(ActionEvent ae){}
}
2nd one
public class MSBoxes extends JPanel implements ActionListener
{
public JButton b1;
ImageIcon panda;
MSBoxes()
{
super();
panda = new ImageIcon("panda.gif");
b1 = new JButton();
add(b1);
b1.addActionListener(this);
b1.setVisible(true);
}
public void actionPerformed(ActionEvent ae)
{
if(b1 == ae.getSource())
{
b1.setVisible(false);
}
}
}
use JToggleButton for Minesweeper game
use putClientProperty
use ItemListener for JToggleButton