The sheepdog canvas is the main game screen. It displays a sheepdog and four sheep on a grassy island surrounded by water. If the screen is smaller than the game area, it scrolls the screen to keep the view focused on the sheepdog.
Create the SheepdogCanvas class file.
Import the required classes.
// unnamed package import java.util.Random; import java.util.Vector; import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import javax.microedition.media.*; import java.io.*;
Set SheepdogCanvas
to
extend GameCanvas
and implement Runnable
.
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.
For more information,
see GameCanvas
in the MIDP 2.0 API specification.
class SheepdogCanvas extends GameCanvas implements Runnable {
Define the constants used by the application.
// shared direction constants static final int NONE = -1; static final int UP = 0; static final int LEFT = 1; static final int DOWN = 2; static final int RIGHT = 3; private static final int MILLIS_PER_TICK = 50; private static final int NUM_SHEEP = 5; private final SheepdogMIDlet midlet; private final Field field; private final Sheepdog sheepdog; private final Vector sheep = new Vector(); private final LayerManager layerManager; private final Graphics graphics; private long gameDuration; private long startTime; private volatile Thread animationThread = null;
Create the SheepdogCanvas
object.
The getGraphics
method obtains the Graphics
object
for rendering a GameCanvas
. The LayerManager
class
manages a series of Layer
s. It simplifies the process
of rendering the Layer
s that have been added to it by
automatically rendering the correct regions of each Layer
in
the appropriate order.
For more information, see getGraphics
and LayerManager
in the MIDP 2.0 API specification.
SheepdogCanvas(SheepdogMIDlet midlet) { super(true); // suppress key events for game keys this.midlet = midlet; setFullScreenMode(true); graphics = getGraphics(); layerManager = new LayerManager(); field = new Field(); sheepdog = new Sheepdog(this); layerManager.append(sheepdog); for (int i = 0; i < NUM_SHEEP; ++i) { Sheep newSheep = new Sheep(this); layerManager.append(newSheep); sheep.addElement(newSheep); } layerManager.append(field); // last layer, behind sprites init(); }
Create a method for detecting key presses. If a key with a negative keycode is used the game opens the menu.
public void keyPressed(int keyCode) { // The constructor suppresses key events for game keys, so we'll // only get key events for non-game keys. The number keys, * & # // have positive keyCodes, so negative keyCodes mean non-game // special keys like soft-keys. We'll use key-presses on special // keys to take us to the menu. if (keyCode < 0) { stop(); midlet.sheepdogCanvasMenu(); } }
Create the init
method
for initializing the game. The getWidth
gets the current
width of a layer, in pixels whereas the getHeigth
method
gets the current height of a layer, also in pixels. The setPosition
method
sets this Layer
's position such that its upper-left corner
is located at (x,y) in the painter's coordinate system.
For more information, see getWidth
, getHeight
, and setPosition
in
the MIDP 2.0 API specification.
void init() { sheepdog.setPosition(field.getSheepdogStartX(), field.getSheepdogStartY()); for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); // find a valid position for the sheep do { int x = midlet.random(field.getWidth() - Sheep.WIDTH); int y = midlet.random(field.getHeight() - Sheep.HEIGHT); sh.setPosition(x, y); } while (field.containsImpassableArea(sh.getX(), sh.getY(), sh.getWidth(), sh.getHeight()) || overlapsSheepdog(sh) || overlapsSheep(sh, i) || field.inFold(sh)); } }
Create the start
and stop
methods
to begin and end the game Thread
s.
public synchronized void start() { animationThread = new Thread(this); animationThread.start(); startTime = System.currentTimeMillis() - gameDuration; } public synchronized void stop() { gameDuration = System.currentTimeMillis() - startTime; animationThread = null; }
Create the run
method
that will keep the game running until the Thread
is set
to null. The flushGraphics
method flushes the off-screen
buffer to the display.
For
more information, see flushGraphics
in
the MIDP 2.0 API specification.
public void run() { Thread currentThread = Thread.currentThread(); try { // This ends when animationThread is set to null, or when // it is subsequently set to a new thread; either way, the // current thread should terminate while (currentThread == animationThread) { long startTime = System.currentTimeMillis(); // Don't advance game or draw if canvas is covered by // a system screen. if (isShown()) { tick(); draw(); flushGraphics(); } long timeTaken = System.currentTimeMillis() - startTime; if (timeTaken < MILLIS_PER_TICK) { synchronized (this) { wait(MILLIS_PER_TICK - timeTaken); } } else { currentThread.yield(); } } } catch (InterruptedException e) { // won't be thrown } }
Create a method
that monitors simultaneous key presses. The getKeyStates
method
gets the states of the physical game keys.
For more information, see getKeyStates
in
the MIDP 2.0 API specification.
private void tick() { // If player presses two or more direction buttons, we ignore them // all. But pressing fire is independent. The code below also ignores // direction buttons if GAME_A..GAME_D are pressed. int keyStates = getKeyStates(); boolean bark = (keyStates & FIRE_PRESSED) != 0; keyStates &= ~FIRE_PRESSED; int direction = (keyStates == UP_PRESSED) ? UP : (keyStates == LEFT_PRESSED) ? LEFT: (keyStates == DOWN_PRESSED) ? DOWN : (keyStates == RIGHT_PRESSED) ? RIGHT : NONE; sheepdog.tick(direction, bark); for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); sh.tick(); } field.tick(); }
Create the various methods used to fetch objects and handle the "barking".
Field getField() { return field; } Sheepdog getSheepdog() { return sheepdog; } Vector getSheep() { return sheep; } void handleDogBark() { for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); sh.handleDogBark(); } }
Create the
methods that handle the overlapping of sprites. A Sprite
is
a basic visual element that can be rendered with one of several frames stored
in an Image
; different frames can be shown to animate
the Sprite
. Several transforms such as flipping and rotation
can also be applied to a Sprite
to further vary its appearance.
The collidesWith
method checks for a collision between
a given Sprite
and the specified other Sprite
.
For
more information, see Sprite
and collidesWith
in the MIDP 2.0 API specification.
boolean overlapsSheepdog(Sprite sprite) { return sprite.collidesWith(sheepdog, false); // false -> not pixelLevel } boolean overlapsSheep(Sprite sprite) { return overlapsSheep(sprite, sheep.size()); } // whether the sprite overlaps the first 'count' sheep boolean overlapsSheep(Sprite sprite, int count) { for (int i = 0; i < count; ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); if (sprite.collidesWith(sh, false)) // false -> not pixelLevel { return true; } } return false; } boolean overlapsOtherSheep(Sprite sprite) { for (int i = 0; i < sheep.size(); ++i) { Object obj = sheep.elementAt(i); if (obj != sprite) { Sheep sh = (Sheep)obj; if (sprite.collidesWith(sh, false)) // false -> not pixelLevel { return true; } } } return false; } void vibrate(int millis) { midlet.vibrate(millis); }
Create a method
for drawing the visuals of the game. The getX
method
gets the horizontal position of this Layer's upper-left corner in the painter's
coordinate system whereas the getY
method does the same
for the vertical position.
For
more information, see getX
and getY
in the MIDP 2.0 API specification.
// draw game private void draw() { int width = getWidth(); int height = getHeight(); // clear screen to grey graphics.setColor(0x00888888); graphics.fillRect(0, 0, width, height); // clip and translate to centre int dx = origin(sheepdog.getX() + sheepdog.getWidth() / 2, field.getWidth(), width); int dy = origin(sheepdog.getY() + sheepdog.getHeight() / 2, field.getHeight(), height); graphics.setClip(dx, dy, field.getWidth(), field.getHeight()); graphics.translate(dx, dy);
The paint
method paints this Layer
if
it is visible.
For
more information, see paint
in the MIDP 2.0 API specification.
// draw background and sprites layerManager.paint(g, 0, 0); // undo clip & translate graphics.translate(-dx, -dy); graphics.setClip(0, 0, width, height); // display time & score long time = (System.currentTimeMillis() - startTime) / 1000; int score = numSheepInFold(); graphics.setColor(0x00FFFFFF); // white graphics.drawString(Integer.toString(score), 1, 1, Graphics.TOP | Graphics.LEFT); graphics.drawString(Long.toString(time), width - 2, 1, Graphics.TOP | Graphics.RIGHT); if (score == sheep.size()) { midlet.sheepdogCanvasGameOver(time); } }
Create a method for calculating the size of the field.
// If the screen is bigger than the field, we center the field // in the screen. Otherwise we center the screen on the focus, except // that we don't scroll beyond the edges of the field. private int origin(int focus, int fieldLength, int screenLength) { int origin; if (screenLength >= fieldLength) { origin = (screenLength - fieldLength) / 2; } else if (focus <= screenLength / 2) { origin = 0; } else if (focus >= (fieldLength - screenLength / 2)) { origin = screenLength - fieldLength; } else { origin = screenLength / 2 - focus; } return origin; }
Write the below code to count the number of sheep in the fold.
int numSheepInFold() { int count = 0; for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); if (field.inFold(sh)) { count++; } } return count; } }