WWDC impressions

I’ve posted a brief summary of my (first EVAR) WWDC over @ the Jet Set Games site.

It was an inspirational conference in many ways, and I often found that something presented in one of the (generally excellent) technical sessions jogged a thought or idea about something else, be it a new approach to a problem, a new problem needing an approach, a new product idea, or affirmation of something we suspected all along (like, why does the ObjectAllocs Instruments readout not correspond with the activity monitor heap view … ).

Altogether, very worthwhile, and well worth attending.

BottomLine for iPhone

It is time for an update, sporadic how they may. We have released our first iPhone application under the Jet Set Apps brand: BottomLine. BottomLine was conceived as an app for small businesses who track daily cash flow, to quote the site:

BottomLine allows small business owners to quickly and easily track key financial data anytime, anywhere. Enter your business’ daily profit or loss via your iPhone or iPod Touch, and BottomLine displays your business’ progress for up to three months.

BottomLine may be found on iTunes or the App Store here.

Meanwhile, we’re working away on updates to BottomLine, in addition to various other iPhone projects. You can follow along on the Jet Set Apps site, plus the Jet Set Games site now has regularly updated blog and twitter feeds - check them out for the latest Jet Set news!

A Shameless Plug

So here is a service announcement by way of a shameless plug for my new gig:

Jet Set Games

Jet Set Apps

Check out the sites, or follow us on Facebook. Become a fan!

Silence …

No sooner do I reboot the blog site, and scribe a flurry of coding related posts, than the blog goes silent again. There are a couple of reasons for this, the first of which is that all my available time has gone into finishing up current projects (well, I did also find time for Fallout 3, which I have thankfully “completed”). The second reason, which is driving the first, is that I’m embarking on a brand new adventure come January 1, 2009. There’s little more to say at this point, save that hopefully I’ll be posting more here in 2009, time permitting …

More Delegates in C#

Previously I covered basic usage of delegates in C#. In this article I will show examples of more powerful applications of delegates.

As previously mentioned, a C# delegate can encapsulate an instance of an object and a method contained in the object. It can also be used statically (ie, without an associated object instance). Here are examples of both types of usage, along with an example of a delegate wrapping an anonymous method (note that I am using System.Diagnostics.Debug.WriteLine() to make the debug output appear on the Output pane in Visual Studio):

    // declare a custom delegate type, which has a
    // method signature taking string and returning void
    // note that this delegate type is declared outside any
    // class definition as a delegate type is a class in its
    // own right.
    public delegate void MyDelegate(string calledFrom);

    // declare a class to contain a method taking
    // an argument of type MyDelegate
    public class DelegateReceiver
    {
        // define a method taking arg of MyDelegate
        // note that this class knows nothing of the DelegateSender class
        // defined below
        public static void MethodTakingMyDelegate(MyDelegate myDelegate)
        {
            // call "through" the given delegate
            myDelegate("DelegateReceiver.MethodTakingMyDelegate()");
        }
    }

    public class DelegateSender
    {

        // declare a string instance variable to illustrate the
        // object encapsulation aspects of delegates
        private string InstanceStringVariable = "An instance string variable";

        // a static method matching the delegate method signature
        private static void MyStaticDelegateMethod(string calledFrom)
        {
            System.Diagnostics.Debug.WriteLine("MyInstanceStaticMethod() called from " + calledFrom);
        }

        // an instance method matching the delegate method signature
        private void MyInstanceDelegateMethod(string calledFrom)
        {
            // as in the static case, write the string argument to the console
            System.Diagnostics.Debug.WriteLine("MyInstanceDelegateMethod() called from " + calledFrom);
            // and then demonstrate the use of the instance encapsulation
            System.Diagnostics.Debug.WriteLine(">> InstanceStringVariable = " + this.InstanceStringVariable);
        }

        public void run()
        {
            // instantiate a delegate, in this case to our static method
            MyDelegate myStaticDelegate = MyStaticDelegateMethod;

            // instantiate another delegate, this time to our instance method
            MyDelegate myInstanceDelegate = MyInstanceDelegateMethod;

            // call the static delegate
            myStaticDelegate("DelegateSender.run()");

            // call the instance delegate
            myInstanceDelegate("DelegateSender.run()");

            // call the DelegateReceiver class with our static delegate
            DelegateReceiver.MethodTakingMyDelegate(myStaticDelegate);

            // call the DelegateReceiver class with our instance delegate
            // note that the DelegateReceiver class will call through the instance delegate
            // and MyInstanceDelegateMethod will run in the context of this instance
            // of DelegateSender, which demonstrates instance + method encapsulation
            DelegateReceiver.MethodTakingMyDelegate(myInstanceDelegate);

            // now declare a delegate wrapping an anonymous block of code
            MyDelegate anonymousMethod = delegate(string calledFrom)
            {
                System.Diagnostics.Debug.WriteLine("anonymousMethod() called from " + calledFrom);
                // and then again demonstrate the use of the instance encapsulation
                System.Diagnostics.Debug.WriteLine(">> InstanceStringVariable = " + this.InstanceStringVariable);
            };

            // and hand this anon code block and instance context to our static receiver
            DelegateReceiver.MethodTakingMyDelegate(anonymousMethod);

        }
    }    

