iOS Programming · · 7 min read

Simple Maze Game Part 2 - Using Accelerometer

Simple Maze Game Part 2 - Using Accelerometer

Editor’s note: This is part 2 of our simple Maze game series. We’ll continue to work on the maze game and you’ll learn how to interact with iPhone’s accelerometer.

In the first part of the maze game tutorial, we set up all the elements needed for the game and we wrote the code to animate the ghost characters. In the second part we are going to show you how to use the accelerometer for moving the pacman.

The iPhone’s built-in accelerometer has created tons of opportunities for iOS developers to create innovative and fun games. It is not uncommon you can find apps that allow user to control a game character by tilting an iPhone. And that’s what we’re going to do with our maze game. In this tutorial, we will give you a brief introduction of accelerometer, cover how to get access to the motion data by using the Core Motion framework and make the pacman move.

Maze Game Part 2

Let’s get started.

Declaring Properties

The first thing we are going to do is to define the variables required for controlling the pacman movement. For the sake of simplicity, we have declared all the variables as properties, though in some cases it was not the best way to do so. Add the following code to the AppViewController.h file:

@property (assign, nonatomic) CGPoint currentPoint;
@property (assign, nonatomic) CGPoint previousPoint;
@property (assign, nonatomic) CGFloat pacmanXVelocity;
@property (assign, nonatomic) CGFloat pacmanYVelocity;
@property (assign, nonatomic) CGFloat angle;
@property (assign, nonatomic) CMAcceleration acceleration;
@property (strong, nonatomic) CMMotionManager  *motionManager;
@property (strong, nonatomic) NSOperationQueue *queue;
@property (strong, nonatomic) NSDate *lastUpdateTime;

Let me briefly go through these variables:

  • currentPoint holds the current position of the pacman
  • previousPoint holds the position from where the pacman has been moved
  • pacmanXVelocity contains the X component of the velocity (remember that velocity is a vector), and pacmanYVelocity the Y component
  • angle is the current angle of pacman, since the sprite will rotate, as we will explain later on
  • acceleration is the current acceleration measured by the accelerometer
  • motionManager is a queue that helps us to receive and process the data sent from the accelerometer
  • lastUpdateTime allows us to control how long has been since the last call from the accelerometer

Managing the Accelerometer

Before we talk about iPhone’s built-in accelerometer, let’s give a brief introduction of accelerometer. As its name suggests, accelerometer measures acceleration. Here, the acceleration doesn’t refer to the rate of change of velocity as you may learn at school. Instead, it measures the force of acceleration, whether caused by gravity or by movement. In other words, when attached to iPhone, the accelerometer can measure the speed of movement and sense the angle at which it is being held.

Note: If you want to learn more about how iPhone’s accelerometer works, check out this video.

You may know that Apple has added another sensor called gyroscope since the release of iPhone 4. If we want to know the precise orientation and movement of an iPhone, we should combine the measurement provided by the accelerometer and gyroscope. However, this sensor is out of the scope for this simple maze game. The accelerometer is just sufficient for detecting the device’s orientation.

The iOS Core Motion framework allows developers to get motion data from device hardware and process that data. Here the hardware includes the accelerometer, magnetometer and gyroscope. By using the CMMotionManager class, we can get the data detected by accelerometer at regular intervals or we can poll for them periodically. For this game we will use the later approach and get the data at regular intervals.

Get Motion Data Using CMMotionManager

Open the AppViewController.h file and add the following code to include the necessary header files:

#import 

Also, define a new constant called kUpdateInterval. This constant specifies how often we want to poll for the data from the accelerometer. The more we poll for the data, the more precise is the pacman movement. That said, this also means more processor time and consumes more battery. In our case, the default value is 60 times per second, that is more than sufficient for a smooth pacman movement.

#define kUpdateInterval (1.0f / 60.0f)

Next, open the AppViewController.m file and come back to the viewDidLoad method. In this method, add the following code to initialize the accelerometer:

    // Movement of pacman
    
    self.lastUpdateTime = [[NSDate alloc] init];
        
    self.currentPoint  = CGPointMake(0, 144);
    self.motionManager = [[CMMotionManager alloc]  init];
    self.queue         = [[NSOperationQueue alloc] init];
    
    self.motionManager.accelerometerUpdateInterval = kUpdateInterval;
    
    [self.motionManager startAccelerometerUpdatesToQueue:self.queue withHandler:
     ^(CMAccelerometerData *accelerometerData, NSError *error) {
         [(id) self setAcceleration:accelerometerData.acceleration];
         [self performSelectorOnMainThread:@selector(update) withObject:nil waitUntilDone:NO];
     }];

First, we initialize the lastUpdateTime to the current time and the currentPoint variable to the initial pacman position. Secondly, we create a CMMotionManager object that is the gateway to access the accelerometer data, followed by a NSOperationQueue object. The operation queue will be used to queue and dispatch the requests sent by the accelerometer.

Lastly, we invoke startAccelerometerUpdatesToQueue: method to start accelerometer updates with the operation queue just created. For each accelerometer update, the handler as we specified will be called.

