J2ME Guide – Part 4

In the third module we studied how to create midlet user interfaces using the high level API. We learned that, using that API, we can develop portable GUIs delegating to the underlying device the creation of specific widgets such as Lists, TextFields and so on. We stated that if we need more control over the device’s screen we need to use a different API – called Low Level API – which allows us to have complete control over the device at the cost of a portability loss. For instance, if we have to build a List using the High Level API we just create a List instance and then we add it to a Form. If we want the same result using the Low Level API, we have to manage list items and draw them on the screen using graphic primitives: lines, curves and rectangles must be painted using specific methods. In this case, of course, we need to know the screen dimension, then we have to choose the colors, draw list items and so on.

This is an operation which must take into account, for instance, that different devices have different screens or different available fonts. Creating the same list widget on every device may become a very complicated task. Nevertheless, sometimes the use of the low level API is a must. We’re talking about games, for instance, which require our code to draw, animate and move different items on the screen. We already learned how to draw widgets, but we must take into account also user events management that, in the case of the Low Level API, is strongly related to the widgets’ appareance.

In this article we’ll deal with three fundamental concepts underlying the Low Level API. They are:

- Items drawing
- User events management
- Collision detection

We’ll see how to manage them using the Low Level API in conjunction with a specific API, known as Gaming API, newly introduced in the 2.0 version of MIDP.

Low Level API

In Fig 4.1 are listed classes – which we studied in module three – we can use to manage devices’ user interfaces. Dealing with the Low Level API, we’ve been talking about the Canvas class, a realization of the Displayable abstract class quite different from the Screen class, superclass of the High Level API. The main difference regards user interaction and paint management. The Canvas class has no add/remove methods for adding and removing Listeners, but just methods called when the user presses a button or – if the device allows it – as a response to pointer events. So, the Canvas class has methods to draw on the screen using the Graphics object methods. Describing all the methods of the Canvas and Graphics classes is out of the scope of this module. What we can notice reading Canvas javadoc is that it’s an abstract class, because of the paint(Graphics g) method. This means that every concrete Canvas must describe what to draw on the Canvas itself. Now we’ll see how to employ the Canvas and the GameCanvas APIs by developing a simple example game.

Fig 4

A Simple game

To explain the Canvas API usage we are going to create a simple game similar to the old console tennis. The first step to do is to define which items will be displayed on the screen. In our case we have two rackets, a ball and a tennis court. Rackets and ball may move on the screen, thus they can collide. The tennis court , on the contrary, doesn’t move and must always be drawn under the other items. In this module we’ll discover two different ways of coding this game: using “old” Canvas tools and the new gaming API. We won’t develop the best solution in terms of performance or graphics but a solution which will allow us to cover as many methods as possibile.
To better understand the Canvas approach, we can use a simile: imagine the Canvas to be a movie projector which, at regular intervals, shows a picture which is part of a sequence of pictures (the movie). Every picture (frame) contains the tennis court, always in the same place, and the rackets and ball changing their position between one frame and the next. In this way we have the feeling of movement. What we have to do, next, is to deal with user events. We have to manage device buttons in order to let the user control the rackets. At the end we want to manage collisions between the ball and the rackets.
Using our movie simile, the first thing to create is the movie projector, which is an object that draws items. To represent each item we can draw, we’ll create a Java interface, called Model. This interface describes the operation the Canvas will call to draw the model itself, to notify some events and to manage collisions. All classes and interfaces of this implementation of the game are contained in the it.actionscript.mobile.m04.canvas package. The first thing we want to do is to manage drawing. We can do it with the operation:

public void paint(Graphics g);


which is the same operation the Canvas uses to draw on the screen. The Canvas will delegate this operation to the model. To display each Model, the Canvas will call their paint method. The second step is to deal with user events. If we read the Canvas javadoc, we see that it contains a set of callback methods the device will call in response to user events, such as a keypress. In our case we consider two specific events, managed by these following callback methods:

