This class is a Sprite
that draws the sheepdog.
Its animation frame image is shown in the figure below. Note that there are
no frames for the sheepdog facing right: when it is facing right, the facing-left
image is used with an image transform of TRANS_MIRROR.
Create the Sheepdog
class
file.
Import the required classes.
// unnamed package import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*;
Set Sheepdog
to
extend Sprite
.
class Sheepdog extends Sprite {
Create the constants and parametres for this application.
static final int WIDTH = 15; static final int HEIGHT = 15; static final int VIBRATION_MILLIS = 200; private final SheepdogCanvas canvas; private boolean barking = false; private int[][][] animations = {{{0}, // stand up {1, 2, 3, 4}}, // run up {{5}, // stand left {6, 7, 8, 9}}, // run left {{10}, // stand down {11, 12, 13, 14}}}; // run down private int animationTick = 0; private static final int STAND = 0; private static final int RUN = 1; private int currentDirection = SheepdogCanvas.LEFT;
Initialize
the Sheepdog
object with the SheepdogCanvas
and
load the dog's image file. The defineCollisionRectangle
method
defines the Sprite's bounding rectangle that is used for collision detection
purposes. The defineReferencePixel
method defines the
reference pixel for this Sprite.
For more information, see defineCollisionRectangle
and defineReferencePixel
in
the MIDP 2.0 API specification.
Sheepdog(SheepdogCanvas canvas) { super(SheepdogMIDlet.createImage("/dog.png"), WIDTH, HEIGHT); defineCollisionRectangle(2, 2, WIDTH-4, WIDTH-4); defineReferencePixel(WIDTH/2, HEIGHT/2); this.canvas = canvas; }
Create the code for the animation, movement and barking of the dog.
void tick(int direction, boolean bark) { animationTick++; Field field = canvas.getField(); boolean moving = false;; switch (direction) { case SheepdogCanvas.UP: currentDirection = direction; if ((getY() > 0) && !field.containsImpassableArea(getX(), getY() - 1, getWidth(), 1) && moveSuccessfully(0, -1)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; case SheepdogCanvas.LEFT: currentDirection = direction; if ((getX() > 0) && !field.containsImpassableArea(getX() - 1, getY(), 1, getHeight()) && moveSuccessfully(-1, 0)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; case SheepdogCanvas.DOWN: currentDirection = direction; if ((getY() + getHeight() < field.getWidth()) && !field.containsImpassableArea(getX(), getY() + getHeight(), getWidth(), 1) && moveSuccessfully(0, 1)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; case SheepdogCanvas.RIGHT: currentDirection = direction; if ((getX() + getWidth() < field.getWidth()) && !field.containsImpassableArea(getX() + getWidth(), getY(), 1, getHeight()) && moveSuccessfully(1, 0)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; default: // must be NONE break; } if (moving) { advanceRunningAnimation(); } else { setStandingAnimation(); } // implement a toggle, so bark only happens once per click // (will therefore not register very rapid multiple-clicks) if (bark) { if (!barking) { SoundEffects.getInstance().startDogSound(); barking = true; canvas.handleDogBark(); } } else { barking = false; } }
Create a method
for checking if the proposed movement of the dog will succeed. The move
method
moves this Layer
by the specified horizontal and vertical
distances.
For
more information, see move
in
the MIDP 2.0 API specification.
private boolean moveSuccessfully(int dx, int dy) { move(dx, dy); if (canvas.overlapsSheep(this)) { move(-dx, -dy); return false; } else { return true; } }
Create the
methods for the animation of the dog. The setTransform
method
sets the transform for this Sprite
and the setFrame
method
selects the current frame in the frame sequence.
For more information, see setTransform
and setFrame
in the MIDP 2.0 API specification.
private void advanceRunningAnimation() { int[] sequence; if (currentDirection == SheepdogCanvas.RIGHT) { sequence = animations[SheepdogCanvas.LEFT][RUN]; setTransform(TRANS_MIRROR); } else { sequence = animations[currentDirection][RUN]; setTransform(TRANS_NONE); } setFrame(sequence[(animationTick >> 1) % sequence.length]); } private void setStandingAnimation() { if (currentDirection == SheepdogCanvas.RIGHT) { setFrame(animations[SheepdogCanvas.LEFT][STAND][0]); setTransform(TRANS_MIRROR); } else { setFrame(animations[currentDirection][STAND][0]); setTransform(TRANS_NONE); } } }
This class is a Sprite that draws a sheep. Its animation frame image is shown in the figure below. Note that there are no frames for the sheep facing right: when it is facing right, the facing-left image is used with an image transform of TRANS_MIRROR.
Create the Sheep
class
file.
Import the required classes.
// unnamed package import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import java.util.*;
Set Sheep
to
extend Sprite
.
class Sheep extends Sprite {
Create the constants and parametres for the application.
static final int WIDTH = 15; static final int HEIGHT = 15; private final SheepdogCanvas canvas; private int[][][] animations = {{{0}, // stand up {1, 2, 3, 4}}, // run up {{5}, // stand left {6, 7, 8, 9}}, // run left {{10}, // stand down {11, 12, 13, 14}}}; // run down private int animationTick; private static int numSheep = 0; private static final int STAND = 0; private static final int RUN = 1; private int currentDirection = SheepdogCanvas.DOWN; private final int flockFactor; private final int minDogFactor; private final int maxDogFactor; private int dogFactor;
Initialize
the Sheep
object with the SheepdogCanvas
and
load the image for the sheep.
Sheep(SheepdogCanvas canvas) { super(SheepdogMIDlet.createImage("/sheep.png"), WIDTH, HEIGHT); defineCollisionRectangle(2, 2, WIDTH-4, WIDTH-4); defineReferencePixel(WIDTH/2, HEIGHT/2); this.canvas = canvas; animationTick = numSheep++; flockFactor = 100 + SheepdogMIDlet.random(100); minDogFactor = SheepdogMIDlet.random(20); maxDogFactor = minDogFactor + 10; dogFactor = minDogFactor; }
Set the tick rate of this object.
void tick() { // sheep are 4x as slow as dogs if ((animationTick++ % 4) != 0) { return; }
Create the "AI" behavior for the sheep. The basics are detailed in the below comment "ARTIFICIAL INTELLIGENCE SECTION".
// adjust dog factor adjustDogFactor(); // ARTIFICIAL INTELLIGENCE SECTION // - wants to move away from dog, if dog is close // - wants to move closer to flock (average position of other // sheep) if they are close // - if preferred direction is diagonal and major direction is // blocked, take minor direction // - each sheep varies in how much it's scared of the dog, and // how much it wants to flock // We do this by calculating a weighted direction vector // First calculate dog effect Sheepdog sheepdog = canvas.getSheepdog(); int dx = getX() - sheepdog.getX(); int dy = getY() - sheepdog.getY(); int sumsq = dx * dx + dy * dy; Field field = canvas.getField(); int dogEffectX = dogFactor * dx * field.getWidth() * field.getWidth() / sumsq; int dogEffectY = dogFactor * dy * field.getHeight() * field.getHeight() / sumsq; // Next calculate flock effect int flockDx = 0; int flockDy = 0; Vector sheep = canvas.getSheep(); for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); if (sh != this) { flockDx += getX() - sh.getX(); flockDy += getY() - sh.getY(); } } int flockEffectX = (flockDx * flockFactor) / (sheep.size() - 1); int flockEffectY = (flockDy * flockFactor) / (sheep.size() - 1); // Now calculate total effect int totalEffectX = dogEffectX - flockEffectX; int totalEffectY = dogEffectY - flockEffectY; // Determine preferred directions int firstDirection; int secondDirection; int thirdDirection; if (Math.abs(totalEffectY) > Math.abs(totalEffectX)) { // Prefer to move vertically if (totalEffectY > 0) { firstDirection = SheepdogCanvas.DOWN; } else { firstDirection = SheepdogCanvas.UP; } if (totalEffectX > 0) { secondDirection = SheepdogCanvas.RIGHT; thirdDirection = SheepdogCanvas.NONE; } else if (totalEffectX < 0) { secondDirection = SheepdogCanvas.LEFT; thirdDirection = SheepdogCanvas.NONE; } else // totalEffectX == 0 { if (SheepdogMIDlet.random(2) == 0) { secondDirection = SheepdogCanvas.LEFT; thirdDirection = SheepdogCanvas.RIGHT; } else { secondDirection = SheepdogCanvas.RIGHT; thirdDirection = SheepdogCanvas.LEFT; } } } else { // Prefer to move horizontally if (totalEffectX > 0) { firstDirection = SheepdogCanvas.RIGHT; } else { firstDirection = SheepdogCanvas.LEFT; } if (totalEffectY > 0) { secondDirection = SheepdogCanvas.DOWN; thirdDirection = SheepdogCanvas.NONE; } else if (totalEffectY < 0) { secondDirection = SheepdogCanvas.UP; thirdDirection = SheepdogCanvas.NONE; } else // totalEffectY == 0 { if (SheepdogMIDlet.random(2) == 0) { secondDirection = SheepdogCanvas.UP; thirdDirection = SheepdogCanvas.DOWN; } else { secondDirection = SheepdogCanvas.DOWN; thirdDirection = SheepdogCanvas.UP; } } } // if we can move in the preferred directions, do so, else // stand facing the dog if (tryMove(firstDirection) || tryMove(secondDirection) || ((thirdDirection != SheepdogCanvas.NONE) && tryMove(thirdDirection))) { advanceRunningAnimation(); } else { if (Math.abs(dx) > Math.abs(dy)) { if (dx > 0) { currentDirection = SheepdogCanvas.LEFT; } else { currentDirection = SheepdogCanvas.RIGHT; } } else { if (dy > 0) { currentDirection = SheepdogCanvas.UP; } else { currentDirection = SheepdogCanvas.DOWN; } } setStandingAnimation(); } // Will baa occasionally if dog is close. Dog distance ranges from // about 11 minimum to double width of field int dogDistance = Math.abs(dx) + Math.abs(dy); if (SheepdogMIDlet.random(dogDistance - 10) == 0) { SoundEffects.getInstance().startSheepSound(); } }
Create a method for adjusting the behavior of the sheep.
private void adjustDogFactor() { dogFactor += SheepdogMIDlet.random(4) - 2; // -2..1 if (dogFactor < minDogFactor) { dogFactor = minDogFactor; } else if (dogFactor > maxDogFactor) { dogFactor = maxDogFactor; } }
Create a method for checking if the proposed move for the sheep is possible.
private boolean tryMove(int direction) { Field field = canvas.getField(); boolean blocked = true; int dx = 0; int dy = 0; switch (direction) { case SheepdogCanvas.UP: if ((getY() > 0) && !field.containsImpassableArea(getX(), getY() - 1, getWidth(), 1)) { blocked = false; dy = -1; } break; case SheepdogCanvas.LEFT: if ((getX() > 0) && !field.containsImpassableArea(getX() - 1, getY(), 1, getHeight())) { blocked = false; dx = -1; } break; case SheepdogCanvas.DOWN: if ((getY() + getHeight() - 1 < field.getWidth()) && !field.containsImpassableArea(getX(), getY() + getHeight(), getWidth(), 1)) { blocked = false; dy = 1; } break; case SheepdogCanvas.RIGHT: if ((getX() + getWidth() - 1 < field.getWidth()) && !field.containsImpassableArea(getX() + getWidth(), getY(), 1, getHeight())) { blocked = false; dx = 1; } break; default: // can't happen break; } boolean success = false; if (!blocked) { boolean wasInFold = field.inFold(this); move(dx, dy); if (canvas.overlapsOtherSheep(this) || canvas.overlapsSheepdog(this) || (wasInFold && !field.inFold(this))) { move(-dx, -dy); } else { currentDirection = direction; success = true; } } return success; }
Create methods for the two animation modes the sheep can have.
private void advanceRunningAnimation() { int[] sequence; if (currentDirection == SheepdogCanvas.RIGHT) { sequence = animations[SheepdogCanvas.LEFT][RUN]; setTransform(TRANS_MIRROR); } else { sequence = animations[currentDirection][RUN]; setTransform(TRANS_NONE); } setFrame(sequence[(animationTick >> 2) % sequence.length]); } private void setStandingAnimation() { if (currentDirection == SheepdogCanvas.RIGHT) { setFrame(animations[SheepdogCanvas.LEFT][STAND][0]); setTransform(TRANS_MIRROR); } else { setFrame(animations[currentDirection][STAND][0]); setTransform(TRANS_NONE); } }
Create a method for adjusting sheep behavior in case the dog barks.
void handleDogBark() { // sheep should get nervous dogFactor += 5; if (dogFactor > maxDogFactor) { dogFactor = maxDogFactor; } } }