Mobilecore: A Cross-Platform Framework For ARM-Based Mobile Games
Developing a mobile game for multiple devices can be quite difficult, given the differences in mobile operating systems. Introducing the Mobilecore library, a new SourceForge project that hides platform differences and offers a common game development base.
As a programmer at Overloaded, a Dutch developer and distributor of games for mobile devices, I have developed several games for Pocket PC and Symbian, like Fantom Overdrive, a 3D arcade racer, and Resistance, a voxel-based shoot-'em-up. Overloaded launched at the end of 2001, initially developing games for Pocket PCs. Shortly after that, we switched to Symbian-based smartphones. Now we primarily target the Symbian OS (series 60) and J2ME. Overloaded games are small -- typically less than 300KB -- as we distribute them "over the air". This makes it challenging to create fun titles, complete with state-of-the-art technology like Bluetooth, multiplayer, and textured 3D graphics. To support 3D graphics of our games, I developed a 3D engine, "NGine3D," specifically designed to be used in small games.
Having developed several games for mobile devices, I understand that the market for mobile games is a tough. There are several advanced platforms, like Microsoft's Pocket PC and Smartphone, and the Symbian OS. However, the individual markets for these platforms are small, and the only way to make profitable quality games is by porting early PC games - work that's typically outsourced to countries where labor is cheap.
Another option is to develop a game for multiple devices simultaneously. Unfortunately, this option requires technical knowledge of multiple operating systems, which (especially in the case of Symbian) can be problematic. But there is a solution to this: using a library that hides platform differences and offers a common application development base. Such a library would help build a multiple platforms and frees the developer of OS specific issues. In this article I explain how to build such a library.
Brothers in ARMs
The devices that Overloaded has developed for so far have much in common. The Pocket PC, Smartphone, and Symbian series 60 and 70 all use ARM or ARM-based processors, clocked somewhere between 100-400MHz. As the devices with smaller screens tend to use the lower clock speeds, the performance levels of the devices are comparable. Most devices use portrait displays. All displays are capable of high-color graphics (12- or 16-bit color), none of them has an FPU, and of course, there's no hard drive, limiting available storage.
There are differences too. Most notable are the varying resolutions: e.g., 176x208 for Symbian, 240x320 for the Pocket PC and 176x220 for Microsoft Smartphones. Pocket PCs use a stylus in addition to the buttons, whereas the Sony P800 (Symbian series 70) also uses a stylus but has virtually no buttons. The most important difference, of course, is the operating system. The Pocket PC operating system closely resembles Windows, but the Symbian OS is quite different.
I included the GP32 in the list because it is quite close to the other devices, although its screen is oriented in the landscape direction. The Zodiac has hardware-accelerated 2D functions, which makes it slightly different from the other devices.
If we ignore the operating system differences for a moment, what we have is an extensive list of devices with similar capabilities. And this represents lots of small markets with consumers waiting for cool games. If you could serve Symbian users, Pocket PC users, and Palm Tungsten-T users the same game, and get the same title working on the GP32 with minimal effort, that would expand your game's market without too much porting effort required.
Mobilecore
Before I started working for Overloaded, I created games for the Pocket PC in my spare time. As I was struggling to get easy access to the frame buffer and other hardware of the devices, I developed a small library called EasyCE. This library served as an abstraction layer on top of the operating system: It initialized the OS, prepared a linear 16-bit frame buffer, and after starting the game, it passed mouse positions and button states to it. To be able to develop and debug under Windows, I also implemented the HAL for windows, using the same API.
EasyCE became quite popular among Pocket PC developers. Since the library and its source code are free, it served as a "knowledge base" for Pocket PC issues, as it was frequently updated to cope with various quirks of newly released devices. These days there are better libraries for the Pocket PC, especially since Pocket PC coders are more frequently coding in assembler to achieve faster screen rotation (not all Pocket PC displays are oriented in portrait mode internally) and direct access to the LCD controller. Nobody dared to follow my "business model" though.
When Overloaded started developing for Symbian, I followed the same approach. The new library, "Easym-b", again allows us to develop for Windows CE and Symbian simultaneously. With it we bypass the ugly Symbian emulator, improve debugging, and generally cut down development time tremendously. Theoretically, you could develop a game for Symbian without actually owning the device.
Obviously, it would be nice if this could be taken a couple of steps further. First, it must be possible to write a game for Symbian and Pocket PC simultaneously. Second, there are some other new devices that look promising as mobile game platforms. The Zodiac, the GP32, and some of the latest Palm devices also happen to use ARM or ARM-based processors. So, ideally, the new library should not only support Symbian and Pocket PC, but also allow new platforms to be plugged in. Once the "OS abstraction layer" for the new device is written, existing games built on the library will at the very least compile without modifications. The bottom line is that game coders can concentrate on what they do best: Game coding, without spending endless hours researching the OS and device vagaries.
"Mobilecore" is a library that aims to do just that. It does nothing more than abstract the absolute core of each supported OS: it initializes the OS (WinMain), and handling hardware (mouse/stylus, buttons, vibration, sound, and perhaps communications). Currently, Mobilecore supports Win32, Pocket PC, and Symbian.
Mobilecore consists of a small set of sources: One source and one header for each supported platform, plus a surface class. The platform-specific source implements a class named Core, from which a game can be derived. The Core class is the interface for the game to the OS, and vice versa.
Abstracting the Symbian OS
As the Symbian OS is the most limiting of the supported operating systems so far, I will use it to describe the structure of the library.
When developing an application for Symbian, you can choose between an "exe" and an "app." The "exe" form is used to build applications that do not need a user interface, so it is primarily used for services. Exe programs do not appear in the menu of the phone, although you could start them using a small launcher app. They also have limited access to other processes and to the Symbian's GUI. It is hard to get to system events such as incoming phone calls. As we want a game to behave as nicely as possible when running on a phone, the preferred type of application is the "app" form.
The "app" is basically a DLL. The "app" must supply a method named NewApplication that returns a pointer to a CEikApplication. From here, a typical Symbian application instantiates a "document" (derived from CEikDocument) which has one or more "controls" (derived from CCoeControl). To behave nicely under the Symbian OS, we will implement our game as a control.
Perhaps you are beginning to understand what the main problem is with Symbian: It's a bit strange Symbian series 60 is notorious for its large number of frameworks, classes, and templates and general unforgiving nature to programmers. Debugging is hard, while information is sparse and often inaccurate or incomplete. And the OS simply does not appear to be made for games. If you thought Windows was terrible after you coded in mode 13h for years, prepare for your next nightmare. Here lies a great task for Mobilecore: Let's get it over with once and for all.
void Core::ConstructL(const TRect& aRect){CreateWindowL();SetRect( aRect );ActivateL();m_OSBitmap.Create( TSize( SCRWIDTH, SCRHEIGHT ), EColor4K );m_Timer = CPeriodic::NewL( CActive::EPriorityStandard );m_Timer->Start( 25000, 25000, TCallBack( Core::Period, this ) );...} |
Listing 1: Setting up the core control under Symbian |
Listing 1 shows the main part of the control initialization. The control creates a window for itself (CreateWindowL), tells the OS how much screen real estate it will be using (SetRect), and activates itself for drawing (ActivateL). It then creates a bitmap and starts a timer that calls a callback function 40 times a second. The timer is an odd thing by the way: Symbian does not let you time things with more accuracy than 1/64th of a second, yet you specify timer intervals in microseconds. This timer issue is nasty: It makes it virtually impossible to determine the frame rate of a 3D game, for example.
The ARM processor has some onboard high-precision timers, so it's possible to get more accurate timing. This requires some assembly coding though.
Since our Symbian application is now heavily based upon this timer ticking 40 times a second (or any other interval), the Mobilecore library does the same. Therefore, the game class that we derive from the Core class must at least implement a Tick() method (see code listing 2). A game based on this is going to be state-driven.
class Core : public CCoeControl{public:Core();~Core();virtual bool Tick() = 0;Surface* GetSurface();bool KeyDown( int a_Key );int ReadKey();int PenX();int PenY();bool PenDown();virtual void ButtonDown( int ) {};virtual void ButtonUp( int ) {};char* FullPath( char* a_File );...} |
Listing 2: Symbian version of the Core class declaration |
The Mobilecore library tries to stick to its core task as much as possible: Hiding OS specifics. Therefore, the other methods in the Core class are all very simple. There are some methods for retrieving the stylus position and whether or not it touches the screen, and there are two callback functions for handling button state changes.
Two methods are more noteworthy, however: GetSurface and FullPath.
The Symbian OS does not use relative paths. That means that opening ERROR.LOG opens a file in the root of the file system, not a file in the directory of the executable. FullPath fixes this problem: It simply expands a given file name so that it includes a full path.
The GetSurface method returns a pointer to a surface, which is a simple class that manages a pixel buffer. Strictly speaking, everything but the creation of the bitmap is platform independent, so the surface class is not absolutely needed. However, I decided not to be extremist here: Symbian uses a 12bit frame buffer, while the Pocket PC uses a 16-bit buffer, and then there's the resolution issue. The surface class is therefore the only piece of code that is going to need a lot of OS-specific code: Loading images and other graphics operations need to be able to handle the vagaries of the devices.
That's pretty much all there is to it. It's a simple frame, and it can easily be ported to the Pocket PC.
You can load the Symbian version of Mobilecore in Visual C++ 6.0; it's included in the workspace file. You cannot build a final version from the Visual Studio IDE this way, but you can check for errors. Just hit F7, and the external compiler will be used to check for problems, which can be clicked in the usual manner. Compiling the final package must be done from the command line. (Check the relevant documentation for details.) Note that we used the 0.9 SDK; later revisions only add support for specific features of newer devices so it is not necessary to upgrade.
______________________________________________________
Mobilecore on Pocket PC
As your game will be derived from the Core class, a Pocket PC version will only require a Pocket PC specific version of the Core class, with the same interface as the Symbian version. Luckily, this is not a problem.
The biggest "problem" on the Pocket PC is the fact that, just as under Win32, we start out in C. A WinMain function is the entry point of the application. The WinMain will create a window for the application, prepare the device for full-screen graphics output, instantiate and initialize the Core object, and start the event loop and Tick() mechanism.
About this full-screen graphics output: On the Pocket PC this is usually handled by the GX library, which is provided by Microsoft. There are some good alternatives, but I'll stick to GX for the moment.
The problem with GX is that it does not get rid of the title bar of the application: There's an area at the top of the screen that does not respond to stylus presses and where the clock sometimes flickers through. Getting rid of the title bar is quite a task. It tends to return whenever the system updates it, which is often: Alarms, the battery icon, and a few other things all re-display the taskbar. Therefore, quite a bit of effort is put into making sure the thing stays away.
Another problem with GX is that it offers the choice between direct or buffered access to the device frame buffer. As some devices have rotated frame buffers, doing a direct blit is not going to do the job. To make things worse, some iPaqs (the 3800 series) perform terribly unless you write directly to a hard-coded frame buffer address. If you want to know all the specifics, check out the Core implementation for Pocket PC. Otherwise, simply use Mobilecore.
The Pocket PC also implements the FullPath method, since the Pocket PC OS has the same problem as the Symbian OS: no relative paths. Perhaps someone can explain to me why Microsoft left this out?
The rest of the Pocket PC implementation will be familiar to Win32 coders: The WinMain starts to call Tick(), and updates the screen. A message pump and a WndProc handle incoming events such as button presses and stylus movement. These events are then passed to Core so that the game can use them.
You can load the Pocket PC version of Mobilecore into Embedded Visual Tools, which can be downloaded for free from the Microsoft website: http://www.microsoft.com/mobile/. The IDE is the same as used in Visual C++, so you'll feel right at home. You will need to transfer files by hand to your device, or you can build a nice installer package using one of the available tools.
Mobilecore on PC
It's good to have a PC port of the library for obvious reasons: An application can be developed more easily on a PC, because the whole edit-compile-run cycle is much shorter (seconds instead of minutes). Besides that, there are very good debugging tools available for PC. I really like the built-in debugger of Visual Studio 6.0, and without it I'm virtually blind. For the tough-to-find problems and a last-minute leak checking I use BoundsChecker.
Obviously, not all bugs can be found by debugging on a PC. One example: x86 CPUs accept byte read and writes on odd memory addresses, while ARM processors don't allow this. That's not a big deal, but it can cause a crash in code that works perfectly on the desktop machine. Other things that can go wrong are path names: Code compiles just fine if you don't use the FullPath method, but it will not behave as you intended when you try it on a Symbian device or Pocket PC.
But there are more advantages to working on a PC than debugging and faster development. If a programmer can use a PC to test the application, he doesn't need to have full-time access to the actual target device. A key advantage is that the PC can run the application in varying resolutions so that the game's graphics code can easily be tested for all target devices. Finally, graphics output can be captured easily from the PC version. It's even possible to send a PC demo of your game around.
Fortunately, the PC version is a piece of cake, especially since the Pocket PC version is already done. I choose to use a simple bit of GDI code for graphics output. It's not the best performing code you'll see, but as the PC only serves as a development and testing platform, it makes no sense to optimize it any further.
Like the Pocket PC, the PC version starts in C with a WinMain. The WinMain instantiates the Core and passes events using a message pump.
Using Mobilecore
To use Mobilecore for a game project, you simply derive a game class from the Core class, and implement the pure virtual methods (see listing 3).
class MyGame : public Core{public:MyGame();~MyGame();bool Tick();void ButtonUp( int a_Key );void ButtonDown( int a_Key );}; |
Listing 3: Minimal declaration of a derived game class. |
That's theory, but reality is a bit more complicated. If your game uses 3D polygonal objects, this is going to do the job just fine, as vector graphics scale perfectly and will run in any resolution. For a demo you don't need controls, so you don't need to worry about stylus input for the P800 (a device that almost completely lacks hardware buttons).
For the average game, you will need to take into account some hardware differences -- most notably the resolution. You can use the Windows version to test your game on various screen sizes. At Overloaded, we found that tile-based games can quite easily be adapted for various resolutions. Often such a game can be left mostly unmodified; the player will simply see less on smaller displays. Some situations in the game may need to be tweaked because of this, but in general, the amount of extra work is limited. When the resolution change is more drastic (we also develop for J2ME devices with very small screens), it may be necessary to have multiple tile and sprite sets.
The "tick" mechanism and the assumption that the game is using a state-driven approach could also limit the usefulness of the library for porting existing applications. Whether or not an existing game can easily be ported to other platforms using Mobilecore depends on whether or not the game can be changed into a state-driven application (if the game is not already working this way, of course).
Now there's one little issue that I didn't mention yet: global variables. Symbian does not support them. As DLLs can be in ROM or RAM on the device, the Symbian OS simply crashes on all write operations to non-static global data. While there are lots of ways to prevent using global data, in some cases it's just very inconvenient. But there is a way around this. Symbian allows you to use one global 32-bit value. It's stored in a memory location pointed to by Dll::Tls() (which stands for "thread local storage"). In this variable, we can store a pointer to a struct or class that contains all the global data that we wish to use. Have a look at listing 4:
struct Globals{Overloaded::Engine* m_Engine;CSymbianContainer* m_System;Overloaded::MManager* m_MManager;Overloaded::TManager* m_TManager;Overloaded::Rasterizer* m_Rasterizer;};#ifndef __SYMBIAN32__extern Globals* DATA;#else#define DATA ((Globals*)Dll::Tls())#endif |
Listing 4: Global data in Symbian. |
This is a snippet from the racing game, Fantom Overdrive. As the game uses a custom memory manager for fast allocation and recycling of vertices and other data, I really needed some global data. In listing 4, the Symbian way of accessing global data is DATA->m_Engine, which maps to ((Globals*)Dl::Tls())->m_Engine, while the PC way is more direct.
The same approach can be used for static data members of classes. If class Vertex needs a static Engine pointer s_Engine, just add s_Vertex_Engine to the Globals struct. From there on, instead of using s_Engine, you use DATA->s_Vertex_Engine. Not perfect, but pretty close.
To compensate for the ugly globals hack, I will demonstrate how to use the Symbian device buttons that are not available to all devices. If you look at the class declaration of the Core for Symbian, you will see a list of keys that is different from the list of buttons defined in the CORE.H file. Mobilecore assumes that all devices have four directional buttons--one "fire" button and two other buttons. Some devices have fewer buttons, so your game needs to be prepared for that, as well as the fact that some devices have many more buttons. Devices such as the N-Gage actually have a full numeric keypad, which may come in handy in some situations. If you want to use Symbian-specific code, you can do so by checking for the __SYMBIAN32__ definition. Likewise, you can check for _WIN32_WCE when running on a Pocket PC device.
Adding Functionality
Mobilecore offers games an easy and platform-independent (though currently processor-dependent) way of accessing a frame buffer, the stylus, and hardware buttons. There are some things missing however.
Sound. Implementing a multi-channel audio system requires an accessible audio stream. I have implemented this for Pocket PC, Symbian and PC, and this code will be available shortly.
Communications. Many (if not all) devices support some form of communication. Pocket PCs can use infrared, Bluetooth or wi-fi to communicate with each other; Symbian phones are Bluetooth enabled and are also capable of infrared communications, in addition to GPRS and GSM data transport. In order to build multiplayer games it would be interesting to have some generic way of communicating. Although it's probably too much to go for device-independent communications (playing a game between a 7650 and a Pocket PC over Bluetooth would be awesome!), these days you can't really get away with a game that does not offer some form of multiplayer gaming.
Vibration, backlight, and scroll wheels. Some devices allow direct manipulation of the backlight or the vibration hardware. It would be nice if Mobilecore were enhanced to supported this, so that a game can use these features on devices that support it.
Further Development
I have created a SourceForge project for Mobilecore at the following URL:
http://www.sourceforge.com/projects/mobilecore/
Here you can download the latest version. If you are familiar with one of the "missing platforms," you are more than welcome to join in.
As the current code is only a few weeks old, there is lots of work to be done. I would really like to support more platforms, but the existing platforms will need continuous improvement too. The Pocket PC version could use more diversity (for instance, there are faster libraries for full-screen frame buffer access than Microsoft's GX).
For Symbian, there are other issues: For instance, while the stdio and stdlib functions are available, they are somewhat hidden in the SDK and documentation, and for a good reason: The implementation is not without problems. If you check the code that determines the full path for a given filename, you'll see that I had to code strstr myself to prevent problems. Issues such as these need to be identified and fixed.
I assume that every platform is going to have its list of issues. Therefore I have submitted Mobilecore to SourceForge; I hope that there will be at least one expert for each platform.
Further Reading
If you want to know more about mobile games development, you may want to check out the following links:
Tapwave developer pages:
http://my.tapwave.com/developers/index.asp
Forum Nokia:
http://www.forum.nokia.com/main.html
3D graphics for mobile devices:
http://www.flipcode.com/articles/article_mobilegfx01.shtml
GP32 coding page that makes you want a GP32 badly:
http://www.devrs.com/gp32/
PalmOS developer community at PalmSource:
http://www.palmsource.com/developers/
Peter van Sebille's MAME for Symbian with lots of technical information:
http://www.symbian.com/developer/techlib/papers/mame/mamedream.html#MAME.MAME-029
______________________________________________________
Read more about:
FeaturesAbout the Author
You May Also Like