Mark Sohm, Research In Motion
I recently decided to create a game for the BlackBerry handheld. I figured this would be an opportunity to not only further my knowledge in some areas of the BlackBerry APIs, but also to have some fun. As I sat down and started to plan out what I wanted to create and how I was going to do so, I realized that the design issues for a game can be quite different than an application. In an application the look and feel, interaction and general layout is for the most part pre-determined by what information you are presenting to the user. In a gaming environment, screen design and the way a player can interact with the game can be highly customized. Controls and layout must be intuitive so a player does not get frustrated trying to figure out how to play, instead of enjoying actually playing the game. Taking these thoughts into consideration, it was time to make some design decisions.
Topics within this section include:
The Screen
The first item on my design list was the screen. Since I wanted animation and custom graphics, using a screen manager and populating it with fields was not suitable. Allowing the game to scale across different BlackBerry handheld models with different resolutions and color or monochrome displays was also important. To accommodate this, I had to allow of all of the objects to correctly scale in size. The simplest method I found to designing my screen layout was to draw my screen by hand on a piece of graph paper. Since I wasn't dealing with any actual coordinates, all I had to do was to draw everything based on a percentage of the screen resolution. This would allow my game to be portable across multiple models.
After completing my layout design, I was ready to start coding the class that would draw the screen. First I had to retrieve the screen resolution, which is used as a base for all future calculations.
private int screenWidth = Graphics.getScreenWidth();
private int screenHeight = Graphics.getScreenHeight() - 20;
Removing 20 pixels off of the height of the screen was necessary because my game requires some blank space at the bottom of the screen. This shrinks my main height so that I can set all of the objects to take up 100% of the specified screen space. Now I am ready to extend the MainScreen class and override the paint method, which is called when the screen is drawn.
private class TumblerScreen extends MainScreen
{
public void start()
{
}
//draw objects to the screen here
public void paint(Graphics graphics)
{
//Draw the 6 drop slots
int fieldStart = screenWidth / 6;
int fieldEnd = screenWidth / 18;
for (count = 0; count < 6; ++count)
{
graphics.drawRect((fieldStart) * count, 0,
fieldStart, fieldEnd);
}
... snip ...
}
}
The first part of my screen consists of six rectangles that I am setting to be one-sixth of the screen width wide and half of that in height. This allows them to properly scale to fit any size of BlackBerry handheld screen. For now, I can preview what it looks like by creating an instance of my TumblerScreen in the main class.
public class Tumbler
{
private TumblerScreen tumblerScreen;
public static void main(String[] args)
{
Tumbler theApp = new Tumbler();
theApp.enterEventDispatcher();
}
public Tumbler()
{
pushScreen(tumblerScreen);
}
}
Controls
Now that I had the screen drawn, it was time to implement a way for my game to receive input from the player. A ball will appear in one of the top drop slots that were drawn using the drawRect method in the paint method above. What I wanted was to have the ball move left or right when the user scrolls the trackwheel. I also needed input for the user to drop the ball from its current drop slot. To do this, trackwheel, Enter, and Space events are captured by implementing TrackwheelListener and KeyListener, overriding trackwheelClick, trackwheelRoll and keyChar.
When a player scrolls the trackwheel up, the ball will move a column to the left. When they scroll down, the ball will move a column to the right. The currTopBallColumn is used in the paint method determining the drop slot to draw the ball. When the game is in an animation state the user controls are disabled by checking a boolean flag triggered before starting the animation and reset after it has finished.
// Invoked when the trackwheel is rolled.
public boolean trackwheelRoll(int amount, int status, int time)
{
//User controls are disabled if the game
//is animating.
if (!currentlyAnimating)
{
if (amount < 0)
{
if (currTopBallColumn == 0)
currTopBallColumn = 5;
else
--currTopBallColumn;
}
else
{
if (currTopBallColumn == 5)
currTopBallColumn = 0;
else
++currTopBallColumn;
}
tumblerScreen.invalidate();
}
return true;
}
// Invoked when the trackwheel is clicked
public boolean trackwheelClick
(int status, int time)
{
if (!currentlyAnimating)
gamePlay();
return true;
}
Pressing Enter, Space or clicking on the trackwheel will initiate the round in the game and drop the ball from its current drop slot. This will call the gamePlay method that will handle the game calculations. To capture these events, override the keyChar method as shown below. As with the trackwheelRoll and trackwheelClick user input is ignored if the game is currently animating.
public boolean keyChar(char key, int status, int time)
{
boolean retval = false;
//User controls are disabled if the
//game is animating.
if (!currentlyAnimating)
{
switch (key)
{
case Characters.ENTER:
gamePlay();
retval = true;
break;
case Characters.SPACE:
gamePlay();
retval = true;
break;
}
}
return retval;
}
Animation
The next step involved is to create a method that would handle the animation for my game. This method would perform the calculations involved for transitioning or transforming all objects on the screen based on events that occur during the current round. I decided to extend Thread for the animation method in case I required the sleep command to slow down the rate of animation. It turns out that the game didn’t require the animation to be slowed down so the sleep command was not required. However, since my animation was originally set to move one pixel each frame a turn took a painful amount of time on the higher resolution BlackBerry handhelds. To combat this issue I allow the user to select an animation rate from 1 through to 5. These values represent the number of pixels each element will move in a given frame. This allows people to customize the game speed to suit their BlackBerry model.
//Handles all animation calculations and collision detection
private class AnimationThread extends Thread
{
public void run()
{
...snip...
//Repaint the screen
UiApplication.getUiApplication().invokeAndWait(new Runnable()
{
public void run()
{
tumblerScreen.invalidate();
}
});
...snip...
}
The animation method is responsible for a lot of calculations. It calculates the screen coordinates for all moving objects, detects when objects collide (a ball landing on a platform, lever or another ball) and starts or stops an object’s animation sequence based on what it may have collided with. After all calculations for the current frame are complete, the animation method invalidates the screen that causes it to be redrawn by calling the paint method.
High Scores - Persistent Storage
What would any game be without keeping track of high scores? It gives users some incentive to keep playing to increase their own high scores or knock others off the list. Storing high scores on the BlackBerry required the implementation of the Persistable class. The storage requirements are quite low, as I am only storing the top five scores, which mean allocating five int and String values.
public final class TumblerHighScores implements Persistable
{
private String[] highNames = new String[5];
private int highScores[] = new int[5];
public TumblerHighScores(String[] names, int scores[])
{
highNames = names;
highScores = scores;
}
public String[] getNames()
{
return highNames;
}
public void setNames(String[] names)
{
highNames = names;
}
public int[] getScores()
{
return highScores;
}
public void setScores(int[] scores)
{
highScores = scores;
}
}
Code Signing
The Persistable class is a secure API that requires signing of your application. For more information, please review Jonathan Nobels's article, Give Me A Sign, which has been published in this issue.
Finishing Up
At this point the main portions of my game were complete. I was capturing user input, displaying custom objects on the screen, performing animation, and storing high scores. There were a few other items that I had to create to put the finishing touches on the game, such as the creation of a main menu screen, an instruction screen, and a screen to display the high scores. These screens were more traditional in the sense that they didn't require any customization.
The complete source code for my Tumbler game can be found at the end of the article. Feel free to use any code from it that you find useful. I hope this can be a resource for those who might start developing for the BlackBerry platform. Creating a game is definitely a fun way to learn about the development environment and producing something that other people enjoy can be very rewarding; not to forget of course the fun you can have playing your own game. Just don't make it too addicting that it keeps you from starting your next project!
Download: Tumbler Source Code | Tumbler Project