I'm trying to build a simple game in Java. Ran into the problem of the JTextPanel not updating until after the game loop terminates, which of course, isn't a good experience for the player.
I'm unfamiliar with multithreading but trying to figure it out. I can run separate code now in multiple threads, but I can't get my head around how to have the Threads interact. It's highly likely I'm missing something simple, but I can't find it by searching, so I throw myself at your mercy. I'm hanging by a thread...
Controller class and main thread. I need the gamePanel and the game to run separately. I've tried to run the Game class in a separate thread, but the game code isn't running in the gamePanel.
Controller:
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Controller_LetterFall{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
public void run(){
new MainFrame();
}
});
}
}
And the MainFrame Class. I try to run gameplay() in a new thread.
package wordFall;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainFrame extends JFrame implements Runnable {
private GamePlay game;
private TextPanel gamePanel;
private Header header;
private Player player;
private Dictionary dictionary;
private GamePlay game;
public MainFrame(){
super("Game");
// Set the size of the frame.
setSize(400,600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
// Establish default player.
player = new Player();
dictionary = new Dictionary();
game = new GamePlay();
header = new Header();
gamePanel = new TextPanel();
add(header, BorderLayout.NORTH);
add(gamePanel, BorderLayout.CENTER);
this.game.setBoardInterface(
new BoardInterface(){
#Override
public void redraw(String text) {
gamePanel.appendText(text);
}
});
}
#Override
public void run() {
game.play();
System.out.println("The game is over.");
}
}
Any help would be appreciated.
You need something like a thread safe data container. Lets call it GameData.
Your GamePlay object needs to know that GameData object and the UI needs to know that as well because changes made by the user has to be propagated to the GameData object.
In your GamePlay object you can look for changes of the GameData object every second or so. If there are changes than you have something to do if not ..
But it would be better use an event like approach i.e. the Observer Pattern. If changes are made by the UI the GamePlay will be notified by the observable GameData object.
Furthermore, the UI can be notified as well when data changes.
This seperates the concerns very clearly and follows the Model View Controller pattern.
Well, in this case, your MainFrame object is being constructed from the swing dispatcher thread. As you said, you have to start a new thread somewhere to handle the game. Assuming you would have this code in an instance method of a MainFrame object, that would be:
Thread thr = new Thread(this);
thr.start();
Then, whenever your separate game thread wants to update the ui object, it should not do it directly, but use SwingUtilities.invokeLater:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
/* Update the ui element... */
}});
That runnable will be executed in a swing dispatcher thread when it will have time.
Related
I want to create a program where each second it prints "tick" to the console.
I want to do this using the ActionListener interface. I created a timer object with a delay of 1000 milli-seconds.
This approach with ActionListener and Timer worked in other projects, but it doesn't here.
I will appreciate it if someone could help me.
package mainPackage;
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
public class Game extends Canvas implements ActionListener {
Timer timer;
public Game() {
timer = new Timer(1000, this);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Tick");
}
public static void main(String[] args) {
new Game();
}
}
I am new to java, so I am sorry if this is a stupid question!
Thanks in advance!
I presume the problem is that your program is finishing immediately. The swing Timer does not prevent your program from ending. Try displaying a swing component. Your program should continue, and the timer run, while the component is visible.
Alternatively you could use java.util.Timer instead of javax.swing.Timer. The util Timer can create non-deamon threads, which should keep your program alive.
I am trying to create some little game where in one Thread is running an Infinite Loop that is going to do Engine job and in JFrame is going to be all stuff that is Outputed on screen.
But I am facing one big problem that I am unable to fix and also not finding any answer on the internet. When I have no immediate output to console in infinite loop in my Thread then it seem like That Thread (in Engine class) is killed by Program and leaving me with JFrame only. But when there is some output to console in infinite loop then Thread (in Engine class) is fully working as expected to do and that is driving me crazy :(
Main CLASS:
package ccarsimulator;
import javax.swing.SwingUtilities;
public class CCarSimulator {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Window window = new Window("CCarsimulator");
});
}
}
Window CLASS:
package ccarsimulator;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Window extends JFrame{
Engine oEngine = new Engine(this);
JLabel LabelOutput = new JLabel();
JPanel PanelCanvas = new JPanel();
JLabel LabelSpeed;
JLabel LabelRPM;
JLabel LabelGearSet;
JLabel LabelTotalTime;
JLabel LabelFPS;
JLabel LabelCPS;
public Window(String WindowTitle){
super(WindowTitle);
this.addKeyListener(new KeyListener(){
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent oKey) {
System.out.println("Key Pressed");
oEngine.bKeyPressed[oKey.getKeyCode()] = true;
}
#Override
public void keyReleased(KeyEvent oKey) {
oEngine.bKeyPressed[oKey.getKeyCode()] = false;
}
});
this.addWindowListener(new WindowAdapter(){
#Override
public void windowClosing(WindowEvent oEvent){
System.out.println("Closing a Window");
oEngine.bStopEngine = true;
}
});
this.add(PanelCanvas);
PanelCanvas.add(LabelOutput);
LabelOutput.setText("Test");
this.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
this.setFocusable(true);
}
}
Engine CLASS:
package ccarsimulator;
import java.awt.event.KeyEvent;
public class Engine extends Thread{
Thread thisSelf = this;
public boolean bStopEngine = false;
public Window oWindow;
public boolean []bKeyPressed = new boolean[KeyEvent.CHAR_UNDEFINED + 1];
public boolean []bKeyReleased = new boolean[KeyEvent.CHAR_UNDEFINED + 1];
public Engine(Window oWindow){
this.oWindow = oWindow;
thisSelf.start();
}
#Override
public void run(){
System.out.println("Waiting for window");
//while(!oWindow.isVisible() && !bStopEngine);
System.out.println("Engine is up and Running");
while(!bStopEngine){
//
//---------------------------------------
//Add some output to console to make this loop working...
//---------------------------------------
//
if(bKeyPressed[KeyEvent.VK_ESCAPE]){
System.out.println("Pressed Escape");
}
}
System.out.println("Engine was disabled");
}
}
Result without some output to console in Engine's class Thread infinite loop is
Waiting for window
Engine is up and Running
Key Pressed
Key Pressed
Key Pressed
Key Pressed
Closing a Window
Result with some output is:
Waiting for window
Engine is up and Running
Some debug
Some debug
...
...
Some debug
Closing a Window
Some debug
Some debug
Engine was disabled
Alright, My Java Guru friend helped me to figure this all out.
And also answered my question why this got disliked in few seconds as I published this Question what I still find unfair because I couldn't describe it much clear, If I did I would find answer on my problem without asking.
Problem was that I had to add volatile keyword on variables these were handled by Two threads (My Engine thread and Swing thread).
Without keyword volatile I was experiencing that my Swing thread was changing these two Engine thread's (bStopEngine and bKeyPressed) variables in RAM memory while these two Engine thread's variables were in CPU Cache memory so Engine thread could not know something changed because these two variables were not changed at right place.
Also helped me understand why my code works with only setting one of these two variables with volatile. It is because Java should by default load about 4KB block of memory page into CPU memory. So if by any luck my volatile variable gets into this page with another used variable by Swing Thread without using volatile keyword then that page is still set on thread synchronization and still synchronizing my variables with Swing thread so that is why it still works.
Just telling to help other people in future to rid off their confusions with similar problem or for myself in future when I once again forget all of this ;)
I was just writing an application and i was not able to set location and size of button to desired values. whenever i open code the button comes at same location and with same size
Here is my code
public class main_class {
public static void main(String args[]){
Main_page mp = new Main_page();
mp.start();
}
}
Second file is:
import javax.swing.*;
import java.awt.*;
public class Main_page extends JPanel {
private static final long serialVersionUID = 1L;
public void start(){
JPanel panel1 = new JPanel();
panel1.setBackground(Color.pink);
JButton Button1 = new JButton("Programmer");
Button1.setSize(10, 100);
Button1.setLocation(200,500);
panel1.add(Button1);
JFrame frame1 = new JFrame("Main Window");
frame1.setSize(700,500);
frame1.setContentPane(panel1);
frame1.setResizable(false);
frame1.setVisible(true);
}
}
what is the problem.
What is the problem?
I'm so glad you asked that question. It's not just a matter of getting a Swing application to work. You must get the Swing application to work correctly so that you can add more functionality to your Swing application without your Swing application breaking.
I added a call to SwingUtilities invokeLater in the main method. This call ensures that the Swing components are defined and used on the Event Dispatch thread (EDT).
I changed the name of your class to MainPage to conform to Java standards for naming classes.
I implemented Runnable to make the invokeLater method parameter easier to define.
I removed the extends of JPanel. You use Swing components. The only reason to extend a Swing component is when you want to override one of the component methods. The only reason you extend any Java class is when you want to override one of the class methods.
I changed the name of your MainPage method to run.
I set a layout (FlowLayout) for your JPanel. You must always use a layout manager for your Swing components.
I made button1 lowercase. Java field names are lowercase, so you can easily differentiate them from class names.
I added a call to the JFrame setDefaultCloseOperation to your run method. Without this call, your Swing application will not stop executing when you close the window. After a while, you'll have dozens of copies of your Swing application running, with no easy way to stop them.
I added a call to the JFrame pack to let the JFrame expand or contract to fit the components, rather than be a fixed size.
Here's the revised code. I put the main method in the MainPage class to make it easier to paste the code.
package com.ggl.testing;
import java.awt.Color;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class MainPage implements Runnable {
#Override
public void run() {
JPanel panel1 = new JPanel();
panel1.setBackground(Color.pink);
panel1.setLayout(new FlowLayout());
JButton button1 = new JButton("Programmer");
panel1.add(button1);
JFrame frame1 = new JFrame("Main Window");
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame1.add(panel1);
frame1.pack();
frame1.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new MainPage());
}
}
if you want to set the size and location manually then you can use panel1.setLayout(null); as people have said in the comments this isn't recommended.
I'm trying to make an application that runs an animation. To do this I've got a Jframe that contains my subclass of Jpanel in which the animation runs. Here are my two classes:
Firstly, here's my driver class:
import javax.swing.*;
public class Life {
public static void main(String[] args){
JFrame game = new JFrame("Life");
game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.setSize(500, 500);
MainPanel mainPanel = new MainPanel();
game.setContentPane(mainPanel);
game.setVisible(true);
}
}
Secondly, here's my subclass of Jpanel:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainPanel extends JPanel implements ActionListener{
int i = 0;
int j = 0;
public MainPanel(){
super();
}
public void paintComponent(Graphics g){
j++;
g.drawLine(10,10, 20 + i, 20 + i);
Timer t = new Timer(1000, this);
t.start();
}
#Override
public void actionPerformed(ActionEvent actionEvent) {
i++;
repaint();
}
}
Notice that the variable i is incremented each time actionPreformed is called and the variable j is called each time paintComponent is called. What winds up happening is that i starts to be much larger than j and the line drawn by paintComponent seems to grow at a faster and faster rate.
Here are my questions:
Why does this happen?
How can I sync things up so that the line gets redrawn each every 1000 ms?
Given what I'm trying to do, is my approach wrong? Should I be doing things differently?
Thanks in advance.
Don't start a Swing Timer from within a paintComponent method.
This method should do your painting, only your painting and nothing but painting. It should contain absolutely no program logic. Understand that you have very limited control over this method since you cannot predict when or if it will be called, how often it will be called. You can't even call it yourself or be guaranteed that when you suggest it be called via repaint() that it will in fact be called.
Also this method must be fast, as fast possible since anything that slows it down, be it object creation or reading in files will reduce the perceived responsiveness of your GUI, the last thing that you want to see happen.
The solution is to separate the program logic out of that method and into better methods such as your constructor. Repeating code should be in a Swing Timer.
Edit:
You state:
I just did that to test things out. One more question: What happens if paintComponent, or some thread on which the work in paintComponent depends, takes longer than 1000 ms (or whatever it is) to do its work? The only thing I can think of is having paintComponent paint the progress of the animation so far, rather than waiting for the animation to reach the next step(if that makes any sense). Thoughts?
You should never have code in paintComponent that takes that long or even 10's of milliseconds. If there's a risk of something like that happening, then do the drawing in a background thread and to a BufferedImage, and then on the Swing event thread show the BufferedImage in paintComponent method using the Graphics#drawImage(...) method.
A few minor additions to #HFoE's essential insights:
A public start() method is a handy way to ensure that your view is completely constructed before starting.
Fields have well-defined default values, and they should be private.
Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
Override getPreferredSize() and pack() the enclosing Window.
Revised code:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Life {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
private final JTabbedPane jtp = new JTabbedPane();
#Override
public void run() {
JFrame game = new JFrame("Life");
game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MainPanel mainPanel = new MainPanel();
game.setContentPane(mainPanel);
game.pack();
game.setVisible(true);
mainPanel.start();
}
});
}
private static class MainPanel extends JPanel implements ActionListener {
private Timer t = new Timer(100, this);
private int i;
private int j;
#Override
public void paintComponent(Graphics g) {
g.drawLine(10, 10, 20 + i, 20 + i);
}
public void start() {
t.start();
}
#Override
public void actionPerformed(ActionEvent actionEvent) {
i++;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
}
}
A Timer by defaults keep on running. Only when you call setRepeats( false ) it will stop.
So the following lines
Timer t = new Timer(1000, this);
t.start();
in your paintComponent method means that after a few repaints you will have a number of Timer instances running, explaining why i increases that much faster then j.
The solution is of course to move your Timer outside the paintComponent method, and stick to one Timer instance.
Further remarks (which weren't said by the others, not gonna repeat their very useful advise):
Never override the paintComponent method without calling the super method
You shouldn't expose the ActionListener interface. Just use an ActionListener internally
I'm trying to make a simple 2D game in Java.
So far I have a JFrame, with a menubar, and a class which extends JPanel and overrides it's paint method. Now, I need to get a game loop going, where I will update the position of images and so on. However, I'm stuck at how best to achieve this. Should I use multi-threading, because surely, if you put an infinite loop on the main thread, the UI (and thus my menu bar) will freeze up?
Here's my code so far:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class GameCanvas extends JPanel {
public void paint(Graphics g) {
while (true) {
g.setColor(Color.DARK_GRAY);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
#SuppressWarnings("serial")
public class Main extends JFrame {
GameCanvas canvas = new GameCanvas();
final int FRAME_HEIGHT = 400;
final int FRAME_WIDTH = 400;
public static void main(String args[]) {
new Main();
}
public Main() {
super("Game");
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem startMenuItem = new JMenuItem("Pause");
menuBar.add(fileMenu);
fileMenu.add(startMenuItem);
super.add(canvas);
super.setVisible(true);
super.setSize(FRAME_WIDTH, FRAME_WIDTH);
super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
super.setJMenuBar(menuBar);
}
}
Any pointers or tips? Where should I put my loop? In my main class, or my GameCanvas class?
Your game loop (model) should not be anywhere near any of your GUI classes (view). It uses your GUI classes--but even that you probably want to do through an intermediary (controller). A good way to ensure that you are doing it right is to check that your model doesn't have a single "include javax.swing.???".
The best thing you could do is to keep the game loop in it's own thread. Whenever you want to make a change in the GUI, use SwingWorker or whatever the young kids use now to force it onto the GUI thread for just that one operation.
This is actually awesome because it makes you think in terms of GUI Operations (which would constitute your controller). For instance, you might have a class called "Move" that would have the GUI logic behind a move. Your game loop might instantiate a "Move" with the right values (item to move, final location) and pass it to a GUI loop for processing.
Once you get to that point, you realize that simply adding a trivial "undo" for each GUI operation allows you to easily undo any number of operations. You will also find it easier to replace your Swing GUI with a web GUI...
You need one thread for you game loop and one thread to handle Swing UI events like mouse clicks and keypresses.
When you use the Swing API, you automatically get an additional thread for your UI called the event dispatch thread. Your callbacks are executed on this thread, not the main thread.
Your main thread is fine for your game loop if you want the game to start automatically when the programs runs. If you want to start and stop the game with a Swing GUI, then have then main thread start a GUI, then the GUI can create a new thread for the game loop when the user wants to start the game.
No, your menu bar will not freeze up if you put your game loop in the main thread. Your menu bar will freeze up if your Swing callbacks take a long time to finish.
Data that is shared between the threads will need to be protected with locks.
I suggest you factor your Swing code into its own class and only put your game loop inside your main class. If you're using the main thread for your game loop, this is a rough idea of how you could design it.
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
class GUI extends JFrame {
GameCanvas canvas = new GameCanvas();
final int FRAME_HEIGHT = 400;
final int FRAME_WIDTH = 400;
public GUI() {
// build and display your GUI
super("Game");
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem startMenuItem = new JMenuItem("Pause");
menuBar.add(fileMenu);
fileMenu.add(startMenuItem);
super.add(canvas);
super.setVisible(true);
super.setSize(FRAME_WIDTH, FRAME_WIDTH);
super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
super.setJMenuBar(menuBar);
}
}
public class Main {
public static void main(String args[]) {
GUI ui = new GUI(); // create and display GUI
gameLoop(); // start the game loop
}
static void gameLoop() {
// game loop
}
}
Java is really suited for event-driven programming. Basically, set up a timer event and listen. At each 'tick-tock' you update your game logic. At each GUI-event you update your data structures that the game logic method will read.