The test code can now be called from other code as follows:

    // instantiate a DelegateSender object
    DelegateSender delegateSender = new DelegateSender();

    // run the tests
    delegateSender.run();

The output from the above is as follows:

MyInstanceStaticMethod() called from DelegateSender.run()
MyInstanceDelegateMethod() called from DelegateSender.run()
>> InstanceStringVariable = An instance string variable
MyInstanceStaticMethod() called from DelegateReceiver.MethodTakingMyDelegate()
MyInstanceDelegateMethod() called from DelegateReceiver.MethodTakingMyDelegate()
>> InstanceStringVariable = An instance string variable
anonymousMethod() called from DelegateReceiver.MethodTakingMyDelegate()
>> InstanceStringVariable = An instance string variable

To recap, this article shows examples of C# delegates used with static methods, with instance methods and with an anonymous instance method. It also shows how a delegate can be passed as a method argument which points the way to use of delegate objects as “callbacks”. This can be particularly powerful when used with Invoke() (for example on a Control) as you can arrange for an instance method (even an anonymous one) to be called in the context of another thread (a UI thread, in the case of a Control).

Delegates in C#

Recently I’ve been immersed in C# as I tackle a Windows Mobile project targeting .NET Compact Framework. I have to say, I am impressed. I’ve spent much of my time over the last couple of years split between J2ME and Qualcomm’s BREW, and by now I am pretty familiar with what each platform does (and does not) offer. The Windows Mobile platform seems to offer much more out of the box, in terms of language features, tools, and scope of the Compact Framework itself.

Over the coming months I’d like to put together a couple of articles (or perhaps short notes) covering specifics areas of Windows Mobile that I have found useful or just interesting. One thing that falls into both categories is the language support of delegates.

In broad terms, a C# delegate can be thought of as a sort of “function pointer” - it provides a means to pass around a method so that the method can be called at some later time. A delegate can be passed as a method parameter, stored as an instance variable of a class, etc. The MSDN description of the “delegate” keyword is thus:

The delegate keyword is used to declare a reference type that can be used to encapsulate a named or an anonymous method. Delegates are similar to function pointers in C++; however, delegates are type-safe and secure.

An example may help explain basic use of delegates. The following example declares a delegate named SimpleDelegate that can encapsulate a method taking a string as an argument and returns void:

public delegate void SimpleDelegate(string msg);

To construct a delegate object, we need to specify a method the delegate will wrap. This method ordinarily needs to match the “signature” of the delegate, so in this case it needs to accept a single string parameter, and return void:

// Create a method for a delegate.
public static void SimpleDelegateMethod(string msg)
{
    System.Console.WriteLine(msg);
}

Next, we instantiate a delegate as follows:

// Instantiate the delegate.
SimpleDelegate simple = SimpleDelegateMethod;

Once a delegate is instantiated, we can treat the delegate as if it were the wrapped method, so this:

// Call the delegate.
simple("A divinity shapes our ends, rough hewn how they may.");

Is equivalent (in broad terms) to this:

SimpleDelegateMethod("A divinity shapes our ends, rough hewn how they may.")

We can pass our simple delegate to methods, store in a class, etc. So far so good. You will note that in this case, the delegate is wrapping a static method (that is, there is no class instance associated with the method). An important aspect of C# delegates is that they can wrap not only the method you give it, but also in the case of a instance method, the particular instance of the class of which the method is a member. So basically, the delegate represents the operation you wish to perform (the method), and the context in which you want to perform it (the class instance associated with the method when it was instantiated). When a delegate wraps a static method, it only references the method. This ability to wrap both the method and its instance is very useful for “callbacks” (in the C++ world a callback is typically a static method with a parameter representing the instance of an object of interest to the callback, such as a pointer to a class instance). In fact, C# delegates are the basis of C# Events, which play a key role in the .NET UI layer.

I’ll cover more uses of delegates, including anonymous delegates and use of delegates with Forms, in a future article.

C++ static / global initialization

I’ve been writing C++ for a while now, but in the process of putting together an article for iphonedevelopertips.com this weekend, I was reminded of how “tricky” static variable initialization can be in C++. When I say “static” I’m referring to both static class members and globally scoped variables (which may or may not have the static keyword attached). In the case of globally scoped variables, static is an access modifier which determines whether the object is available outside the translation unit - basically if it is “static” you cannot “extern” it in another file. Per MSDN:

When modifying a variable, the static keyword specifies that the variable has static duration (it is allocated when the program begins and deallocated when the program ends) and initializes it to 0 unless another value is specified. When modifying a variable or function at file scope, the static keyword specifies that the variable or function has internal linkage (its name is not visible from outside the file in which it is declared).

And,

Objects and variables defined outside all blocks have static lifetime and external linkage by default. A global object or variable that is explicitly declared as static has internal linkage.

So far so good. When it comes to initializing static variables, within a translation unit (ie a preprocessed .cpp file) the initialization order is well defined - it is the order of declaration. Between translation units however, the order is undefined, this is part of the language specification.

This in itself can cause issues - if you have global or global static objects, they need to cope with arbitrary initialization order. What may not be immediately clear is that static class members are also subject to this same arbitrary order. To illustrate this, consider (for msvc):

Define a class, SomeClass as follows:

// SomeClass.h
#pragma once

