I'm probably doing this wrong, so please be nice.
I'm developing a Java game, and I'm at the stage of testing character movement / animation.
The "person" can move up down left and right on a grid.
The class the grid is drawn in is the gamePanel class.
The buttons are in the gameControlPanel class.
I have a button which spawns a person on the grid.
I then have a button to move the person up down left and right.
When the move up button is pressed, it calls the move up method from the person class.
(At the moment, I'm only testing one "person" at a time.)
In that method is the following code...
int move = 10;
while(move!=0)
{
setTopLeftPoint(new Point((int)getTopLeftPoint().getX(),
(int)getTopLeftPoint().getY() - 3));
try
{
Thread.sleep(300);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
move-=1;
}
The problem is that I can't seem to call the repaint method for the gamePanel class from within the Person class.
To get around this, I created a timer in the gamePanel class which repaints every 20ms.
When I press the up button after the person is spawned, the button remains pressed down until the cycles of the while loop have been completed, and then the circle representation of the person is displayed in the grid square above.
I will try to answer any questions regarding this.
repaint() does not immediately repaint the GUI. Rather, it posts a message to the AWT thread telling it to paint at the next convenient opportunity. When it gets the chance, it will repaint your app. However, if you do this in an event handler, then the AWT thread is already busy and you need to exit the handler to give control back to the AWT handler.
As a general rule of thumb, you don't want to do any long-running calculations on the AWT thread (including in event handlers) since these will stop the app from responding to other events until your calculations are done. This will often appear to the user as stuck buttons like you described. To get around this, use a SwingWorker which can do the calculations on a separate thread.
Finally, something to be aware (but not necessarily to change) is that timers and sleeps do not guarantee when they will awaken. Rather, they guarantee that they will not waken before the amount of time elapses, but can theoretically sleep indefinitely. Also, not all machines have 1 ms timer resolution. In particular, on many windows machines the timers only have 55 ms resolution, so a 20 ms timer may give weird results.
If you want to repaint at a certain interval, javax.swing.Timer is probably the class for you. In the specific case of repaint you can call it from a non-EDT thread, but you may get yourself into difficulty as you are now dealing with multiple threads.
I don't have much experience making games but having a loop to control all animation is a fundamental aspect of game programming. Most simple 2d games only have 1 loop to render most of its animation.
In my experience a good way to render a whole bunch of stuff is to have a collection of all the entities in your game in one place and just loop over this collection passing the Graphics object to each entity.
This will allow each entity to draw itself onto the graphics object. Although this is just one way to do it.
synchronized ( entities ) {
for ( Entity e : entities ) {
e.draw( g );
e.doAction();
}
}
Is the button press handled by the event-dispatch thread of your GUI?
If this is a case, then the repaint method on the GUI will not fire until the event dispatch thread ends (i.e. when the button is released and it breaks out of the loop). I had this problem recently, and the best solution I can suggest is to make the class with the movement algorithm threadable and fire off a thread when the keypress is detected. This allows the event-dispatch thread to finish and therefore allows the gui to repaint.
For more information on threading see Starting a thread.
Related
I have searched through the many questions saying wait until repaint has finished but I have never found an answer that I could really implement or understand!
I don't want the code to continue when the screen has been repainted!
I have a simple function:
public static void clearScreen()
{
panel.repaint();
}
I can tell you now that this works!
The only problem being that it does not wait to repaint so for example:
public static void blah()
{
drawSomething();
clearScreen();
drawSomething();
}
There is a good chance that both drawings would get clear from the screen. As you might be able to guess I do not want this!
I would just like to mention that all the separate functions work!
Requesting a repaint on a component will add a repaint event to the Event Dispatch Thread which is a separate background thread that will actually call paint() later.
Depending on what you are doing in drawSomething() may happen before or after the event dispatch thread has repainted. The best thing to do is to do all of your work on the event dispatch thread by doing your "draw something" inside a call to SwingUtilities.invokeLater. Which will run your code on the event dispatch thread. All processes on the event dispatch thread are executed in the order they are submitted (usually, some repaint requests are combined etc) so the logical order of calls are preserved.
Learn more about the Event Dispatch Thread in the Oracle Java Tutorial
Swing uses a passive painting system, that is, the screen is not updated on a regular bases, but is painted as is required (or more precisely, when ever the RepaintManager thinks it should be).
You can make requests that UI be updated by calling repaint, buts it's up to the RepaintManager to decide what and when that should occur. The RepaintManager will place a repaint event on the event queue which will be processed by the Event Dispatching Thread at some time in the future.
What this basically means, is that is generally not possible to know when a paint may occur (as painting may happen for all sorts of reasons).
Take a look at Painting in AWT and Swing for more details
You could...
Employee your own buffer, onto which you paint when ever you want, but is the painted to the screen independently. You can use a BufferedImage for this. The problem with this is you're going to have to monitor the changes to the container to ensure that the buffer is the right size
You could...
Implement your own queue, which you add "commands" to, which is then processed by your paint method
The problem with this is ensuring that you lock or copy the queue before processing it
jf is a JFrame I'm trying to move a pixel in every 200 milliseconds. I created this method so I ciould pause the software for 200 milliseconds before cntinuing. millis and millisn are static longs.
public static void waitsec() {
millis =System.currentTimeMillis();
millisn =System.currentTimeMillis();
while (millisn<(millis+200)){
millisn=System.currentTimeMillis();
}
}
the following part is the part where I'm trying to make my JPanel (jp) to move slowly 50 pixels when a button is being pressed:
public void actionPerformed(ActionEvent h){
if (h.getSource() == button){
for (int i = 0; i <50; ++i){
waitsec();
out.println ("" + i);//I'm checkinf if the waitsec() is working OK
x +=1;
jp.setLocation(x, 0);
totalGUI.repaint();
jp.setVisible(true);//setting visible so I could focus on it
jp.requestFocusInWindow (); //suspicious part
jp.requestFocus ();
}}
}
The results of running this program are:
The numbers appear in the console one after another with 200 mmillis between them as expected.
The JPanel is not moving all the way once the numbers stop appearing (the program is done running). and if I try to minimize and maximize the window it doesn't show up till the program is done running as if the program has no focus at all...
why doesn't it focus although I had set it visible and focused it?
A number of things...
Firstly, you are blocking the Event Dispatching Thread, preventing it from processing any new events, including repaint events. This will make your application appear as it has hung, because essentially, it has.
Secondly, instead of rolling your waitSec method, you should be taking advantage of the available API functionality, for example, instead of trying to loop until a time period has passed, which while consume CPU cycles, you should use Thread.sleep instead.
Having said that though, you should NEVER sleep within the context of the EDT.
Instead, you should use something like a javax.swing.Timer which can be configured to raise an event on a regular bases. The benefit of this is it raises the event within the context of the EDT, making it safe to update the UI from within (unlike making your own Thread for example)
Take a look at Concurrency in Swing and How to use Swing Timers for more details
Thirdly, JPanel is not focusable by default, so calling requestFocusInWindow is not likely to have any effect
Fourthly, by default, Swing makes use of layout managers, so you may actually be struggling against this as well
So I'm making a graphical card game. Each card is a JPanel with a button and two images associated with it. I have a flip method, which is the first thing I call in my action listener when a card is clicked on.
public void flip()
{
if(b1.getIcon() == card2) b1.setIcon(card1);
else b1.setIcon(card2);
revalidate();
repaint();
}
However, for some reason the card doesnt flip (meaning the icons don't change) until some point after I call this method. For example, when I put a Thread.sleep after I call flip, I would assume that my program would pause after flip is completed, but it doesnt! It sleeps with the images not yet switched, and only switches them some at point after the sleep time has ended.
This is causing some major problems when I have a human play an AI in this card game, because AI events are happening before cards flip on the screen, even though I am calling flip() before I do any AI code. Can anyone clue me in as to what is going on here?
Repaint is a request to Swing to redraw not a call to redraw. Under the covers repaint() is posting a repaint job onto a queue of events. Along with that repaint job things like mouse moves, mouse clicks, keyboard activites, etc are posted there too. The Swing thread comes by and periodically executes jobs from that queue to redraw the UI, deliver mouse and keyboard events to components, etc. In fact the swing thread delivers these events quite frequently, but it just depends on how much work your UI is doing on that Swing thread. When it's out delivering mouse clicks, keyboard events, and redraws it can't read from that queue. So if your code takes a long time to repsond to any event the ability for swing to respond timely to new events is reduced or all together blocked.
That's why if you execute a blocking service call over the network with the Swing event dispatch thread your UI stops drawing until that call returns. This is why its important to move long running jobs like network calls off the Swing Thread, and use SwingUtilities.invokeLater() when the call comes back so you can update the UI on the Event thread. (invokeLater() does this by posting a job onto the Swing event queue we talked about above).
This is also why it's a terrible idea to call sleep on the Swing event thread. If you put that thread to sleep it can't service events from the queue. And the repaint() you requested isn't going to be done while the Swing thread is sleeping.
First remove your sleep call. Second. Don't write code that depends on painting to finish before executing more logic. Repaint and revalidate are special calls, and Swing will combine requests to repaint/revalidate so it doesn't redraw 10 times in a row (wasting vital cpu time).
Swing can't redraw your panel until you let the thread that called you leave the method it first called you back on. The sooner the thread leaves that method the sooner your repaint() will happen.
You haven't explained why you want the repaint to happen immediately so I can't give you any advice on how to structure your code so you don't care about it. But I'm telling you, you shouldn't care that the repaint hasn't happened yet.
Totally agree with #chubbsondubs. I just wanted to add that if your goal is to have some event happen some time after flip is called, consider using a swing timers. As you are painfully aware, calling Sleep does not have the desired effect. But if instead, you
create a timer for say 1 second
create an action to be performed when the timer fires
start the timer
then you can create the delay that you want without blocking the event dispatch thread.
public void flip()
{
if(b1.getIcon() == card2) b1.setIcon(card1);
else b1.setIcon(card2);
revalidate();
repaint();
javax.swing.Timer t = new javax.swing.Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// do something interesting
}
});
t.repeats(false);
t.start();
}
Ive coded two Computers to play each other in Reversi, however when they play each other. The board only updates after the game has finished.
From some googling around I know its has something to do the AWT Event Thread, but I still have no idea how to force the JFrame to refresh.
My function works by changing the icons and then calling revalidate and repaint.
Any pointers would be wonderful.
If you start your AI game from an actionPerformed(), it is executed in EDT thread. You should move your logic (and your sleep()'s outside of EDT thread by starting a new Thread to allow Swing to repaint UI properly and post updates to UI as following:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myUI.update(); // repaint(), etc. according to changed states
}
});
Also consider use of javax.swing.SwingWorker, javax.swing.Timer and take a look at Concurrency in Swing.
Sounds like a threading issue - Event Thread is not being given a chance to execute. I would start with some sleep commands in the logic / action threads to give time for the UI to update.
Also, you could have an "updateUI" thread and run that on an invokeAndWait to force it to update the display. Call that after each move has been completed.
If you need to force the application to redraw, you should invoke the repaint() method on the component containing the game board. This should cause Swing to repaint the board.
I wanted to rewrite my simple game engine to run on Android and I am wondering how can I synchronize two running threads. Right now I have the following:
Runner is the main activity, entry point for this game;
CanvasView is just a canvas on which drawing is being made;
GameWorld is - as the name suggests - class which stores the current information about the state of the game. For now, lets just say that it also contains a Level.
GameLoop is a separate thread which is updating the game state;
CanvasThread is a separate thread, which is being run to draw the current Level on CanvasView.
As the level is just a simple array, CanvasThread just iterates through the array and draws it on screen. And I have few questions regarding this:
is there a possibility to run the onDraw() method of CanvasThread on demand? In current state of the engine it is just being relaunched when the execution of previous one is finished.
I want to implement some kind of three way handshake between GameLoop and CanvasThread, something similar to:
GameLoop -> CanvasThread: please stop updating
Canvas -> GameLoop: ok, stopped
GameLoop -> CanvasThread: ok, you may resume.
What is the best way to do so? I am a total Java / Android newbie so my way of setting the engine is most probably not the best / optimal one. If you have any suggestions to the design, I will gladly appreciate them.
Thank you.
PS: If I had violated all best practices while creating the diagram above, please forgive me.
Android has an easy way to handle thread communication. You can use a Looper and a Handler to send messages, or complete Runnables, between the Threads.
Look at Android Guts: Intro to Loopers and Handlers on mindtherobot.com for an introduction.
You can send an "empty" message to signal something, or supply some arguments. You can send the message immediately, or with some delay.
If you design your game loop by sending messages to itself, it would be easy to inject messages from other threads.
What I created long long time ago. Maybe it helps a little
http://code.google.com/p/candroidengine/
I believe you'll find it a lot easier if they don't communicate.
Instead, put a couple constraints on to keep yourself sane:
Make a very tight game loop. If you're going to be updating your canvas at, let's say, 30 fps (about 33 ms) make your game loop take no longer than 10 or 20 ms.
Put a lock on your game loop and canvas update. Now, each one will wait for the other to finish before doing their thing. You won't end up with a canvas that has the half the screen representing before the loop and the other half of the screen after the loop.
Example:
class GameLoop extends Thread {
public void run() {
while(true) {
synchronized(theGameWorldObject) {
// update game info here
}
}
}
}
class Canvas {
updateCanvas() { // or whatever you call it
synchronized(theGameWorldObject) {
// update canvas here
}
}
}