- Cocos2d Game Development Blueprints
- Jorge Jordán
- 2205字
- 2025-02-20 12:35:08
Handling the accelerometer input
Since the apparition of the first iPhone generation, mobile devices are equipped with a 3-axis accelerometer (x, y, and z) to detect orientation changes and it has modified the course of handheld games. It allows players to interact with games without the need to touch the screen, and it allows developers to create new subgenres of games too.
Throughout this chapter, you will mix this hardware feature with a classic arcades genre to develop the following game: the planet Earth has been attacked by an army of UFOs whose sole purpose is to wipe out all of mankind, but fortunately, a mad scientist equipped with some of his inventions is brave enough to defend us.
To control the movements of Dr. Nicholas Fringe, our mad scientist, you will take advantage of the accelerometer, but first of all you need a clean Xcode project. As you learned in the previous chapter how to create a new project and how to get it ready to start developing, we will skip this step, so open the code files of this chapter from the code bundle, where you'll find ExplosionsAndUFOs_init.zip
, which contains the initial project.
Linking the CoreMotion framework
The first thing we will need to do to enable accelerometer management is link the CoreMotion framework:
- Open the
ExplosionsAndUFOs
project you just unzipped and go to the General properties screen where you will see the already linked frameworks at the bottom. - Click on + and a dialog will appear where you will see the available frameworks to be linked.
- Look for CoreMotion.framework and click on Add.
This framework allows our game to receive gyroscope and accelerometer data, which we can process and work with. It includes a set of classes to get, manage, and measure motion data such as attitude, rotation rate, acceleration, or number of steps taken by the user. In our case, we are going to use CMMotionManager
, the class in charge of the management of four types of motion: raw accelerometer data, raw gyroscope data, raw magnetometer data, and processed device-motion data; in other words, what Dr. Nicholas Fringe needs to fly over the clouds.
The next thing we need to do is include the CoreMotion
classes; to achieve this, add the following line to GameScene.h
after import "cocos2d.h"
:
#import <CoreMotion/CoreMotion.h>
Then you will need to declare a private instance variable of CMMotionManager
, so go to GameScene.m
and replace @implementation GameScene
with the following block of code:
@implementation GameScene { // Declaring a private CMMotionManager instance variable CMMotionManager *_accManager; }
Now you just need to initialize this variable by adding the next line in the init
method before return self;
:
// Initialize motion manager _accManager = [[CMMotionManager alloc] init];
This way, you've made your motion manager ready to start receiving data from the accelerometer and gyroscope.
If you've been working with accelerometer events in Cocos2d 2.x, you will realize that we are initializing accelerometer handling in a different way. In fact, in the previous version, you didn't need to initialize CMMotionManager
but to enable isAccelerometerEnabled
and to implement the accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UiAcceleration *)acceleration
method. This method in a class is derived from CCLayer
, which is currently deprecated in Cocos2d 3.x.
Once you have initialized the CMMotionManager
instance, there is one thing left to do to start and finish receiving motion data and that is manually starting and stopping it. An effective strategy to carry it out is to take advantage of the onEnter
and onExit
methods available in every class that inherits CCNode
, and remember CCScene
is one of them.
onEnter and onExit
These two methods are very useful to control what is happening when a node enters or leaves the main screen, as they are called as soon as a node comes into action or leaves. In total, there are four methods to cover the application behavior when a node enters or leaves with a transition:
We need to activate accelerometer management as soon as the scene appears and deactivate it when the scene disappears; that's why we're going to focus just on the common versions of onEnter
and onExit
.
Go ahead and include the following method implementations in GameScene.m
:
// Start receiving accelerometer events - (void)onEnter { [super onEnter]; [_accManager startAccelerometerUpdates]; } // Stop receiving accelerometer events - (void)onExit { [super onExit]; [_accManager stopAccelerometerUpdates]; }
One thing to emphasize is the call to the super implementations [super onEnter]
and [super onExit]
. This means that we are not overriding these methods but extending their behavior because we want to keep their parent functionality.
Converting accelerometer input into movements
Now that our game is waiting for moving data, we need something to realize what is happening when you move your device.
Let's create a sprite for our crazy scientist so we can move him along the screen. In Xcode, follow these steps:
- Right-click on the Resources group and select Add Files to "ExplosionsAndUFOs"….
- Select the
scientist.png
image you will find in theResources
folder. - Be sure that Copy items into destination group's folder (if needed) is selected and click on Add.
First, we will declare private instance variables for both accelerometer data and acceleration by adding the next lines after CMMotionManager *_accManager;
:
// Declare accelerometer data variable CMAccelerometerData *_accData; // Declare acceleration variable CMAcceleration _acceleration;
Then declare the CCSprite
private instance variable for our scientist by adding the following line:
// Declaring a private CCSprite instance variable CCSprite *_scientist;
Initialize it by adding the following lines to the init
method just before return self;
:
// Initialize the scientist _scientist = [CCSprite spriteWithImageNamed:@"scientist.png"]; CGSize screenSize = [CCDirector sharedDirector].viewSize; _scientist.position = CGPointMake(screenSize.width/2, _scientist.contentSize.height); [self addChild:_scientist];
This block of code is pretty simple: you're initializing a sprite using the scientist's image you just added to the project and then you're placing it in a relative position before adding it to the scene.
Nothing new so far. Now it's time to make the scientist move on the palm of our hand. First add the following block of code to GameScene.m
:
-(void) update:(CCTime)delta{ // Getting accelerometer data _accData = _accManager.accelerometerData; // Getting acceleration _acceleration = _accData.acceleration; // Calculating next position on 'x' axis CGFloat nextXPosition = _scientist.position.x + _acceleration.x * 1500 * delta; // Calculating next position on 'y' axis CGFloat nextYPosition = _scientist.position.y + _acceleration.y * 1500 * delta; // Avoiding positions out of bounds nextXPosition = clampf(nextXPosition, _scientist.contentSize.width / 2, self.contentSize.width - _scientist.contentSize.width / 2); nextYPosition = clampf(nextYPosition, _scientist.contentSize.height / 2, self.contentSize.height - _scientist.contentSize.height / 2); _scientist.position = CGPointMake(nextXPosition, nextYPosition); }
We are using the update
method to retrieve data from the accelerometer, from which we take the acceleration information. Then we calculate the next position on the x and y axes as the sum of the current scientist position plus the multiplication of the acceleration on the corresponding axis, delta
, and a constant that I decided to be 1500
.
By default, the delta
variable's value is 1/60 as the update
method is called every frame and the default frame rate is 60 fps. The act of multiplying the velocity of a node by delta
is what developers call framerate independent movement and it's a very sensitive issue. It means that if the framerate drops below 60 fps, the movement of the node won't be affected. However, it will affect a framerate-dependent node (the node's velocity isn't multiplied by delta
), decreasing its performance by slowing it down and being overloaded.
But what causes a framerate drop? System events, loading textures, or large sprites may cause your game's framerate to drop, so you will need to keep in mind the debug stats while developing the game. Applying the delta solution can have side effects too, such as bad collision detection on low framerates. But for now and for convenience, we will keep our node's framerate independent, that is, we will multiply its velocity by delta
.
Going back to the preceding code, once we've calculated the next positions, we seek to ensure that these positions are inside the screen. Do you remember how we solved this issue in the previous chapter? The clampf
function is a fancier way of dealing with nodes going out of bounds, you just need to provide the position you want to check, and the minimum and maximum positions available and it will do the rest for you.
Okay, time to run the game for the very first time!

As you can see, there are two unwanted things: the movement of our sprite is opposite to what we expected and the display orientation is landscape, but we are developing a shoot 'em up so it should be portrait.
In fact, the unexpected movement is due to the display orientation but wait and breathe, as we're going to solve both problems with just one line of code; if you don't believe me, keep reading.
Go to the didFinishLaunchingWithOptions
method on AppDelegate.m
and add the following setup option to setupCocos2dWithOptions
:
CCSetupScreenOrientation: CCScreenOrientationPortrait,
Run the game again and this will verify that everything is under control now.

There is another way of managing accelerometer input and that is making use of the startAccelerometerUpdatesToQueue:withHandler
method available in CMMotionManager
. It receives a queue of operations and invokes a CMAccelerometerHandler
block to handle the accelerometer data. For now, we will keep it simple using the startAccelerometerUpdates
approach.
Calibrating the accelerometer
As one last thing, you may have noticed that you must keep your device flat because if you position it in the usual way, the scientist will move to the bottom of the screen as soon as the game starts. This has a pretty easy solution; we just need to take into account the initial acceleration values so we can compensate for them and stand the device comfortably as we wish.
Declare the next variable just after CMAcceleration _acceleration;
:
// Declare the initial acceleration variable CMAcceleration _initialAcceleration;
Go back to the update
method, and add these lines after _acceleration = _accData.acceleration;
:
// As soon as we get acceleration store it as the initial acceleration to compensate if (_initialAcceleration.x == 0 && _initialAcceleration.y == 0 && _acceleration.x != 0 && _acceleration.y != 0) { _initialAcceleration = _acceleration; }
This block detects when the device starts receiving acceleration data and we store the first piece of information in the initialAcceleration
variable so we can compensate for the inclination.
Find these two lines:
CGFloat nextXPosition = _scientist.position.x + _acceleration.x * 1500 * delta; CGFloat nextYPosition = _scientist.position.y + _acceleration.y * 1500 * delta;
Replace them with these two lines:
CGFloat nextXPosition = _scientist.position.x + (_acceleration.x - _initialAcceleration.x) * 1500 * delta; CGFloat nextYPosition = _scientist.position.y + (_acceleration.y - _initialAcceleration.y) * 1500 * delta;
We are modifying the original movement strategy; now we subtract the initial acceleration on both axes from the current acceleration so the user is not required to keep the device flat.
If you run the game now, you will notice that you can stand your device as you want!
Before going ahead, let's clean the code a little. I decided to multiply the node's velocity by 1500
as it provided the desired results, but you can modify it at your convenience. Add the following line at the top of GameScene.m
just after #import "GameScene.h"
:
// Acceleration constant multiplier #define ACCELERATION_MULTIPLIER 1500.0
Modify these lines:
CGFloat nextXPosition = _scientist.position.x + (_acceleration.x - _initialAcceleration.x) * 1500 * delta; CGFloat nextYPosition = _scientist.position.y + (_acceleration.y - _initialAcceleration.y) * 1500 * delta;
Replace them with the following ones:
CGFloat nextXPosition = _scientist.position.x + (_acceleration.x - _initialAcceleration.x) * ACCELERATION_MULTIPLIER * delta; CGFloat nextYPosition = _scientist.position.y + (_acceleration.y - _initialAcceleration.y) * ACCELERATION_MULTIPLIER * delta;
We have created a constant and replaced the hardcoded values so we can quickly change the velocity of both axes by just updating a constant. This way, the code looks cleaner.
On the other hand, let's make screenSize
a private variable so we can use its value whenever we need. First, add the following line after the line declaring the _scientist
sprite:
// Declare global variable for screen size CGSize _screenSize;
Then go back to the init
method and find the following line:
CGSize screenSize = [CCDirector sharedDirector].viewSize;
Replace it with the following one:
_screenSize = [CCDirector sharedDirector].viewSize;
Finally, find this line:
_scientist.position = CGPointMake(screenSize.width/2, _scientist.contentSize.height);
Replace it with this line:
_scientist.position = CGPointMake(_screenSize.width/2, _scientist.contentSize.height);
Again, this last block of code is a variable replacement so we can use screenSize
globally in the class.
Now that we've put our scientist on the screen and we can move him, let's give him a sky to fly through!