Augmented Reality Tutorial for iOS

An augmented reality tutorial for iOS. By Nicholas Waynik.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

How Did That Work?!

Although that worked, you might be wondering how it did! Let’s take a minute and explain the above code section by section.

Start by downloading the FREE Gyrosocope app from the iTunes app store. Run the app, and you’ll see a cool visual representation of what is actually going on when we move the phone around.

Free gyroscope app on iTunes App Store

The value we need to focus on is the Yaw. This is the movement from turning right or left. In the Gyroscope app this is measured in degrees, however the values presented to us from the motion manager is in radians. That is the reason why we needed to use the built-in CC_RADIANS_TO_DEGREES function to perform the conversion.

So in section 1 we are getting the yaw value in radians, converting it to degrees, and assigning it to the yaw variable. Section 2 simply displays this yaw value on the screen. If you run the app, you will see that the values range from 0 to 180 then from -180 back to zero.

Gyroscope yaw values displayed on a number line

If you look at section 3, you are probably wondering what the positionIn360 variable is. Well, this is just a trick we’re going to use to make things simple with placing flying saucers on the screen.

The logic used is very simple. If the yaw value is positive then we do nothing, but if it is negative then we subtract if from 360 (Adding a negative number is the same as subtracting). The final line of code just displays that value on the screen.

Converting yaw values to 0-360 range

If you haven’t already done so, go ahead and run the app and check out how the values change.

Lights, Camera, Action!

Now that we have the groundwork laid for the gyroscope let’s move on to adding space ships! We are going to start out by creating a new file. Ctrl click (left click) on ARSpaceships and choose New File. Select iOS\Cocoa Touch\Objective-C class and click Next. Make sure Subclass of NSObject is selected, and click Next. Name the file EnemyShip.m and click Save.

Replace the contents of EnemyShip.h with:

#import "cocos2d.h"

@interface EnemyShip : CCSprite {
    int yawPosition;
    int timeToLive;
}

@property (readwrite) int yawPosition;
@property (readwrite) int timeToLive;

@end

Replace the contents of EnemyShip.m with:

#import "EnemyShip.h"


@implementation EnemyShip

@synthesize yawPosition, timeToLive;

-(id)init {
    self = [super init];
    if (self){ 
        yawPosition = 0;
        timeToLive = 0;
	}
    return self;
}

@end

Now open back up HelloWorldLayer.h. Add the import statement for the EnemyShip at the top of the file.

#import "EnemyShip.h"  

Inside the interface add:

NSMutableArray *enemySprites;
int enemyCount;
CCSpriteBatchNode *batchNode;

Finally below the interface, add the property for the enemyCount and the definition of methods:


@property (readwrite) int enemyCount;

-(EnemyShip *)addEnemyShip:(int)shipTag;
-(void)checkEnemyShipPosition:(EnemyShip *)enemyShip withYaw:(float)yawPosition;
-(void)updateEnemyShipPosition:(int)positionIn360 withEnemy:(EnemyShip *)enemyShip;
-(void)runStandardPositionCheck:(int)positionIn360 withDiff:(int)difference withEnemy:(EnemyShip *)enemyShip;

Jump over to the HelloWorldLayer.m file, and make the following modifications to the file:

// Place after the #import statement
#include <stdlib.h>

// Place after the other @synthesize statement
@synthesize enemyCount;
#define kXPositionMultiplier 15
#define kTimeToLive 100

// Add to the bottom of init
batchNode = [CCSpriteBatchNode batchNodeWithFile:@"Sprites.pvr.ccz"];
[self addChild:batchNode];   
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"Sprites.plist"];

In these lines we are loading the spritesheet we added to our project at the beginning of this tutorial.

Next we will add the method to create new enemy space ships. Above the dealloc method add the following code.

-(EnemyShip *)addEnemyShip:(int)shipTag {

    EnemyShip *enemyShip = [EnemyShip spriteWithSpriteFrameName:@"enemy_spaceship.png"];
    
    // Set position of the space ship randomly
    int x = arc4random() % 360;
    enemyShip.yawPosition = x;   
    
    // Set the position of the space ship off the screen, but in the center of the y axis
    // we will update it in another method
    [enemyShip setPosition:ccp(5000, 160)];
    
    // Set time to live on the space ship
    enemyShip.timeToLive = kTimeToLive;
    enemyShip.visible = true;
    
    [batchNode addChild:enemyShip z:3 tag:shipTag];
    
    return enemyShip;
}

This method accepts an integer value for the tag and returns an EnemyShip CCSprite. The next line of code we are creating an EnemyShip sprite from the spritesheet. Next we are using the arc4random method to get a random integer from 0 to 360. Finally we set the position of the ship, set the timeToLive value to 100, adding the ship to the batch node, and returning the ship.

Below the addEnemyShip method add the following code for the checkEnemyShipPosition method:

-(void)checkEnemyShipPosition:(EnemyShip *)enemyShip withYaw:(float)yawPosition {
    // Convert the yaw value to a value in the range of 0 to 360
    int positionIn360 = yawPosition;
    if (positionIn360 < 0) {
        positionIn360 = 360 + positionIn360;
    }
    
    BOOL checkAlternateRange = false;
    
    // Determine the minimum position for enemy ship
    int rangeMin = positionIn360 - 23;
    if (rangeMin < 0) {
        rangeMin = 360 + rangeMin;
        checkAlternateRange = true;
    }
    
    // Determine the maximum position for the enemy ship
    int rangeMax = positionIn360 + 23;
    if (rangeMax > 360) {
        rangeMax = rangeMax - 360;
        checkAlternateRange = true;
    }    
    
    if (checkAlternateRange) {
        if ((enemyShip.yawPosition < rangeMax || enemyShip.yawPosition > rangeMin ) || (enemyShip.yawPosition > rangeMin || enemyShip.yawPosition < rangeMax)) {
            [self updateEnemyShipPosition:positionIn360 withEnemy:enemyShip];
        }        
    } else {
        if (enemyShip.yawPosition > rangeMin && enemyShip.yawPosition < rangeMax) {
            [self updateEnemyShipPosition:positionIn360 withEnemy:enemyShip];
        } 
    }
}