class SomeClass
{
public:
    SomeClass(int integer)
    {
        this->integer = integer;
	printf("SomeClass(%d)\n", integer);
    }
    int getInteger()
    {
        return integer;
    }
private:
    int integer;
};

Now, define a second class, GlobalClass, thus:

// GlobalClass.h
#pragma once

#include "SomeClass.h"

class GlobalClass
{
public:
    GlobaClass()
    {
        printf("GlobalClass()\n");
	printf("someClass.getInteger(): %d\n", someClass.getInteger());
    }
    ~GlobalClass()
    {
    }
private:
   static SomeClass someClass;
};

Then the implementation of GlobalClass is:

// GlobalClass.cpp

#include "GlobalClass.h"

// initialize the static class member
SomeClass GlobalClass::someClass(1);

And then in some other file, at global (file) scope, we write:

// SomeOtherClass.cpp

#include "GlobalClass.h"

GlobalClass gc1;

The output from this program looks like this (on my compiler - VS2005, on my machine):

GlobalClass()
someClass.getInteger(): 0
SomeClass(1)

Notice that the constructor for the GlobalClass object gc1 has been called before the static members of GlobalClass have been initialized! The call to someClass.getInteger() in the GlobalClass constructor will return the value of (in this case) 0, rather than the anticipated 1.

In this synthetic example, the problem could easily be addressed by moving the initialization of the GlobalClass static members ahead of the declaration of the first instance GlobalClass (ie, in the SomeOtherClass file above gc1). Another approach might be to move all globals and static initializations into one file. The point is that naive initialization may blow up if you are not very careful.

It is worth mentioning that this anomaly will only really happen for objects defined at global scope, where construction happens before the main() program entry point is called. Once the main() (or equivalent) method has been called it is guaranteed that all static initialization has been completed. Within the static initialization phase however, order of initialization across translation units is very much undefined. And again, order of initialization can vary by compiler, module link order, and no doubt other factors, so YMMV, as they say.

Hopefully none of the detail was lost while transcribing this from the source code. Feedback on errors and omissions welcome.

EA Air Hockey: designing a one-button mobile game

Introduction

Note: This article was originally published on a previous, now defunct, incarnation of this site. I’ve reposted here to make the content available again.

EA Mobile Air Hockey running on LG VX9800 phone

This article covers the design and development process of a shipping one-button mobile game - EA Air Hockey, which was released in the first half of 2006. This is not so much a post mortem as it is a dissection of the game. I was the designer and lead coder on this game, and I’ve taken the opportunity here to go into some detail about how the design evolved and how various technical and playability problems were addressed. Hopefully this will prove interesting or perhaps even useful for others who are tackling the same sort of design challenges.

Credit where credit is due

Before embarking on a detailed discussion of EA Air Hockey, I want to acknowledge the valuable input and guidance I received from key people @ EA Mobile (then Jamdat). First, credit goes to Zack Norman for signing the game in the first place, for his valuable input and suggestions throughout development, and mostly for coming up with the idea of doing an Air Hockey game. Next, credit goes to Travis Boatman, again for his valuable input through development and for his help with project administration (and for giving permission for me to publish this!). And last, but by no means least, credit goes to Adam Schwenk - my long-suffering producer who put up with all the stuff that producers have to put up with.

What is ‘Air Hockey’?

Air Hockey is the game where two players each use a disc shaped “mallet” (otherwise known as a “stick” or “hammer”) to hit a “puck” around a table. The table has a perforated surface through which air is blown by a fan - the air provides a cushion for the puck, which literally floats around the table due to the reduced friction. Each player defends a “goal”, which is simply a slot which is several puck-widths wide, and basically the first player to score 7 goals wins.

Why one-button?

The primary reason to develop a one-button game for a mobile phone is one of simple practicality: the keypads on most mobile phones are not really very good for playing games on. The trend with phones is always smaller, slimmer, flatter - and of course much of the “size” of a phone device comes from the keypad itself. Keypads are designed to look cool (e.g. RAZR), and so that you can dial a number and (maybe) text a message. Expecting anything much more than that is a luxury for many of the phones out there. Requiring only one button really nullifies this concern - no scrambling to find the right key.

The other compelling reason to create a one-button game is to appeal to the so-called “casual” gamers and the non-gamers (the “to be converted”) as it’s these folks who makeup the vast majority of “mobile phone users”. A game that is playable with one button should contain little to scare away or embarrass such a player. I wanted to make a game that the salesperson in the cellphone store could just hand to any customer and say, “just press the OK button to play”.

One-button design

So, I wanted to make a game that would work on any phone on which the following statements are true:

  • Program code can detect that a single key is ‘pressed’.
  • Program code can detect that a single key is ‘held’.
  • Program code can detect that a single key is ‘released’.

Thus, the game design I had in mind had to allow the player to control the entire game using just a single key, and where 100% of player input would resolve to pressing, holding and releasing a single button.

Now at this point, some readers will have no doubt noted that there are already games like this for mobile, and there have been for some time. In fact, some of the most successful mobile games ever already use such a system - Jamdat Bowling for example. So where’s the innovation with what I had in mind? Well, the big difference is this: what I wanted to do was a “real time” game - one that is not quantized into “strength, aim, spin, watch” slices. Rather, I wanted to apply a single-button control dynamic to a game that runs in real time, and which at no time pauses to allow the player to set a strength-gauge, or an aim-meter, or anything like that. The goal was to create a fluid, controllable “action” game where the only control available to the player was a single button.

