|
Tilt
Java Game
|
|
Introduction
Many new cellphones are Java enabled, allowing custom applications
to be run. Games have been the most commercially successful applications,
although the appearance of phones combined with Personal Digital
Assistants (PDA) may broaden the range of applications available.
Applications are normally downloaded Over The Air (OTA) from a
website using the Wireless Access Protocol (WAP). The user merely
clicks on a link on a WAP page and the application installs.
|
Tilt Game
The
design and coding of a simple game has been undertaken to improve
understanding and assess the potential of this new platform.
The programming tools required are discussed in a separate article
on the Techniques page.
The completed game is available for OTA download from www.circuitswamp.org/wap.
|
|
|
Design Constraints
The wide range of screen sizes, keyboard layouts and processing
power in cellphones makes developing generic java applications a
tricky business. The compromises that must be made to ensure that
the application runs on all platforms severely limits functionality
and performance. Most games are therefore written specifically for
a family of phones from a single manufacturer. Popular games may
be rewritten to provide versions for a number of manufacturers.
These issues notwithstanding, it was decided to design and implement
a generic game, based on rolling a ball through a maze. The game
was designated 'Tilt'
|
Screen
The screen on a mobile can be black & white or colour and the
size may vary from 80 pixels wide by 60 pixels height to (say) 300
pixels in each dimension. Most Java phones have a colour screen
with a resolution of around 100 x 80. The solution adopted for Tilt
is to implement the screen as a scrolling window on a larger playing
area. This works well for all platform type games. The colour issue
is resolved by designing graphics which display well in black and
white as well as in colour. Where necessary, Tilt can detect the
number of colours available and adapt the graphics to suit.
|
Keyboard
Most mobiles can only detect a single keypress at a time. Furthermore
the keys are not usually sufficiently sturdy to withstand constant
bashing. This rules out using directional keys to steer whilst simultaneously
pounding a fire key. Fortunately Tilt only requires directional
control and the application is designed to reduce key pounding by
allowing holding a key down to repeat the action.
|
Processing Power
The processing power available in a cellphone varies widely, but
generally is very low. A game that works correctly on the PC based
emulator may not run at an acceptable frame rate on the phone itself.
This limitation rules out computationally intensive games (e.g.
3D games like Doom) on all but the fastest of phones. Even platform
games can run into problems if the screen has to be redrawn every
frame. Tilt draws the entire game area onto a bitmap and then copies
the visible area to the screen buffer during each frame, which provides
a fairly quick solution.
|
|
Design Concept
The object is to use the directional keys to control the velocity
of the ball as it rolls through the maze. The ball has momentum
and continues in the same direction in the absence of keypresses
until it hits a wall upon which it rebounds. The object is to negotiate
the maze to bring the ball into contact with an 'exit' symbol, which
advances the game to the next level. Should the ball come into contact
with a 'hazard' symbol, then the game restarts at the previous level.
Points are awarded for levels completed and deducted for hazards
encountered.
|
Level Editor
To extend the length of the game, a level editor has been included.
The directional keys select a block in the level to be changed.
Other keys allow selection of the replacement block and colour cycling.
Short Message Service (SMS)
Levels can be transmitted between phones using SMS. This feature
is not yet generic across all phones. The application checks for
the Siemens SMS classes, and provides SMS functions if supported.
|
Detailed Design
A number of additional features have been incorporated to support
the above functionality. Five game levels have been implemented
which can be loaded or saved as a group by commands on the main
menu. An additional 'Inbox' level provides a space to receive incoming
levels sent by SMS.
An option for setting the current level allows a game level to
be selected before editing or playing. Finally a copy command allows
a copy of an existing level to be made for editing purposes or for
a level received in the Inbox to be moved to one of the five game
levels.
The resulting game source code is reviewed
in the following sections. The executable code is available for
OTA download at www.circuitswamp.org/wap.
|
Main Menu Structure
- Start
- Options
- Set Level
- Inbox
- Level 1 to 5 - Load Game - Edit Level - Copy Level
- Inbox
- Level 1 to 5 - Send Level
- Send
- Cancel - Save Game - Exit
|
|
|
|
Tilt.java
This file contains the main application class Tilt and two
alternative classes for controlling the display. Class SmsForm
is used when support for SMS is detected, while class NoSmsForm
is used when no SMS support is available. This technique results
in considerable code duplication, when compared to just encapsulating
all SMS related functions in a separate helper class, but has the
advantage of placing all the high priority keyboard and SMS handling
in a single class. This appears to improve the response time of
the application when receiving SMSs, which proved to be a critical
issue when the application was tested on a Siemens S55 phone.
|
Class Tilt
This implements the standard MIDlet functions startApp,
pauseApp and DestroyApp. The only one of these of
interest is StartApp, which runs when the application is
started.
StartApp
Firstly an attempt is made to dynamically load one of the Siemens
SMS classes, so as to determine whether SMS can be supported. If
SMS is supported the class SmsForm is dynamically loaded,
if no SMS support is present the class NoSmSForm is loaded.
The classes must be loaded dynamically because normal static linking
would cause the application to fail to load on phones without SMS
support.
|
Next the startup splash screen is presented using the class
Splash. The phone will not display the screen until after the
StartApp function is completed. This makes it necessary to
exit immediately and provide some other means of completing the
initialisation. This is implemented by inheriting interface Runnable,
which allow the class to support multithreading. The new thread
is started and the remainder of the initialiation is completed in
the function run. Lastly the display context is changed
to route keyboard and display event to class SmsForm (or
class NoSmsForm).
|
public class Tilt extends MIDlet implements Runnable {
public Display display; public Splash splash; private Thread th = null; public Wrapper sms; public boolean initialised = false;
public void startApp() { /* ... */ } public void pauseApp() { /* ... */ } public void destroyApp(boolean unconditional) { /* ... */ } public void run() { /* ... */ } class Splash extends Canvas { public Splash() { /* ... */ } public void paint(Graphics g) { /* ... */ }
} class Wrapper extends Form { /* ... */ }
|
|
Class SmsForm
This is a very large class as it contains all the keyboard and
SMS handling for the application. Class NoSmsForm is similar,
but omits all SMS related functions. Firstly the initialise
function is called by function run of class Tilt.
This instantiates all the other classes used by the game and constructs
the display for the main menu. Lastly it registers the application
to receive incoming SMSs using the registerListener function.
The application then waits for a keyboard event or an incoming SMS.
Keyboard Events
Keyboard events are captured by classes implementing the interface
CommandListener and result in a call to function commandAction.
This checks for selections from the main menu (and submenus). Where
a submenu is selected, the display is set to the class implementing
the submenu. Such submenu classes include Options, Level,
Save and SendForm. To play the game the Start command
is selected from the main menu, resulting in the class Game
being selected for display. Class Game provides its own keyboard
handling functions, so SMS receive is disabled prior to playing
to avoid SMS receive errors.
|
SMS Receive Events
SMS Receive events are captured by classes implementing the interface
ConnectionListener and result in a call to function receiveData.
This saves the data in a FIFO buffer and then changes control of
the display to class Receive. The FIFO is required as it
is possible that several SMSs are queued for reception and will
result in events regardless of whether the first has been fully
processed. For each SMS received, the user is given the option of
saving it in the Inbox level, or over the top of an existing game
level. The data is uncompressed using function expand and
stored.
SMS Transmit
If the user selects the option to transmit the current level by
SMS, then a form is displayed by class SendForm to allow
entry of the destination phone number. Once confirmed, the level
data is reduced in size using the compress function before
being transmitted as a Siemens Exchange Object (SEO) by function
send. The limited payload of a single SMS means that complex
levels may not compress enough for transmission, in which case the
command to send the SMS is disabled. The send function must exit
immediately after transmission to allow the phone to receive incoming
SMSs, as otherwise sending SMSs to your own number during testing
will fail.
|
class SmsForm extends Wrapper implements CommandListener, ConnectionListener, Runnable { private volatile byte rPtr = 0, wPtr = 0; private byte[][] in = new byte[8][]; Tilt parent; private Thread th1 = null, th2 = null; private boolean terminate = false, saving = false; private Command startCommand; private Command optionsCommand; public Connection conn = null; public Game gm; public Options op; public Level lv; public SendForm tx; public Form hg; public Save sa; public Receive rx; public byte[] sd = new byte[Board.X_MAX * Board.Y_MAX]; public byte[] rd; private byte buf1[] = new byte[256]; private byte buf2[] = new byte[256]; private byte out[]; private boolean changedInbox = false, changed = false; Image level, gamepad, editpad;
SmsForm() { /* ... */ } public void initialise(Tilt p) { /* ... */ } public void reset(boolean reg) { /* ... */ } private void append2(Image img) { /* ... */ } public void commandAction(Command cmd, Displayable disp) { /* ... */ } void send(byte[] data, String receiver) { /* ... */ } private void registerListener() { /* ... */ } private void removeListener() { /* ... */ } public void receiveData(byte[] data) { /* ... */ } private void saveReceivedData(byte[] data) { /* ... */ } public void storeReceivedData(int l) { /* ... */ } private void showAlert(String str) { /* ... */ } public byte[] compress(byte[] in) { /* ... */ } public byte[] expand(byte[] in) { /* ... */ } public void run() { /* ... */ } class Options extends List { /* ... */ } class Level extends List { /* ... */ } class Save extends Form { /* ... */ } class SendForm extends Form { /* ... */ } class Receive extends Form { /* ... */ }
}
|
|
Game.java
This file contains the single class Game. When the class
is instantiated, it creates an instance of the class Board
and determines the size of the System font. It also implements its
own thread by inheriting interface Runnable. This thread
is created by class SmsForm after it has instantiated class
Game.
|
Class Game
This class contains numerous minor functions for initiating and
terminating games and loading and saving game data. It also contains
three important functions (keypressed, run & paint)
which are the core of the game functionality.
Keyboard Events
Function keyPressed is inherited from class Canvas
and handles individual keypress events. When editing a level, each
directional key press moves the cursor one block in the appropriate
direction. The numeric keys are used to select the type of block
to be displayed at the current cursor position. When playing the
game, the directional keys are examined and used to set variable
direction. The keyReleased event is also monitored
during gameplay and used to cancel any existing directional command.
|
Game Thread
The thread contained within function run provides the path
of execution for all autonomous events related to game play. This
is primarily movement of the ball, which is handled by routines
within class Board.
The game thread also supports the loading and saving of games.
This controls access to the game data and reduces the chance of
two threads attempting to access the same data simultaneously.
Drawing the Screen
All classes inheriting from class Canvas are responsible
for drawing their own display when the paint function is
called. Firstly it is determined whether the screen needs to be
cleared. This is only performed when necessary as it is computationally
intensive. Next the paint function in class Board is called to redraw
the board and the ball. Finally, when playing the game, the score
is displayed.
|
class Game extends Canvas implements Runnable { public Wrapper parent; public Board bd; private volatile byte direction = 0; // Last keypress direction private boolean terminateGame = false; private boolean loadGame = false; private boolean saveGame = false; public boolean gamePaused = true; // Game Paused public boolean gameOver = false; // Game Over public int score = 0; // Game Score private Font f; private int height; private int showScore = 0; private boolean clearScreen; public boolean editMode = false; private int colour; Game(Wrapper p) { /* ... */ } public void setLevel(int l) { /* ... */ } public int getLevel() { /* ... */ } public void setEditMode() { /* ... */ } public void setNewGame() { /* ... */ } public void updateScore(int s) { /* ... */ } public void saveGame() { /* ... */ } public void loadGame() { /* ... */ } public void copyLevel(int level) { /* ... */ } public void terminateGame() { /* ... */ } public void keyPressed(int keyCode) { /* ... */ } public void keyReleased(int keyCode) { /* ... */ } public void run() { /* ... */ } protected void paint(Graphics g) { /* ... */ }
}
|
|
Board.java
This file contains the class Board, which in itself contains
two important subclasses (class Images & class Ball).
Class board primarily manages display of the game board,
including scrolling the viewing area within the game level as the
ball moves. Class Images loads all the graphics required
by the game, while class Ball controls ball movement including
collision detection.
|
Class Board
On instantiation, this class determines the display capabilities
and creates class Images to generate suitable graphics. A
large image is created to store the level graphics. Lastly an instance
of class Ball is created.
Apart from the routines to load and save level data, the only noteworthy
function in the class is paint. This renders the complete
board to the screen buffer when a new level is started or a level
is being editted. Subsequent calls during play will cause the appropriate
part of the buffer to be copied to the screen. This results in a
short delay when a new level is started, but a higher subsequent
framerate than if the display was regenerated from scatch every
time. This method works fine for the fairly small board area used
by Tilt, but can run into problems for larger game areas due to
runtime Java limitations on the maximum image size.
|
Class Images
This class creates the images for the blocks used on the board.
It takes account of the availability of colour on the phone so as
to always generate an easily readable display.
Class Ball
Whenever the ball is to be moved, function move checks for
collisions at the proposed new position. Collision detection occurs
north, east, south and west of the ball by calling function Sensor
for each point to be tested. The results are used to determine the
angle at which the collision occurred and appropriate changes are
made to the ball's velocity. If the collision was with the exist
marker, then the level is complete and the next level is started.
However, if the collision was with a hazard marker, then the previous
level is restarted.
|
class Board {
public Game parent;
public Images im;
private Ball bl;
public Image sc;
public Image icon[][];
public int height, width, colours;
public boolean colour;
private boolean newLevel = false;
public int xm, ym;
public int level = 1;
public int xp=0, yp=0, xv=0, yv=0;
static int X_MAX = 15;
static int Y_MAX = 15;
static int Z_MAX = 6;
private int i, j, k;
private RecordStore rs;
public byte b[][][] = { /* ... */ }
Board(Game p) { /* ... */ } public void newLevel(int l) { /* ... */ } public void move(int d) { /* ... */ } public void saveInbox() { /* ... */ } public void loadInbox() { /* ... */ } public void saveGame() { /* ... */ } public void loadGame() { /* ... */ } public void copyLevel(int copyTo) { /* ... */ } public void paint(Graphics g) { /* ... */ } class Images { /* ... */ } class Ball { /* ... */ } Ball(Board p) { /* ... */ } public void newLevel() { /* ... */ } public int Sensor(int x, int y) { /* ... */ } public void move(int d) { /* ... */ }
}
|
Copyright © 2003 Alan Waddington
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version. This program is distributed
in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License
for more details. You should have received a copy of the GNU General
Public License along with this program; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA
|
|