I am working on a personal project to experiment with 2D physics. Right now, I am trying to set up the JFrame so that it updates 60 times per second. However, when I call the method to start updating the JFrame, I cannot close the JFrame. But if I leave out the part where I call the method, I can close the JFrame. This is the main code:
import javax.swing.JFrame;
import utils.Frame;
public class Engine {
Frame w;
boolean running = false;
JFrame f;
public void start() {
init();
updater();
}
public void init() {
w = new Frame();
running = true;
f = w.create(500, 500, "Work please");
}
public void updater() {
int fps = 60;
double timePerTick = 1000000000 / fps;
double delta = 0;
long now;
long lastTime = System.nanoTime();
long timer = 0;
while (running) {
now = System.nanoTime();
delta += now - lastTime;
timer += now - lastTime;
lastTime = now;
if (delta >= timePerTick) {
//render
delta -= timePerTick;
}
}
}
}
And this is the code for Frame:
import javax.swing.JFrame;
public class Frame {
public JFrame create(int width, int height, String title) {
JFrame f = new JFrame();
f.setSize(width, height);
f.setTitle(title);
f.setLocationRelativeTo(null);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
return f;
}
}
If I don't call updater() in start(), I can close the JFrame. I know that I leave running = true, but it should still close because that would work on my old computer. And even if I need to make running = false, I already tried that by adding a WindowListener to f in Frame. It would call a method to make running = false, but for some reason the WindowListener wouldn't active when I hit the close button. Thank you for your help in advance. If this will help you in any way, I had to download a direct package of the Eclipse IDE from the website because every time I used the installer, every project would get the error "Failed to init ct.sym ..." And for some reason, when I run a Java program with a JFrame in it, instead of the usual Jar logo, the Java mascot "Duke" shows up as the icon, which has been throwing me off.
Make sure you have two methods that control whether the program is running.
However make sure both methods are synchronized by using the-
synchronized
-keyword.
public synchronized void start() {
if (running) return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop() {
if (!running) return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Related
I'm working on my idle game, and I'm starting to add in an adventure side to it, but it's not working. I had it to where it would flicker on and off the screen, but when I started working on it more, it started to not work.
Here is the threaded code:
thread = new Thread(this);
IH = new ImageHandler(this);
listener = new Listener(this);
removeEntity = new ArrayList<>();
addEntity = new HashMap<>();
frame = new JFrame("IDLE");
frame.add(this);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setResizable(false);
frame.setUndecorated(true);
frame.addWindowListener(listener);
frame.addMouseListener(listener);
frame.addMouseMotionListener(listener);
frame.addKeyListener(listener);
frame.pack();
Image bgBase = IH.getImg("background");
frame.setVisible(true);
WIDTH = frame.getWidth();
HEIGHT = frame.getHeight();
background = IH.getBG();
adv = new Adventures(this);
//Stuff here to load in the variables and entity data
start();
Now, here are my start and stops:
private synchronized void start() {
thread.start();
}
public synchronized void stop() {
save();
frame.setVisible(false);
try {
thread.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
System.exit(1);
}
And now my "run" class (because of the Thread):
public void run() {
long lastTime = System.nanoTime();
final double ns = 1000000000.0 / 60.0;//60 times per second
double delta = 0;
requestFocus();
while(frame.isVisible()) {
long now = System.nanoTime();
delta = delta + ((now-lastTime) / ns);
lastTime = now;
while (delta >= 1)//Make sure update is only happening 60 times a second
{
//handles all of the logic restricted time
update();
delta--;
}
this.repaint();//displays to the screen unrestricted time
}
}
If you need more code, please let me know!
Please make sure you actually implemented on of the interface methods the KeyListener addresses.
I see the declaration and binding in your code frame.addKeyListener(listener); but there should be something like a keyPressed(KeyEvent e)method.
So this is my code:
System.out.println("System Exiting...");
long current = System.currentTimeMillis();
long disired = current + 4000;
boolean done = false;
while (!done)
{
current = System.currentTimeMillis();
if (current == disired)
{
done = true;
System.exit(0);
}
}
My problem is that the print statement doesn't run, well it does run, but it runs at the same time as the exit statement, so you don't see it
[EDIT] Ok, so i just ran this code in its own file(with nothing else), and it works as i want, it prints "system Exiting..." it waits 4 seconds, and the code exits.
so it has to be something to do with the fact that i have this code inside an event listener
Your if condition is much too restrictive since your code will almost never get the times to be exactly equal, but the change needed is very simple:
Change
// hitting this exactly is like finding the proverbial needle
// in the haystack -- almost impossible to do.
if (current == disired)
to
// this is guaranteed to work.
// note if this is in English, you'll want to change disired to desired
if (current >= disired)
Having said this, your while (true) loop is not a good thing to do as it will needlessly tie up the CPU with empty cycles. Instead use some type of event notification or call-back system like ChangeListener or a PropertyChangeListener or a Timer.
You state:
yes it is within a swing GUI
You're calling a long while (true) block of code on the Swing event thread, rendering this thread ineffective. Since the event thread is responsible for all Swing graphics and user interactions, this effectively freezes your GUI until the while loop completes. The solution is obvious: 1) use a Swing Timer for your delay, not a while true loop (this is the callback mechanism that I mentioned in my original answer), and 2) in the future, please give us this important relevant information with the original question since it changes the entire nature of the question.
e.g.
// caveat: code not tested
System.out.println("System Exiting...");
int delay = 4 * 1000;
new Timer(delay, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
System.out. println("Exited");
System.exit(0);
}
}).start();
e.g.,
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestDelayedExit extends JPanel {
private static final int GAP = 100;
public TestDelayedExit() {
add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
}
private static void createAndShowGui() {
JFrame frame = new JFrame("TestDelayedExit");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new TestDelayedExit());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
private class DisposeAction extends AbstractAction {
private int count = 4;
private Timer timer;
public DisposeAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic); // for alt-key combo
}
#Override
public void actionPerformed(ActionEvent e) {
if (timer != null && timer.isRunning()) {
return;
}
final Component c = (Component) e.getSource();
int timerDelay = 1000;
putValue(NAME, String.valueOf(count));
timer = new Timer(timerDelay, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (count == 0) {
((Timer) e.getSource()).stop();
// this will not work for JMenuItems, and for that
// you would need to get the pop up window's parent component
Window win = SwingUtilities.getWindowAncestor(c);
if (win != null) {
win.dispose();
}
} else {
count--;
putValue(NAME, String.valueOf(count));
}
}
});
timer.start();
}
}
}
I am new to programming (I'm 11 and hoping for java coding to be my career, but its just a hobby right now :)) and I just made a countdown program, here is the class:
package me.NoahCagle.JAVA;
import javax.swing.JFrame;
public class Main extends JFrame implements Runnable {
private static final long serialVersionUID = 1L;
public static int width = 600;
public static int height = 500;
public static String title = "Countdown!";
public static boolean running = false;
public int number = 11;
public Thread thread;
Dimension size = new Dimension(width, height);
public Main() {
super(title);
setSize(size);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
Main m = new Main();
m.start();
}
public void start() {
if (running) {
return;
}
running = true;
Thread thread = new Thread(this);
thread.start();
}
#SuppressWarnings("static-access")
public void run() {
while (running) {
number--;
if (number == -1) {
System.out.println("Done!");
System.exit(0);
}
try {
thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
System.out.println("" + number);
}
}
public void stop() {
if (!running) {
return;
}
running = false;
try {
thread.join();
}catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
}
That may not have been necessary, but whatever. Well like I was saying, if you read the code, you will notice that it prints the value to the console. Well, if I could get that to display on a JLabel, while updating at the same time. I have tried just doing setText("" + number) thinking that because I have a thread going, it would repaint. But that didn't happen. It was just stuck at 11. Can someone please help me? Thanks
First, you may want to take a read through Concurrency in Swing. There are some very important constraints when it comes to dealing with multiple threads and Swing.
For your problem, you really should be using a javax.swing.Timer, and with examples...
Java Label Timer and Saving
Adding a timer and displaying label text
How could I add a simple delay in a Java Swing application?
As 11yrs old you have done good job here. But where did you add any panel to the frame on which u want to show the number? Once you do it and put some label to add the number you will need to call the repaint method. Also to use threads with swings, there are many libraries you can use like Timer.
Happy Coding!
I've been working on a simple game engine. All was going pretty well, and I got the foundation finished. By that, I mean, the game sets up with user defined display settings, initializes stuff, and starts the game loop. Everything works great, except I have an issue in both Windowed mode and full screen:
In Windowed mode, sometimes the screen will appear blank, until I resize or minimize the window.
In full screen, I only get a blank screen if I use the default Display resolution. Most of the other resolutions appear just fine.
Here is some code relevant to displaying the screen:
This is performed when the use presses the play button on the display settings dialog
public void actionPerformed(ActionEvent e) {
DisplayMode dm = modes[resolutionBox.getSelectedIndex()];
boolean fs = fullscreenBox.getSelectedItem().equals ("Fullscreen");
boolean vs = vSyncCheckBox.isSelected();
game.initScreen (dm, fs, vs);
runGame (game);
f.dispose();
}
});
private final void runGame(EGame g)
{
Thread t = new Thread (g);
t.start();
}
This is the run method in the EGame class
public final void run() {
start();
isRunning = true;
gameLoop();
endGame();
}
The user of the engine will call the method setGameCanvas() in the start() method. Other things can go in the start() method, but for my test games, it just that method:
protected final void setGameCanvas(EGameCanvas c)
{
screen.remove (canvas);
canvas = c;
canvas.addKeyListener (keyboard);
screen.add (canvas);
}
And finally, here is the gameLoop:
private final void gameLoop()
{
// Final setup before starting game
canvas.setBuffer (2);
screen.addKeyListener (keyboard);
canvas.addKeyListener (keyboard);
int TARGET_FPS = screen.getTargetFps();
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
long lastLoopTime = System.nanoTime();
long now = 0;
long lastFpsTime = 0;
long updateLength = 0;
double delta = 0;
int fps = 0;
while (isRunning)
{
now = System.nanoTime();
updateLength = now - lastLoopTime;
lastLoopTime = now;
delta = updateLength / ((double) OPTIMAL_TIME);
lastFpsTime += updateLength;
fps++;
if (lastFpsTime >= 1000000000)
{
screen.setFps (fps);
lastFpsTime = 0;
fps = 0;
}
keyboard.poll();
keyboardEvents();
update (delta);
render();
try {
Thread.sleep( (lastLoopTime-System.nanoTime() + OPTIMAL_TIME)/1000000 );
}
catch (Exception ex) {}
}
}
Now, keep in mind, neither of my issues existed, until I added the run method and ran it in a separate thread. I can about guarantee it's a threading issue.
But, I'm no expert, so any advice would be appreciated.
I believe the issue is that you're trying to modify GUI-related components outside of the event dispatch thread - that is the only thread allowed to modify the graphical interface, otherwise you might encounter issues like this.
To check this try if the following solves the issue:
try {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeAndWait( new Runnable() {
#Override
public void run() {
// Do rendering and other stuff here...
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
If that helps, I'd recommend taking a look on the Concurrency in Swing tutorial.
I have (what should be) a simple problem to tackle and I'm open to other ways to solve it. I am open to other solutions.
The problem:
We are using java swing to display the graphics of a turn-based, tile-based game. I'm using jlabels with icons, absolutely positioned.
To animate the movement, I am using a swing timer that updates the location by 4 pixels at a time, slowly moving the sprite right, left, etc.
To achieve this initially, I was running a timer, which works wonderfully. The problem comes in when I try to move down, then move right.
The sprite moves down, never moves right, and if I watch the execution with some console printing, it's clear to see that both timers are running at the same time. I've done a fair amount of digging on the internet and I wasn't able to find a way to tell a swing timer not to execute until the first timer has stopped, and if I try to busy-wait until one timer finishes (yuck) the UI never displays at all (clearly a step in the wrong direction.)
Now I can convert away from timers altogether and either have the sprite teleport to its new location, or use some awful busy-wait movement scheme, but I'm hoping some kind soul has a solution.
In short: I need a way to run a swing timer for a set period of time, stop it, and then start a new timer, so that they do not overlap. Preferably this method would allow each timer to be in its own method, and I could then call the methods one after the other.
Thanks in advance for any advice you might have.
Edit: Expanded example code. If a full scsse is a requirement for your advice then I'm sorry to have wasted your time, because the full code is a beast. This sample code does not work at all as it stands, sorry, but it should illustrate the point.
So. We have two functions, each with a timer that runs an animation cycle, one for moving down and right diagonally, one for moving straight down.
public class TestClass {
static int counter = 0;
static int counter2 = 0;
static Timer timerC;
static Timer timerX;
public static void main(String[] args) {
moveC();
moveX();
}
public static void moveC() {
int delay = 200; // milliseconds
timerC = new Timer(delay, null);
timerC.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (counter < 32) {
counter = counter + 4;
System.out.println("*C*");
} else {
timerC.stop();
System.out.println("*C STOP*");
}
}
});
timerC.start();
}
public static void moveX() {
int delay = 200; // milliseconds
timerX = new Timer(delay, null);
timerX.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (counter < 32) {
counter = counter + 4;
System.out.println("*X*");
} else {
timerX.stop();
System.out.println("*X STOP*");
}
}
});
timerX.start();
}
}
What I would want to see here eventually would be
*C*
*C*
*C*
*C*
*C STOP*
*X*
*X*
*X*
*X*
*X STOP*
What I actually get is
*C*
*X*
*C*
*X*
*C*
*X*
*C*
*X*
*C STOP*
*X STOP*
The point I'm trying to get at here is running one animation cycle to completion, then the other.
Thanks again.
Don't use multiple Timers, but rather only one Timer that deals with each direction as it's needed. You need some type of queue to hold the direction information, either a formal queue or a collection that you use as a queue (first in, first out), and then have your Timer extract the direction from this queue as it's running. For example, here I use my JList's model as my queue by removing and using the Direction that was added first (at the top of the JList):
import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;
public class TimerPlay extends JPanel {
private DefaultListModel directionJListModel = new DefaultListModel();
private JList directionJList = new JList(directionJListModel);
JButton startTimerButton = new JButton(
new StartTimerBtnAction("Start Timer"));
public TimerPlay() {
ActionListener directionBtnListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actEvt) {
String actionCommand = actEvt.getActionCommand();
Direction dir = Direction.valueOf(actionCommand);
if (dir != null) {
directionJListModel.addElement(dir);
}
}
};
JPanel directionBtnPanel = new JPanel(new GridLayout(0, 1, 0, 10));
for (Direction dir : Direction.values()) {
JButton dirBtn = new JButton(dir.toString());
dirBtn.addActionListener(directionBtnListener);
directionBtnPanel.add(dirBtn);
}
add(directionBtnPanel);
add(new JScrollPane(directionJList));
add(startTimerButton);
}
private class StartTimerBtnAction extends AbstractAction {
protected static final int MAX_COUNT = 20;
public StartTimerBtnAction(String title) {
super(title);
}
#Override
public void actionPerformed(ActionEvent arg0) {
startTimerButton.setEnabled(false);
int delay = 100;
new Timer(delay, new ActionListener() {
private int count = 0;
private Direction dir = null;
#Override
public void actionPerformed(ActionEvent e) {
if (count == MAX_COUNT) {
count = 0; // restart
return;
} else if (count == 0) {
if (directionJListModel.size() == 0) {
((Timer)e.getSource()).stop();
startTimerButton.setEnabled(true);
return;
}
// extract from "queue"
dir = (Direction) directionJListModel.remove(0);
}
System.out.println(dir); // do movement here
count++;
}
}).start();
}
}
private static void createAndShowGui() {
TimerPlay mainPanel = new TimerPlay();
JFrame frame = new JFrame("TimerPlay");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum Direction {
UP, DOWN, LEFT, RIGHT;
}
For reference, this example manages four instances of Timer, two of which run (interleaved) while hovering in any corner. You might compare it to your approach. This related answer discusses animation in a similar tile-based game.
put all Icons in some form of array
create a single Swing Timer with a short delay
in Swing ActionListener, take each `Icon from the array, getBounds from screen, move Icon one step
repeat until target reached.