Archive for May, 2008

Creating a Blackberry Game - Part 4

Looking for part 3?

Cue the Objects

Just like Object is the root for all classes in Java, OBJ is the root class for all of our classes in our game that represent sprites on the screen. This is where object oriented programming really shines through for game creation. By setting up a parent class, we can assign properties that are common to all our sprite objects, like position, velocity, a bitmap, etc. Then we can define classes for each individual type of object, like a Hero class, EnemyDrone class, and Photon class that will extend our base OBJ class. This allows them to inherit common properties, and add their own that are specific to them (For example, our hero will have a properties for how many lives it has left, which no other class has, as the rest of the objects simply go away after being destroyed). Since sometimes we work with our objects as OBJ in a vector, we also have a "type" property that is simply an identifying string that allows us to easily determine exactly what our object is (hero, enemy, or photon).

In addition to our OBJ classes, we also have a few static methods that are designed to work with multiple objects as opposed to any single object. CollisionDetect will loop through all objects and check for bounding box collisions, while cleanObjects will loop through all objects and remove dead ones from our object vector.

OBJ.java

 
package com.synthdreams.GalacticBlast;
 
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Graphics;
import java.util.*;
 
// OBJ is our root class for all objects, it defines behaviors and properties
// that are the same for all objects, whether its the hero, enemies, or photons
class OBJ
{
    // Objects can have different states, these are general ones
    public static int STATE_NORMAL = 0;  // Object is alive and functioning normally
    public static int STATE_HIT = 100; // Object just got hit, flash red
    public static int STATE_DYING = 1000; // Object is dying, show an explosion
 
    int _posX, _posY, _velX, _velY;  // All objects have position and velocity
    int _life, _value, _state; // All objects have life, point value, and a current state
    OBJ _parent; // All objects can have a parent (e.g. a photon belongs to the object that shot it)
    String _type; // Type is a string that stores what the object is for easy reference
    Bitmap _bitmap; // Bitmap to be drawn for the object
 
    // Objects are initialized globally with a position
    OBJ(int passX, int passY)
    {
        _posX = passX;
        _posY = passY;
        _velX = 0;
        _velY = 0;
        _life = 1;
        _value = 0;
        _state = STATE_NORMAL;
        _type = "generic";
 
    }
 
    // Setters and getters
    public int getX() { return _posX; }
    public int getY() { return _posY; }
    public void setX(int passX) { _posX = passX; }
    public void setY(int passY) { _posY = passY; }
    public int getVelX() { return _velX; }
    public int getVelY() { return _velY; }
    public void setVelX(int passX) { _velX = passX; }
    public void setVelY(int passY) { _velY = passY; }
    public int getLife() { return _life; }
    public void setLife(int passLife) { _life = passLife; }
    public int getValue() { return _value; }
    public int getState() { return _state; }
    public void setState(int passState) { _state = passState; }
    public String getType() { return _type; }
    public Bitmap getBitmap() { return _bitmap; }
    public OBJ getParent() { return _parent; }
 
    // Process, think, and damager are all specific to the object, so these are blank
    public void process() { }
    public void think(Vector passObjects) { }
    public void damage() { }
 
    // In our game, firing means shooting a photon and playing the zap tone, this is the same
    // for all objects
    public void fire(Vector passObjects, OBJ passParent, int passVelocity)
    {
        Photon tempPhoton;
 
        // If the photon is going up, start it from the top of the object firing it.
        // If its going down, start it from bottom of object firing it
        if (passVelocity > 0)
            tempPhoton = new Photon(passParent.getX(),passParent.getY()+passParent.getBitmap().getHeight(), 0, passVelocity, passParent);
        else
            tempPhoton = new Photon(passParent.getX(),passParent.getY(), 0, passVelocity, passParent);    
 
        // set X coordinate of photon to the middle of the object firing it
        tempPhoton.setX(tempPhoton.getX() + passParent.getBitmap().getWidth()/2);
 
        // Add the photon object to our object vector
        passObjects.addElement(tempPhoton);
 
        // Play a zap tone
        GamePlay.snd.playSound();
 
    }
 