public void keyPressed(int gameCode);

public void keyRepeated(int gameCode);


We must be careful about using the second method, since it doesn’t belong to the MIDP 2.0 standard, but is present in most MIDP 2.0 device implementations. Each model must implement these methods and modify its own state accordingly.
Sometimes we may need to modify a model state even in the absence of user events. In order to change a model state, we declare the method:

public void changeModelState();


in whose body we’ll put the code for managing model state changes. For instance, a ball moving in some direction changes its own position: the state, in this example, is the position.
The last method we have to define in the Model interface is:

public Shape getActiveShape();


which is fundamental for collision management. At every time, this methods returns the rectangle occupied by the model that we can consider for collision detection.
The description of the interface tells us what the Canvas – the movie projector – will do. Once started, it will repeat in an endless cycle the operations we can see in the body of the run method of the AnimatedCanvas class.

public void run(){
while(running){
// Change state
if(model !=null){
model.changeModelState();
}
// We request a repaint
repaint();
// Flush repainting action
serviceRepaints();
// Wait for the next frame
try{Thread.sleep(INTERVAL);}catch(InterruptedException ie){}
}
}


In every cycle the run method calls the changeModelState() method of the model to change its state (if necessary). Then it calls the repaint() method which performs a very important action: it adds a repaint event to a queue that the method serviceRepaints() will flush in an optimized way. Every repaint() request will produce a call to the Canvas paint(Graphics) method that, in our example, is:

protected void paint(Graphics g) {
if(model!=null){
model.paint(g);
}
}


which delegates the paint operation to the Model. So, the Canvas pushes the repaint event into a queue and, when the device decides to repaint the graphics after the serviceRepaints() method invocation, it lets the Model decide what to draw. What about user events? We learned that the Canvas class has a set of callback methods the device may call to notify user events. In our example we chose to manage just two of them, so we have to override those two methods (keyPressed and keyRepeated) in this way:

protected void keyPressed(int keyCode) {
// Delegate to model
if(model !=null){
// Convert keyCode to gameCode
model.keyPressed(getGameAction(keyCode));
}
}

protected void keyRepeated(int keyCode) {
// Delegate to model
if(model!=null){
// Convert keyCode to gameCode
model.keyRepeated(getGameAction(keyCode));
}
}


Before going on we’ll say something about the keyCode parameter appearing in the callback methods. As we can see, these methods use a keyCode value which is a number representing a specific device key. The Canvas class has a method called getGameAction() which converts this codes into another set of values called gameAction codes, that can be used in a easier way in game programming, as we’ll see reading some of our Model implementations.
In our AnimatedCanvas class, we just overrode the callback methods, thus delegating the same events to the model. Our AnimatedCanvas class manages just a single Model. To manage multiple models we implemented the Composite pattern, in the following way: we created a class, called CompositeModel – which is a Model – and delegated all its methods to the list of Models it contains. So, we just had to create a CompositeModel and add to it all the necessary Model instances. Each CompositeModel’s method will delegate its execution to the same method of every Model. For instance the keyPressed() callback method is:

public void keyPressed(int gameCode) {
for(int i=0;i<models.size();i++){
((Model)(models.elementAt(i))).keyPressed(gameCode);
}
}


So, if we want to create a game employing this simple framework, we just have to create Model realizations and then register them to a CompositeModel set in the AnimatedCanvas. What we need now is collision detection. To see how we implemented that, we’ll have a look at one of our Model implementation: the RacketModel class. It’s a Model implementation with a paint() method whose task is to draw a small rectangle. It has a boolean parameter, used to set if the racket is the right or the left one. The most important snippet of code, contained in the paint() method implementation, is listed here:

- - -
// Check collision between Active Shapes
Shape ballShape = ballModel.getActiveShape();
Shape thisShape = getActiveShape();
boolean collides = (ballShape.x>=thisShape.x) && (ballShape.x<(thisShape.x+thisShape.width)) &&
(ballShape.y>thisShape.y) && (ballShape.y<(thisShape.y+thisShape.height));
if(collides){
collidesWithBall(ballShape.y-(thisShape.y+HEIGHT/2));
}

