Cocoa for Scientists (XXXII): The Physics of Sumos (A Flirtation with iPhone Game Development)

·

·

I’ve spent most of my developer life solving scientific problems and developing tools to make people more productive in one way or another. A field of software development with which I have had very little involvement is games development. To be honest, I am not even much of a game player, let alone developer. Sure, I enjoy 5 minutes alone with Flight Control or Fieldrunners as much as the next guy, but beyond that, most games don’t really hold my interest. I would rather be creating something myself.

So I am perhaps the last person you would expect to develop an iPhone game. Nevertheless, my first — and probably last — game appeared in the App Store last week: Sumo Master. The story of Sumo Master extends back almost a year. In this tutorial, I want to tell the story of Sumo Master, why I developed it, and what I learnt along the way.

Conception

Sumo Bill.

The idea for Sumo Master was actually conceived by my brother during one of many iPhone app brainstorming email exchanges. In amongst a list of potential iPhone apps, one more ludicrous than another, was the idea for a sumo game in which you tried to push other sumos out of the ring. At the time there were no similar games on the iPhone, and it seemed well suited to the device, so the idea stuck.

The original concept was quite a bit different to the final game. The intention was that sumos would be controlled much like balls, ala Super Monkey Ball. You would roll your Sumo by tilting the device, and try to knock other ball-like sumos out of the ring. (As an aside, a game like this, called iBot Sumo, is now in the App Store. Shows there is nothing new under the sun!)

I pretty quickly moved away from the ‘rolling’ sumos idea, but it took longer to realize that the tilt control was also not right for this game. More on that later.

Simple Rules, Complex Behavior

The game probably would never have been developed if not for a period of boredom around the 2008 Christmas break. I decided to take on a short, fun project unrelated to my work, and chose the sumo game. After all, how hard could it be, right?

I began with square UIViews, and used Core Animation to move them around on the screen. The rules of movement were quite simple: Squares would attack any nearby, smaller square, while trying not to go out of the ring. Apart from these simple behavioral rules, the squares did not actually interact, so they could move right through one another.

Putting aside the sumo game for a moment, this was a very interesting exercise. The simulation I developed was not totally dissimilar to an astronomical simulation of a star field. It was fun to just watch the squares cluster and move apart, and observe the effect of changing parameters such as the number and size of squares. As is now well established in science, simple dynamical rules can lead to interesting and seemingly complex behavior.

During this phase, I setup the basics of the game internals. There was a simple game loop, in the form of an NSTimer that would fire every 0.2 seconds. When the timer fired, the model would be propagated 0.2 seconds into the future. The resulting position of each square would be used to set the target position of the corresponding UIView. The actual animation of the square on the screen would be left to the Core Animation background thread.

Here is the source code that was used to update the position of the squares, and the corresponding views on the screen.

-(void)nextAnimationStep:(NSTimer *)timer {
    [UIView beginAnimations:@"Sumo Moves" context:NULL];
    [UIView setAnimationDuration:ANIMATION_TIME_INTERVAL];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    for ( SSSumo *sumo in sumos ) {
        [sumo updateDynamicQuantities];
        SSSumoView *sumoView = [sumoViews objectForKey:[NSValue valueWithPointer:sumo]];
        sumo.positionX += sumo.velocityX * ANIMATION_TIME_INTERVAL;
        sumo.positionY += sumo.velocityY * ANIMATION_TIME_INTERVAL;
        sumoView.center = CGPointMake(sumo.positionX, sumo.positionY);
    }
    [UIView commitAnimations];
}

The Euler method, probably the simplest of all propagators, was used in this and all later versions of the game. In a scientific simulation, this would be a no-no, but in games, you can cut corners as long as nobody notices. That’s one of the big differences between developing a game and a scientific app.

The updateDynamicQuantities method hides quite a lot of the complexity. Each square already had the properties of a sumo: mass, radius, velocity, maximum speed, acceleration, and maximum acceleration. These quantities remained the basis of sumo movement throughout the game’s development, though the values assigned to each were regularly varied to produce different physics.

Collisions