So, here’s what I came up with. I am going to cover the design in terms of the primary challenges encountered, by detailing the solution to each challenge.

Timing of player shots

When the player presses the OK button, the mallet moves to intercept the puck. When the player presses the OK button, the mallet moves to intercept the puck.

Timing of shots was one of the more approachable design challenges. All that happens is that when a player presses OK, his mallet starts to move toward the puck. Shot aiming was a different matter, but the only real wrinkle with the shot timing is that pressing the button commences the shot, but the actual collision with the puck will happen at some time in the future, when the mallet (hopefully) collides with the puck. It is a subtle point, but this does cause some complications in the actual aiming logic, which I will cover below. The player must hold the button in order to continue the shot.

When the player releases the button, when a collision with the puck occurs, or when the mallet reaches the center line, the shot is canceled and the mallet returns to a more defensive position on the table (covered in mallet positioning below).

Strength of player shot

In order to provide some nuance and subtlety to the control, I modeled shot strength based in the length of time the button is held. So, if your mallet is near your goal, and you anticipate a cracking shot near the center line, you’d get pretty close to full strength. More defensive shots nearer your goal line would have less strength. Essentially, the longer you hold the button, the further your mallet moves, the stronger the shot. Note that the speed of your shot is always constant - there is no shot acceleration.

Positioning of player mallet

The player's mallet performs an oscillating arc around the current position of the puck. The player’s mallet performs an oscillating arc around the current position of the puck.

Positioning the mallet has some challenges. Taking a shot can be controlled with one button in a fairly obvious way, but how to position the mallet when not taking a shot? Using just one button, there’s really no alternative but for the mallet to position itself. I considered some other approaches, such as adding an “override” so that more advanced players could alter the mallet position using the d-pad arrow keys, but in the end I eliminated this logic and instead worked on ways to make the mallet move in useful ways with no player input. After analyzing how the real game is played, I realized that there are two main modes of play: offensive positioning (where the puck is in front of your mallet - i.e., closer to the opponent goal), and defensive positioning (where the puck is behind your mallet - closer to your goal). There’s also a transition mode, where the best mallet position is likely to be obtained by moving the mallet so that the puck is between it and the player’s own goal. Here’s what I did:

When the puck is in front of the mallet, the mallet performs an oscillating arc around the position of the puck. Essentially, a line is imagined between the center of the puck and the mallet, a position along the line is chosen (it is a fixed proportion of the line length) and then this distance is used as the radius of an arc. Then, the angle around this arc is simply oscillated and the mallet moved toward the resultant location. Since the mallet “falls back” into this oscillation mode after the player takes a shot, I was concerned that the resume position on the arc would need to know the context (i.e., where is the mallet coming from), but in practice the angle simply keeps oscillating at all times, and the mallet just “tends toward” the location currently dictated by the oscillation.

When the puck is between the mallet and the players goal, the positioning logic smoothly transitions into an alternate arc pattern. When the puck is between the mallet and the players goal, the positioning logic smoothly transitions into an alternate arc pattern.

When the puck is within a certain distance of the player’s own goal and moving toward that goal line, the mallet enters a different mode. This time the mallet moves itself to a location further away from the player’s own goal line than the puck, and again performs an orbiting oscillation around the puck.

In the transition between the two main modes (which appear seamless to the player - that is, the positioning of the mallet does not have any obvious discontinuity), there are some subtleties - the mallet has to make sure not to travel through the path of the puck, for example, and cause an inadvertent collision with the puck. This was more of a problem when the mallet was moving from defensive mode back to offensive mode, as the likelihood is that a collision with the puck will result in an own goal.

In both cases, the oscillation is performed in order to present alternate shot possibilities to the player. Fundamentally, this is really an application of the oscillating “aim” bar in games such as “Jamdat Bowling” to a game that is happening in real time.

Aiming player shots

Player shots have to take into account the path of the puck, including rebounds from the sides of the table. Player shots have to take into account the path of the puck, including rebounds from the sides of the table.

Aiming was one of the most interesting challenges. Given that the player has only one button to control the game, and that we want the button to cause the player mallet to do something sensible at all times (the player may need to defend or attack, and the puck could be located anywhere on the table relative to the mallet), there are quite a few factors to take into account. After much experimentation, I was finally able to resolve the reaction of the mallet to the player’s input consistently at all times. The solution to how to move the mallet resolved into an intercept problem. Essentially, the mallet moves in a direction that will result in the shortest (time wise) intercept of the puck, given the puck’s current velocity and location. I am not going to get into the detail of how to solve that problem mathematically, but suffice it to say that the solution takes into account the fact that the puck is decelerating, that it may bounce off any of the edges of the table multiple times before the mallet can hit it, and that the puck might actually go into one or the other players’ goals before the mallet has a chance to hit it! In practice, the intercept code proved to “feel” right to the player - it seems to intuitively move toward where a player would move the puck, including anticipating bounces and so on.

The latter point is a key factor in the controllability of the game, because it changes the notion of “aiming” into an action of “timing” - pressing the button earlier or later will result in different collision points with the puck, giving the player the opportunity to try different shots against the opponent (bank shots, etc). This works nicely with the one-button control mechanism, and again this takes your Jamdat Bowling “choose when to press the button” sort of activity and maps it onto a real time game.

