Using the Sprite Class and Sprite Movement in MIDP 2.0

This short tutorial will show you how to use the built-in Sprite class and how to move it about on the screen. This may come a bit late but I just thought I should throw it in for variety.

We will be using the project templates which you can get from this post: Clean Project Templates. Download and extract them to a folder of your choice.

I have prepared a new image strip for us to play with. It's an 18x18 pixel white van drawn in 4 directions from frame 0 to frame 3, UP, DOWN, LEFT, and RIGHT.

It's an 18x18 pixel white van!
White Van Image
Frame Size:18x18 pixels

I know the drawing is not politically correct so please bear with me on this one XD. Firefox users can right-click on the image and choose "Save Image As" from the context menu and IE users can choose "Save Picture As" instead. Save it inside the images folder, inside the src folder of the project template you downloaded.

Fire up NetBeans to open your project and navigate your way to the clsCanvas source code.

Let's add a couple of variables, one for the image and one for the sprite. Add these lines above the clsCanvas constructor:


private Image imgVan;
private Sprite Van;

/** Creates a new instance of clsCanvas */
public clsCanvas(midMain m) {



Now we'll load the image and initialize the Sprite object so modify the load() method like so:


public void load(){
try{
// load the images here
imgVan = Image.createImage("/images/van.png");

}catch(Exception ex){
// exit the app if it fails to load the image
isRunning = false;
return;
}

// initialize the Sprite object
Van = new Sprite(imgVan, 18, 18);

// show the frame 1 - the second frame
Van.setFrame(1);

// move to 50, 50 (X, Y)
Van.setPosition(50, 50);

}



The Sprite() constructor we're using takes in 3 parameters:
  1. Image image - The Image object that contains the frames/pictures
  2. int frameWidth - the width of each frame in the image
  3. int frameHeight - the height of each frame in the image

You must make sure that the width and height of the picture assigned to the Image object must be exactly divisible by the frameWidth and frameHeight that you defined. Otherwise, you will get an exception error at runtime. In this example, each frame of the image is 18 pixels wide and 18 pixels high.

After initializing the sprite we use the setFrame() method of the Sprite object to set the current visible frame to 1, which is the second frame counting from left to right and starting with 0. We also set the initial position to 50, 50 (X, Y) on the screen.

We will also add some code to the unload() method of clsCanvas to make sure the objects we made gets destroyed:


public void unload(){
// make sure the object gets destroyed

Van = null;
imgVan = null;

}



The next line of code must be placed before the call to flushGraphics():


// draw the sprite
Van.paint(g);

flushGraphics();



To test the MIDlet, press F6 on your keyboard then press Enter when the emulator shows up. You should see something like this:

Van Sprite on Emulator


For our next trick, we're going to make the van move in all 4 directions: Up, Down, Left, Right. We'll begin by getting the current position of the sprite and storing them in two variables. Add these lines in the run() method just before the call to setClip():


// get the current position of the van
int cx = Van.getX();
int cy = Van.getY();

//restore the clipping rectangle to full screen
g.setClip(0, 0, screenW, screenH);



We will change the value of those variables depending on what keys are being pressed. Add the following lines beneath the lines you just added:


// get the current position of the van
int cx = Van.getX();
int cy = Van.getY();

if ((iKey & GameCanvas.UP_PRESSED) != 0){
// show the van facing up
Van.setFrame(0);
// move the van upwards
cy--;
} else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){
// show the van facing down
Van.setFrame(1);
// move the van downwards
cy++;
} else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){
// show the van facing left
Van.setFrame(2);
// move the van to the left
cx--;
} else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){
// show the van facing right
Van.setFrame(3);
// move the van to the right
cx++;
}

// update the vans position
Van.setPosition(cx, cy);

//restore the clipping rectangle to full screen
g.setClip(0, 0, screenW, screenH);



The code above checks which key is being pressed and changes the current frame the sprite is showing to match the direction of it's movement. It then computes for the next location the sprite should move to and updates the sprites position.