    // Collision detection routine using an AABB test (Axis Align Bounding Box).  This
    // is a quick and easy test great for games with simple squarish sprites which simply
    // looks to see if the bounding boxes overlap in any way.
    public static void collisionDetect(Vector passObjects)
    {
        OBJ tempObject1, tempObject2; // temporarily points to the two objects being tested
        boolean intersect, check; // flags during testing
 
        // Loop through all objects in our vector
        for (int lcv = 0 ; lcv < passObjects.size() ; lcv++)
        {
            // Set tempObject1 to the current object
            tempObject1 = (OBJ) passObjects.elementAt(lcv);
 
            // Now loop from the current object to the end of the vector
            for(int lcv2 = lcv ; lcv2 < passObjects.size() ; lcv2++)
            {
                // Set tempObject2 to the current object of the nested loop
                tempObject2 = (OBJ) passObjects.elementAt(lcv2);
 
                // See if we need to check for collision (e.g. some objects dont matter if
                // they collide, enemy with enemy or fire with fire for example)
 
                // Assume we dont need to check
                check = false;
 
                // Hero and enemy would be something to check for
                if (tempObject1.getType() == "hero" && tempObject2.getType().startsWith("enemy"))
                    check = true;
 
                // Hero and enemy fired photons would be something to check for
                if (tempObject1.getType() == "hero" && tempObject2.getType().startsWith("fire") && tempObject2.getParent().getType().startsWith("enemy"))
                    check = true;
 
                // Enemy and hero fired photons would be something ot check for
                if (tempObject1.getType().startsWith("enemy") && tempObject2.getType().startsWith("fire") && tempObject2.getParent().getType() == "hero")
                    check = true;
 
                // If our check flag is set to true, and the state of the objects is normal
                // (e.g. an object in a hit or exploded state can't collide with something),
                // then lets check for the actual collision
                if (check && tempObject1.getState() == 0 && tempObject2.getState() == 0)
                {
 
                    // We assume the two objects collided
                    intersect = true;
 
                    // Left and Ride sides of bounding box check
                    if (!(Math.abs((tempObject1.getX() + tempObject1.getBitmap().getWidth()/2) - (tempObject2.getX() + tempObject2.getBitmap().getWidth()/2)) <= tempObject1.getBitmap().getWidth() / 2 + tempObject2.getBitmap().getWidth() / 2))
                        intersect = false;
 
                    // Top and Bottom sides of bounding box check
                    if (!(Math.abs((tempObject1.getY() + tempObject1.getBitmap().getHeight()/2) - (tempObject2.getY() + tempObject2.getBitmap().getHeight()/2)) <= tempObject1.getBitmap().getHeight() / 2 + tempObject2.getBitmap().getHeight() / 2))
                        intersect = false;
 
                    // If the objects collided, damage each one.
                    if (intersect)
                    {
                        tempObject1.damage();
                        tempObject2.damage();
                    }
                }
            }
        }
    }
 
 
    // Clean up objects that have died or are way off screen.
    public static int cleanObjects(Vector passObjects)
    {
        OBJ tempObject; // Temporary points to object we're checking
        boolean delFlag; // Flag if we should get rid of it or not
        int scoreAdd; // Aggregate points to add to user's score
 
        // Start out with no points added
        scoreAdd = 0;
 
        // Loop through all objects in our vector
        for (int lcv = 0 ; lcv < passObjects.size() ; lcv++)
        {
            // Set tempObject to current object
            tempObject = (OBJ) passObjects.elementAt(lcv);
 
            // Assume we're not deleting it
            delFlag = false;
 
            // Check the object's state.  If its been dying for 10 refreshes, its
            // time to get rid of it.
            if (tempObject.getState() > STATE_DYING + 10)
            {
                // In the case of our hero, the player has lives, so if the hero
                // dies, we need to check to see if any lives are left before
                // quitting the game
                if (tempObject.getType() == "hero")
                {
                    // If there are lives left...
                    if (((Hero)tempObject).getLives() > 0)
                    {
                       // Decrement the number of lives left, set state, bitmap,
                       // and position back to normal
                       ((Hero)tempObject).setLives(((Hero)tempObject).getLives()-1);
                       tempObject.setLife(5);
                       tempObject.setState(STATE_NORMAL);
                       tempObject.setX(Graphics.getScreenWidth() / 2);
                       tempObject.setY(Graphics.getScreenHeight() - 50);
                       tempObject._bitmap = Bitmap.getBitmapResource("herogame.png");
                    }
                    else
                    {
                       // The player is out of lives, lets destroy the hero (which will
                       // end the game)
                       delFlag = true;
                    }
                }
                else
                {
                    // Enemies only have 1 life, so they are set to be deleted, and we
                    // add their value to the total score the player got this cleanup.
                    delFlag = true;
                    scoreAdd += tempObject.getValue();
                }
            }
 
            // If the object is a photon...
            if (tempObject.getType() == "firephoton" && delFlag == false)
            {
                // Delete if off left side of screen
                if(tempObject.getX() + tempObject.getBitmap().getWidth() < 0)
                    delFlag = true;
 
                // Delete if off right side of screen
                if(tempObject.getX() > Graphics.getScreenWidth())
                    delFlag = true;
 
                // Delete if off top of screen
                if(tempObject.getY() + tempObject.getBitmap().getHeight() < 0)
                    delFlag = true;
 
                // Delete if off bottom of screen
                if(tempObject.getY() > Graphics.getScreenHeight())
                    delFlag = true;
            }
 
            // We need to check for enemies that are way off screen.
            // Normally enemies will swarm around here, but there are
            // also kamikaze enemies that will aim toward the hero,
            // and if miss, keep going forever
            if (tempObject.getType() == "enemydrone" && delFlag == false)
            {
                // Check each of the four sides of the screen, if an enemy is
                // past any of them plus 100 pixels its considered lost.  No
                // points are scored for these enemies though.
                if(tempObject.getX() + tempObject.getBitmap().getWidth() < -100)
                    delFlag = true;
 
                if(tempObject.getX() > Graphics.getScreenWidth() + 100)
                    delFlag = true;
 
                if(tempObject.getY() + tempObject.getBitmap().getHeight() < -100)
                    delFlag = true;
 
                if(tempObject.getY() > Graphics.getScreenHeight() + 100)
                    delFlag = true;
            }
 
            // If the delete flag is true
            if (delFlag)
            {
                // Remove the object from the vector
                passObjects.removeElementAt(lcv);
 
                // Set our temporary object to null
                tempObject = null;
 
                // If this was our hero object (eg location 0), then we return a -1
                // to communicate this back to our processing routine
                if (lcv == 0)
                {
                    return(-1);
                }
            }
        }
 
        // If our hero is still alive, we return to the number of points scored
        return scoreAdd;
    }
 
