Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.java.gui > #1279

Re: Requesting tips, comm

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

Show all headers | View raw


  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 | NextPrevious in thread | Next in thread | Find similar | Unroll thread


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