/*
* @(#)GameCanvas.java 1.39 02/10/11 @(#)
*
* Copyright (c) 2002 Sun Microsystems, Inc. All rights reserved.
* PROPRIETARY/CONFIDENTIAL
* Use is subject to license terms.
*/
package javax.microedition.lcdui.game;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Canvas;
import com.sun.midp.lcdui.DisplayAccess;
import com.sun.midp.lcdui.GameMap;
/**
* The GameCanvas class provides the basis for a game user interface. In
* addition to the features inherited from Canvas (commands, input events,
* etc.) it also provides game-specific capabilities such as an
* off-screen graphics buffer and the ability to query key status.
* <p>
* A dedicated buffer is created for each GameCanvas instance. Since a
* unique buffer is provided for each GameCanvas instance, it is preferable
* to re-use a single GameCanvas instance in the interests of minimizing
* heap usage. The developer can assume that the contents of this buffer
* are modified only by calls to the Graphics object(s) obtained from the
* GameCanvas instance; the contents are not modified by external sources
* such as other MIDlets or system-level notifications. The buffer is
* initially filled with white pixels.
* <p>
* The buffer's size is set to the maximum dimensions of the GameCanvas.
* However, the area that may be flushed is limited by the current
* dimensions of the GameCanvas (as influenced by the presence of a Ticker,
* Commands, etc.) when the flush is requested. The current dimensions of
* the GameCanvas may be obtained by calling
* {@link javax.microedition.lcdui.Canvas#getWidth getWidth} and
* {@link javax.microedition.lcdui.Canvas#getHeight getHeight}.
* <p>
* A game may provide its own thread to run the game loop. A typical loop
* will check for input, implement the game logic, and then render the updated
* user interface. The following code illustrates the structure of a typcial
* game loop: <code>
* <pre>
* // Get the Graphics object for the off-screen buffer
* Graphics g = getGraphics();
*
* while (true) {
* // Check user input and update positions if necessary
* int keyState = getKeyStates();
* if ((keyState & LEFT_PRESSED) != 0) {
* sprite.move(-1, 0);
* }
* else if ((keyState & RIGHT_PRESSED) != 0) {
* sprite.move(1, 0);
* }
*
* // Clear the background to white
* g.setColor(0xFFFFFF);
* g.fillRect(0,0,getWidth(), getHeight());
*
* // Draw the Sprite
* sprite.paint(g);
*
* // Flush the off-screen buffer
* flushGraphics();
* }
* </pre>
* </code>
* <P>
* @since MIDP 2.0
**/
public abstract class GameCanvas extends Canvas
{
/**
* The bit representing the UP key. This constant has a value of
* <code>0x0002</code> (1 << Canvas.UP).
*/
public static final int UP_PRESSED = 1 << Canvas.UP;
/**
* The bit representing the DOWN key. This constant has a value of
* <code>0x0040</code> (1 << Canvas.DOWN).
*/
public static final int DOWN_PRESSED = 1 << Canvas.DOWN;
/**
* The bit representing the LEFT key. This constant has a value of
* <code>0x0004</code> (1 << Canvas.LEFT).
*/
public static final int LEFT_PRESSED = 1 << Canvas.LEFT;
/**
* The bit representing the RIGHT key. This constant has a value of
* <code>0x0020</code> (1 << Canvas.RIGHT).
*/
public static final int RIGHT_PRESSED = 1 << Canvas.RIGHT;
/**
* The bit representing the FIRE key. This constant has a value of
* <code>0x0100</code> (1 << Canvas.FIRE).
*/
public static final int FIRE_PRESSED = 1 << Canvas.FIRE;
/**
* The bit representing the GAME_A key (may not be supported on all
* devices). This constant has a value of
* <code>0x0200</code> (1 << Canvas.GAME_A).
*/
public static final int GAME_A_PRESSED = 1 << Canvas.GAME_A;
/**
* The bit representing the GAME_B key (may not be supported on all
* devices). This constant has a value of
* <code>0x0400</code> (1 << Canvas.GAME_B).
*/
public static final int GAME_B_PRESSED = 1 << Canvas.GAME_B;
/**
* The bit representing the GAME_C key (may not be supported on all
* devices). This constant has a value of
* <code>0x0800</code> (1 << Canvas.GAME_C).
*/
public static final int GAME_C_PRESSED = 1 << Canvas.GAME_C;
/**
* The bit representing the GAME_D key (may not be supported on all
* devices). This constant has a value of
* <code>0x1000</code> (1 << Canvas.GAME_D).
*/
public static final int GAME_D_PRESSED = 1 << Canvas.GAME_D;
/**
* Height available to draw on in full screen mode.
*
*/
private static final int FULLSCREEN_HEIGHT;
/**
* Width available to draw on in full screen mode.
*
*/
private static final int FULLSCREEN_WIDTH;
/**
* currently every GameCanvas has one offscreen buffer
* can be optimized so that we put a limit on no of offscreen buffers
* an application can have
*/
private Image offscreen_buffer;
/**
* Creates a new instance of a GameCanvas. A new buffer is also created
* for the GameCanvas and is initially filled with white pixels.
* <p>
* If the developer only needs to query key status using the getKeyStates
* method, the regular key event mechanism can be suppressed for game keys
* while this GameCanvas is shown. If not needed by the application, the
* suppression of key events may improve performance by eliminating
* unnecessary system calls to keyPressed, keyRepeated and keyReleased
* methods.
* <p>
* If requested, key event suppression for a given GameCanvas is started
* when it is shown (i.e. when showNotify is called) and stopped when it
* is hidden (i.e. when hideNotify is called). Since the showing and
* hiding of screens is serialized with the event queue, this arrangement
* ensures that the suppression effects only those key events intended for
* the corresponding GameCanvas. Thus, if key events are being generated
* while another screen is still shown, those key events will continue to
* be queued and dispatched until that screen is hidden and the GameCanvas
* has replaced it.
* <p>
* Note that key events can be suppressed only for the defined game keys
* (UP, DOWN, FIRE, etc.); key events are always generated for all other
* keys.
* <p>
* @param suppressKeyEvents <code>true</code> to suppress the regular
* key event mechanism for game keys, otherwise <code>false</code>.
*/
protected GameCanvas(boolean suppressKeyEvents) {
// Create and offscreen Image object that
// acts as the offscreen buffer to which we draw to.
// the contents of this buffer are flushed to the display
// only when flushGraphics() has been called.
offscreen_buffer =
Image.createImage(FULLSCREEN_WIDTH, FULLSCREEN_HEIGHT);
setSuppressKeyEvents((Canvas)this, suppressKeyEvents);
}
/**
* Obtains the Graphics object for rendering a GameCanvas. The returned
* Graphics object renders to the off-screen buffer belonging to this
* GameCanvas.
* <p>
* Rendering operations do not appear on the display until flushGraphics()
* is called; flushing the buffer does not change its contents (the pixels
* are not cleared as a result of the flushing operation).
* <p>
* A new Graphics object is created and returned each time this method is
* called; therefore, the needed Graphics object(s) should be obtained
* before the game starts then re-used while the game is running.
* For each GameCanvas instance, all of the provided graphics objects will
* render to the same off-screen buffer.
* <P>
* <P>The newly created Graphics object has the following properties:
* </P>
* <ul>
* <LI>the destination is this GameCanvas' buffer;
* <LI>the clip region encompasses the entire buffer;
* <LI>the current color is black;
* <LI>the font is the same as the font returned by
* {@link javax.microedition.lcdui.Font#getDefaultFont
* Font.getDefaultFont()};
* <LI>the stroke style is {@link Graphics#SOLID SOLID}; and
* <LI>the origin of the coordinate system is located at the upper-left
* corner of the buffer.
* </ul>
* <p>
* @return the Graphics object that renders to this GameCanvas'
* off-screen buffer
* @see #flushGraphics()
* @see #flushGraphics(int, int, int, int)
*/
protected Graphics getGraphics() {
return offscreen_buffer.getGraphics();
}
/**
* Gets the states of the physical game keys. Each bit in the returned
* integer represents a specific key on the device. A key's bit will be
* 1 if the key is currently down or has been pressed at least once since
* the last time this method was called. The bit will be 0 if the key
* is currently up and has not been pressed at all since the last time
* this method was called. This latching behavior ensures that a rapid
* key press and release will always be caught by the game loop,
* regardless of how slowly the loop runs.
* <p>
* For example:<code><pre>
*
* // Get the key state and store it
* int keyState = getKeyStates();
* if ((keyState & LEFT_KEY) != 0) {
* positionX--;
* }
* else if ((keyState & RIGHT_KEY) != 0) {
* positionX++;
* }
*
* </pre></code>
* <p>
* Calling this method has the side effect of clearing any latched state.
* Another call to getKeyStates immediately after a prior call will
* therefore report the system's best idea of the current state of the
* keys, the latched bits having been cleared by the first call.
* <p>
* Some devices may not be able to query the keypad hardware directly and
* therefore, this method may be implemented by monitoring key press and
* release events instead. Thus the state reported by getKeyStates might
* lag the actual state of the physical keys since the timeliness
* of the key information is be subject to the capabilities of each
* device. Also, some devices may be incapable of detecting simultaneous
* presses of multiple keys.
* <p>
* This method returns 0 unless the GameCanvas is currently visible as
* reported by {@link javax.microedition.lcdui.Displayable#isShown}.
* Upon becoming visible, a GameCanvas will initially indicate that
* all keys are unpressed (0); if a key is held down while the GameCanvas
* is being shown, the key must be first released and then pressed in
* order for the key press to be reported by the GameCanvas.
* <p>
* @see #UP_PRESSED
* @see #DOWN_PRESSED
* @see #LEFT_PRESSED
* @see #RIGHT_PRESSED
* @see #FIRE_PRESSED
* @see #GAME_A_PRESSED
* @see #GAME_B_PRESSED
* @see #GAME_C_PRESSED
* @see #GAME_D_PRESSED
* @return An integer containing the key state information (one bit per
* key), or 0 if the GameCanvas is not currently shown.
*/
public int getKeyStates() {
DisplayAccess displayAccess = GameMap.get(this);
if (displayAccess != null) {
return displayAccess.getKeyMask();
}
return 0;
}
/**
* Paints this GameCanvas. By default, this method renders the
* the off-screen buffer at (0,0). Rendering of the buffer is
* subject to the clip region and origin translation of the Graphics
* object.
* @param g the Graphics object with which to render the screen.
* @throws NullPointerException if <code>g</code> is <code>null</code>
*/
public void paint(Graphics g) {
// NullPointerException will be thrown in drawImage if g == null
g.drawImage(offscreen_buffer, 0, 0, Graphics.TOP|Graphics.LEFT);
}
/**
* Flushes the specified region of the off-screen buffer to the display.
* The contents of the off-screen buffer are not changed as a result of
* the flush operation. This method does not return until the flush has
* been completed, so the app may immediately begin to render the next
* frame to the same buffer once this method returns.
* <p>
* If the specified region extends beyond the current bounds of the
* GameCanvas, only the intersecting region is flushed. No pixels are
* flushed if the specified width or height is less than 1.
* <p>
* This method does nothing and returns immediately if the GameCanvas is
* not currently shown or the flush request cannot be honored because the
* system is busy.
* <p>
* @see #flushGraphics()
* @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) {
if (width < 1 || height < 1) {
return;
}
DisplayAccess displayAccess = GameMap.get(this);
if (displayAccess != null) {
displayAccess.flush(this, offscreen_buffer,
x, y, width, height);
}
}
/**
* Flushes the off-screen buffer to the display. The size of the flushed
* area is equal to the size of the GameCanvas. The contents
* of the off-screen buffer are not changed as a result of the flush
* operation. This method does not return until the flush has been
* completed, so the app may immediately begin to render the next frame
* to the same buffer once this method returns.
* <p>
* This method does nothing and returns immediately if the GameCanvas is
* not currently shown or the flush request cannot be honored because the
* system is busy.
* <p>
* @see #flushGraphics(int,int,int,int)
*/
public void flushGraphics() {
DisplayAccess displayAccess = GameMap.get(this);
if (displayAccess != null) {
displayAccess.flush(this, offscreen_buffer,
0, 0, getWidth(), getHeight());
}
}
/**
* Set a private field in the <code>Canvas</code> object. We use a
* native method to work around the package boundary.
* @param c this <code>GameCanvas</code> cast to a <code>Canvas</code>
* @param suppressKeyEvents whether or not to suppress key events
*/
private native void setSuppressKeyEvents(Canvas c,
boolean suppressKeyEvents);
static {
// do this to get the HEIGHT available to draw on in full screen mode.
GameDeviceCaps gdc = new GameDeviceCaps();
FULLSCREEN_WIDTH = gdc.width;
FULLSCREEN_HEIGHT = gdc.height;
gdc = null;
}
}
/*
* ************* Class, DeviceCaps
*/
/**
* Current device capabilities.
* Can't access Display's DeviceCaps. So make game's own.
*
*/
class GameDeviceCaps {
/** horizontal width of the current device */
int width;
/** vertical height of the current device */
int height;
/**
* Create a new GameDeviceCaps object that retrieves
* the devices capabilities
*/
GameDeviceCaps() {
init();
}
/**
* native method to retreive initial settings for
* display capabilities
*/
private native void init();
}
|