|
Features

Dealing With A Fragmented Java Landscape For Mobile Game Development
Coping With Device Fragmentation
In
the summer of 2001 we at Sumea realized that the number of platforms
our games would have to support in the future would be vast. As
such, we started to develop technology and tools to ease our daily
work. We targeted five key areas for our efforts: reusable components,
scalability, localization, profile management, and testing.
Reusable
components. Development cycles for mobile games are short compared
to traditional game development--currently development cycles range
from three to six months. On the other hand, when comparing one
mobile game to another, one can see that there are many components
in common: The games have a certain menu structure, program flow,
features such as sounds, vibration and high scores, instruction
page, and so on. In figure 1, only the screen framed in yellow is
unique to this specific game-all the other screens can be reused
in other games. By reusing components we shorten the development
time of the game significantly.
Reusing
code is an obvious advantage. To take full advantage of these components,
we defined an abstract interface between the game and the generic
components. We call these generic components our "game toolkit"
The game has access to the toolkit as well as to standard APIs such
as MIDP and CLDC, and the toolkit takes care of interfacing with
proprietary APIs. This interface makes it possible to develop the
game once, and exchange the underlying implementation to support
any handset or any third-party API (see figure 2). For any new device
or high score interface that didn't exist at the time the game was
originally created, we update only the toolkit part, and keep the
game code intact.
Scalability.
Making games scale to the capabilities of the various mobile devices
is not always easy. Games cannot be developed for the lowest common
denominator platform; we have to take advantage of the capabilities
each platform provides. This means that the game should look top-notch
on each platform. For example, in a racing game we may have raindrops
and lightning effects for high-end devices, and leave them out for
the low-end devices. Scalability means just this: to make a customized
version of the game for a certain platform as cost-efficiently as
possible.
Scalability
covers static content, such as graphics and audio, game specific
features, such as level of detail, and devicespecific features.
A good example of device-specific features are game controls. The
devices out in the shops have various different keypad configurations:
Most of the devices have an ordinary 3×4 keypad, some have
game controllers, some support multiple key presses, and some devices
have exotic keypad layouts, such as the Nokia 3650.
In
the case of static content, the problem is solved by using different
sets of content. For graphics this means that we use different sets
of game graphics, and full-screen images are be customized for each
screen resolution (see figure 3). For audio, the situation is even
simpler: Either the device does not support sounds, it supports
MIDI, or uses some proprietary audio technology.
On
the coding side, the problem can be solved in a similar way: by
introducing different sets of features. A feature can be, for example,
a lightning effect, support for sounds, or extra levels. We solved
this problem in two ways: developing features at the component level
and at the function level.
Component-level
features are easy: we define an interface for a component, and select
the version of the component to be used at compile time. In figure
4 you can see a practical example of this: We have a static component
Sounds, which is used to play sounds in a game. The interface will
remain the same, but the component can be exchanged to contain any
sound implementation. This way we will get rid of an extra file
for an actual interface, and Java obfuscators can optimize a class
containing only static functions.
|
|
public class Sounds
{
public static void initialize( MIDlet mid
)
{
try
{
sm_title
= readSound( mid, "/title.ott" );
:
}
public
class Sounds
{
public static void initialize( MIDlet mid
)
{
try
{
sm_title
= Manager.createPlayer(
mid.getClass().getResourceAsStream(
"/title.mid"
), "audio/midi" );
:
}
|
 |
 |
 |
Figure
4: Code example of sound component for Nokia proprietary mono-sounds
and Mobile Media API MIDI-sounds
|
Function-level
features are more complicated: applications for wireless devices
have to be optimized for speed and memory as well as for size. This
means, for example, that we cannot have numerous different files.
That, in turn, means that we must forget about pure object-oriented
programming for a moment, even when using compile-time interfaces.
Component-level features are not sufficient, as we cannot divide
the code into as many components as we would need to. To tackle
this problem we developed code preprocessing tools: If a certain
tag is defined, the code remains intact-otherwise it is removed.
An extreme example of this method can be seen in figure 5. The example
is taken from a game in which the player can collect coins. When
a coin is collected, a sound effect is played. When all the coins
have been collected, a bonus game is opened. We defined two features:
whether sounds are used (USE_SOUNDS) and whether the bonus game
is included (BONUS_GAME). Either one of these features can be enabled
or disabled. In figure 6 you can see the preprocessed code, where
sounds have been disabled and the bonus game is enabled.
|
|
// <IF_DEFINED> BONUS_GAME
if ( m_coinCnt == m_totalCoinCnt )
{
m_bonusGameOpened[0] = 1;
m_showBonusGameActivated = 2000;
// <IF_DEFINED> USE_SOUNDS
Sounds.playAllCoinsCollected();
// </IF_DEFINED> USE_SOUNDS
}
// </IF_DEFINED> BONUS_GAME
// <IF_DEFINED> BONUS_GAME & USE_SOUNDS
else
// </IF_DEFINED> BONUS_GAME & USE_SOUNDS
// <IF_DEFINED> USE_SOUNDS
Sounds.playCollectCoin();
// </IF_DEFINED> USE_SOUNDS
|
 |
 |
 |
Figure
5: Code example of original source code
|
|
|
if ( m_coinCnt == m_totalCoinCnt )
{
m_bonusGameOpened[0] = 1;
m_showBonusGameActivated = 2000;
}
|
 |
 |
 |
Figure
6: Code example of preprocessed source code
|
With
these types of features, porting a game to new handsets becomes
easier. It is a matter of selecting the features to use, and compiling
the game. The process is a bit like playing with building blocks.
Localization.
The most important reason to localize games is that localized
games sell better. Usually all the text in a game is translated--in
some special cases even the title screen is translated. From Sumea's
point of view, the most exotic language our games have been localized
to is Chinese. By default we support five languages (English, French,
German, Italian, and Spanish), and the number is increasing.
Usually
the localization process goes like this: we deliver text assets
for a certain game to the party taking care of the localization,
get the translated assets back, build the game, and then deliver
it to our customer. In some cases the game bounces back from the
distributor's QA team due to errors in the localized assets. In
some cases the context is wrong: it might be that a device uses
certain terminology for navigation, and using different terminology
confuses the user. This is straightforward work, and usually solving
the problem once is enough.
Another
problem arises from the different platforms supported. For example,
controls have to be customized for certain handsets due to different
keypad layouts. The easiest way of solving this is to have parameterized
text assets. This means that we define the text once, define device
specific parts as parameters, and fill those in either at compile
time or at run time. In figure 7 you can see some of the English
text assets used in Yoyo Fighter.
|
|
Parameterized
instructions for Yoyo Fighter:
Controls:
%0U, %1U - Run left and right
%2U - Jump left
%3U - Jump up
%4U - Jump right
%5U - Flick yo-yo
%6U - Enter doors, drop down a platform
%7U - Cycle between carried items
You can
only hit a baddie when a red box surrounds them.
After
you obtain more items, press %7U to switch between them.
Story:
A magical portal
transports you [Yozo] to the World of Stone where you must
defeat the Golem Boss and save your helpless friend Jaco.
Your only weapon is your yo-yo!
Use your
yo-yo wisely to slay energy-draining bats and rock monsters!
Look out for Extra Lives and Power Ups that will make your
mission easier. Walk over info boxes for extra hints and
tips on what to do in a particular situation.
Jaco
can only be saved once you have successfully defeated the
hideous Golem Boss!
Have
fun! But be very careful!
Parameters
for Nokia 7210:
4, 6
1
2
3
5, Green dial key
8
* and #
Parameters
for Nokia 3650:
Left,
right
0
Up
1
Navi key, 5
Down
* and #
|
 |
 |
 |
Figure
7: Some text assets used in Yoyo Fighter.
|
From
an implementation point of view, the simplest way to localize content
is to use standard components provided by MIDP. These components
take care of all the different aspects you can encounter: length
of text in various languages, different font sizes, reading direction,
handset user interface, and so on. The drawback to using system
components is that you lose control over the graphical appearance.
For example, it might be disturbing to show a system text box in
the middle of a hectic gaming session.
The
solution is to implement text components of your own. There are
two simple rules to follow: never embed text in an image, and be
conscious of the restrictions when drawing text. The reason for
the first rule is simple: text is easier to replace than images
(and it takes up far less memory). The second rule is more complicated,
and is not easily fulfilled with a generic implementation because
of strict limitations. We have compromised on this rule: When we
know the limitations exactly, we use components of our own. In other
cases we rely on the system components.
Profile
management. Because of strict filesize limitations we will have
to build a game file of its own for each device or device platform.
To make things more straightforward, we include text assets for
one language into a single build. One game build consists of JAD
and JAR files. In practice, this means that for a game which supports
eleven different platforms and eight languages, we have 88 different
game builds-176 separate files. In most cases both of these files
contain device-specific information. Managing all these files can
be a tedious task, but luckily the process can be automated.
At
Sumea we call the parameters that define a devicespecific version
of a game a "profile". The profile defines source code
and resource files to be used in a certain build of the game, as
well as all device-specific parameters. These parameters define
general properties such as whether sounds are used, whether the
build is a debug or release version, whether the game is a demo
version, and which system components are used, as well as game-specific
properties such as which game controls and text assets will be used.
We have developed tools with which we can define the profiles to
be compiled with certain text assets, using just a single command.
Testing.
Almost every device manufacturer provides device emulators to ease
application development. The problem is that these emulators provide
(at best) only an approximation of the actual device. Therefore
we cannot rely on the emulators; we have to test the game in the
actual handset. As most of the devices don't support on-device debugging,
we have had to implement a logging mechanism to make the testing
process easier. This logging mechanism gives the developer a means
to store text-based information in the persistent memory, then to
view it later. A good practice is to test games in new device software
releases: software is rarely bug-free, and it might be that there
are new bugs in the device software, or functionality of a certain
API has changed enough to reveal or cause a bug in your game.
Fortunately,
though, the emulators are getting better. In the early days we often
received a device and a piece of documentation stating which APIs
the device supports. The rest we had to figure out ourselves. Now
emulators are starting to be more accurate and the documentation
is, in the best cases at least, more than sufficient. The base software
in devices is getting more stable as well-this means that when a
new handset with a proven base software is released, we don't have
to necessarily test the game in this new device, but can generally
rely on it working flawlessly.
______________________________________________________
|