One button AI

Early in development, the AI was quite a simplistic affair. It would pleasantly take shots back at you, without any real strategy or tactical analysis on its part. In fact, this played just fine, but it was decided that we needed to up the AI intelligence level so that those non-casual gamers could be presented with more of a challenge. It would have been easy enough to cheat and give the AI abilities that the human player does not have (in terms of positioning for shots, etc). However, I decided to “take the high road” and make the AI follow the same rules as the player (more or less). What I did was to make the positioning of the AI player’s mallet 100% identical to the player. So, the AI mallet would do the oscillating arc movement, etc. This left just one problem to solve, “when should the AI take a shot?”. What I did was to periodically calculate the result of an AI shot. The frequency of this evaluation was proportional to the smartness of the AI, and is one of the main factors between the three difficulty levels offered by the game. During the AI shot evaluation, various statistics are collected, obvious ones such as whether the shot is a scoring shot (or a home goal!), but also additional stats such as how many bounces the puck will take, whether the puck hits the other player’s “goal line” and so on. These are then fed into the AI logic, and filtered using the current “mindset” of the AI, which changes according to a table of “moods” defined for each difficulty level. As an example, some AI moods dictate that only direct scoring shots will be considered, others that only indirect scoring shots will be considered, and yet others that appraise a shot positively if it merely hits the player’s goal line. By stepping through AI moods, and using different tables of moods for different difficulty settings I was able to implement a scalable and challenging AI.

Referring to the AI moods - these are named “banker”, “acer” etc in the source code as there was some initial thought to surfacing the AI opponents with characterizations, as has been done with other tables sports games (pool, etc). In the end, we did not go down that road, but it would make a nice addition if there’s ever an Air Hockey 2.

One-button gamesmanship

Gamesmanship is the act of “gaming the game”. In other words, it is the use of tactics that exploit a weakness in a particular game’s design to the advantage of the player. In this case, there was one such issue which pervaded the testing process - “button-mashing”. It was discovered during the testing process that by “mashing” the button (ie, repeatedly pressing the button as fast as possible) it was possible to effectively block the AI. This did not give the player an unfair scoring advantage directly, but since the AI would not retaliate in this fashion (one possible solution was to make the AI button-mash in response, but that did not seem very satisfying), the AI was prevented from scoring and the random goals scored by the player while doing this resulted in player wins. It’s an interesting problem, and it’s something that would not happen in real life - if you were to employ this tactic against a human opponent, your opponent would simply slap you upside the head. This is generally borne out in EA Air Hockey’s 2-player mode - not (that I’ve witnessed) the head-slapping, but the fact that humans tend not to try to game each other in the same way as they would with a computer opponent. With regard to the button-mashing, part of me felt that if a player wanted to game the game in this way (futile as it would be to do so) then so be it. But the another part of me felt challenged to address this. In any case, after a while the folks in QA were complaining so loudly about this “flaw” that something just had to be done. I ended up trying many different solutions, but QA proved very adept at poking holes in any anti-mashing defenses, so I just had to keep trying different things. In the end, the solution turned out to be in-keeping with the design of the game. What I needed to do was step back, and think a little about the “real” Air Hockey, and some of the physical realities of playing the game. What I realized is that when, in the real game, a player goes to make a shot from the center line, they will physically have their playing arm at full stretch. Accordingly, shots played from the center line (that is, shots that originate when the player’s mallet is at or near the center line) will of necessity be lower-powered. This was simple to model, and fed nicely into the “shot strength” dynamic described above. The result is that button-mashing will still cause the player’s mallet to hover around the middle of the table, and this will still potentially “block” the AI, but the resulting blocked shot will be very weak, leaving the AI plenty of time to counter, and get a shot past the player.

One button per player, two players per game?

Two-player mode switches to a top-down perspective where players controls one button.

When I started work designing EA Air Hockey, I’d been given a mandate by Zack Norman that the game must feature a two player mode. Initially, thoughts were along the lines of doing a split screen, with each player having his own “perspective” view of the the action. Ultimately, I implemented a top down view, which was more satisfying for a number of reasons. With the top-down view, I was able to orient the table sideways, so that two players could hold the phone between them. Then, to play the game, player 1 (at the right hand side of the screen) would use the ‘6? button, and player 2 (at the left) would use the ‘4? button. Each player has the exact same controls and in effect the phone is treated like a miniature arcade table.

One-button final thoughts

Here are some final thoughts on the completed game, some things that worked, others that worked out differently to my expectations, and details of a couple of features that did not make it into the game.

Intuitive controls

Although much time and energy was devoted to creating an intuitive control system, in practice there’s one issue that people seem to trip over. The idea of pressing and then holding the OK button to take your shot seems to trip some people up. When the controls are explained to people, they understand it quickly enough, but people, especially non-gamers, seem to intuitively press OK and then immediately release the button. In retrospect, this is understandable because that’s what you do in regular use of the phone, dialing numbers, sending a text message, etc. As a direct result of this, I ended up implementing a fairly extensive tutorial system which aims to train the player to hold the button down for longer shots. It was interesting just how much the use of a single button could be misinterpreted - but since there is only one button, it’s really an all-or-nothing proposition in some ways!

