HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

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.

Figure 1

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 ){}
}

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.