As a reference for beginners, here is a list of operations to do to move an object in different directions:
  • UP - decrease the value of the Y coordinate
  • Down - increase the value of the Y coordinate
  • Left - decrease the value of the X coordinate
  • Right - increase the value of the X coordinate


If you run the project now, you should be able to control the van by pressing the arrow/directional keys.

Here's the completed clsCanvas source code:


package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.Sprite;

public class clsCanvas extends GameCanvas implements Runnable {
// key repeat rate in milliseconds
public static final int keyDelay = 250;

//key constants
public static final int upKey = 0;
public static final int leftKey = 1;
public static final int downKey = 2;
public static final int rightKey = 3;
public static final int fireKey = 4;

//key states for up, left, down, right, and fire key
private boolean[] isDown = {
false, false, false, false, false
};

//last time the key changed state
private long[] keyTick = {
0, 0, 0, 0, 0
};

//lookup table for key constants :P
private int[] keyValue = {
GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED,
GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED,
GameCanvas.FIRE_PRESSED
};

private boolean isRunning = true;
private Graphics g;
private midMain fParent;

private Image imgVan;
private Sprite Van;

/** Creates a new instance of clsCanvas */
public clsCanvas(midMain m) {
super(true);
fParent = m;
setFullScreenMode(true);
}

public void start(){
Thread runner = new Thread(this);
runner.start();
}

public void load(){
try{
// load the images here
imgVan = Image.createImage("/images/van.png");

}catch(Exception ex){
// exit the app if it fails to load the image
isRunning = false;
return;
}

// initialize the Sprite object
Van = new Sprite(imgVan, 18, 18);

// show the frame 1 - the second frame
Van.setFrame(1);

// move to 50, 50 (X, Y)
Van.setPosition(50, 50);

}

public void unload(){
// make sure the object gets destroyed
Van = null;
imgVan = null;
}

public void checkKeys(int iKey, long currTick){
long elapsedTick = 0;
//loop through the keys
for (int i = 0; i < 5; i++){
// by default, key not pressed by user
isDown[i] = false;
// is user pressing the key
if ((iKey & keyValue[i]) != 0){
elapsedTick = currTick - keyTick[i];
//is it time to toggle key state?
if (elapsedTick >= keyDelay){
// save the current time
keyTick[i] = currTick;
// toggle the state to down or pressed
isDown[i] = true;
}
}
}
}

public void run() {
int iKey = 0;
int screenW = getWidth();
int screenH = getHeight();
long lCurrTick = 0; // current system time in milliseconds;

load();
g = getGraphics();
while(isRunning){

lCurrTick = System.currentTimeMillis();
iKey = getKeyStates();

checkKeys(iKey, lCurrTick);

if (isDown[fireKey]){
isRunning = false;
}

// get the current position of the van
int cx = Van.getX();
int cy = Van.getY();

if ((iKey & GameCanvas.UP_PRESSED) != 0){
// show the van facing up
Van.setFrame(0);
// move the van upwards
cy--;
} else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){
// show the van facing down
Van.setFrame(1);
// move the van downwards
cy++;
} else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){
// show the van facing left
Van.setFrame(2);
// move the van to the left
cx--;
} else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){
// show the van facing right
Van.setFrame(3);
// move the van to the right
cx++;
}

// update the vans position
Van.setPosition(cx, cy);

//restore the clipping rectangle to full screen
g.setClip(0, 0, screenW, screenH);
//set drawing color to black
g.setColor(0x000000);
//fill the screen with blackness
g.fillRect(0, 0, screenW, screenH);

// draw the sprite
Van.paint(g);

flushGraphics();

try{
Thread.sleep(30);
} catch (Exception ex){

}
}
g = null;
unload();
fParent.destroyApp(false);
fParent = null;
}
}



The code presented in this tutorial also shows the basics of how to make a user controlled sprite or character. I hope it was of some help to you.

You got a question? I made a typo? Something is amiss? Please don't hesitate to post a comment.


15 comments   |   post a comment
said...
I think one very useful optimization is to not redraw the full screen everytime, but only the changed parts, so the old location of the van back to black and then the new van. And when nothing changed, nothing has to be redrawn.