    // A quick method simply to ensure screen bound objects don't go off screen.
    // For now this is just our hero, but there may be other objects that function
    // like this.
    public void boundToScreen()
    {
      // If the coordinates are off screen in any direction, correct the coordinate and
      // set that velocity to 0
      if (_posX < 0)
      {
         _posX = 0;
         _velX = 0;
      }
 
      if (_posY < 0)
      {
          _posY = 0;
          _velY = 0;
      }
 
      if (_posX > Graphics.getScreenWidth() - _bitmap.getWidth())
      {
          _posX = Graphics.getScreenWidth() - _bitmap.getWidth();
          _velX = 0;
      }
 
      if (_posY > Graphics.getScreenHeight() - _bitmap.getHeight())
      {
          _posY = Graphics.getScreenHeight() - _bitmap.getHeight();
          _velY = 0;
      }
 
    }
 
}
 
// Hero object
class Hero extends OBJ
{
   int _lives; // The hero (the player) has multiple lives, different from other objects
 
   Hero(int passX, int passY)
   {
      super(passX, passY);
 
      // Set bitmap to herogame
      _bitmap = Bitmap.getBitmapResource("herogame.png");    
 
      // Set coordinates, velocity, lives, and type identifier
      _velX = 0;
      _velY = 0;
      _life = 5;
      _lives = 2;
      _type = "hero";
   }    
 
