J2ME's Mobile Information Device Profile (MIDP) promises to become
one of the most widespread computing platforms over the next few
years as an increasing number of mobile phones include a Java Virtual
Machine.
Given the popularity of preinstalled games on mobiles, Java
games are likely to captivate a large audience. The first commercial
download services for MIDP applications or "MIDlets" have already
been launched and the market is set to grow at an explosive rate.
Nokia alone expects to sell more than 50 million of these devices by
the end of 2002, so the opportunities associated with writing good
games for Java-enabled devices are staggering
However, while learning the basics of MIDP is simple enough
for most developers, understanding the constraints of the platform
and the tricks to get around them is crucial for efficient
development. Creating compelling content, especially the type that
uses the mobile network, involves a lot of trial and error. This
article is a collection of lessons I've learned from coding games for
the first generation of J2ME devices.
The key factor to consider when designing a MIDP game is your
target audience and hence the set of target devices. If your MIDlet
needs to run across devices with varying performance and graphical
abilities, you need to take this into account in
the design of your code. This is especially important when it comes
to the use of libraries proprietary to a handset manufacturer, the
use of graphics, and CPU-intensive AI calculations. If the aim is to
capture the mass market, the game needs to be playable on the lowest
common denominator device - currently a 64x95 pixel 1-bit screen, a
slow, low-end CPU, and less than 100KB of heap memory. Modifying the
code to cater to high-end devices should then ideally be a matter of
modifying only a clearly defined portion of the application. It pays
to spend some additional time on design. For example, a game written
using the full Siemens game API for the 101x80 pixel screen of the
SL45i will be very difficult to port to the Nokia 6310i if
portability has not been designed into the code from day one.
One of the most notable shortcomings of game development in
MIDP 1.0 is its lack of advanced graphical features. The Graphics
class does not provide any form of transparency, whether you're using
an image mask or defining a transparent color. There's also no way to
read or write to individual pixels, which prevents even basic image
manipulations, such as fading an image to black.
These limitations may not seem too serious at first, as the
obvious way around them would be to read in local image files
directly using the "javax.microedition.io" package and write
decoders of JPEG or GIF or other
image file formats. You could then map out the bytes of the image
manually and overcome the above limitations. While this would result
in full control over the image pixels, it's prohibitively slow. MIDP
does not provide a fast pixel plotting function and there's no quick
way of getting the resulting image buffer painted onto the screen. In
fact, the only option is to draw lines 1 pixel in length with the
drawLine() function. Filling a 100x100 screen with 10,000 drawLine()
calls on a mobile device is just not feasible with near future device
performance!
Most device manufacturers have responded to the limitations
of the native graphics in J2ME by adding additional classes in their
own proprietary API. However, it's dangerous to rely on these, as it
breaks the portability of the code and is likely to result in lengthy
and frustrating modifications to the code for different
manufacturers' handsets. Generating an abstraction layer is, of
course, an option, but with a maximum JAR file size of 30KB for
typical low-end handsets, this type of layer must be extremely light.
MIDP NG (Next Generation), scheduled for public review in the JCP in
the near future, will fix some of these problems. Similarly, the Open
Mobile Architecture initiative driven by Nokia promises much, but it
remains to be seen whether it will be enough to make handset
manufacturers converge on APIs.
When programming fast-moving games, there are a few
additional points to keep in mind. Anyone who has done game
programming knows that double buffering is an important tool to
achieving smooth and "tear-free" animations. The implementation of
the Graphics class is double-buffered on many devices, as can be seen
by a quick call to the Canvas.isDoubleBuffer() method.
This means that any graphics drawing functions are buffered
off-screen before they're displayed on-screen. However, as double
buffering usually refers to switching between one back-screen buffer
and one front-screen buffer, not buffers for individual Graphics
calls, this has been the source of some confusion. Since all the
screen paintings are done within the paint
(Graphics g) method, it's not hard to imagine what happens when there
are many Graphics drawing calls and each drawing call is displayed
onto the screen one by one.
It's not difficult to write a double-buffering system. First
set up an off-screen buffer in the form of a
"javax.microedition.lcdui.Image", then do every drawing operation
using the Graphics obtained from this buffer instead of from the
actual screen. After you're done with the drawing, emulate the
"flipping" of the buffers by drawing the whole offscreen buffer onto
the actual screen in one go with one drawImage() call (see Listing 1).
Another issue to take into account is CPU speed and speed
throttling. Since speed can vary markedly between devices, speed
throttling should be applied to any games with animations. Some
people calculate their own timed delays in their animation thread,
while others prefer simply using the "java.util.Timer" to schedule a
"java.util.TimerTask" for all the animation duties. Depending on the type of
game, you may want to try scheduling the Timer to go off with a
50-100ms delay (10-20 fps).
Designing Networked J2ME Games
The prospect of providing multiplayer games for mobile phones
is exciting. However, before you write your first line of code for
your multiplayer first person shooter, keep in mind some important
limitations. A good place to start is to look at the constraints of
the network and what is possible using the J2ME communications
library.
The oft-cited limitations in the bandwidth of current 2G and
2.5G networks are an important bottleneck for networked games.
However, lesser-known but equally significant constraints are packet
latency and variability in network performance. A limited bandwidth
implies that communication over the network should be reduced to a
minimum, while packet latency and variability mean that the code must
run and be playable even in vastly variable network conditions. The
problems are compounded by the implementation of network
communication in MIDP 1.0.
All J2ME devices implement HTTP connections and some also
offer a socket API. The package containing the communication classes
is "javax.microedition.io", with the most important classes being
Connector, Connection, and HttpConnection. The best way to
communicate with a Web server is to use POST requests. Listing 2
shows an outline of an HTTP POST connection.
Alternatively, Listing 3 provides an outline of a sample
socket connection.
The J2ME communication API has some notable limitations. It
does not implement "java.io.Serializable" and neither does it support
HTTP session functionality. If these features are required, there's
no option but to re-implement them.
Figure 1 provides an overview of the elements in a networked
J2ME game architecture using either GSM CSD or GPRS as a carrier.
Relatively good performance can be achieved even on a GSM network, if
the size of the message transmitted is kept to a minimum.
However, GPRS is the carrier of choice, as the user doesn't
need to wait to connect and pays only for the data transmitted. While
bandwidth is unlikely to be a problem, achieving an acceptable
average latency over the network can pose a significant challenge. A
round-trip time of 10 seconds over a CSD call on GSM and 4 seconds
over GPRS can be considered average even with some optimizations.
This limits games to the turn-based type that utilize only current
network infrastructure. Real-time multiplayer games will become a
reality only when 3G networks promising sub-100ms latencies
are launched.
Despite the limitations, there are many things you can do
with the network. Shared high scores or level downloads are a good
example. Another is the possibility of playing against advanced game
servers. Because of hardware limitations it's sometimes not feasible
to develop a sufficiently powerful AI on mobile devices. Game servers
have a vast advantage in computing power, which makes utilizing a
server-side AI a compelling proposition. Take a scenario where the
user plays locally and the AI uses a minimax alpha beta algorithm
with a depth of two. On low-end devices this takes between 20 seconds
and 2 minutes to calculate, and the game is hardly playable. Compare
that to a scenario in which the user plays against a remote game
server where the AI uses the same algorithm with a more powerful
search depth of four. The server computes the move instantaneously,
it takes 4-8 seconds to get the move from the server, and the game is
much more playable.
The game server itself can be implemented as an HTTP server
or a socket server. A socket server is faster than an HTTP server, as
there's no HTTP overhead, but it can be used only for those devices
that support sockets. In addition, using HTTP helps the long-term
scalability of the service, as the number of active devices polling
for moves and messages increases. As the client is written in Java,
it's easy to connect to servlets and to implement the server in Java:
the HTTP requests coming from J2ME devices are exactly the same as
other HTTP requests. The standard Java Serialization API cannot be
used in this case, but it's very easy to write your own. XML is a
standard way to communicate with other types of Web servers like PHP
or CGI, but it's obviously much less efficient.
Using a standard Web server like Apache/Tomcat or Jetty has
the advantage of leveraging the scalability that has already been
achieved for other Web/WAP applications. However, due to the
nature of the wireless client and the wireless
network infrastructure, some modifications need to be made in order
to optimize the data traffic, as Web server providers have yet to
fully adapt to the wireless market. In the short term, the use of
open source products may be a good solution.
An even better solution is to use a Java application server
as a back end. EJB/JMS/J2ME integration is a very active topic at the
moment and this is definitely the way forward to ensure speed,
availability, fault tolerance, and quality of service for all
networked wireless applications, including multiplayer games. This
will also require some tweaking in these early days. For example, a
combination of J2ME, Jetty, and JBoss is a good open source (nearly)
100% Java solution.
These are just a few technical issues to consider in the
development of MIDP games. Many times an equally challenging task is
handling the creative and business side of MIDP game development.
With so many game ideas already exhausted and brands becoming
increasingly important in the mobile marketplace, coding a clone of
an '80s hit is no longer enough to convince an operator or download
portal to resell your application.
A solid technology base and a good creative team is a must
for a successful company in the MIDP games arena. But as in all
things, it's on the business side where the battles for market share
will eventually be won and lost.
Author Bio
Sami Lababidi is chief technology officer at Macrospace, Ltd., a
provider of J2ME solutions. Sami has focused on J2ME since its
inception and has developed and deployed J2ME products and services
throughout Europe.
sami@macrospace.com
Listing 1
javax.microedition.lcdui.*;
public class gameCanvas extends Canvas
{
...
Image backBuffer;
Graphics backBufferGraphics;
...
public gameCanvas()
{
....
// initialize the back-buffer to be the same dimension as the canvas,
// and get the Graphics of the buffer for off-screen drawing
backBuffer = Image.createImage( getWidth(), getHeight() );
backBufferGraphics = backBuffer.getGraphics();
....
}
....
public void paint(Graphics g)
{
...
// do all the Graphics drawing onto the back-buffer
backBufferGraphics.draw...
...
// now "flip" the back-buffer to the front
// by drawing the whole back-buffer image onto the screen.
// since individual Graphics call is "double-buffered",
// this wonÕt create any "tearing" on the screen.
g.drawImage( backBuffer, 0, 0 Graphics.TOP|Graphics.LEFT );
}
....
}
Listing 2
HttpConnection c = null;
InputStream is = null;
OutputStream os = null;
try
{
// open http connection
c = (HttpConnection)Connector.open( url, Connector.READ_WRITE );
// Set the request method and headers
c.setRequestMethod( HttpConnection.POST );
c.setRequestProperty( "User-Agent" , "Profile/MIDP-1.0 Configuration/CLDC-1.0" );
c.setRequestProperty( "Content-Language" , "en-US" );
c.setRequestProperty( "Connection" , "close" );
// write request
os = c.openOutputStream();
....
// get response
is = c.openInputStream();
...
...
}
finally
{
try{ os.close(); }catch( Exception e ){}
try{ is.close(); }catch( Exception e ){}
try{ c.close(); }catch( Exception e ){}
}
Listing 3
StreamConnection connection = null;
InputStream is = null;
OutputStream os = null;
try
{
Connection aConnection = Connector.open( "socket://" + host
+ ":" + port);
connection = (StreamConnection) aConnection;
os = connection.openOutputStream();
..
is = connection.openInputStream();
...
}
finally
{
try{ os.close(); }catch( Exception e ){}
try{ is.close(); }catch( Exception e ){}
try{ c.close(); }catch( Exception e ){}
}