Collisions are probably one of the harder aspects of game physics to get right. I didn’t want to resort to using an off-the-shelf game physics engine with Sumo Master, because it was a hobby project, and part of the fun was just trying to figure out how to do it yourself. (If you do want a game engine, take a look at Cocos2d.)

It soon became abundantly clear that the basic physics you learn in high school, or even university, were not really going to help that much. My first attempts involved completely elastic collisions. As you can see in this movie of an early simulation with colored dots, the balls don’t feel much like sumo wrestlers (although it is still fun to watch).

Even when you put a sumo-like JPEG on each one, it still felt more like a bucket of golf balls than soft spongy sumos.

In a perfectly elastic world, where objects are like eight balls and all collisions instantaneous, it may have been enough, but sumos are fat and spongy, and stick together. In short, I needed a way to model inelasticity.

I actually don’t know what real scientific models of inelasticity look like, but I came up with something that seemed to work OK. Basically, I used a simple power law to model the force exerted versus the compression.


F is the force at a separation of d. r1 and r2 are the radii of the respective sumos. In this equation, a simple harmonic force has been assumed with a force constant of k, though higher order formulas were also tested.

This formula doesn’t in itself result in any inelasticity; in order to absorb energy during a collision, the force constant, k, needs to be varied during the collision. If the sumos are coming together, with the separation (d) decreasing, one value is used, and when the separation is increasing, a smaller value is used. The effect of this is that the forces for expansion are smaller than compression at any given separation, leading to an overall loss of energy.

By varying the magnitudes of the two force constants, you can make the sumos bounce like a ball, or stick together like they are smothered in glue, or anything in between. By making the force constant bigger for expansion than compression, you end up introducing new energy into the system, and you get a pin ball cushion type effect. This was used for the red pylons in the game, which fling the sumos off at speed.

Biomechanics

Even with a means of modeling elasticity, the game still didn’t feel ‘realistic’. For example, if a sumo charged another sumo from behind, it gained no advantage, and if a smaller sumo took a long run up to charge another stationary sumo, it came almost immediately to a halt. These aspects did not fit with my mental picture of a sumo contest. What was missing?

Biomechanics. My model was fine for ball-like objects, but sumos are people, and they have the physical limitations of people. If you push one from behind, its ability to remain on the spot is less than if you push it from in front. A sumo that is stationary or walking backwards is much less capable of exerting a force than one that is moving forward (eg charging).

Without going to the extreme of modeling a complete human body, I was able to come up a simple rule which made the sumos in the game feel more realistic. I basically took the projection of the velocity of each sumo along the line of centers, and determined the ratio. This ratio — having been conditioned a bit to ensure it was not too big or small — was used as a multiplier for the force. If one sumo had a greater component of velocity toward the other, this resulted in a greater force. If a sumo was walking backwards, its velocity component toward the other sumo would be negative, and its exertion impeded as a result.

Control

The original plan to use tilt control for the sumos was soon abandoned. It was just too difficult to control a sumo when other sumos were actively trying to push it out of the arena. This type of control works OK when you have almost full control of what happens to your character, but when opponents have a say too, it becomes unwieldly.

In the end, I decided to use about the most simple gesture you can imagine: point to destination. The sumo would just go where you pointed…or try to.

I only made one major change to the original implementation: Initially, if you took your finger off the screen, the sumo would stop moving. Effectively this meant you had to pretty much keep your finger constantly on the screen, which would mean your hand could get in the way of the action.

At some point I realized that by simply making the sumo go to the last point of finger contact, it would be easier to control the game. The last point of contact was delineated on the screen by a gray target, so the player could see where the sumo was headed at all times. Using this approach, you can control the sumo with infrequent taps, rather than constant dragging on the screen. Such a small change, but it made a big difference.

Performance and The Game Loop

Toward the end of development, we started to look at performance. The game would jump a little at times, especially when there were lots of sumos on the screen at once. The problem seemed to be purely related to graphics, rather than anything to do with game mechanics.

One thing I did to help matters was decrease the size of the sumos a little. This improved graphics performance by causing less of the screen to redraw in each frame.

Another change I made at this point, though it is questionable whether it led to much performance gain, was to replace the UIImageViews used for the sumos with plain CALayer’s. By setting the content property to a CGImageRef, the layer will display an image.

