One last (?) bug in my game library.

Hi!

After reading trough the documentation for the MIDP game library in J2ME I decided to make a clone of that library that could be used with J2SE (i.e. for game programming on the PC).

I’ve created the basic GameCanvas class (see below) but there seems to be a problem with the call to createBufferStrategy(2) in the constructor. If you compile and run the code you’ll get a IllegalStateException saying “Component must have a valid peer”. If I try to fix this by calling addNotify() which is supposed to add a peer I get a NullPointerException saying “peer” instead.

/*
 * Created on Apr 26, 2003
 */
package se.bostream.gbg.game;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;

/**
 * The GameCanvas class provides the basis for a game user interface.
 * 
 * @author Johan Tibell
 */
public class GameCanvas extends Canvas
{
      /** The bit representing the DOWN key. */
      public static final int DOWN_PRESSED = KeyEvent.VK_DOWN;
      /** The bit representing the LEFT key. */
      public static final int LEFT_PRESSED = KeyEvent.VK_LEFT;
      /** The bit representing the RIGHT key. */
      public static final int RIGHT_PRESSED = KeyEvent.VK_RIGHT;
      /** The bit representing the UP key. */
      public static final int UP_PRESSED = KeyEvent.VK_UP;
      /** The bit representing the SPACE key. */
      public static final int SPACE_PRESSED = KeyEvent.VK_SPACE;
      /** The bit representing the ESCAPE key. */
      public static final int ESCAPE_PRESSED = KeyEvent.VK_ESCAPE;
      /** The bit representing the ENTER key. */
      public static final int ENTER_PRESSED = KeyEvent.VK_ENTER;
      
      private BufferStrategy strategy;
      private int keyStates = 0;
      
      /**
       * Creates a new instance of a GameCanvas.
       * 
       * @param suppressKeyEvents true to suppress the regular key event mechanism for game keys, otherwise false
       */
      public GameCanvas(boolean suppressKeyEvents)
      {
            super();
            
            if (!suppressKeyEvents)
                  addKeyListener(new KeyStateListener(this));
            
            // TODO: Fix NullPointerException bug.
            //addNotify();
            createBufferStrategy(2);
            strategy = getBufferStrategy();
      }
      
      /**
       * Flushes the off-screen buffer to the display.
       */
      public void flushGraphics()
      {
            // If the buffer is intact draw it on the screen.
            if (!strategy.contentsLost())
                  strategy.show();
      }

      /**
       * Flushes the specified region of the off-screen buffer to the display.
       * 
       * @param x the left edge of the region to be flushed
       * @param y the top edge of the region to be flushed
       * @param width the width of the region to be flushed
       * @param height the height of the region to be flushed
       */
      public void flushGraphics(int x, int y, int width, int height)
      {
            throw new UnsupportedOperationException();
      }
      
      /**
       * Gets the states of the physical game keys.
       * 
       * @return An integer containing the key state information (one bit per key), or 0 if the GameCanvas is not currently shown
       */
      public int getKeyStates()
      {
            int currentKeyStates;
            
            if (this.isVisible())
                  currentKeyStates = keyStates;
            else
                  currentKeyStates = 0;
            
            keyStates = 0;      
            return currentKeyStates;
      }
      
      /**
       * Obtains the Graphics object for rendering a GameCanvas.
       * 
       * @return the Graphics object that renders to this GameCanvas' off-screen buffer 
       */
      public Graphics getGraphics()
      {
            return strategy.getDrawGraphics();
      }
      
      /**
       * Paints this GameCanvas.
       * 
       * @param g the Graphics object with which to render the screen
       */
      public void paint(Graphics g)
      {
            // TODO: Perhaps implement in a different way.
            super.paint(g);
            //flushGraphics();
      }
      
      private class KeyStateListener implements KeyListener
      {
            private GameCanvas receiver;
            
            public KeyStateListener(GameCanvas receiver)
            {
                  this.receiver = receiver;
            }
            
            public void keyTyped(KeyEvent e)
            {
                  receiver.keyStates |= e.getKeyCode();
            }

            public void keyPressed(KeyEvent e)
            {
                  receiver.keyStates |= e.getKeyCode();
            }

            public void keyReleased(KeyEvent e)
            {
                  receiver.keyStates |= e.getKeyCode();
            }
      }
}

createBufferStrategy only works on Components that are visible.

hence,
if called on a Frame, the Frame must first be visible.
if called on a Canvas, the Canvas must have been added to a visible Container.

p.s.

i’d recommend have a GameFrame class that extended Frame, rather than a GameCanvas that extends Canvas.

p.p.s

the way you are attempting to do your key handling will not work.

[quote]createBufferStrategy only works on Components that are visible.

hence,
if called on a Frame, the Frame must first be visible.
if called on a Canvas, the Canvas must have been added to a visible Container.

p.s.

i’d recommend have a GameFrame class that extended Frame, rather than a GameCanvas that extends Canvas.
[/quote]
Why? I’ve done that and I run into some issues when drawing on the graphics return by the buffer strategy causes drawing under the frame’s title bar at the top. Thus I’m forced to work with and offset all the time.

Why?

Dunno why you should use a Canvas rather than a Frame :smiley:

Does it work properly fullscreen?

I have to confess, I havn’t actually tried it.

I’ll have a go, and get back to you. You never know, it may be a workaround for the bugs relating to fullscreen<->windowed mode switching :stuck_out_tongue:

as for the key input…

receiver.keyStates |= e.getKeyCode();

e.getKeyCode() returns the ascii value of the key, not a bitwise mask for the key.

therefor, OR’ing it with keyStates will got give the behaviour you intend.

[quote]Dunno why you should use a Canvas rather than a Frame :smiley:

Does it work properly fullscreen?

I have to confess, I havn’t actually tried it.

I’ll have a go, and get back to you. You never know, it may be a workaround for the bugs relating to fullscreen<->windowed mode switching :stuck_out_tongue:
[/quote]
Perhaps I should have said that the engine isn’t meant for fullscreen… But perhaps it works for fullscreen if you just add it to a frame, haven’t tried it either.

Stupid of me. I should have looked up what the return value from getKeyCode() actually was… Guess I have to set up a translation table if I want my method. Or do some shifting magic to get an unique value…

  • Johan

I tried something slightly different but it failed as well. The startup code looks like this now:

public static void main(String[] args)
{
      JFrame mainFrame = new JFrame("GameCanvas Test");
      mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            
            
      mainFrame.setSize(640, 480);
      mainFrame.setVisible(true);

      mainFrame.getContentPane().add(new GameCanvas(true));
}

And the GameCanvas constructor looks like this:

public GameCanvas(boolean suppressKeyEvents)
{
      super();
            
      if (!suppressKeyEvents)
            addKeyListener(new KeyStateListener(this));
            
      // TODO: Fix NullPointerException bug.
      addNotify();
      setVisible(true);
      try { Thread.sleep(500); } catch (Exception e) {}
      addNotify();
      try { Thread.sleep(500); } catch (Exception e) {}

      createBufferStrategy(2);
      strategy = getBufferStrategy();
}

It still doesn’t work even if I create a visible JFrame (or do I?) before I add the canvas. I also tried to make the canvas visible in the constructor…

Or perhaps I could take another route and init the buffer strategy in the getGraphics() like this:

public Graphics getGraphics()
{
      if (strategy == null)
      {
            createBufferStrategy(2);
            strategy = getBufferStrategy();
      }
            
      return strategy.getDrawGraphics();
}

What do you think? It will add some overhead when the graphics object is requested the first time instead for at load time. I also added some sleeps to see if it took some time to set the canvas visible. Didn’t work

  • Johan