In this case, the handler is not a normal call to a method. Instead we have to use a block declaration. The explanation of what is a block is out of the scope of this tutorial. However, if you are not familiar with blocks, just think of them as a function call. In the block, we simply save the current acceleration data (i.e. accelerometerData.acceleration) into the acceleration property and then it calls the update: method, that we’ll implement in the next section, to update the pacman’s position.

Moving the Pacman

What we have done so far is to retrieve the acceleration data and what we will do is to use that data to calculate the new position of the pacman. We will compute the new position of the pacman in the update: method. Add the following code in the AppViewController.m:

- (void)update {
    
    NSTimeInterval secondsSinceLastDraw = -([self.lastUpdateTime timeIntervalSinceNow]);
        
    self.pacmanYVelocity = self.pacmanYVelocity - (self.acceleration.x * secondsSinceLastDraw);
    self.pacmanXVelocity = self.pacmanXVelocity - (self.acceleration.y * secondsSinceLastDraw);
        
    CGFloat xDelta = secondsSinceLastDraw * self.pacmanXVelocity * 500;
    CGFloat yDelta = secondsSinceLastDraw * self.pacmanYVelocity * 500;
        
    self.currentPoint = CGPointMake(self.currentPoint.x + xDelta,
                                    self.currentPoint.y + yDelta);
        
    [self movePacman];
    
    self.lastUpdateTime = [NSDate date];
    
}

The pacman’s new velocity vector depends on the previous velocity vector, the current acceleration vector (given by the accelerometer) and the time elapsed since the last calculation. In the previous section, we have configured the accelerometer to send data 60 times per second. However, this is only an approximation. For some reasons, we may get the data at irregular intervals. This is why we have to compute the time elapsed since the last call explicitly.

Once we have the new velocity value we have to compute a delta vector with the change to be applied to pacman’s current position. This delta vector depends on the time elapsed, the current (just recalculated) velocity and a scaling factor (a fixed value of 500). We have to use this scaling factor since the delta vector is so small that the pacman sprite will barely move. The higher the scaling factor, the faster is the pacman moves.

Finally, we tell pacman to move to its new position by invoking the movePacman: method. Add the following code:

- (void)movePacman {
    
    self.previousPoint = self.currentPoint;
    
    CGRect frame = self.pacman.frame;
    frame.origin.x = self.currentPoint.x;
    frame.origin.y = self.currentPoint.y;
    
    self.pacman.frame = frame;
}

The method is very straightforward. We just change the frame where the packman draws to its new position.

Pacman Rotation Using CABasicAnimation

The app should work now. If you try to run the app, however, you may find that the movement of the pacman seems a little bit static and unrealistic. Obviously, you expect the pacman to rotate while it’s moving around the maze.

Maze Game Pacman without rotation

Pacman won’t rotate and always faces the same direction

Fortunately, we have already learnt about the CABasicAnimation object in the previous tutorial. We will add a few lines of code to make the pacman rotate. Copy the following code and add them to the end of the movePacman: method:

    // Rotate the sprite
    
    CGFloat newAngle = (self.pacmanXVelocity + self.pacmanYVelocity) * M_PI * 4;
    self.angle += newAngle * kUpdateInterval;
    
    CABasicAnimation *rotate;
    rotate                     = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotate.fromValue           = [NSNumber numberWithFloat:0];
    rotate.toValue             = [NSNumber numberWithFloat:self.angle];
    rotate.duration            = kUpdateInterval;
    rotate.repeatCount         = 1;
    rotate.removedOnCompletion = NO;
    rotate.fillMode            = kCAFillModeForwards;
    [self.pacman.layer addAnimation:rotate forKey:@"10"];

The calculation of the angle of rotation (newAngle) is a little bit tricky and it involves some mathematics. I had to play with different formulas until I came up with something that looks realistic. You can play with other formulas and see what happens. The important point here is that the rotation angle is not applied directly, instead we use an exponential transition from the old angle to the new one (self.angle += newAngle * kUpdateInterval), in this way we get a smoother rotation of pacman.

Then we create the CABasicAnimation object. This time we say that the animation is a rotation (transform.rotation). The rotation angle goes from 0 to self.angle. We use kUpdateInterval as the duration that it is the expected time before we get a new angle. We specify that we only rotate once and that upon completion we should leave the graphic in its final state. Once we configure the CABasicAnimation object, we apply the animation to the pacman object.

Test the App

Okay, compile and run the app on a real iPhone. Yes, you need to physical iPhone to test the app as the Simulator doesn’t support the simulation of accelerometer. If the app works properly, you should see that pacman moves and rotates smoothly when tilting your iPhone.

Maze Game Pacman rotates

Pacman can now rotate as it moves

What’s Coming Next?

Wait, the app is buggy! Why the pacman can pass through walls and go beyond the screen?! We know that and we’ll leave the rest of the implementation to part 3 of the tutorials. We’ve discussed quite a lot of stuffs in this tutorial and we think it’s time to take a break.

For your complete reference, you can download the full source code from here. Stay tuned and we’ll soon publish the last tutorial of the maze game.

As always, leave us comment and share your thought. We love to hear your feedback and further improve our tutorials.

Update: Check out part 3 of the maze game tutorials.

Read next