Dropped game play modes

4-player mode was dropped early on, but it might have looked like this. 4-player mode was dropped early on, but it might have looked like this.

As with most projects, some features were cut due to lack of time or space, or both. A couple of the more interesting (although possibly marginal in terms of additional value) modes were the 2 x players vs 2 x AI mode, and the 2 x players vs 2 x players modes. In principle, there’s no reason why both of these modes can’t be implemented using exactly the same techniques used elsewhere in the game, but there was just no time to include them in the game.

Leading development on BREW

I developed the game initially on the BREW platform, and therefore it was developed in native code (C++ and ARM assembler for some special effects). The game runs marvelously on BREW, and because you have raw access to such things as the primary graphics buffer and so on, you can squeeze every ounce of performance out of the BREW devices. When it came time to get the game finalized on the J2ME platform, it became clear that performance was going to be an issue. Ultimately, things resolved themselves in a fairly satisfactory way - we achieved the frame rates that are expected on the J2ME devices. That said, when the “expected” frame rate is 5 fps or less, you can imagine that the gameplay experience is going to be significantly different to that on BREW platforms which ran at 15 fps on all but one of the reference platforms (the VX6000 is well known to be a slug, and even that ran at a respectable 12 fps - though not without some hoop jumping it must be said!).

Creating art assets with a 3D package

One design decision I made early on was to create all of the in-game art assets directly using a 3D art package. The game uses sprites to display its perspective view of the table, and generating these with a 3D package provided a neat way to generate all the different sizes images needed to give the illusion of perspective movement. There were some issues with this approach, such as the edge aliasing problem that always arises when attempting to create 2D “sprites” using a 3D package, but those problems were overcome and we did in fact create 100% of the in game art using 3DS Max on all but some of the very tiniest screens. On some of the really small screens (the KX414 BREW phone for example) we applied some simple filtering to the output of Max, so it really was not an issue. The other advantage of using a 3D package is that generating assets for varying screen sizes is as easy as changing the render target size, and then adjusting a couple of constants in the code. In this way the game artist, Shaun Tsai, was able to generate the art for all BREW (from the 104×68 pixel KX414 up to the 320×240 pixel VX9800) and J2ME versions from the exact same art assets by simply rendering to the specifications I gave him for the various screen sizes needed. It’s an approach that will not work well for all games, but for Air Hockey with its non-organic, solid game objects, this was ideal. This approach does also require that the game moves objects in a virtual space (not pixel space), and in fact, internally, Air Hockey tracks all objects in a virtual 3D coordinate space. I’ve even created a test version of the game that replaces the 2D sprite rendering with real 3D, though that’s just a personal project at this point.

AI surprises

The AI evaluates all shots equally, it does not distinguish between offensive and defensive play - if there’s a scoring opportunity while in defense mode, go ahead and take it! An interesting side-effect of this is that the AI us quite capable of bouncing a scoring shot against the player off its own back table edge (the goal line). It does not occur to frequently in regular play, but it does happen, and when it does it is quite intentional. :)

Rejected art

Vector style (I kinda like this one). Vector style art sketch (I kinda like this one).
Flying saucer theme. Flying saucer style art sketch.
Stone age style. This stone age style looks cool - the physics would be interesting though!

The final art for the game I think does a good job of representing the look of a classic Air Hockey table, garish primary colors are all the rage for the real game. We did create a number of other prototype art styles for the game (there was at one point some thought of unlocking these), which were rejected for one reason or another. I’ve included a couple of samples of these prototypes here.

Genesis of EA Air Hockey

Projectyle on the Atari ST circa 1991. Projectyle on the Atari ST circa 1991.
Projectyle running on a mobile device circa 2003. Projectyle prototype running on a mobile device circa 2003.
Projectyle running in 3D on a mobile device circa 2004. Projectyle prototype running in 3D on a mobile device circa 2004.

Although I give Zack Norman credit for the idea of doing an Air Hockey game, in fact the mobile game came about directly because of another game I designed and developed some 17 years or so ago. The game was “Projectyle”, which was published (ironically, as it turns out) by Electronic Arts for Atari ST and Amiga in about 1990. Projectyle was not a one-button game, and it wasn’t that Zack had been a huge fan, or anything like that (I doubt he knew it existed), but I had obtained the rights to the game from EA and as I’ve mentioned previously on this site, there does exist a playable mobile version of the game - actually, both 2D and 3D prototypes were developed. It was the 2D mobile version of Projectyle, running on a Microsoft Smart Phone as I recall, that I showed to Zack, this would be E3 2004, and it was Projectyle that peaked Zack’s interest in developing an Air Hockey game with me. Projectyle (the original) was a three-player game (using keyboard and multiple joysticks) which I’ve described previously (on this site) as a cross between Air Hockey and Subbuteo. The mobile versions of the game support three player multiplayer over bluetooth, but remain unpublished.

EA Mobile official page for EA Air Hockey.

Article originally posted over @ GamesOnDeck.com, part one and part two.

Inside the BREW helper macro function table.

Inside the BREW helper macro function table.

Note: This article was originally published on a previous, now defunct, incarnation of this site. I’ve reposted here to make the content available again.

In need of a listing of the function table behind the BREW helper macros, and unable to quickly find one, I created the following table derived from information in the BREW AEEStdLib.h header file. It’s simply a listing of the function table index and offset, in decimal and hex, of each BREW helper macro. I’ve also included the name of the function table entry for each helper macro.