In addition to moving from layers to views, I also took over most of the animation myself, and implemented a more traditional game loop. The NSTimer was made to fire at 30fps, rather than 5, and all Core Animation actions were disabled during movement of the sumos. The code looks like this

// Sumos
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
for ( SSSumo *sumo in mutableSumos ) {
    SSSumoLayer *sumoLayer = [sumoLayers objectForKey:[NSValue valueWithPointer:sumo]];
    sumoLayer.position = CGPointMake(sumo.positionX, sumo.positionY);
    if ( sumo.isAlive ) {
        [sumoLayer updateRotationForSumo:sumo];
    }
}
[CATransaction commit];

At about this time, I read about dynamic frame skipping in the Apress book iPhone Game Projects, and decided to implement that too. It’s actually a pretty simple algorithm:

  • See how much time has passed since the last update.
  • Calculate how many steps to propagate for that time.
  • Propagate the model that number of steps.
  • Update the view for the new model state.

Basically, instead of doing a fixed number of steps each time, you check to see how much time actually passed, and adapt to that.

In code it looks like this

// Determine how many steps to propagate
NSTimeInterval propTimeStep = ANIMATION_TIME_INTERVAL / PROPAGATION_STEPS_PER_ANIMATION_STEP;
NSTimeInterval timeSinceLast = ANIMATION_TIME_INTERVAL;
if ( lastUpdate ) timeSinceLast = -1.0f * [lastUpdate timeIntervalSinceNow];
NSInteger numberPropagationSteps = MAX(0.0f, timeSinceLast / propTimeStep);
...
lastUpdate = [NSDate dateWithTimeIntervalSinceNow:(numberPropagationSteps*propTimeStep - timeSinceLast)];

Core Animation

Core Animation was used for the game itself, but also for all of the peripheral screens that you need in a game. One of my favorites is the sumo chooser screen, where you pick which sumo you are going to play with. Originally, I had in mind quite a simple animation, where you would click a sumo, and a picture would descend from above, and some statistics from below, like in this version.

When I presented this to my designer, Marcello Luppi, he wasn’t impressed, and came back the next day with the complex sequence of animations and fades that appear in the shipping product.

Marcello designed the sequence in Photoshop, but he used a neat trick to show me how it should look: he made a bunch of JPEG snapshots, and sent them to me. He told me to open the first one in QuickLook, and then scan through them quickly by holding the down arrow key. The effect is the same as a kid’s flick book. Worked great to see the intricacies of the animation so that I could figure out how to code it.

If you haven’t seen the game, here is what the final sumo chooser screen behaves like.

The coding itself is not terribly difficult, but quite laborious. It’s all about knowing who goes where and when. Here is a little of the code to give you a flavor:

static CGFloat AnimationBeginSumoHighlight = 0.0f;
static CGFloat AnimationBeginSumoTurn = 0.1f;
static CGFloat AnimationDurationSumoHighlight = 0.15f;
static CGFloat AnimationDurationSumoTurn = 0.2f;

