Subscribe to News

How to develop a MIDP game

Author : Jbuenol

From TechnologicalWiki

Jump to: navigation, search

Contents

[edit] Introduction

With J2ME is very easy to create games for mobile devices. Every situation which the user often find in a game is very easy to introduce in a game using J2ME ( character graphics, movement, collisions with the walls , ... ). This article pretends to show the basic principles to create a game for J2ME. It shows explanations about the main concepts to understand the different parts of a game and it also shows a example of a game implementation.

[edit] Preambles

The first thing you have to do is prepare the enviroment. Now install a JAVA tool development.

[edit] Drawing graphics

Canvas is the basic class to work with the screen to low level : Pixels, lines, figures, ... So, GameCanvas improves the Canvas class adding features to load images, sprites,TiledLayers and even a better control of the phone buttons.

With these classes, the developer is not worried due to classic problems like :

[edit] The Structure

A game usually is structured in at least 2 classes :

  • The main MIDlet ( MyGame ).
  • A class which inherits from GameCanvas ( MyClassGameCanvas ). This class is responsible for managing the game. In a game, It's usual each entity ( characters, enemies , ... ) is represents by a derived class of GameCanvas.

[edit] MyGameCanvas MIDlet

The main MIDlet is the main application, which is executed in the phone. It throws in the initialization ( startApp() method ). The class which inherits from GameCanvas is throws from the body. See the next example of a simple implementation of the startApp method of the MIDlet:


     import javax.microedition.midlet.*;
     import javax.microedition.lcdui.*;
     public class MyGame extends MIDlet implements CommandListener {
     private Display display;
     private MyClassGameCanvas myClassGameCanvas;
     private Command exit;
     /* This method is thrown when the game is executed. */
     public void startApp() {
      try {
            /* The MIDlet takes the display control. */
            display = Display.getDisplay(this);
            myClassGameCanvas = MyClassGameCanvas();
            /* Init the thread. The GameCanvas object is responsible for managing of the game. */
            myClassGameCanvas.start();
            /* Add a exit command to exit to the application. */
            exit = newCommand("Exit",Command.EXIT,0);
            myClassGameCanvas.addCommand(exit);
            /* The MIDlet manages the pulsation. */
            myClassGameCanvas.setCommandListener(this);
            display.setCurrent(myClassGameCanvas);
      } catch (Exception ex) {
              System.out.println(ex);
      }
    }
    /* Game in pause. */
    public void pauseApp() { }
    /* Quit the game. */
    public void destroyApp(boolean unconditional) {
         if (myClassGameCanvas != null) {
              /* Stop the thread. */
              myClassGameCanvas.stop();
         }
    }
    /* Push a command. */
    public void commandAction(Command c, Displayable s) {
         if (c.getCommandType() == Command.EXIT) {
              /* J2ME uses gc method to throw the garbage recolector a synchronous.   */
              /* In devices is very important to free the memory as soon as possible  */
              /* due to the small size of the memory in this type of the devices      */
              System.gc();
              destroyApp(true);
              notifyDestroyed();
         }
    }

[edit] MyClassGameCanvas class

This class inherits from the GameCanvas class and is responsible for the game management :

[edit] Keyboard Read

In a game usually exists a starring which is controlled by the user. It must be controlled through a set of keys of the device. The state of the keys is checked through of the method getKeyStates() of GameCanvas. The returned value is the state of the keys. The possible value are the next ones :


    int keyStates = getKeyStates() // keyStates represents the value of the key bit level.
    Values of keyStates :
           DOWN_PRESSED - This constant has a value of 0x0040 (1 << Canvas.DOWN).
           UP_PRESSED - This constant has a value of 0x0002 (1 << Canvas.UP).
           LEFT_PRESSED - This constant has a value of 0x0004 (1 << Canvas.LEFT).
           RIGHT_PRESSED - This constant has a value of 0x0020 (1 << Canvas.RIGHT).
           FIRE_PRESSED - This constant has a value of 0x0100 (1 << Canvas.FIRE).
    ALSO : GAME_A_PRESSED - This constant has a value of 0x0200 (1 << Canvas.GAME_A).
           GAME_B_PRESSED - This constant has a value of 0x0400 (1 << Canvas.GAME_B).
           GAME_C_PRESSED - This constant has a value of 0x0800 (1 << Canvas.GAME_C).
           GAME_D_PRESSED - This constant has a value of 0x1000 (1 << Canvas.GAME_D).
           (They may not be supported on all devices)

We need to create a method to check which buttons are being pressed :

e.g.

    private void readKeyboard() {
        int keyStates = getKeyStates();
        /* LEFT */
        if ((keyStates & LEFT_PRESSED) != 0) {
            /* ACTIONS TO PUSH THE LEFT KEY */
        }
        /* RIGHT */
        if ((keyStates & RIGHT_PRESSED) != 0) {
            /* ACTIONS TO PUSH THE RIGHT KEY */
        }
                        ...
   }

[edit] Drawing on the screen. The Graphics class.

The images are represented in 2D on the screen and each position is defined by a bidimensional point (x,y) :

Graphics is the class which is responsible for drawing in 2D in J2ME.

       Graphics g = getGraphics();
                  ...

Select a colour to draw anything.

       g.setColor(0xbbecf3);

It writes a rectangle from (x,y) to (width,heigth).

       g.fillRect(x,y,width,heigth);

It writes a line from (x,y) to (x2,y2).

       g.drawLine(x,y,x2,y2);

It draws an arc :

  • startAngle - the beginning angle
  • arcAngle - the angular extent of the arc, relative to the start angle
       g.drawArc(x, y, width, height, startAngle, arcAngle);

Create a image from file and shows it.

       image = Image.createImage ("image.png");
       g.drawImage(image,x, y, Graphics.TOP | Graphics.LEFT);

Draw a String of size widths X heigths align in Right-Bottom

       g.drawString("Hello",widths,heigths,Graphics.RIGHT | Graphics.BOTTOM);
                                      ...

[edit] Sprites

size 32 x 24

The Sprite class is responsible for Characters management. It Load the Sprite in a file of X positions. The meaning of this is the character is compound by X different animations.

e.g.

The file /rocket.png contains the rocket of the starring. When a Sprite wrap the file, it's possible to access to any animation. In this case, we have 2 animations. The code to load it is:



     Sprite rocket;
     Imagen image;
     image = Image.createImage("/rocket.png");
     rocket =  new Sprite(image,16,24);

To establish one of another frame as the current frame the setFrame(index) method is used(in this case 0-1):

     rocket.setFrame(0); /* For the first frame */
     rocket.setFrame(1); /* For the second frame */

You can see the second frame represents when the rocket is turnning to the left. There is a method which shows a frame in symmetrical fashion himself, so it's not necessary to create another frame to turn to the right in the image file.

This method is setTransform( modifier ), which applies to the frame a little transformation depending to the parameter passed. For this example to flip the rocket the parameter is Sprite.TRANS_MIRROR. If you don't want to do this transformation the parameter is Sprite.TRANS_NONE

The list of the constants is :

     Sprite.TRANS_NONE, Sprite.TRANS_MIRROR, Sprite.TRANS_ROT90, Sprite.TRANS_ROT180,
     Sprite.TRANS_ROT270, Sprite.TRANS_MIRROR_ROT90, Sprite.TRANS_MIRROR_ROT180,
     Sprite.TRANS_MIRROR_ROT270.

To position the Sprite in a place of the screen is the setPosition() method.

      /* Establish the Sprite on (200,300 */
      rocket.setPosition(200,300);

Finally the paint() method is used to paint on the screen the Sprite;

      /* g is the Graphics object */
      rocket.paint(g);

[edit] TiledLayer

scenario.png file
The result on screen
[edit] Creating the TiledLayer

This class is responsible for building the scenaries. His translation is tile, so, the scenary is compound by small tiles.

  • The first step is to create a file with the tiles for insert them into the image. To do it you must load the image, in this case scenario.png.
    Image tileImages=Image.createImage("\scenario.png);

Now, establish the scenary size :

    /* The size of the scenary is 3x10 with a  */
    /* tile size of 8x8                        */
    TiledLayer scenary=new TiledLayer(3,10,tileImages,8,8);


  • The second step is to create a skeleton of the main scene. This skeleton is represented by an array with the values of the number of the tile in the file :
        int[] = map = {16,17,18,
                       16,17,18,
                       16,17,18,
                       2,2,2,
                       2,2,2,
                       2,2,2,
                       2,2,2,
                       2,2,2,
                       2,2,2,
                       2,2,2}
order in which the tiles are taken

The next step is to insert each tile (of the map created) into the scenary:

     for( int i = 0; i < map.length; i++ ) {
          int column = i % 3;
          in row = i / 3;
          scenary.setCell(column,row,map[i]);
     }

Now, the scenary is formed by the tiles selected. From now, to show the scenary, it only we will do reference to the TiledLayer object.

[edit] Moving the scenary

To move a TiledLayer :

     /* the scenary is positionated on ( x,y ) point */
     scenary.setPosition(scenX,scenY);

When we want to draw the scenary, perhaps we also want to establish an image as background.

For it, We can use the method drawImage() :

     Graphics g;
     Image image;
         ...
     g.drawImage(image,scenX,scenY,0);

TIP : A good efect is to do background moves slower than the scenary. Normally, The scenes farthest move slower than the nearest ones. For it, use a divisor :

     /* The background is 4 times slower than the scenary */
     g.drawImage(image,scenX/4,scenY/4,0);

[edit] LayerManager

You can add more scenaries to you main scene. Use the LayerManager object :

             ...
     /* Create the object */
     layermanager = new LayerManager();
             ...
     /* It adds the scenary to the manager */
     layermanager.append( scenary );
             ...
     /* It method establish the size of the viewer */
     layermanager.setViewWindow(x, y, width, height);
             ...
     /* It controls where the view window is rendered relative to the screen (x , y) point. */
     /* For example, if a game uses the top of the screen to display the current score,     */
     /* the view window may be rendered at (17, 17) to provide enough space for the score.  */
     layermanager.paint(g,x,y); // (0, 0)
             ...
     /* Update the screen */
     flushGraphics();

[edit] Collisions

The most important event in games is the collision. A collision allows the control of the interaction between entities of the scene, e.g. , impacts of weapons, limits of the scene ...

It's posible to know if a Sprite crash with another Sprite, Image or TiledLayer.

There are 2 ways to detect these events :

  • Pixel Level ( pixelLevel=true ). Only is considered as collision when the boundary of a Sprite touchs to the boundary of another entity.
  • Rectangle Level ( pixelLevel=false ). In this case, not the contact between pixels is considered, only between Sprites.

The following example shows how to detect the collision between the starring rocket and each entity of the game :

    Boolean crash;
         ...
    /* Check if rocket has collided with an image */
    crash = rocket.collidesWith(image,x,y,pixelLevel);
    /* Check if rocket has collided with an enemy (GameCanvas object) */
         ...
    crash = rocket.collidesWith(enemy,x,y,pixelLevel);
         ...
    /* Check if rocket has collided with the scenary */
    crash = rocket.collidesWith(scenary,x,y,pixelLevel);

[edit] Main loop ( game engine )

The main execution runs like a loop in which there are a fixed number of events. The execution of the application must be as follows :

  /* In MyClassGameCanvas */
  public void run() {
    Graphics g = getGraphics();
    /* while the user is playing */
    while( playing == true ) {
          /* The next method is the responsible for */
          /* checking every collision.              */
          colisions();    // Method implemented by the user.
          /* Read the key pressed.                  */
          drawScreen(g);  // Method implemented by the user.
          try { Thread.sleep( delay /* in miliseconds */ ); }
          catch (InterruptedException ie) { }
    }
  }

The thread of the game is thrown as follows :

  public void start() {
      Thread t;
      playing = true;
      t = new Thread( this );
      t.start();
 }

... and to stop the execution :

  public void stop() {
     playing = false;
  }

[edit] RMS (Record Management System)

Record Management System (RMS), is both an implementation and API for persistent storage on J2ME devices, such as cell phones. For example, the Highscores in a game, a user profile ... RMS allows save numered registers. The content of a register is an array of bytes. The RecordStore class is the responsible for managing of the registers. Each RecordStore is characterized by a name. A RecordStore can store several registers sorted by an ID. Each ID has linked a value. The next table represents a RecordStore content :

RMS Content
Register ID Content
1 value1
2 value2

RecordStore allows realize operations with the registers, creation, insertion, deletion, ...

These operations need a TRY - CATCH Block manager :

   RecordStore recordstore;
   try {
         /* RMS instructions */
   } catch (Exception error) {
        /* Exception management */    }
  • Opening a registry :
    recordstore = RecordStore.openRecordStore( "name_of_the_record", true );
  • Writing in a registry :
    String outputData = "string_to_write_in_the_register";
    /* String to bytes conversion */
    byte[] byteOutputData = outputData.getBytes();
    /* Add array of bytes to the register 0 con longitud (length) */
    recordstore.addRecord(byteOutputData,0,byteOutputData.length);
  • Reading from a registry :
    /* Cretae an array of bytes to read data.*/
    byte[] byteInputData = new byte[30];
    String inputData = new String ("");
    /* Loop to all registers (getNumRecords) */
    for (int i = 1 ; i <= recordstore.getNumRecords(); i++ ) {
         length = recordstore.getRecord(i, byteInputData,0);
         inputData = inputData + " " + new String(byteInputData);
    }
  • Deleting and Closing a registry :
    /* close RecordStore */
    recordstore.closeRecordStore();
    /* delete all RecordStore */
    recordstore.deleteRecordStore("name_of_the_record");
    /* delete a register (by index)*/
    recordstore.deleteRecord(3);

[edit] Using RMS in the Emulator

The Wirelees Toolkit provides the enviroment the feature to store RMS data. The Recordstore are stored in name_of_devide folder (DefaultColorPhone by default):

    C:\WTK22\appdb\DefaultColorPhone

For example, if we have a RecordStore named "counter", the Record Store is strored in :

    C:\WTK22\appdb\DefaultColorPhone\run_by_class_storage_counter.db

The run_by_class_storage_ prefix is always added to the name.

If you want to store and read complex data the best way is to use Data Streams object, DataInputStream to read and DataOutputStream to write data. The next table represents the methods used to

[edit] See also

Mobile 3D Graphics for J2ME

Geeting Started - 3D Graphics in J2ME -

OpenGL

3D graphics for Java mobile devices PART 1

3D graphics for Java mobile devices PART 2

[edit] References

http://www.j2medev.com/api/midp/javax/microedition/lcdui/game/Sprite.html

http://www.iua.upf.es/~mkalten/teaching/sysintII/midp20/javax/microedition/lcdui/game/TiledLayer.html

http://www.j2medev.com/api/midp/javax/microedition/lcdui/game/LayerManager.html

http://en.wikipedia.org/wiki/Record_Management_System

Main Collaborators