Matt Rajca

blog projects github twitter email

Writing a Bouncing Ball Physics Simulation for iOS

May 21, 2011

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

Getting Started

To get started, create a new iOS Xcode project based off of the "Single View Application" template. Name the project 'BounceyBalls' and select 'iPad' for the target device so we get the greatest amount of screen real estate. Turn off Storyboards and Unit Tests.

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 your Xcode project. Select "Copy items into destination group's folder (if needed)" if it is unchecked, and hit 'Finish'.

If we were to build the project at this point, we would get a ton of build errors. To get around this, 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 recursively include the path BounceyBalls/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 need to link the product against the QuartzCore framework. Select the Xcode project in the file navigator and add QuartzCore.framework to the list of "Linked Frameworks and Libraries".

Chipmunk Overview

Before we dive into the code, let's review some of the basics of 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 objects used in Chipmunk are spaces, bodies and shapes:

  • The 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, i.e. the ground, are not.
  • A shape defines how a body interacts with other bodies, i.e. 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 ViewController.h file with the code below:

    
    #import "chipmunk.h"
 #import <QuartzCore/QuartzCore.h>
 
    @interface ViewController : 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 the objects in the simulation.

Now, switch over to the ViewController.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->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 simply crash. Then we initialize our simulation's space and set its initial gravitational vector. Next, we need to define the "edges" of our virtual viewport to prevent balls from falling 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, ranging from 0.0 to 1.0). It is best to experiment with these values to find one that feels natural. Once each static shape is set up, we add it to our space.

Before we forget, let's implement -dealloc:

    
    - (void)dealloc {
        cpSpaceFree(space);
    }
    

Note that a call to [super dealloc] is not necessary since we're compiling under ARC.

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];
    }
    
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
        return NO;
    }
    

In the code above, we set up our Chipmunk world and attach a gesture recognizer to our controller's view. When the user taps the screen, we will add a new ball to the simulation. Since we defined a gravitational vector, the ball will fall and bounce off of the four edges of the screen. 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 = (__bridge void *) 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 on-screen. The layer is initially positioned at the tapped location. Note that setting cornerRadius to half of the layer's width results in a circular 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 position. We also create a circle shape model object which corresponds to the layer's shape. This will allow Chipumunk to perform collision detection against other objects 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 that 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 advance 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 cpSpaceEachShape 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];
        cpSpaceEachShape(space, &updateSpace, (__bridge void *) self);
        [CATransaction setDisableActions:NO];
    }
    
    static void updateSpace (cpShape *shape, void *data) {
        CALayer *layer = (__bridge CALayer *) shape->data;
        
        if (!layer)
            return;
        
        layer.position = shape->body->p;
    }
    

Lastly, we will implement the 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 an 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 as the gravitational field changes.

The completed Xcode project can be found on GitHub.