- - -


In the previous lines of code we can see how we used the getActiveShape() Model interface operation to check if the returned rectangle collides with another one. Without going into details, we can see that it’s an operation which can be difficult and error prone. Every Model implementation must have a reference to the model it may collide with, and manage the corresponding code. Our result game is shown in Fig4_2.

Fig 4_2

Performance and Double Buffering

If we run our first tennis game implementation we can see how slowly it runs. This behaviour has different causes. One of them is the high number of threads. There’s one thread for our AnimatedCanvas and many others for callback events notifications. Every time one event occurs, there’s a corresponding callback method invocation, which can be very heavy. Performance is poor also from the memory point of view, since we have to create a lot of classes, one for each Model implementation. One of the major problems we can meet when developing in this way stands in the repainting of things that don’t change. One of these is, for instance, the CourtModel implementation. If we read the code of this class, and in particular the paint(Graphics g) method, we can see a lot of code which always draws the same thing: the tennis court. This paint implementation paint(Graphics g) is called in every cycle of the AnimatedCanvas thread. A way we can use to optimize the application is called double buffering and is used in the example class BufferedCourtModel. The idea is that we can draw the tennis court just once, take a picture of it and reuse it in the next cycles. To do that we can initialize a buffered image the first time we want to display it, using the method:

Image.createImage(g.getClipWidth(),g.getClipHeight());


After that we can use the method

bufferImage.getGraphics();


to obtain a reference to the Graphics object we can use later to draw the image. This approach is employed in every Model implementation of this version of the game.

Gaming API

A way to speed up the game may be to use native implementations of the drawing methods, and to delegate event management and the collision detection operations to the underlying device OS environment. That’s the solution made available by the Gaming API (since the 2.0 version of MIDP). We can see these advantages by means of a single class we created in the it.actionscript.mobile.m04.gaming package, called AnimatedGameCanvas. It’s a class similar to the previous one, but containing a few fundamental differences. In the first place, we can notice that it now extends the GameCanvas class of the javax.microedition.lcdui.game package. Now the operations inside the thread run() method are:

public void run(){
while(running){
// Gets a reference to the graphics context
Graphics g = getGraphics();
// Event management
manageEvents();
// Change state
changeState();
// Repaints the model contained in the buffer
render(g);
// Flush for double buffering
flushGraphics();
// Wait delay time
try{Thread.sleep(delay);}catch(InterruptedException ie){}
// Increment clock
clock++;
}
}// end run


The first thing we do is to get the Graphics object associated with the GameCanvas itself. This reference is made available everywhere in the class by the getGraphics() method. On the contrary, when we dealed with the Canvas class we could use it only in the paint() method. After that we call a custom method named manageEvents(). This is a method containing a fundamental feature of the GameCanvas class we can see reading the following code:

protected void manageEvents() {
// Get key state
int keyStates = getKeyStates();
// Check key state
if(keyStates == GameCanvas.UP_PRESSED){
leftRacket.move(0,-1*RACKET_STEP);
rightRacket.move(0,-1*RACKET_STEP);
}else if(keyStates == GameCanvas.DOWN_PRESSED){
leftRacket.move(0,RACKET_STEP);
rightRacket.move(0,RACKET_STEP);
}

}// end


