Matt Rajca

Jul 01 2011
May 21 2011

Writing a Bouncing Ball Physics Simulation for iOS

Recently, I have been doing some iOS game development on top of the Chipmunk physics engine. Chipmunk is an exceptionally well-designed library which makes coding some basic physics simulations really fun. The goal of this article is to introduce you to the basics of Chipmunk while creating a bouncing ball physics simulation for iOS. When you tap on your device’s screen, a new ball will be added to the scene. As you tilt your device, all of the balls will fall in respect to the new gravitational field, reported by the accelerometer.

Getting Started

To get started, create a new Xcode project based off of the ‘View-based Application’ template. Select ‘iPad’ for the target device to get the greatest amount screen real estate. Name the new project “BounceyBalls”.

Xcode Project

Next, we need to download the latest release of the Chipmunk physics library, found here. Extract the downloaded archive and drag the src and include subfolders into the Classes group of your Xcode project. Select “Copy items into destination group’s folder (if needed)” if it is unchecked and hit “Add”.

If we were to build the project, we would get a ton of build errors at this point. We need to tell Xcode where Chipmunk’s include files reside. To do so, select the Xcode project in the file navigator. Click on the ‘BounceyBalls’ target and switch to the ‘Build Settings’ tab. Find the ‘Header Search Paths’ setting and edit its value to include the path Classes/include/chipmunk. Compiling the Xcode project should now yield no build errors or warnings.

Since we will be using Core Animation in this project, we also need to make sure the product gets linked against the QuartzCore framework. Right-click on the Frameworks group, select Add > Existing Framework, and choose QuartzCore.framework from the list.

Chipmunk Overview

Before we dive into the code, let’s review some basic information about Chipmunk. Chipmunk is a fast and portable 2D physics engine written in C and distributed under the MIT license. It is a nice and lightweight alternative to the heftier Box2D library (written in C++). Even though the Chipmunk library comes with a simple API, it also includes support for advanced features such as joints, collision detection handlers, and queries. While I don’t cover them in this article, they are well-documented on the Chipmunk website. Chipmunk also includes functions for performing common operations with vectors.

The three fundamental concepts used in Chipmunk are spaces, bodies, and shapes.

  • A space is the “world” in which the physics simulation takes place - it serves as the container for bodies and shapes.
  • A body is any object that has a mass and a position in space. Chipmunk supports both dynamic bodies and static bodies. As their names imply, dynamic bodies are capable of movement while static bodies, such as the ground, are not.
  • A shape defines how a body interacts with other bodies, especially during collisions. It can take the form of a circle, segment, or polygon.

Writing the Simulation

Let’s dive into the code. Replace the contents of the BounceyBallsViewController.h file with the code below.

#import "chipmunk.h"
#import <QuartzCore/QuartzCore.h>

@interface BounceyBallsViewController : UIViewController
            < UIAccelerometerDelegate > {

    cpSpace *space;
}

@end

The code above should be fairly straightforward. We import the necessary header files, configure our view controller so it conforms to the UIAccelerometerDelegate protocol, and declare a pointer to a cpSpace, which serves as the container for all of the objects in the simulation.

Now, let’s switch over to the BouncingBallsViewController.m file. We will define a method, setupSpace, which configures the “world” for our physics simulation.

#define GRAVITY 400.0f

- (void)setupSpace {
    cpInitChipmunk();

    space = cpSpaceNew();
    space->elasticIterations = space->iterations;
    space->gravity = cpv(0.0f, GRAVITY);

    CGSize size = [[self view] bounds].size;

    cpBody *edge = cpBodyNewStatic();
    cpShape *shape = NULL;

    // left
    shape = cpSegmentShapeNew(edge, cpvzero, cpv(0.0f, size.height), 0.0f);
    shape->u = 0.1f;
    shape->e = 0.7f;
    cpSpaceAddStaticShape(space, shape);

    // top
    shape = cpSegmentShapeNew(edge, cpvzero, cpv(size.width, 0.0f), 0.0f);
    shape->u = 0.1f;
    shape->e = 0.7f;
    cpSpaceAddStaticShape(space, shape);

    // right
    shape = cpSegmentShapeNew(edge, cpv(size.width, 0.0f),
                              cpv(size.width, size.height), 0.0f);
    shape->u = 0.1f;
    shape->e = 0.7f;
    cpSpaceAddStaticShape(space, shape);

    // bottom
    shape = cpSegmentShapeNew(edge, cpv(0.0f, size.height),
                                    cpv(size.width, size.height), 0.0f);
    shape->u = 0.1f;
    shape->e = 0.7f;
    cpSpaceAddStaticShape(space, shape);
}