This method might seem a little bit confusing with the alternate, min, and max ranges. Don’t worry it is pretty simple. First we start out by checking the yaw position of the device (positionIn360) and placing it in the range from 0 to 360 (think full circle).

Gyroscope yaw position visualized on iPhone

Since we have two ends to our number line of 0 to 360, we will need to check to see if the device’s positionIn360 is on either end. We use 23 as an arbitrary number representing the number of degrees that will show on the half of the screen.

Visible degrees on iPhone

So we only need to worry about the ranges from 0 to 23 and 337 to 360 because the other end of the line will need to wrap around.

Lastly we update the position of the enemy space ship if it is in the 46-degree range of the screen. The (checkAlternateRange) if statement is used for determining when to update the position of the enemy spaceship.

If checkAlternateRange is true, then we check to see if the enemy spaceship’s position falls within the min and max range. All of the checks in the first part of this if statement may seem extreme, but if we walk through it using values it makes perfect sense. Let’s assume:

positionIn360 = 20
rangeMin = 357
rangeMax = 20
enemyShip.yawPosition = 359

Because we have to account for both ends of the number line, our min range is greater than the max range. Now we do all of the checks and find out that the enemy ship’s position is greater than rangeMin so we will display the ship on the screen.

The else in that if statement is more straightforward. It just checks to see if the enemy ship’s position is within the min and max range.

What a great segway into the update method! Add the following code below the checkEnemyShipPosition method.

-(void)updateEnemyShipPosition:(int)positionIn360 withEnemy:(EnemyShip *)enemyShip {
    int difference = 0;
    if (positionIn360 < 23) {
        // Run 1
        if (enemyShip.yawPosition > 337) {
            difference = (360 - enemyShip.yawPosition) + positionIn360;
            int xPosition = 240 + (difference * kXPositionMultiplier);
            [enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
        } else {
            // Run Standard Position Check
            [self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];
        }
    } else if(positionIn360 > 337) {
        // Run 2
        if (enemyShip.yawPosition < 23) {
            difference = enemyShip.yawPosition + (360 - positionIn360);
            int xPosition = 240 - (difference * kXPositionMultiplier);
            [enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
        } else {
            // Run Standard Position Check
            [self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];
        }
    } else {
        // Run Standard Position Check
        [self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];
    }
}

In this method we are testing to see if the device’s positionIn360 is in one of the three ranges. In the first test we look to see if the positionIn360 is less than 23, if so we want to check to see if there are any enemy ships on the other end of the line (greater than 337).

The second test we look to see if the positionIn360 is greater than 337. If so we want to do the exact opposite of what we just did (check if the enemy ship is less than 23).

The third test (final outer else) we look to we set the position for the enemy ship if it falls between 23 and 337. We are calling the method runStandardPositionCheck. Place the following code below the last method.

-(void)runStandardPositionCheck:(int)positionIn360 withDiff:(int)difference withEnemy:(EnemyShip *)enemyShip {
    if (enemyShip.yawPosition > positionIn360) {
        difference = enemyShip.yawPosition - positionIn360;
        int xPosition = 240 - (difference * kXPositionMultiplier);
        [enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
    } else {
        difference = positionIn360 - enemyShip.yawPosition;
        int xPosition = 240 + (difference * kXPositionMultiplier);
        [enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
    }
}

In this method we check to see if the enemyShip position is to the left or right of the device’s positionIn360. When the enemyShip position is less than the positionIn360, it appears on the left side of the screen. When the enemyShip position is greater than the positionIn360 it appears on the right.

Now you say wait a minute! You forgot the difference variable and describing what it does. Okay, here it goes.

If the enemy ship’s yaw position is in the screen’s range (from positionIn360 – 23 to positionIn360 + 23), then first we figure out which side of the screen it is on. If it is greater than the positionIn360 then it goes on the right side of the screen, else it goes on the left.

The difference variable is used to measure the degrees of difference between the device’s positionIn360 and the yaw position of the enemy ship. Once that is known, we multiply the difference by an arbitrary multiplier. This multiplier represents the amount of pixels for each degree. In this case we choose 15.

Based on which side of the screen we will add or subtract this calculated value from 240(screen width divided by 2). And that’s it for the updateEnemyShipPosition method.

Now that all of the required methods are in place we will move on to calling those methods.

At the bottom of the init method, add the following code to add five enemy space ships on the screen.

// Loop through 1 - 5 and add space ships
enemySprites = [[NSMutableArray alloc] init];
for(int i = 0; i < 5; ++i) {
    EnemyShip *enemyShip = [self addEnemyShip:i];
    [enemySprites addObject:enemyShip];
    enemyCount += 1;
}

Since we added the enemy space ships to the screen, we need to make sure their positions update. At the very end of the update method add the following code:

// Loop through array of Space Ships and check the position
for (EnemyShip *enemyShip in enemySprites) {
    [self checkEnemyShipPosition:enemyShip withYaw:yaw];
}

And before we forget, add the following to the bottom of your dealloc method to clean up the enemySpritesArray we created in init:

[enemySprites release];

Now for the moment of truth, go ahead and run your app! You will see 5 space ships at different locations when you rotate the device around.

A simple augmented reality game for the iPhone!

Nicholas Waynik

Contributors

Nicholas Waynik

Author

Over 300 content creators. Join our team.