-(void)showDescriptionForSumoIndex:(NSUInteger)sumoIndex {
    UIButton *sumoButton = [sumoButtons objectAtIndex:sumoIndex-1];

    // Select the clicked sumo
    UIImageView *sumoHighlightImageView = [sumoHighlightImageViews objectAtIndex:sumoIndex-1];
    [UIView beginAnimations:@"Sumo Highlight" context:NULL];
    [UIView setAnimationDuration:AnimationDurationSumoHighlight];
    [UIView setAnimationDelay:AnimationBeginSumoHighlight];
    sumoHighlightImageView.alpha = 1.0f;
    [UIView commitAnimations];

    // Unselected sumos turn to walk out
    [UIView beginAnimations:@"Sumos Turn" context:NULL];
    [UIView setAnimationDuration:AnimationDurationSumoTurn];
    [UIView setAnimationDelay:AnimationBeginSumoTurn];
    NSUInteger i;
    for ( i = 1; i <= 4; ++i ) {
        UIButton *button = [sumoButtons objectAtIndex:i-1];
        button.userInteractionEnabled = NO;
        if ( i != sumoIndex ) {
            button.transform = CGAffineTransformMakeRotation( (i % 2 ? -1 : +1) * M_PI / 2 );
        }
    }
    [UIView commitAnimations];

This piece of code only covers the point in time when you tap a sumo, it lights up, and the other sumos turn to the side in order to walk off. The rest of the animation is similar, but there is quite a lot of it all up. It took about a day to code, but I think the end result was worth it, with all the component animations working together to create a fluid whole.

Squashing a Sumo

I ended up using Core Animation in some ways that I hadn’t anticipated. For example, initially the sumos were completely static images that would rotate and translate, but nothing more. They didn’t feel like living things, or even bouncy toys.

We didn’t want to go down the route of completely animating the sumos, because that wasn’t really the point of this particular game. In some ways, they were supposed to feel a bit like inanimate toys, but even inanimate toys move a bit.

I thought I would try to use Core Animation transforms to make the sumos appear to squash or even wobble. As it turned out, I was able to do a reasonable job of both effects, but only the squash made it into the game, due to performance constraints. Below is a movie with some particularly bouncy sumos, that demonstrate the wobble and squash effects. All are generated using a single static image.

I realized pretty quickly that what was needed to squash a sumo was a scale transform. But blindly scaling the sumo along the axis of collision would cause it to become discus shaped, with both the back and front squashing inwards (see diagram below). I only wanted the collision side to squash in, and the back side to remain relatively unaffected.

The trick I came up with is to first translate the sumo so that the origin of the coordinate system is approximately along its spinal column, and then to scale. That way, areas furthest from the origin, such as the naval, will translate the greatest distance, and the back of the sumo will scale very little in absolute terms.

Just like a Real Game

Even when the game is working the way you want it to, there are still a lot of last minute things to do. For example, you need sound effects, and music. For music, we purchased some stock Japanese pieces for a small fee, and played them back using the AVAudioPlayer class. Sound effects were either taken from Garage Band, or created ourselves: every grunt you hear is yours truly, with a lick of Amadeus Pro effect for disguise. Sound effects were played back using the System Sound Services API.

One of the last aspects added to the game, but one which makes it much more interesting, were the moving red pillars. Initially you would just fight other sumos in a static arena. We were playing with the idea of having some static pillars at the side that you could bounce off, but at the last minute we came up with the idea of having the pillars move in different patterns, sometimes even crossing the arena itself. This added immensely to the game play.

In terms of implementation, these pillars were very easy to introduce, because they are actually just other sumos in disguise. By changing the inelasticity force constants, they were made very bouncy, like pin ball cushions, but otherwise, they are just sumos following a predefined path.

Multiplayer

When Apple released the 3.0 SDK, we knew we couldn’t really put out a fighting game without a multiplayer mode. It gave me a few days of headaches, but it worked out in the end. What I found difficult was that events could happen in different ways when you have a multiplayer UI. For example, one player could pause; you have to make sure the other player also gets paused, and then also ensure that both players can reenter the game together. All in all, a bit of a harrowing experience, but doable.

The Game Kit API actually does make it pretty easy to communicate. You simply send any data you like to a peer in a single call. You can send the data in one of two modes: reliable or unreliable. If you send it in reliable mode, it will get to the destination, barring network disconnection or some other catastrophe. If you use the unreliable mode, you can’t guarantee the data will get across, or the order in which it will arrive, but if it does make it, it will get there fast.

I ended up using the reliable mode for all essential information, such as initial setup, pausing or stopping the game, etc. I used the unreliable mode to update sumo state several times a second.

Game for more?

All in all, it took me about 2 months of solid coding to develop Sumo Master, and Marcello was busy coming up with graphics for a big chunk of that time too. Quite a lot of work for what you would probably think was a very simple game just to look at it.

Probably the most important thing I learnt in the process was that although there is some overlap between games programming and scientific programming, in that both deal with physical modeling, with games programming it comes down to giving the impression of completeness and complexity, when in reality you are really cutting corners and doing everything as simply as possible. The skills you carry over from science are useful, but the attitude you need for games programming is different.


Leave a Reply

Your email address will not be published. Required fields are marked *