First, we initialize the Chipmunk library. Without this, Chipmunk will, quite simply, crash. Then we initialize our simulation’s space and set its initial gravitational vector. Next, we need to define the “edges” of our viewport to prevent objects from moving off-screen. We create a new static body as well as four segment shapes, which correspond to the four edges of the screen. The u field of the shape structure defines the friction coefficient (which ranges from 0.0 to 1.0). The e field defines the coefficient of restitution (or quite simply, bounciness, between 0.0 to 1.0). It is best to experiment with these values to find one which feels natural. Once each static shape is set up, we add it to our space.

While we’re at it, let’s also implement dealloc before we forget:

- (void)dealloc {
    cpSpaceFree(space);
    [super dealloc];
}

Next, we will implement the viewDidLoad method and override shouldAutorotateToInterfaceOrientation to return NO, like so:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupSpace];

    UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                         action:@selector(tap:)];

    [self.view addGestureRecognizer:gr];
    [gr release];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
    return NO;
}

In the code above, we setup our Chipmunk world and attach a gesture recognizer to our view. When a tap occurs, we will add a new ball to the simulation. Since we initially set up the gravitational vector, the ball will drop to the ground and bounce off of one of the four edges we defined. To learn more about gesture recognizers, consult Apple’s documentation.

Now, let’s implement the dropBallAtPoint: method, which will get called whenever the user taps on the screen.

#define RADIUS 32.0f

- (void)dropBallAtPoint:(CGPoint)pt {
    CALayer *layer = [CALayer layer];
    layer.position = pt;
    layer.bounds = CGRectMake(0.0f, 0.0f, RADIUS*2, RADIUS*2);
    layer.cornerRadius = RADIUS;
    layer.backgroundColor = [UIColor purpleColor].CGColor;

    [[self.view layer] addSublayer:layer];

    cpBody *body = cpBodyNew(1.0f, INFINITY);
    body->p = pt;
    cpSpaceAddBody(space, body);

    cpShape *ball = cpCircleShapeNew(body, RADIUS, cpvzero);
    ball->data = layer;
    ball->e = 0.7f;
    cpSpaceAddShape(space, ball);
}

- (void)tap:(UITapGestureRecognizer *)gr {
    CGPoint pt = [gr locationInView:self.view];
    [self dropBallAtPoint:pt];
}

We create a new Core Animation layer, which will represent our ball. Note that setting cornerRadius to half of the layer’s width will result in a circle shape. Next, we create a new dynamic body which will define the mass and position of the ball in space. Its center position corresponds to the layer’s center position. We also create a circle shape model object which corresponds to our layer’s shape. This will allow Chipumunk to perform collision detection against any other object in space. Finally, we associate the Core Animation layer with the shape for future reference, give the ball some bounciness, and add the shape to our space.

In order to run the actual simulation, we need to set up a timer which will periodically tell Chipmunk to update its bodies’ positions in space. We also configure the accelerometer in the viewDidAppear: method:

#define STEP_INTERVAL 1/60.0f

- (void)viewDidAppear:(BOOL)animated {
    [NSTimer scheduledTimerWithTimeInterval:STEP_INTERVAL
                                     target:self
                                   selector:@selector(step)
                                   userInfo:nil
                                    repeats:YES];

    [UIAccelerometer sharedAccelerometer].updateInterval = 1/30.0f;
    [UIAccelerometer sharedAccelerometer].delegate = self;
}

In the step method which we will implement next, we tell Chipmunk to step the simulation forward in time. It is optimal to use a fixed interval here. We also loop through every active shape in space by calling cpSpaceHashEach and update each ball’s position. Note that we disable the default Core Animation actions beforehand.

- (void)step {
    cpSpaceStep(space, STEP_INTERVAL);

    [CATransaction setDisableActions:YES];
    cpSpaceHashEach(space->activeShapes, &updateSpace, self);
    [CATransaction setDisableActions:NO];
}

static void updateSpace (void *obj, void *data) {
    cpShape *shape = (cpShape *) obj;
    CALayer *layer = shape->data;

    if (!layer)
        return;

    layer.position = shape->body->p;
}

Lastly, we will implement our accelerometer’s delegate method and adjust the gravitational field of our space. A low-pass filter is used to filter out random jerks.

#define FILTER_FACTOR 0.05f

- (void)accelerometer:(UIAccelerometer *)accelerometer
        didAccelerate:(UIAcceleration *)acceleration {

    static float prevX = 0.0f, prevY = 0.0f;

    prevX = (float) acceleration.x * FILTER_FACTOR + (1-FILTER_FACTOR) * prevX;
    prevY = (float) acceleration.y * FILTER_FACTOR + (1-FILTER_FACTOR) * prevY;

    space->gravity = cpv(prevX * GRAVITY, -prevY * GRAVITY);
}

That’s it! If you run the app on your iPad and add a few balls, you will notice they fall to the ground and bounce off. If you tilt the device, the balls will move since the gravitational field is adjusted.

The completed Xcode project can be found on github (updated).

8 notes

Page 2 of 2 Newer entries →