The main difference between this implementation of the game and the previous one is that now we don’t have a heavy callback method system for user events management, but we employ a “pull solution”. It’s our application, now, that asks the environment which events are active at a given time; it’s no more the device that notifies them to us. As we can notice, there’s not a callback method for each different event; instead, there’s just one int value returned by the method getKeyStates() which is a bit mask of all events. To check if an event occurred we just compare the int number returned by this method with a set of values stored as GameCanvas constants. This new approach is much more effective.
After checking user events we can change items state using the custom method changeState(). We can now change items’ states as we did in the Model implementation. We’ll see later what we mean with “items”. The next step is similar to the previous solution’s one: we render items and flush all the things to paint using the flushGraphics() method. That’s a fundamental method that optimizes items rendering and that is managed by the native device implementation.
In the previous game implementation, items were Model interface realizations. Now, the gaming API gives us two important classes: Sprite and TiledLayer. The first is a class that allows us to create an item which can move and animate on the screen. As we can see in our code, creating a Sprite is very simple. We can use the following lines of code:

ball = new Sprite(ballImage,7,7);


to create a Sprite instance called ball which is created from an image and that contains a set of 7×7 frames. In our case the ballImage has three 7×7 pixel frames, so the image has a total size of 21×7. Each 7×7 sized fragment of the image is treated as a single frame of the sprite. An important information about a sprite is its reference point, which is the point to consider for collision management and sprite movement. We need to use it in case we don’t want to employ the default upper left point of the frame as reference point, but a different one. So, we used the following method:

defineReferencePixel(x,y)


to define the reference pixel of the sprite, and method:

setRefPixelPosition(x,y)


to set the sprite position on the screen.
As we said before, Sprite class allows us to change the active frame, thus simulating animations (the pacman animation, for instance). We can manage it just using the method:

setFrame(int frame)


In our example we passed a value between 0 and 2 because we had 3 frames. We did the same for sprites representing rackets. The sprite object also allows us to perform some kinds of transformations, employing the method:

setTransform(int transf)


The transformation type is defined by the transf parameter (a constant of the Sprite class).

For the tennis court we used a different kind of instrument: the TiledLayer class. That’s a class to employ when we need to fill a large area of the screen with the same pattern. To create a TiledLayer we just need to write:

court = new TiledLayer(colsNumber,rowsNumber,courtImage,10,10);


The TiledLayer just created will manage colsNumber colums and rowsNumber rows, taking 10×10 sized fragments of the image courtImage. To create a specific Tile we need then to fill the matrix with the specific image fragment, accessing it by its index in the source image. In our example we use a 20×10 image, thus containing two different 10×10 tiles. If we want to fill a matrix of colsNumber columns and rowsNumber rows, starting at position 0,0 using the second tile of the image, we can employ the following method:

court.fillCells(0, 0,colsNumber,rowsNumber,2);


With the TiledLayer class we can fill a big area of the screen dividing it into a matrix and then assigning to each cell of the matrix the index of a specific image tile.
After defining the items, we just have to draw them using their paint(Graphics g) method, in a way similar to the one we followed for the Model interface. The paint method is common to the Sprite and the TiledLayer classes, since it’s inhereted from the same Layer superclass.
What about collision management? Every Sprite has the following methods:

public boolean collidesWith(Sprite s, boolean pixelLevel);
public boolean collidesWith(TiledLayer t, boolean pixelLevel);
public boolean collidesWith(Image img, int x, int y, boolean pixelLevel);


to check if it collides with another Sprite, a TiledLayer or an image. These methods are implemented in a native way and so we don’t need to deal with Sprite dimension and position as we did in the previous implementation. In our example we just check for collisions between the ball and one of the rackets. Our result game now is shown in Fig4_3.

Fig 4_3

Conclusion

In this module we learned how we can develop the same game application using two different approaches. We started describing the Canvas methods and how they can be employed to code a simple game. We met a lot of performance problems related to events management and collision detection. Besides, we wrote many lines of code to achieve a simple task. Then, we introduced some improvements employing the double buffering technique. After that, we used the new Gaming API to rewrite the same game application (with some small differences due to the new API features), getting the best perfomance and simplicity, employing the Sprite and TiledLayer classes.

References

[1] MIDP 2.0 API http://java.sun.com/javame/reference/apis/jsr118/
[2] API MIDP 2.0 documentation

Related Download: work.zip

Comments

comments

Leave a Reply

Your email address will not be published.


*