   // Hero processing
   public void process()
   {
      // The hero has a max velocity of 10 in any direction
      if (_velX > 10)
        _velX = 10;
 
      if (_velX < -10)
        _velX = -10;
 
      if (_velY > 10)
        _velY = 10;
 
      if (_velY < -10)
        _velY = -10;
 
      // Movement is simply adding velocity to position
      _posX += _velX;
      _posY += _velY;  
 
      // If the current life of the hero is less than 1, we check the state of hero
      if (_life < 1)
      {
 
        // If the hero is currently in a normal state, it is put into a dying state
        if (_state == STATE_NORMAL)
        {
            // Set hero bitmap to an explosion
            _bitmap = Bitmap.getBitmapResource("explosiongame.png");    
 
            // Set state to dying
            _state = STATE_DYING;
 
            // Vibrate the phone
            GamePlay.snd.vibrate(180);
        }
 
      }
 
      // If the hero is in an abnormal state (hit or dying), there is additional processing
      // that must happen
      if (_state > STATE_NORMAL)
      {
          // First, we increment the state by 1.  Non normal states are temporary and are checked
          // for terminate by seeing if the initial state value plus a certain number of
          // refreshes has been reached.  It allows the object to be in a abnormal state for 10
          // refreshes, 3 refreshes, however many necessary, and then continue onto
          // some other state, either back to normal or deleted.
          _state++;
 
          // If the ship has been in the hit state for more than 3 refreshes, set it back to normal
          if ((_state > STATE_HIT + 3) && (_state < STATE_DYING))
          {
            _state = STATE_NORMAL;
            _bitmap = Bitmap.getBitmapResource("herogame.png");
          }
      }
 
      // Bound our hero to the screen
      boundToScreen();
   }
 
   // Our hero's damage method
   public void damage()
   {
      // Decrease life (not lives) by 1
      _life--;
 
      // If life is still above 0, change our hero to the hit state
      if (_life > 0)
      {
        _bitmap = Bitmap.getBitmapResource("herogamehit.png");
        _state = STATE_HIT;
      }
   }
 
   // Our hero's fire method for when it fires a photon
   public void fire(Vector passObjects)
   {
      //Call the parents fire method, with a velocity of -20
      super.fire(passObjects, this, -20);
   }
 
   // Lives setter and getter
   public int getLives() { return _lives; }
   public void setLives(int passLives) { _lives = passLives; }
 
}
 
 
// Photon class
class Photon extends OBJ
{
 
    // Initializes like hero object, only has photon.png as a bitmap, and starts
    // with a program specified velocity
    Photon(int passX, int passY, int passVelX, int passVelY, OBJ passParent)
    {
      super(passX, passY);
      _bitmap = Bitmap.getBitmapResource("photon.png");
      _velX = passVelX;
      _velY = passVelY;
      _type = "firephoton";
      _parent = passParent;
    }
 
    // Photon processing is simple, we simply move the object in accordance to its
    // velocity
    public void process()
    {
 
      // Position = Position + Velocity
      _posX += _velX;
      _posY += _velY;  
 
    }
 
    // When a photon is damaged, it simply dies and disappears instantly.
    // This is accomplished by setting the state to STATE_DYING + 11.  Since
    // we put objects in the dying state for 10 frames before delition, this
    // immediate deletes it.
    public void damage()
    {
      _life = 0;
      _state = STATE_DYING+11;
    }
 
}
 