In my case, I had a stray static constructor which was being called in the BREW emulator before BREW proper was initialized. The emulator complained that it could not load my module, with an error message like this:

BREW_Simulator.exe’: Loaded ‘C:\brew-app\brew-app.dll’, Symbols loaded.
First-chance exception at 0×03cc41ad (brew-app.dll) in BREW_Simulator.exe: 0xC0000005: Access violation reading location 0×000000f4.
‘BREW_Simulator.exe’: Unloaded ‘C:\brew-app\brew-app.dll’
*AEEShell.c:6736 - #*g*C=1034758:6

In the above, the “Access violation reading location 0×000000f4? was because aforementioned static constructor was calling the BREW helper function STRDUP() (which @ static constructor time calls through an as-yet uninitialized helper function table), which as can be seen from the table below is at offset 0×00f4 in the helper function table.

It should be noted that on an actual BREW device, static constructors are typically not called at all …

So here is the table, HTH, YMMV, E&OE. :)

Index Function name MACRO name Offset
(dec)
Offset
(hex)
0 memmove MEMMOVE 0 0×0000
1 memset MEMSET 4 0×0004
2 strcpy STRCPY 8 0×0008
3 strcat STRCAT 12 0×000C
4 strcmp STRCMP 16 0×0010
5 strlen STRLEN 20 0×0014
6 strchr STRCHR 24 0×0018
7 strrchr STRRCHR 28 0×001C
8 sprintf SPRINTF 32 0×0020
9 wstrcpy WSTRCPY 36 0×0024
10 wstrcat WSTRCAT 40 0×0028
11 wstrcmp WSTRCMP 44 0×002C
12 wstrlen WSTRLEN 48 0×0030
13 wstrchr WSTRCHR 52 0×0034
14 wstrrchr WSTRRCHR 56 0×0038
15 wsprintf WSPRINTF 60 0×003C
16 strtowstr STRTOWSTR 64 0×0040
17 wstrtostr WSTRTOSTR 68 0×0044
18 wstrtofloat WSTRTOFLOAT 72 0×0048
19 floattowstr FLOATTOWSTR 76 0×004C
20 utf8towstr UTF8TOWSTR 80 0×0050
21 wstrtoutf8 WSTRTOUTF8 84 0×0054
22 wstrlower WSTRLOWER 88 0×0058
23 wstrupper WSTRUPPER 92 0×005C
24 chartype GETCHTYPE 96 0×0060
25 SetupNativeImage CONVERTBMP 100 0×0064
26 malloc MALLOC 104 0×0068
27 free FREE 108 0×006C
28 wstrdup WSTRDUP 112 0×0070
29 realloc REALLOC 116 0×0074
30 wwritelongex WWRITELONGEX 120 0×0078
31 wstrsize WSTRSIZE 124 0×007C
32 wstrncopyn WSTRNCOPYN 128 0×0080
33 OEMStrLen OEMSTRLEN 132 0×0084
34 OEMStrSize OEMSTRSIZE 136 0×0088
35 GetAEEVersion GETAEEVERSION 140 0×008C
35 GetAEEVersion AEE_IS_PATCH_PRESENT 140 0×008C
36 atoi ATOI 144 0×0090
37 f_op FADD 148 0×0094
37 f_op FSUB 148 0×0094
37 f_op FMUL 148 0×0094
37 f_op FDIV 148 0×0094
37 f_op FPOW 148 0×0094
38 f_cmp FCMP_L 152 0×0098
38 f_cmp FCMP_LE 152 0×0098
38 f_cmp FCMP_E 152 0×0098
38 f_cmp FCMP_G 152 0×0098
38 f_cmp FCMP_GE 152 0×0098
39 dbgprintf __DBGPRINTF 156 0×009C
40 wstrcompress WSTRCOMPRESS 160 0×00A0
41 LocalTimeOffset LOCALTIMEOFFSET 164 0×00A4
42 GetRand GETRAND 168 0×00A8
43 GetTimeMS GETTIMEMS 172 0×00AC
44 GetUpTimeMS GETUPTIMEMS 176 0×00B0
45 GetSeconds GETTIMESECONDS 180 0×00B4
46 GetJulianDate GETJULIANDATE 184 0×00B8
47 sysfree SYSFREE 188 0×00BC
48 GetAppInstance GETAPPINSTANCE 192 0×00C0
49 strtoul STRTOUL 196 0×00C4
50 strncpy STRNCPY 200 0×00C8
51 strncmp STRNCMP 204 0×00CC
52 stricmp STRICMP 208 0×00D0
53 strnicmp STRNICMP 212 0×00D4
54 strstr STRSTR 216 0×00D8
55 memcmp MEMCMP 220 0×00DC
56 memchr MEMCHR 224 0×00E0
57 strexpand STREXPAND 228 0×00E4
58 stristr STRISTR 232 0×00E8
59 memstr MEMSTR 236 0×00EC
60 wstrncmp WSTRNCMP 240 0×00F0
61 strdup STRDUP 244 0×00F4
62 strbegins STRBEGINS 248 0×00F8
63 strends STRENDS 252 0×00FC
64 strchrend STRCHREND 256 0×0100
65 strchrsend STRCHRSEND 260 0×0104
66 memrchr MEMRCHR 264 0×0108
67 memchrend MEMCHREND 268 0×010C
68 memrchrbegin MEMRCHRBEGIN 272 0×0110
69 strlower STRLOWER 276 0×0114
70 strupper STRUPPER 280 0×0118
71 wstricmp WSTRICMP 284 0×011C
72 wstrnicmp WSTRNICMP 288 0×0120
73 inet_aton INET_ATON 292 0×0124
74 inet_ntoa INET_NTOA 296 0×0128
75 swapl SWAPL 300 0×012C
76 swaps SWAPS 304 0×0130
77 GetFSFree GETFSFREE 308 0×0134
78 GetRAMFree GETRAMFREE 312 0×0138
79 vsprintf VSPRINTF 316 0×013C
80 vsnprintf VSNPRINTF 320 0×0140
81 snprintf SNPRINTF 324 0×0144
82 JulianToSeconds JULIANTOSECONDS 328 0×0148
83 strlcpy STRLCPY 332 0×014C
84 strlcat STRLCAT 336 0×0150
85 wstrlcpy WSTRLCPY 340 0×0154
86 wstrlcat WSTRLCAT 344 0×0158
87 setstaticptr SETSTATICPTR 348 0×015C
88 f_assignstr FASSIGN_STR 352 0×0160
89 f_assignint FASSIGN_INT 356 0×0164
90 wwritelong WWRITELONG 360 0×0168
91 dbgheapmark DBGHEAPMARKEX 364 0×016C
92 lockmem LOCKMEM 368 0×0170
93 unlockmem UNLOCKMEM 372 0×0174
94 dumpheap DUMPHEAP 376 0×0178
95 strtod STRTOD 380 0×017C
96 f_calc FFLOOR 384 0×0180
96 f_calc FCEIL 384 0×0180
96 f_calc FSQRT 384 0×0180
96 f_calc FSIN 384 0×0180
96 f_calc FCOS 384 0×0180
96 f_calc FTAN 384 0×0180
96 f_calc FABS 384 0×0180
97 sleep MSLEEP 388 0×0184
98 getlasterror GETLASTFPERROR 392 0×0188
99 wgs84_to_degrees WGS84_TO_DEGREES 396 0×018C
100 dbgevent DBGEVENT_EX 400 0×0190
101 AEEOS_IsBadPtr ISBADREADPTR 404 0×0194
101 AEEOS_IsBadPtr ISBADWRITEPTR 404 0×0194
102 aee_basename BASENAME 408 0×0198
103 aee_makepath MAKEPATH 412 0×019C
104 aee_splitpath SPLITPATH 416 0×01A0
105 aee_stribegins STRIBEGINS 420 0×01A4
106 GetUTCSeconds GETUTCSECONDS 424 0×01A8
107 f_toint FLTTOINT 428 0×01AC
108 f_get FGETHUGE_VAL 432 0×01B0
108 f_get FGETFLT_MAX 432 0×01B0
108 f_get FGETFLT_MIN 432 0×01B0
109 qsort QSORT 436 0×01B4
110 trunc TRUNC 440 0×01B8
111 utrunc UTRUNC 444 0×01BC
112 err_realloc ERR_REALLOC 448 0×01C0
113 err_strdup ERR_STRDUP 452 0×01C4
114 inet_pton INET_PTON 456 0×01C8
115 inet_ntop INET_NTOP 460 0×01CC
116 GetALSContext GETALSCONTEXT 464 0×01D0