Especially for bagger screens that can have a significant impact. If you have any other insights, please let me know.

But again, an excellent tutorial.

Iwan
said...
Yes, optimizations such as the one you mentioned are good for older phones and given some conditions:

1) You are drawing all the stuff on screen yourself and not using the built-in layer/sprite classes.

2) Your game is constrained to the screen size (no scrolling).

3) You are not drawing too many moving/animated objects at the same time. Otherwise, your optimization can make the game run slower instead of faster.


If your game scrolls continuously you will have no choice but to redraw the whole screen or playing area anyway.

The good news is such optimizations aren't that important anymore as the drawing functions(or phones) are fast enough even if you're drawing the whole screen yourself as long (as you don't rely on the layer classes, heh). Still, that depends on your target phone.

The best way to find out is to test your game on an actual device.
said...
Great Article again!!
Was wondering why we need in this code snippet as this is the only place we have set the clip g.setClip(0, 0, screenW, screenH);
Thanks,
vJ
said...
That line of code was carried over from previous articles and is not really needed in this sample code. But it is better to have it there right from the start specially when you intend to use setClip() many times in your drawing logic. That way you don't forget to reset the clipping rectangle and your drawing code will work as intended.

Some phones will also display graphics incorrectly when drawn or partially drawn outside the screen borders. Using the setClip() method fixes that.
said...
Great Great Great Blog Devlin!!!
Its been great help to get started in the gaming thingy!!!
Hi there,

help me please, I created a game and ran it on a N70 phone and Motorola v3i.

The program crashed on N70 but ran on motorola, in the program i have two image containers where one is the background and the other is for use in a sprite. i removed these two along with a thread and ran it again on N70 and it didn't crash.

But I need those two images so can you tell me any remedy?
said...
I can't give you a definite answer without looking at your code but here's some tips that might come in handy:

a) Use images with smaller file sizes. Use an 8-Bit PNG format and use alpha transparency sparingly.

b) If you're using more than one thread, try making it run with only one thread at first. It might help you find the problem faster.

c) Try to reduce the number of variables or objects your game is using if you can so it will not need more memory than the phone can handle. Make sure to assign null to objects you don't need anymore.

d) Somehow, the N70 has a buggy CLDC implementation, try to limit the platform to CLDC-1.0 if you're not using any CLDC-1.1 specific features.

A simple way to test if the images are being loaded properly is to make a simple project with a GameCanvas that does nothing more than loads the images and displays them on the screen. If that works on your phone then the images might not be the problem at all.

Good Luck!!!
This comment has been removed by the author.
Hiya devlin, remember me? i sent you those codes asking for help, remember?

anyway I was wondering on how I'm gonna be able to free up memory space whenever I'm done with one class and transferring to another and thought what if i pass a reference of the current class to the main midlet along with the next class so that the midlet can set the current class to null before transferring to the next class. Is this effective?

in coding terms, if you still remember my code, it goes like this:

Main.showpage(new Ranch(m,p,ch),this);

//Main midlet showpage method
public void showpage(Displayable nx,Displayable del){
del=null;
Display.setCurrent(nx);
}

is it effective? or am i making a stupid mistake?
said...
Hi jan martin,

I think the best thing to do is to set it up so that the you have a global variable in the parent class that will hold the new class instance so you can set that to null after switching the current display to another class instance.

Good luck!
said...
This comment has been removed by the author.
said...
Hi Jan Martin,
Using one utility class for class navigation, you can show any page from anywhere. Those utility class should be derived from Stack class so that we can easily pop/push our displayable object.

GoodLuck
Nifty Stuff :D I have a question about sprite movement however, On this test game i am creating, one of the floor has ice on it where the sprite would slide continously following it's momentum till it reaches a non-ice platform or ground. Any way i could edit the codes to implement this?
said...
Yes, use "states" with your sprite and when it's on an ice tile save the current direction then change the "state" to "slide" and continue moving the sprite in the saved direction while preventing further use of the controls.
excuse me sir..

can we make spinning earth move like van using Sprite class?

thx b4..