// Our enemy class
class EnemyDrone extends OBJ
{
   int _AIRoutine; // AI routine stores what kind of enemy this is, normal or kamikaze
 
   // Enemy initialization is like other objects, except we randomly choose what
   // kind of AI routine it should use
   EnemyDrone(int passX, int passY)
   {
      super(passX, passY);
      _bitmap = Bitmap.getBitmapResource("enemygame.png");
      _value = 50;
      _type = "enemydrone";
 
      // Statistically, 3 out of 5 enemies are normal, 2 are kamikaze
      if (GamePlay.rndGenerator.nextInt() % 10 < 6)
         _AIRoutine = 0;
      else
         _AIRoutine = 1;
 
   }    
 
   // Enemy processing is identical to hero processing, except enemies don't
   // have a hit state, since they have one life (not to be confused with lives),
   // one hit kills them, hence theres no need for a hit state
   public void process()
   {
      _posX += _velX;
      _posY += _velY;  
 
      if (_life < 1)
      {
        if (_state == STATE_NORMAL)
        {
            _bitmap = Bitmap.getBitmapResource("explosiongame.png");
            _state = STATE_DYING;
            GamePlay.snd.vibrate(180);
        }
 
        _state++;
      }
 
   }
 
   // The think method is where the individual enemy AI takes place
   public void think(Vector passObjects)
   {
 
      // If they've blown up, they can no longer think
      if (_life < 1)
         return;
 
      // We start off with a velocity of 0 in both directions
      _velX = 0;
      _velY = 0;
 
      // Grab a handle on the hero object so we know how to direct our enemies
      Hero tempHero = (Hero) passObjects.elementAt(0);
 
      // If we're in normal AI mode
      if (_AIRoutine == 0)
      {
        // If hero is to our right, set velocity to right
        if (_posX + _bitmap.getWidth() / 2 < tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = 5;
 
        // If hero is to our left, set velocity to our left
        if (_posX + _bitmap.getWidth() / 2 > tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = -5;
 
        // Enemies try to stay 40 pixels above hero
        if (_posY + _bitmap.getHeight()  < tempHero.getY() - 40)
            _velY = 5;
 
        // If enemy is below hero, they move up
        if (_posY > tempHero.getY() + tempHero.getBitmap().getHeight())
            _velY = -5;
 
        // Add a little bit of random movement in
        _velX += GamePlay.rndGenerator.nextInt() % 4 - 2;
        _velY += GamePlay.rndGenerator.nextInt() % 4 - 2;
 
        // Random firing, fire 1 in 7 times thinking
        if (GamePlay.rndGenerator.nextInt() % 4 == 1)
        {
            fire(passObjects);
        }
     }
     else
     {
        // Kamikaze AI is the same for as above for horizontal processing.
        if (_posX + _bitmap.getWidth() / 2 < tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = 5;
 
        if (_posX + _bitmap.getWidth() / 2 > tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = -5;
 
        // For vertical though, the enemy drone is always going downward at a faster rate
        _velY = 8;
     }
   }
 
   // If enemy is damaged, their life is decreased
   public void damage()
   {
     _life--;
   }
 
   // An enemy firing calls the Object's fire method with a downward direction
   public void fire(Vector passObjects)
   {
      super.fire(passObjects, this, 20);
   }
 
}
 

Now that we have our Gameplay and OBJ defined, we've taken care of pretty much all of our game logic. Now all that's left is the code to drive our graphics and sound processing. These classes are called, aptly, GFX and SND!

Onto GFX in part 5...

Creating a Blackberry Game - Part 3

Looking for part 2?

Gameplay

Now that our initial menu switches control to the Gameplay class (by putting an instance of it on the screen stack - [gameplay extends FullScreen]), we need to actually define it! Gameplay takes care of high level game functionality by multitasking actions via threads.

One thread, "Refresher", takes care of creating new objects, looping through existing objects and telling them to process themselves, then checks for collision detection, adds points gained to total score, tells the graphics engine to redraw the screen, etc. It is basically the primary heartbeat loop that ensures everything keeps updating properly.

The other thread is the EnemyAI thread, which is responsible for looping through all the objects and telling enemies to run their intelligence processing, which sets their movement and firing.

Please note - this is one of the areas where the Blackberry emulator seemed to differ from the real thing. On the emulator, thread processing would simply halt if no key was being pressed, while on the Blackberry it would have no issues. While this might be a fault of mine, it doesn't change the fact that the emulator acted very much differently than the real thing.

Lastly, gameplay has keychar and navigationMovement overridden to process when the Escape or Spacebar is pressed, as well as when the trackball is moved.

GamePlay.java

 
package com.synthdreams.GalacticBlast;
 
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.container.FullScreen;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.system.Characters;
import net.rim.device.api.system.Application;
import java.lang.Thread;
import java.lang.Math;
import java.util.*;
 
// Gameplay extends FullScreen only, it doesn't need the functionality of the
// MainScreen object.  Either are screens though, and can be pushed onto the
// screen stack
// Special Note: For those familiar with the concept double buffering, supposedly
// the rim UI automatically does this.  It seems to from what I can see, I
// notice no tearing or graphic anomalies.
// For those unfamiliar, if you blit (write) graphic data directly primary graphic
// buffer, it is out of sync with the refresh of the screen.  This can cause
// tearing or other weird graphic issues, as half of the screen is still being shown
// where the objects were, and the other half where the objects are now.  This
// is solved by first drawing to a back buffer, and then flipping the entire
// back buffer with the front during the screen's refresh.  In java there are
// specific game-oriented screen classes to do this, but supposedly the RIM
// libs do this automatically.
public class GamePlay extends FullScreen
{
    // These three objects are called from many places and are made public
    public static GFX gfx;  // Static object for graphics control
    public static SND snd;  // Static object for sound control
    public static Random rndGenerator; // Static object for random numbers
 
    Refresher _refresher;  // Thread that continually refreshes the game
    EnemyAI _enemyai; // Thread when the enemies think
    Vector _objects; // A vector of all objects currently in play
 
    boolean _active; // Flag if our game is currently active or not (eg did we lose)
    int _score; // Player's current score
 
    // Getters for active and score
    boolean getActive() { return _active; }
    int getScore() {return _score; }
 
    // Refresh is a type of thread that runs to refresh the game.
    // Its job is to make sure all the processing is called for each object, update the background,
    // update score, check for end of game, etc.  This is the main heartbeat.
    private class Refresher extends Thread
    {
        // When the object is created it starts itself as a thread
        Refresher()
        {
           start();
        }
 
        // This method defines what this thread does every time it runs.
        public void run()
        {
            // Temporary variable that stores the score value of all
            // the objects that were just cleaned (destroyed/removed)
            // return a negative number if we died
            int cleanReturn;
 
            // This thread runs while the game is active
            while (_active)
            {
 
                // Level population/processing, responsible for creating new enemies
                processLevel();
 
                // Perform physics by calling each object's process command.  Objects
                // are unique and control their own physics (e.g. a photon blast can move
                // faster than our hero ship), so we loop through all our objects
                // and call the process method on each.
                for (int lcv = 0 ; lcv < _objects.size() ; lcv++)
                {
                    ((OBJ) _objects.elementAt(lcv)).process();
                }
 
                // Collision detection of objects.  If they collide, this method
                // will call the damage method of each object, which might lead
                // to no life for the object
                OBJ.collisionDetect(_objects);
 
                // Clean up stuff that's gone (e.g. life of 0 and in
                // destroyable state, eg explosion graphic), quit if we were destroyed
                cleanReturn = OBJ.cleanObjects(_objects);
 
                // If we didn't die, add the score, redraw, etc.
                if (cleanReturn >= 0)
                {
                   _score += cleanReturn;
 
                   // Now that all our processing is done, tell the graphics engine to
                   // redraw the screen through a call to invalidate (invalidates the screen
                   // and automatically causes a redraw)
                   invalidate();
                }
                else
                {
                   // if we died, mark active as false.
                   _active = false;
                }
 
                try
                {
                    // Attempt to sleep for 50 ms
                    this.sleep(50);
 
                }
                catch (InterruptedException e)
                {
                    // Do nothing if we couldn't sleep, we don't care about exactly perfect
                    // timing.
                }
            }
        }
 
    }
 
    // We have a separate thread that takes care of enemy AI.  This allows us to control how
    // quickly the enemies think outside of our main refresh thread.  That way we can make
    // dumb enemies that think much slower than the action happening around them, or smart
    // enemies that think as fast.
    private class EnemyAI extends Thread
    {
        EnemyAI()
        {
           start();
 
        }
 
        public void run()
        {
            // Just make sure the thread doesn't accidentally run when the game is over
            while (_active)
            {
                // Loop through all the objects and call the think method, which
                // controls the actions of that object.  Technically, this call's
                // the hero's think method as well, but Hero doesn't have the
                // think method overridden from the parent's, which is just blank,
                // so the hero does no automatic thinking.
                for (int lcv = 0 ; lcv < _objects.size() ; lcv++)
                {
                    if (_active)
                        ((OBJ) _objects.elementAt(lcv)).think(_objects);
                }
 
                try
                {
                    // Enemies think 5 times a second
                    this.sleep(1000/5);
 
                }
                catch (InterruptedException e)
                {
                    // Do nothing, again we don't care if timing isn't exact
                }
            }
 
        }
 
    }
 
    public GamePlay()
    {
        snd = new SND(); // Create sound engine
        gfx = new GFX(); // Create graphics engine
 
        rndGenerator = new Random(); // Create random number generator
 
        _active = true; // Mark the game as active
        _score = 0; // Start with a score of 0
 
        // Set our background to stars.png with a speed of 3 pixels per refresh
        gfx.initBackground("stars.jpg", 3);
 
        // Start our music playing.  Something odd I noticed with my Blackberry 8830,
        // which may either be a bug or some functionality I'm missing from the
        // sound routines, is the volume starts out very quiet the first time you
        // start playing. If you stop the music playing and play it again, the volume
        // is fine.  This might not be true for all Blackberrys, but it doesn't hurt
        // anything to start, stop, and start again, so that's what we've done here,
        // just to solve the volume bug/misundertsanding.
        snd.playMusic("music.mid");
        snd.stopMusic();
        snd.playMusic("music.mid");
 
        // Create a new vector to hold all the active objects (hero, enemies, photons, etc)
        _objects = new Vector();
 
        // Our hero will always be the very first object in the vector.
        _objects.addElement(new Hero(Graphics.getScreenWidth() / 2, Graphics.getScreenHeight() - 50));
 
        // Create the refresher and enemy AI last, we want to make sure all our objects are setup first
        // so the threads don't make use of uninitialized objects
        _refresher = new Refresher();
        _enemyai = new EnemyAI();
 
    }
 
    // Process level is responsible for new computer generated events in the game.  In our
    // case, the only one really is creating new enemies.
    public void processLevel()
    {
        OBJ tempObject;
 
        // Throw a new enemy in every 25 pixels.  This can be made more complex if desired
        // of course
        if (gfx.getBackPos() % 25 == 0)
        {
            // Create a new enemy drone with the X coordinate somewhere between the two edges of the
            // screen
            tempObject = new EnemyDrone(rndGenerator.nextInt() % Graphics.getScreenWidth(), 0);
 
            // Set the Y coordinate to above the screen, so it comes in from the top
            tempObject.setY(tempObject.getY() - tempObject.getBitmap().getHeight() + 3);
 
            // Add the enemy to the object vector
            _objects.addElement(tempObject);
        }
    }
 
 
 
    // This method is called when the invalidate method is called from the refresh thread.
    // We have it passing the graphics object over to our graphics engine so our
    // custom graphics routines can take care of any drawing necessary.
    protected void paint(Graphics graphics)
    {
       gfx.process(graphics, _objects, _score, ((Hero)_objects.elementAt(0)).getLives());
    }
 
    // The keyChar method is called by the event handler when a key is pressed.
    public boolean keyChar(char key, int status, int time)
    {
 
        boolean retVal = false;
 
        switch (key)
        {
            // If escape is pressed, we set the game to inactive (quit)
            case Characters.ESCAPE:
                _active = false;
                retVal = true;
                break;
 
            // If the spacebar is pressed, we call the fire method of our
            // hero object, causing him to fire a photon
            case Characters.SPACE:
                ((Hero)_objects.elementAt(0)).fire(_objects);
 
                retVal = true;
                break;
 
            default:
               break;
        }
 
        return retVal;
    }
 
    // The navigationMovement method is called by the event handler when the trackball is used.
    protected boolean navigationMovement(int dx, int dy, int status, int time)
    {
        // If the trackball was scrolled in the horizontal direction, we add that amount to
        // our hero's X velocity
        if (dx != 0)
            ((OBJ)_objects.elementAt(0)).setVelX(dx/Math.abs(dx)*5+((OBJ)_objects.elementAt(0)).getVelX());
 
        // If the trackball was scrolled in the vertical direction, we add that amount to
        // our hero's Y velocity
        if (dy != 0)
            ((OBJ)_objects.elementAt(0)).setVelY(dy/Math.abs(dy)*5+((OBJ)_objects.elementAt(0)).getVelY());
 
        return true;
    }
 
}
 

Gameplay together with the OBJ set of classes is the heart of the logic of our game. Gameplay represents the manager, the high level control that directs actions, while the OBJ classes do the nitty gritty work of processing for each object (objects being things like the hero, the enemies, and photons). Now that we've seen Gameplay, lets take a look at OBJ (objects).

Onto the objects in part 4...

Creating a Blackberry Game - Part 2

Looking for part 1?

Creating the Initial Class

Now that our project is open, let's add the source for our initial class. While the initial class name doesn't have to match the project name, it is a good idea to avoid confusion. Most importantly, the initial class must contain a static "main" method, and must be the only class in the project to do so. This is where the Blackberry will begin execution. It is typical for this main method to instantiate the initial class. The class constructor will immediately create the menu screen and push it onto the screen stack (read below), and the main function will enter into the event dispatcher / message pump loop, allowing the program to intercept keystrokes and trackball movement.

Note - my project name is GalacticBlast - you'll of course want to substitute that for whatever your project/class name is.

GalacticBlast.java

 
package com.synthdreams.GalacticBlast;
 
import net.rim.device.api.ui.UiApplication;
 
/**
 * Initial main class that starts the ball rolling
 * UiApplication is extended to provide Blackberry
 * functionality, specifically the Event Dispatcher
 * and Screen pusher
 */
public class GalacticBlast extends UiApplication
{
 
    public static void main(String[] args)
    {
        GalacticBlast GalacticBlast = new GalacticBlast();
 
        // The Blackberry's message pump, handles key
        // presses and system events
        GalacticBlast.enterEventDispatcher();
    }
 
    public GalacticBlast()
    {
        // The first thing we do is show the menu screen, defined by the
        // 'Menu' class.  We accomplish this with a call to pushScreen,
        // which puts screens on the stack (Class specified must be a type
        // of screen, such as MainScreen).  The top most screen is the
        // one shown.  Our first one will be the menu, and later the game
        // itself will sit on top of this.  When the game quits, it will
        // pop that screen off the stack and return to the menu screen.
        pushScreen(new Menu()<