Debugging BREW Applications

<rant>

I’ve been developing for Qualcomm BREW for about 4 years now. By and large it has been an “OK” experience, but sometimes I tear out my hair for lack of an on-device debugging solution (that works).

To my left is a BREW device, connected via USB umbilical to the Mac, where I do BREW development using XP running in VMWare Fusion. BREW offers no on-device debugging. Or rather, it technically does, but what is offered scares the heck out of me. So much so, in fact, that though I have downloaded Qualcomm’s “on-device debugger” BREW Extension, the special module startup code, and read about how to hook it in, that is where it ends. The known issues document alone is enough to send me running for the hills, and add to that the fact that the “BREW on-device debugger” is really little more than a monitor program that understands the GDB debugging protocol. You don’t actually get a Windows (or anything else) debug interface, rather you are told to download and compile the GDB client, or Insight if you want an actual GUI. It reminds me of where Palm OS was 5 years ago: that was terrible even back then, but at least they gave you a GUI. Heck, in 1989 I was doing on-device remote debugging using SNASM on the Amiga and ST - that’s almost 20 years ago

Suck it up and be a man, I know.

And on my right is the iPhone hooked up to the Mac with a remote debugging GUI right out of the box. As if to add insult to injury, the iPhone is based on the same family of CPU as the BREW devices - ARM. I wonder if Xcode could be made to talk to a BREW device (it uses GDB too). They use the same compiler too. Ah well.

I guess I felt compelled to rant a bit about this as I’ve spent several days over the past week looking at DBGPRINTF() output from a gnarly crasher that would move around as the debug logging was added.

On the plus side, debugging BREW apps in the Windows “BREW Simulator” using the Visual Studio debugger works great 99.99% of the time; however, that is useless for finding device freeze or crash problems.

</rant>