Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.java.gui > #1279
| From | "Oliver Wong" <oliver.wong@THRWHITE.remove-dii-this> |
|---|---|
| Subject | Re: Requesting tips, comm |
| Message-ID | <SUDMh.3449$NM.43499@wagner.videotron.net> (permalink) |
| Newsgroups | comp.lang.java.gui |
| References | <44AMh.104676$BK1.87802@newsfe13.lga> |
| Date | 2011-04-27 15:32 +0000 |
| Organization | TDS.net |
To: comp.lang.java.gui
"Knute Johnson" <nospam@rabbitbrush.frazmtn.com> wrote in message
news:44AMh.104676$BK1.87802@newsfe13.lga...
>
> For active rendering I would take your rendering out of the EDT
> altogether. The problem that you are going to run into is that
> eventually too much will be happening on the EDT and you won't be able
> to do any user input processing or that it will delay your rendering.
This key piece of information is pretty central to my
(mis?)understanding. I thought it's written that whenever you touch Swing
components, you should do so from within the EDT. If that's the case,
shouldn't you perform painting of JFrames and other widgets within the
EDT?
> You only need to create the BufferStrategy once not every time in the
> rendering loop.
Right: in the code I posted, I actually create the BufferStrategy
once, but call getBufferStrategy() repeatedly from within the loop (as
opposed to, in your code, calling getBufferStrategy once and storing its
return value somewhere). From peering into the source code, it looks like
getBufferStrategy() is just a plain getter anyway.
> I like to use java.util.Timer to drive the loop but you can use
> Thread.sleep().
The Timer class seems to have its own internal "task queue". From the
javadocs:
http://java.sun.com/javase/6/docs/api/java/util/Timer.html
<quote>
Timer tasks should complete quickly. If a timer task takes excessive time
to complete, it "hogs" the timer's task execution thread. This can, in
turn, delay the execution of subsequent tasks, which may "bunch up" and
execute in rapid succession when (and if) the offending task finally
completes.
</quote>
I haven't spent too much time analyzing the impact of this (presumably
there will only be one if I use Timer.schedule() at multiple locations in
my code), but it sounds like I can avoid worrying about it entirely with a
tight loop and Thread.sleep(1).
I realize that the javadocs themselves claim that your design is
appropriate for animations (It says "Fixed-delay execution is appropriate
for recurring activities that require 'smoothness.' In other words, it is
appropriate for activities where it is more important to keep the
frequency accurate in the short run than in the long run. This includes
most animation tasks"), but this fixed-delay behaviour sounds like it's
incompatible with the way *I* want rendering to work in my engine.
It's not shown in my code, but in a "real" engine, I usually calculate
the amount of time that has passed between each frame of animation (I've
got this library which tries to use native C code on platforms it
recognizes, and falls back on System.currentTimeMillis() on those it
doesn't), and then I use that elapsed time to calculate whether to skip
ahead a couple of frames (on computers that are a bit slow for this game)
or to interpolate between two frames of animations (on computers that are
a bit fast for this game).
For example, I might have a scripting language which says "It should
take the character 2 seconds to walk from point A to point B", and the
engine will smoothly animate a sprite moving from point A to point B at
the highest frame rate that the computer can handle.
Long story short, the "Fixed-delay execution" system seems like it'll
work counter to my goal there.
> As Daniel said I would do my user input event driven.
Really? I find a polling-based input system much more natural to
program for, in the context of making a game. For example, if the player
presses the "Right" cursor key, I want Sonic the Hedgehog to start running
towards the right. And if in the next frame, he's still holding down the
"right" cursor I want Sonic to keep running (otherwise, he should start to
brake, and slow down). And the longer the button is held down, the more
Sonic accelerates (up to a certain terminal velocity).
With a polling system, the logic would look something like:
{
if (rightButtonIsDown()) {
if (sonic.xVel < MAX_XVEL) {
sonic.xVel += 1.0;
}
} else if (leftButtonIsDown()) {
if (sonic.xVel > -MAX_XVEL) {
sonic.xVel -= 1.0;
}
} else {
sonic.xVel /= 2; /*Quickly approaches zero.*/
}
}
The only way I could think of implementing this with Swing's event
model would be to set a flag when a keypress occurs, and then unset a flag
when the key is released... which is pretty much emulating a polling
system ontop of Swing's event system... which is pretty much what the code
I posted does.
> You can use a Frame or JFrame but you have to deal with the insets and
> title bar your self. If you use an undecorated frame you might as well
> use a window.
Right, the insets are a bit of a pain, and I sometimes wish JFrame had
a setSizeWithoutInsets() method in addition to its standard setSize()
method. But I *do* want a decorate frame (at least when the game is not
running in full screen mode), so that the player can move the window
around and resize it. Of course, if the player switches the game to
fullscreen mode, then at that point, the window should be undecorated.
>
> Below is how I would do it. See if you like any of it.
[informative code snipped]
Thanks. You introduced me to the WindowAdapter class, and it looks
like I'll have to re-read the javadocs for BufferStrategy (since it's not
clear to me what you're trying to do with your calls to contentsLost() and
contentsRestored()) It looks like it has something to do with losing image
data due to the use of VolatileImage, but I'm wondering whether it's
necessary for my game: In most of my games, so much is going on screen at
any one point that I typically assume that the whole image is always lost
and redraw the whole frame from scratch. The game is implemented as a
state machine (not illustrated in the simplified version of the code I've
been posting), and any state which needs access to previous frame contents
(e.g. for a motion-blurring effect) needs to implement storing previous
image data itself.
Here's yet another iteration of my design, taking into account your
advice of pulling the active-drawing code out of the EDT. I've had to
reintroduce the synchronize() statements, because otherwise it's possible
that the user might close the window (and thus invalidate the graphics
object) while I'm still using the graphics object to render the next
frame. I've added a Thread.sleep(1000) statement to increase the
likelihood of this race condition occurring for illustrative purposes.
I've also used your WindowAdapter (and KeyAdapter) trick.
<SSCCE>
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferStrategy;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
public class Test {
private static final int DEFAULT_RENDERING_WIDTH = 640;
private static final int DEFAULT_RENDERING_HEIGHT = 480;
private static JFrame mainWindow;
/**
* Returns true if the game is in the process of shutting down and
quitting.
* If it's in the middle of a game-loop iteration, it'll finish that
* iteration (potentially drawing 1 more frame of animation, and then
quit.
*/
private static boolean timeToQuit = false;
/**
* True if the player pressed the "action" key since the last iteration
* through the game loop, false otherwise. In a real game, I'd probably
have
* many of these booleans (e.g. an "up" key, a "down" key, etc., and move
it
* into a seperate class for organization purposes.
*/
private static boolean pressedActionKey = false;
public static void main(String[] args) throws InterruptedException,
InvocationTargetException {
/*
* This first invokeAndWait initializes all the GUI components, and
* registers the appropriate listeners to hook into the main game
* engine.
*/
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run() {
mainWindow = new JFrame("My game");
mainWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
mainWindow.setIgnoreRepaint(true);
mainWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
timeToQuit = true;
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
synchronized (mainWindow) {
mainWindow.dispose();
mainWindow = null;
}
}
});
}
});
mainWindow.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
pressedActionKey = true;
}
}
});
mainWindow.setPreferredSize(new Dimension(
DEFAULT_RENDERING_WIDTH, DEFAULT_RENDERING_HEIGHT));
mainWindow.pack();
mainWindow.setVisible(true);
mainWindow.createBufferStrategy(2);
}
});
/*
* This while loop is the main game loop. It basically iterates through
* 3 stages forever: getting the player input, reacting to it, and
* drawing the results on screen.
*/
while (!timeToQuit) {
getPlayerInput();
processGameLogic();
synchronized (mainWindow) {
if (mainWindow != null) {
Thread.sleep(1000);
BufferStrategy strategy = mainWindow.getBufferStrategy();
Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
Insets insets = mainWindow.getInsets();
g.translate(insets.left, insets.top);
g.setTransform(AffineTransform.getScaleInstance(
(double) (mainWindow.getWidth() - insets.right)
/ (double) DEFAULT_RENDERING_WIDTH,
(double) (mainWindow.getHeight() - insets.bottom)
/ (double) DEFAULT_RENDERING_HEIGHT));
updateScreen(g);
g.dispose();
strategy.show();
}
}
Thread.sleep(1);
}
}
public static void processGameLogic() {
/*
* Check if pacman touched a ghost, stuff like that. Doesn't touch any
* Swing components.
*/
}
public static void getPlayerInput() {
/*
* Doesn't touch any Swing components.
*/
if (pressedActionKey) {
/* make mario jump */
System.out.println("Mario jumps.");
pressedActionKey = false;
}
}
public static void updateScreen(Graphics2D g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, 640, 480);
/*
* TODO: Draw all sorts of amazing eye-candy.
*/
}
}
</SSCCE>
- Oliver
---
* Synchronet * The Whitehouse BBS --- whitehouse.hulds.com --- check it out free usenet!
--- Synchronet 3.15a-Win32 NewsLink 1.92
Time Warp of the Future BBS - telnet://time.synchro.net:24
Back to comp.lang.java.gui | Previous | Next — Previous in thread | Next in thread | Find similar | Unroll thread
Requesting tips, comments "Oliver Wong" <oliver.wong@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Knute Johnson" <knute.johnson@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Daniel Pitts" <daniel.pitts@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Oliver Wong" <oliver.wong@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Oliver Wong" <oliver.wong@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Knute Johnson" <knute.johnson@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Oliver Wong" <oliver.wong@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Knute Johnson" <knute.johnson@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
Re: Requesting tips, comm "Oliver Wong" <oliver.wong@THRWHITE.remove-dii-this> - 2011-04-27 15:32 +0000
csiph-web