Categories
Articles

Kobold2D XCode 4 Introduction Tutorial Lesson 7 – Display Score Using Graphic Glyphs


<== Lesson 6 || Lesson 8 ==>

The scoring in this game needs a display. Up to this point you can only see the score in the XCode console window.

Learn cocos2D Game Development
Learn cocos2D Game Development

The scoring data you are tracking is the number of attempts and the number of hits. The number of attempts is a class property that increments each time a new moving target is started from the top of the screen.

The number of hits is incremented each time the player intercepts the moving target.

Using the number of attempts and number of hits, we can display three values. One is the total attempts. The second is the number of misses which is the number of hits minus the number of misses. The third value is the number of hits.

We will just display them at the top middle of the screen with a colon separating each value.

For the score display, we will use a graphic containing the exact characters we need. Those characters are the digits 0 to 9, a space and a colon.

Finished Lesson Running on IPad2 Simulator

To display the graphic characters you will use the Cocos2D CCLabelBMFont class. You can pack all the characters into one file. Then the CCLabelBMFont treats each character like a CCSprite. This means that each individual character can be rotated, scaled, translated, and tinted like other Cocos2D CCSprite objects. We do not have much more of a need other than to place them on the screen and layer them underneath the game pieces.

Glyph Designer is the tool I use to create the files needed for CCLabelBMFont. There is a fee for Glyph Designer. I have not used another tool like it but the $30 US was a great value and the online tutorial videos made getting use out of it a snap. Plus Cocos2D is a product Glyph Designer supports.

Lesson Downloads

  1. Game piece images, score glyph files including Glyph Designer project file and sound for project.
  2. IPhone images for project. Icons and splash screen.
  3. Completed Project. This is built in Kobold2d 1.0.1.

Step 1 – Build an Empty-Project Template

You can continue with the Lesson 6 project and make changes noted here.

Otherwise the steps for creating this project from an Empty-Project template are the same as Lesson 1 except to substitute Lesson 7 for Lesson 1 in the project name and to include the red_ball.png, red_ball-hd.png and explosion.caf files when you add the game pieces. The explosion.caf goes in same group as the images. Then you can use the code presented here.

In either case your Projectfiles->Resources group should have the files shown in the image to the left.

[ad name=”Google Adsense”]

Step 2 – Set Properties in config.lua

There are no new configuration properties from Lesson 6. Complete config.lua file is included here for your copy convenience.

--[[
* Kobold2D™ --- http://www.kobold2d.org
*
* Copyright (c) 2010-2011 Steffen Itterheim. 
* Released under MIT License in Germany (LICENSE-Kobold2D.txt).
--]]


--[[
* Need help with the KKStartupConfig settings?
* ------ http://www.kobold2d.com/x/ygMO ------
--]]


local config =
{
    KKStartupConfig = 
    {
        -- load first scene from a class with this name, or from a Lua script with this name with .lua appended
        FirstSceneClassName = "GameLayer",

        -- set the director type, and the fallback in case the first isn't available
        DirectorType = DirectorType.DisplayLink,
        DirectorTypeFallback = DirectorType.NSTimer,

        MaxFrameRate = 60,
        DisplayFPS = YES,

        EnableUserInteraction = YES,
        EnableMultiTouch = NO,

        -- Render settings
        DefaultTexturePixelFormat = TexturePixelFormat.RGBA8888,
        GLViewColorFormat = GLViewColorFormat.RGB565,
        GLViewDepthFormat = GLViewDepthFormat.DepthNone,
        GLViewMultiSampling = NO,
        GLViewNumberOfSamples = 0,

        Enable2DProjection = NO,
        EnableRetinaDisplaySupport = YES,
        EnableGLViewNodeHitTesting = NO,
        EnableStatusBar = NO,

        -- Orientation & Autorotation
        DeviceOrientation = DeviceOrientation.LandscapeLeft,
        AutorotationType = Autorotation.None,
        ShouldAutorotateToLandscapeOrientations = NO,
        ShouldAutorotateToPortraitOrientations = NO,
        AllowAutorotateOnFirstAndSecondGenerationDevices = NO,

        -- Ad setup
        EnableAdBanner = NO,
        PlaceBannerOnBottom = YES,
        LoadOnlyPortraitBanners = NO,
        LoadOnlyLandscapeBanners = NO,
        AdProviders = "iAd, AdMob",	-- comma seperated list -> "iAd, AdMob" means: use iAd if available, otherwise AdMob
        AdMobRefreshRate = 15,
        AdMobFirstAdDelay = 5,
        AdMobPublisherID = "YOUR_ADMOB_PUBLISHER_ID", -- how to get an AdMob Publisher ID: http://developer.admob.com/wiki/PublisherSetup
        AdMobTestMode = YES,

        -- Mac OS specific settings
        AutoScale = NO,
        AcceptsMouseMovedEvents = NO,
        WindowFrame = RectMake(1024-640, 768-480, 640, 480),
        EnableFullScreen = NO,
    },
}

return config

Step 3 – GameLayer.h

Line 18 shows the CCLabelBMFont object for displaying the score.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "kobold2d.h"

@interface GameLayer : CCLayer
{
    CCSprite* player;
    CGPoint playerVelocity;
    CCSprite* movingTarget;
	float movingTargetMoveDuration;
    int totalAttempts;
    int totalHits;
    CCLabelBMFont* scoreLabel;
}
@end

Step 4 – GameLayer.m: Add initScore and setScore Methods for Score Display

Complete GameLayer.m file is included here for your copy convenience.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"
#import "SimpleAudioEngine.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
-(void) checkForCollision;
-(void) initScore;
-(void) setScore;
@end

// Velocity deceleration
const float deceleration = 0.4f;
// Accelerometer sensitivity (higher = more sensitive)
const float sensitivity = 6.0f;
// Maximum velocity
const float maxVelocity = 100.0f;

@implementation GameLayer

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Preload the sound effect into memory so there's no delay when playing it the first time.
		[[SimpleAudioEngine sharedEngine] preloadEffect:@"explosion.caf"];
        // Initialize the total attempts to hit a moving target.
        totalAttempts = 0;
        // Initialize the total hits
        totalHits = 0;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label z:-1];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 7"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2 z:-1];
        // The score label initialized
        [self initScore];
        // Add below game action
		[self addChild:scoreLabel z:-1];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}
-(void) dealloc
{
#ifndef KK_ARC_ENABLED
	[super dealloc];
#endif // KK_ARC_ENABLED
}
#pragma mark Player Movement
-(void) acceleratePlayerWithX:(double)xAcceleration
{
    // Adjust velocity based on current accelerometer acceleration
    playerVelocity.x = (playerVelocity.x * deceleration) + (xAcceleration * sensitivity);
    // Limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    if (playerVelocity.x > maxVelocity)
    {
        playerVelocity.x = maxVelocity;
    }
    else if (playerVelocity.x < -maxVelocity)
    {
        playerVelocity.x = -maxVelocity;
    }
}
#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
    // Collision check
    [self checkForCollision];
}  
#pragma mark MovingTarget

-(void) initMovingTarget
{
    NSLog(@"initMovingTarget");
    // This is the image
	movingTarget = [CCSprite spriteWithFile:@"red_ball.png"];
    // Add CCSprite for movingTarget
    [self addChild:movingTarget z:0 tag:2];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
    movingTargetMoveDuration = 4.0f;
   	// Unschedule the selector just in case. If it isn't scheduled it won't do anything.
	[self unschedule:@selector(movingTargetUpdate:)];
	// Schedule the movingTarget update logic to run at the given interval.
	[self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Update score to display increment in totalAttempts
    [self setScore];
    // Increment total attempts to hit a moving target.
    totalAttempts ++;
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) movingTargetUpdate:(ccTime)delta
{
    // CCSprite->CCNode no sequence of actions running.
    if ([movingTarget numberOfRunningActions] == 0)
    {
        NSLog(@"movingTargetUpdate");
        // Determine below screen position.
        CGPoint belowScreenPosition = CGPointMake(movingTarget.position.x, - ( [movingTarget texture].contentSize.height));
        // CCAction to move a CCNode object to the position x,y based on position. 
        CCMoveTo* moveEnd = [CCMoveTo actionWithDuration:movingTargetMoveDuration position:belowScreenPosition];
        // Call back function for the action.
        CCCallFuncN* callEndMovingTargetSequence = [CCCallFuncN actionWithTarget:self selector:@selector(endMovingTargetSequence)];
        // Create a sequence, add the actions: the moveEnd CCMoveTo and the call back function for the end position.
        CCSequence* sequence = [CCSequence actions:moveEnd,callEndMovingTargetSequence, nil];
        // Run the sequence.
        [movingTarget runAction:sequence];
    }
}
-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    [movingTarget stopAllActions];
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Decrease the moving target move duration to increase the speed.
    movingTargetMoveDuration -= 0.1f;
    // Moving target move duration is below 2 then hold at 2.
    if (movingTargetMoveDuration < 2.0f)
    {
        movingTargetMoveDuration = 2.0f;
    }
    NSLog(@"movingTargetMoveDuration: %f",movingTargetMoveDuration);
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
    
}
#pragma mark Collision Check
-(void) checkForCollision
{
	// Size of the player and target. Both are assumed squares so width suffices.
	float playerImageSize = [player texture].contentSize.width;
	float targetImageSize = [movingTarget texture].contentSize.width;
	// Compute their radii. Tweak based on drawing.
	float playerCollisionRadius = playerImageSize *.4;
	float targetCollisionRadius = targetImageSize *.4;
	// This collision distance will roughly equal the image shapes.
	float maxCollisionDistance = playerCollisionRadius + targetCollisionRadius;
    // Distance between two points.
    float actualDistance = ccpDistance(player.position, movingTarget.position);
    
    // Are the two objects closer than allowed?
    if (actualDistance < maxCollisionDistance)
    {
        // Play a sound effect
        [[SimpleAudioEngine sharedEngine] playEffect:@"explosion.caf"];
        totalHits++;
        NSLog(@"HIT! Total attempts: %i. Total hits: %i", totalHits, totalAttempts);
        [self setScore];
        [self endMovingTargetSequence];
    }
}
#pragma mark score
-(void) initScore
{
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Load font information.
    scoreLabel = [CCLabelBMFont labelWithString:@"+0 : 0" fntFile:@"score_glyphs.fnt"];
    scoreLabel.position = CGPointMake(screenSize.width / 2, screenSize.height);
    // Set anchorPoint y position to align with the top of screen.
    scoreLabel.anchorPoint = CGPointMake(0.5f, 1.0f);
}
-(void) setScore
{
    [scoreLabel setString:[NSString stringWithFormat:@"%i : %i : %i", totalAttempts,totalAttempts-totalHits, totalHits]];
}
@end

Two private methods initScore and setScore are declared on lines 17 and 18.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"
#import "SimpleAudioEngine.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
-(void) checkForCollision;
-(void) initScore;
-(void) setScore;
@end

The CCLabelBMFont could be declared in the init method. I choose a separate method to stop the growth of the init method and also organize related coded into reusable pieces.

That called for getting the screen center again so the score can be centered.

The score_glyphs.fnt file is human readable and contains the information CCLabelBMFont needs to break out the images in score_glyphs.png. Both score_glyphs.fnt and score_glyphs.png where exported from Glyph Designer.

Line 235 sets up the storing score so the score_glyphs.png is cached.

The anchorPoint property is a convenient way to offset the y position. The Cocos2d documentation recommends not to change the defaults: “All inner characters are using an anchorPoint of (0.5f, 0.5f) and it is not recommend to change it because it might affect the rendering”. However it works so we will use it.

Line 242 displays the three score values of totalAttempts, the number of misses and the totalHits.

#pragma mark score
-(void) initScore
{
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Load font information.
    scoreLabel = [CCLabelBMFont labelWithString:@"0 : 0 : 0" fntFile:@"score_glyphs.fnt"];
    scoreLabel.position = CGPointMake(screenSize.width / 2, screenSize.height);
    // Set anchorPoint y position to align with the top of screen.
    scoreLabel.anchorPoint = CGPointMake(0.5f, 1.0f);
}
-(void) setScore
{
    [scoreLabel setString:[NSString stringWithFormat:@"%i : %i : %i", totalAttempts,totalAttempts-totalHits, totalHits]];
}

[ad name=”Google Adsense”]
Step 5 – GameLayer.m: Initialize the Score in the init Method

On line 66 the score added to the game layer.

On lines 57 and 64 the game information overlay is moved back so the moving target appears over them.

Line 59 updates the subtitle which helps keep track what we are looking at when testing.

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Preload the sound effect into memory so there's no delay when playing it the first time.
		[[SimpleAudioEngine sharedEngine] preloadEffect:@"explosion.caf"];
        // Initialize the total attempts to hit a moving target.
        totalAttempts = 0;
        // Initialize the total hits
        totalHits = 0;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label z:-1];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 7"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2 z:-1];
        // The score label initialized
        [self initScore];
        // Add below game action
		[self addChild:scoreLabel z:-1];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}

Step 6 – GameLayer.m: Update Score For Misses

Line 155 updates the score before incrementing the number of attempts on line 157. If we do it after, the misses would show a value of 1 more than actual each time a new moving target it put into play.

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Update score to display increment in totalAttempts
    [self setScore];
    // Increment total attempts to hit a moving target.
    totalAttempts ++;
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

Step 7 – GameLayer.m: Update Score For Hits

On each hit you want to update the score. The checkForCollision method handles the hits for this game.

You are already displaying score data in the checkForCollision method. So you just need to drop in a call to the setScore method on line 226 just after the class property totalHits is incremented.

#pragma mark Collision Check
-(void) checkForCollision
{
	// Size of the player and target. Both are assumed squares so width suffices.
	float playerImageSize = [player texture].contentSize.width;
	float targetImageSize = [movingTarget texture].contentSize.width;
	// Compute their radii. Tweak based on drawing.
	float playerCollisionRadius = playerImageSize *.4;
	float targetCollisionRadius = targetImageSize *.4;
	// This collision distance will roughly equal the image shapes.
	float maxCollisionDistance = playerCollisionRadius + targetCollisionRadius;
    // Distance between two points.
    float actualDistance = ccpDistance(player.position, movingTarget.position);
    
    // Are the two objects closer than allowed?
    if (actualDistance < maxCollisionDistance)
    {
        // Play a sound effect
        [[SimpleAudioEngine sharedEngine] playEffect:@"explosion.caf"];
        totalHits++;
        NSLog(@"HIT! Total attempts: %i. Total hits: %i", totalHits, totalAttempts);
        [self setScore];
        [self endMovingTargetSequence];
    }
}

That should give you a good starting point for displaying a graphic glyph score.


<== Lesson 6 || Lesson 8 ==>

[ad name=”Google Adsense”]

Categories
Articles

Kobold2D XCode 4 Introduction Tutorial Lesson 6 – Add Collision Sound Effect


<== Lesson 5 || Lesson 7 ==>

So far you can see the XCode console window reports the collisions between the player and moving target.

Learn cocos2D Game Development
Learn cocos2D Game Development

Now is time to add some sound. Audio is not integrated into Cocos2D and so a library is required. Fortunately CocosDenshion is an audio library with IOS gaming in mind and is a part of Kobold2D. CocosDenshion is referred to as SimpleAudioEngine in the code.

The file format for playing background sound with CocosDenshion is mp3. For short sound effects, the format recommendation is 16 bit mono wave files for uncompressed audio and IMA4 for lossy compressed audio. You can read more on formats and sound quality choices at CocosDenshion .

Finished Lesson Running on IPad2 Simulator

Lesson Downloads

  1. Game piece images and sound for project
  2. IPhone images for project. Icons and splash screen.
  3. Completed Project. This is built in Kobold2d 1.0.1.

Step 1 – Build an Empty-Project Template

You can continue with the Lesson 5 project and make changes noted here. Otherwise the steps for creating this project from an Empty-Project template are the same as Lesson 1 except to substitute Lesson 6 for Lesson 1 in the project name and to include the red_ball.png, red_ball-hd.png and explosion.caf files when you add the game pieces. The explosion.caf goes in same group as the images. Then you can use the code presented here.

[ad name=”Google Adsense”]

Step 2 – Set Properties in config.lua

There are no new configuration properties from Lesson 5. Complete config.lua file is included here for your copy convenience.

--[[
* Kobold2D™ --- http://www.kobold2d.org
*
* Copyright (c) 2010-2011 Steffen Itterheim. 
* Released under MIT License in Germany (LICENSE-Kobold2D.txt).
--]]


--[[
* Need help with the KKStartupConfig settings?
* ------ http://www.kobold2d.com/x/ygMO ------
--]]


local config =
{
    KKStartupConfig = 
    {
        -- load first scene from a class with this name, or from a Lua script with this name with .lua appended
        FirstSceneClassName = "GameLayer",

        -- set the director type, and the fallback in case the first isn't available
        DirectorType = DirectorType.DisplayLink,
        DirectorTypeFallback = DirectorType.NSTimer,

        MaxFrameRate = 60,
        DisplayFPS = YES,

        EnableUserInteraction = YES,
        EnableMultiTouch = NO,

        -- Render settings
        DefaultTexturePixelFormat = TexturePixelFormat.RGBA8888,
        GLViewColorFormat = GLViewColorFormat.RGB565,
        GLViewDepthFormat = GLViewDepthFormat.DepthNone,
        GLViewMultiSampling = NO,
        GLViewNumberOfSamples = 0,

        Enable2DProjection = NO,
        EnableRetinaDisplaySupport = YES,
        EnableGLViewNodeHitTesting = NO,
        EnableStatusBar = NO,

        -- Orientation & Autorotation
        DeviceOrientation = DeviceOrientation.LandscapeLeft,
        AutorotationType = Autorotation.None,
        ShouldAutorotateToLandscapeOrientations = NO,
        ShouldAutorotateToPortraitOrientations = NO,
        AllowAutorotateOnFirstAndSecondGenerationDevices = NO,

        -- Ad setup
        EnableAdBanner = NO,
        PlaceBannerOnBottom = YES,
        LoadOnlyPortraitBanners = NO,
        LoadOnlyLandscapeBanners = NO,
        AdProviders = "iAd, AdMob",	-- comma seperated list -> "iAd, AdMob" means: use iAd if available, otherwise AdMob
        AdMobRefreshRate = 15,
        AdMobFirstAdDelay = 5,
        AdMobPublisherID = "YOUR_ADMOB_PUBLISHER_ID", -- how to get an AdMob Publisher ID: http://developer.admob.com/wiki/PublisherSetup
        AdMobTestMode = YES,

        -- Mac OS specific settings
        AutoScale = NO,
        AcceptsMouseMovedEvents = NO,
        WindowFrame = RectMake(1024-640, 768-480, 640, 480),
        EnableFullScreen = NO,
    },
}

return config

Step 3 – GameLayer.h

There are no changes from Lesson 5 so the entire code is here for your copy and paste convenience.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "kobold2d.h"

@interface GameLayer : CCLayer
{
    CCSprite* player;
    CGPoint playerVelocity;
    CCSprite* movingTarget;
	float movingTargetMoveDuration;
    int totalAttempts;
    int totalHits;
}
@end

Step 4 – Include the SimpleAudioEngine Library

Complete GameLayer.m file is included here for your copy convenience. I will focus on the code changes to accommodate the collision sounds.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"
#import "SimpleAudioEngine.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
-(void) checkForCollision;
@end

// Velocity deceleration
const float deceleration = 0.4f;
// Accelerometer sensitivity (higher = more sensitive)
const float sensitivity = 6.0f;
// Maximum velocity
const float maxVelocity = 100.0f;

@implementation GameLayer

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Preload the sound effect into memory so there's no delay when playing it the first time.
		[[SimpleAudioEngine sharedEngine] preloadEffect:@"explosion.caf"];
        
        // Initialize the total attempts to hit a moving target.
        totalAttempts = 0;
        // Initialize the total hits
        totalHits = 0;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 6"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}
-(void) dealloc
{
#ifndef KK_ARC_ENABLED
	[super dealloc];
#endif // KK_ARC_ENABLED
}
#pragma mark Player Movement
-(void) acceleratePlayerWithX:(double)xAcceleration
{
    // Adjust velocity based on current accelerometer acceleration
    playerVelocity.x = (playerVelocity.x * deceleration) + (xAcceleration * sensitivity);
    // Limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    if (playerVelocity.x > maxVelocity)
    {
        playerVelocity.x = maxVelocity;
    }
    else if (playerVelocity.x < -maxVelocity)
    {
        playerVelocity.x = -maxVelocity;
    }
}
#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
    // Collision check
    [self checkForCollision];
}  
#pragma mark MovingTarget

-(void) initMovingTarget
{
    NSLog(@"initMovingTarget");
    // This is the image
	movingTarget = [CCSprite spriteWithFile:@"red_ball.png"];
    // Add CCSprite for movingTarget
    [self addChild:movingTarget z:0 tag:2];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
    movingTargetMoveDuration = 4.0f;
   	// Unschedule the selector just in case. If it isn't scheduled it won't do anything.
	[self unschedule:@selector(movingTargetUpdate:)];
	// Schedule the movingTarget update logic to run at the given interval.
	[self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Increment total attempts to hit a moving target.
    totalAttempts ++;
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) movingTargetUpdate:(ccTime)delta
{
    // CCSprite->CCNode no sequence of actions running.
    if ([movingTarget numberOfRunningActions] == 0)
    {
        NSLog(@"movingTargetUpdate");
        // Determine below screen position.
        CGPoint belowScreenPosition = CGPointMake(movingTarget.position.x, - ( [movingTarget texture].contentSize.height));
        // CCAction to move a CCNode object to the position x,y based on position. 
        CCMoveTo* moveEnd = [CCMoveTo actionWithDuration:movingTargetMoveDuration position:belowScreenPosition];
        // Call back function for the action.
        CCCallFuncN* callEndMovingTargetSequence = [CCCallFuncN actionWithTarget:self selector:@selector(endMovingTargetSequence)];
        // Create a sequence, add the actions: the moveEnd CCMoveTo and the call back function for the end position.
        CCSequence* sequence = [CCSequence actions:moveEnd,callEndMovingTargetSequence, nil];
        // Run the sequence.
        [movingTarget runAction:sequence];
    }
}
-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    [movingTarget stopAllActions];
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Decrease the moving target move duration to increase the speed.
    movingTargetMoveDuration -= 0.1f;
    // Moving target move duration is below 2 then hold at 2.
    if (movingTargetMoveDuration < 2.0f)
    {
        movingTargetMoveDuration = 2.0f;
    }
    NSLog(@"movingTargetMoveDuration: %f",movingTargetMoveDuration);
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
}
#pragma mark Collision Check
-(void) checkForCollision
{
	// Size of the player and target. Both are assumed squares so width suffices.
	float playerImageSize = [player texture].contentSize.width;
	float targetImageSize = [movingTarget texture].contentSize.width;
	// Compute their radii. Tweak based on drawing.
	float playerCollisionRadius = playerImageSize *.4;
	float targetCollisionRadius = targetImageSize *.4;
	// This collision distance will roughly equal the image shapes.
	float maxCollisionDistance = playerCollisionRadius + targetCollisionRadius;
    // Distance between two points.
    float actualDistance = ccpDistance(player.position, movingTarget.position);
    
    // Are the two objects closer than allowed?
    if (actualDistance < maxCollisionDistance)
    {
        // Play a sound effect
        [[SimpleAudioEngine sharedEngine] playEffect:@"explosion.caf"];
        totalHits++;
        NSLog(@"HIT! Total attempts: %i. Total hits: %i", totalAttempts, totalHits);
        [self endMovingTargetSequence];
    }
}
@end

First the sound effect library is needed. Line 9 accomplishes that for you.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"
#import "SimpleAudioEngine.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
-(void) checkForCollision;
@end

Step 5 – Preload the Collision Sound Effect

Next you can preload the sound effect file to avoid a delay the first time it is played. The init method is the perfect place to do the preloading and line 36 shows how it is done with SimpleAudioEngine.

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Preload the sound effect into memory so there's no delay when playing it the first time.
		[[SimpleAudioEngine sharedEngine] preloadEffect:@"explosion.caf"];
        
        // Initialize the total attempts to hit a moving target.
        totalAttempts = 0;
        // Initialize the total hits
        totalHits = 0;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 6"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}

Step 6 – Play the Collision Sound Effect

Playing the sound effect is done in the checkForCollision method and is a single line of code on line 215.

#pragma mark Collision Check
-(void) checkForCollision
{
	// Size of the player and target. Both are assumed squares so width suffices.
	float playerImageSize = [player texture].contentSize.width;
	float targetImageSize = [movingTarget texture].contentSize.width;
	// Compute their radii. Tweak based on drawing.
	float playerCollisionRadius = playerImageSize *.4;
	float targetCollisionRadius = targetImageSize *.4;
	// This collision distance will roughly equal the image shapes.
	float maxCollisionDistance = playerCollisionRadius + targetCollisionRadius;
    // Distance between two points.
    float actualDistance = ccpDistance(player.position, movingTarget.position);
    
    // Are the two objects closer than allowed?
    if (actualDistance < maxCollisionDistance)
    {
        // Play a sound effect
        [[SimpleAudioEngine sharedEngine] playEffect:@"explosion.caf"];
        totalHits++;
        NSLog(@"HIT! Total attempts: %i. Total hits: %i", totalAttempts, totalHits);
        [self endMovingTargetSequence];
    }
}

You should be all set to hear crashes as you tilt your IPad or IPhone and try to intercept our falling target.


<== Lesson 5 || Lesson 7 ==>

[ad name=”Google Adsense”]

Categories
Articles

Kobold2D XCode 4 Introduction Tutorial Lesson 5 – Add Collision Detection


<== Lesson 4 || Lesson 6 ==>

Now its time to detect the collision of the moving target and the player.

Learn cocos2D Game Development
Learn cocos2D Game Development

For this game, the collision is a good thing as that is the goal. So we will call collisions between the player and the moving target a hit.

To plan ahead, you will also add a counter to detect the number of hits. For now you will just display that value in the XCode console window. That will help debug the collision detection and set up for use of that value.

Finished Lesson Running on IPad2 Simulator

Lesson Downloads

  1. Game piece images for project
  2. IPhone images for project. Icons and splash screen.
  3. Completed Project. This is built in Kobold2d 1.0.1.

Step 1 – Build an Empty-Project Template

You can continue with the Lesson 4 project and make changes noted here. Otherwise the steps for creating this project from an Empty-Project template are the same as Lesson 1 except to substitute Lesson 5 for Lesson 1 in the project name and to include the red_ball.png and red_ball-hd.png files when you add the game pieces. Then you can use the code presented here.
[ad name=”Google Adsense”]

Step 2 – Set Properties in config.lua

There are no new configuration properties from Lesson 4. Complete config.lua file is included here for your copy convenience.

--[[
* Kobold2D™ --- http://www.kobold2d.org
*
* Copyright (c) 2010-2011 Steffen Itterheim. 
* Released under MIT License in Germany (LICENSE-Kobold2D.txt).
--]]


--[[
* Need help with the KKStartupConfig settings?
* ------ http://www.kobold2d.com/x/ygMO ------
--]]


local config =
{
    KKStartupConfig = 
    {
        -- load first scene from a class with this name, or from a Lua script with this name with .lua appended
        FirstSceneClassName = "GameLayer",

        -- set the director type, and the fallback in case the first isn't available
        DirectorType = DirectorType.DisplayLink,
        DirectorTypeFallback = DirectorType.NSTimer,

        MaxFrameRate = 60,
        DisplayFPS = YES,

        EnableUserInteraction = YES,
        EnableMultiTouch = NO,

        -- Render settings
        DefaultTexturePixelFormat = TexturePixelFormat.RGBA8888,
        GLViewColorFormat = GLViewColorFormat.RGB565,
        GLViewDepthFormat = GLViewDepthFormat.DepthNone,
        GLViewMultiSampling = NO,
        GLViewNumberOfSamples = 0,

        Enable2DProjection = NO,
        EnableRetinaDisplaySupport = YES,
        EnableGLViewNodeHitTesting = NO,
        EnableStatusBar = NO,

        -- Orientation & Autorotation
        DeviceOrientation = DeviceOrientation.LandscapeLeft,
        AutorotationType = Autorotation.None,
        ShouldAutorotateToLandscapeOrientations = NO,
        ShouldAutorotateToPortraitOrientations = NO,
        AllowAutorotateOnFirstAndSecondGenerationDevices = NO,

        -- Ad setup
        EnableAdBanner = NO,
        PlaceBannerOnBottom = YES,
        LoadOnlyPortraitBanners = NO,
        LoadOnlyLandscapeBanners = NO,
        AdProviders = "iAd, AdMob",	-- comma seperated list -> "iAd, AdMob" means: use iAd if available, otherwise AdMob
        AdMobRefreshRate = 15,
        AdMobFirstAdDelay = 5,
        AdMobPublisherID = "YOUR_ADMOB_PUBLISHER_ID", -- how to get an AdMob Publisher ID: http://developer.admob.com/wiki/PublisherSetup
        AdMobTestMode = YES,

        -- Mac OS specific settings
        AutoScale = NO,
        AcceptsMouseMovedEvents = NO,
        WindowFrame = RectMake(1024-640, 768-480, 640, 480),
        EnableFullScreen = NO,
    },
}

return config

Step 3 – GameLayer.h

Added are two variables to track the play. Line 16 is keeping track of the total times a moving target was started to descend down the screen.

Line 17 tracks the number of times the moving target and the player collided.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "kobold2d.h"

@interface GameLayer : CCLayer
{
    CCSprite* player;
    CGPoint playerVelocity;
    CCSprite* movingTarget;
	float movingTargetMoveDuration;
    int totalAttempts;
    int totalHits;
}
@end

Step 4 – Add checkForCollision Private Method

Complete GameLayer.m file is included here for your copy convenience. I will focus on the code changes to accommodate the collision detection.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
-(void) checkForCollision;
@end

// Velocity deceleration
const float deceleration = 0.4f;
// Accelerometer sensitivity (higher = more sensitive)
const float sensitivity = 6.0f;
// Maximum velocity
const float maxVelocity = 100.0f;

@implementation GameLayer

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Initialize the total attempts to hit a moving target.
        totalAttempts = 0;
        // Initialize the total hits
        totalHits = 0;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 5"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}
-(void) dealloc
{
#ifndef KK_ARC_ENABLED
	[super dealloc];
#endif // KK_ARC_ENABLED
}
#pragma mark Player Movement
-(void) acceleratePlayerWithX:(double)xAcceleration
{
    // Adjust velocity based on current accelerometer acceleration
    playerVelocity.x = (playerVelocity.x * deceleration) + (xAcceleration * sensitivity);
    // Limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    if (playerVelocity.x > maxVelocity)
    {
        playerVelocity.x = maxVelocity;
    }
    else if (playerVelocity.x < -maxVelocity)
    {
        playerVelocity.x = -maxVelocity;
    }
}
#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
    // Collision check
    [self checkForCollision];
}  
#pragma mark MovingTarget

-(void) initMovingTarget
{
    NSLog(@"initMovingTarget");
    // This is the image
	movingTarget = [CCSprite spriteWithFile:@"red_ball.png"];
    // Add CCSprite for movingTarget
    [self addChild:movingTarget z:0 tag:2];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
    movingTargetMoveDuration = 4.0f;
   	// Unschedule the selector just in case. If it isn't scheduled it won't do anything.
	[self unschedule:@selector(movingTargetUpdate:)];
	// Schedule the movingTarget update logic to run at the given interval.
	[self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Increment total attempts to hit a moving target.
    totalAttempts ++;
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) movingTargetUpdate:(ccTime)delta
{
    // CCSprite->CCNode no sequence of actions running.
    if ([movingTarget numberOfRunningActions] == 0)
    {
        NSLog(@"movingTargetUpdate");
        // Determine below screen position.
        CGPoint belowScreenPosition = CGPointMake(movingTarget.position.x, - ( [movingTarget texture].contentSize.height));
        // CCAction to move a CCNode object to the position x,y based on position. 
        CCMoveTo* moveEnd = [CCMoveTo actionWithDuration:movingTargetMoveDuration position:belowScreenPosition];
        // Call back function for the action.
        CCCallFuncN* callEndMovingTargetSequence = [CCCallFuncN actionWithTarget:self selector:@selector(endMovingTargetSequence)];
        // Create a sequence, add the actions: the moveEnd CCMoveTo and the call back function for the end position.
        CCSequence* sequence = [CCSequence actions:moveEnd,callEndMovingTargetSequence, nil];
        // Run the sequence.
        [movingTarget runAction:sequence];
    }
}
-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    [movingTarget stopAllActions];
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Decrease the moving target move duration to increase the speed.
    movingTargetMoveDuration -= 0.1f;
    // Moving target move duration is below 2 then hold at 2.
    if (movingTargetMoveDuration < 2.0f)
    {
        movingTargetMoveDuration = 2.0f;
    }
    NSLog(@"movingTargetMoveDuration: %f",movingTargetMoveDuration);
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
}
#pragma mark Collision Check
-(void) checkForCollision
{
	// Size of the player and target. Both are assumed squares so width suffices.
	float playerImageSize = [player texture].contentSize.width;
	float targetImageSize = [movingTarget texture].contentSize.width;
	// Compute their radii. Tweak based on drawing.
	float playerCollisionRadius = playerImageSize *.4;
	float targetCollisionRadius = targetImageSize *.4;
	// This collision distance will roughly equal the image shapes.
	float maxCollisionDistance = playerCollisionRadius + targetCollisionRadius;
    // Distance between two points.
    float actualDistance = ccpDistance(player.position, movingTarget.position);
    
    // Are the two objects closer than allowed?
    if (actualDistance < maxCollisionDistance)
    {
        totalHits++;
        NSLog(@"HIT! Total attempts: %i. Total hits: %i", totalAttempts, totalHits);
        [self endMovingTargetSequence];
    }
}
@end

First set of changes are in the declaration of a new private method checkForCollision.

#import "GameLayer.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
-(void) checkForCollision;
@end

[ad name=”Google Adsense”]
Step 5 – Initialize Scoring Properties and Update Subtitle

The scoring properties are initialized on lines 35 and 37.

Line 54 clarifies the lesson in the subtitle.

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Initialize the total attempts to hit a moving target.
        totalAttempts = 0;
        // Initialize the total hits
        totalHits = 0;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 5"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}

Step 6 – Call the Collision Detection Method

Each time the game data is updated you also can check for any collisions. The update method is called for each iteration in the game.

You can just put the message for the checkForCollision, shown on line 122, at the end. By the end of checkForCollision the player and moving targets are in their new positions and a perfect place to check for an overlap.

#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
    // Collision check
    [self checkForCollision];
}  

Step 7 – Kill The Moving Target Actions

The endMovingTargetSequence method can be called once a collision is detected. It is set up to clean up the current moving target and call the startMovingTargetSequence for the next moving target.

However endMovingTargetSequence presents a timing issue between the update of the game and the number of times the moving target actions occur. For each game update you will observe multiple calls for the moving target CCMoveTo moveEnd action defined in movingTargetUpdate.

That creates an opening for multiple hits before line 181 unschedule gets fired. So the moving target will proceed through the position of the player and each update will detect a collision.

The solution is to add the stopAllActions method is from Cocos2D CCNode class. Making stopAllActions the first line results in immediate termination of all actions for the moving target. See line 179.

-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    [movingTarget stopAllActions];
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Decrease the moving target move duration to increase the speed.
    movingTargetMoveDuration -= 0.1f;
    // Moving target move duration is below 2 then hold at 2.
    if (movingTargetMoveDuration < 2.0f)
    {
        movingTargetMoveDuration = 2.0f;
    }
    NSLog(@"movingTargetMoveDuration: %f",movingTargetMoveDuration);
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
}

Step 8 – Count the Total Moving Targets

The startMovingTargetSequence is fired each time a new moving target is added. This is where, see line 146, to increment the counter for the number of moving targets.

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Increment total attempts to hit a moving target.
    totalAttempts ++;
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

[ad name=”Google Adsense”]
Step 9 – Add Collision Detection Method

The checkForCollision method is our new method that handles collisions between the moving target and the player.

The process is twofold. One to compute the distance that defines a collision. Lines 196 – 203 do that. The measurement is based on the graphics and their center registration point and any offset you want to add to that based on observing the collisions.

Second part of the collision detection process is computing the actual dist and and line 205 does that for us.

Line 208 defines a code block if there is hit and in that code block the totalHits counter is incremented and the endMovingTargetSequence method is called to remove the moving target.

#pragma mark Collision Check
-(void) checkForCollision
{
	// Size of the player and target. Both are assumed squares so width suffices.
	float playerImageSize = [player texture].contentSize.width;
	float targetImageSize = [movingTarget texture].contentSize.width;
	// Compute their radii. Tweak based on drawing and observations.
	float playerCollisionRadius = playerImageSize *.4;
	float targetCollisionRadius = targetImageSize *.4;
	// This collision distance will roughly equal the image shapes.
	float maxCollisionDistance = playerCollisionRadius + targetCollisionRadius;
    // Distance between two points.
    float actualDistance = ccpDistance(player.position, movingTarget.position);
    
    // Are the two objects closer than allowed?
    if (actualDistance < maxCollisionDistance)
    {
        totalHits++;
        NSLog(@"HIT! Total attempts: %i. Total hits: %i", totalAttempts, totalHits);
        [self endMovingTargetSequence];
    }
}



<== Lesson 4 || Lesson 6 ==>

[ad name=”Google Adsense”]

Categories
Articles

Kobold2D XCode 4 Introduction Tutorial Lesson 4 – Progressively Increase Moving Target Speed


<== Lesson 3 || Lesson 5 ==>

This is a short lesson that adds a tad of challenge to trying to intercept the moving target.

Learn cocos2D Game Development
Learn cocos2D Game Development

The challenge is to increase the speed of each iteration of the moving target. The movingTargetDuration property has the length of time to “fall” from the top of the screen to the bottom. Progressively decreasing that value will increase the rate of movement for each interval.

However you eventually need to tweak game challenges so they are not impossible while remaining doable with practice. In this case a moving target duration below 2 appears to be close to the limit of impossible for the IPad in landscape mode.

Your testing may conclude differently that the speed could get higher. This is just a lesson and hopefully opens you to thoughts for varying the challenge. For example the decrease in the duration could be random number from a range say 0.0 – 0.2. Or you could have a random hyper change in the duration for one iteration only.

One other item that can vary the challenge is game levels. Our tutorial is limited to one level at this lesson. If more levels are added, then variations on the moving target speed, direction and size could be possible properties to add challenge.

Finished Lesson Running on IPad2 Simulator

Lesson Downloads

  1. Game piece images for project
  2. IPhone images for project. Icons and splash screen.
  3. Completed Project. This is built in Kobold2d 1.0.1.

Step 1 – Build an Empty-Project Template

You can continue with the Lesson 3 project and make changes noted here. Otherwise the steps for creating this project from an Empty-Project template are the same as Lesson 1 except to substitute Lesson 4 for Lesson 1 in the project name and to include the red_ball.png and red_ball-hd.png files when you add the game pieces. Then you can use the code presented here.
[ad name=”Google Adsense”]

Step 2 – Set Properties in config.lua

There are no new configuration properties from Lesson 3. Complete config.lua file is included here for your copy convenience.

--[[
* Kobold2D™ --- http://www.kobold2d.org
*
* Copyright (c) 2010-2011 Steffen Itterheim. 
* Released under MIT License in Germany (LICENSE-Kobold2D.txt).
--]]


--[[
* Need help with the KKStartupConfig settings?
* ------ http://www.kobold2d.com/x/ygMO ------
--]]


local config =
{
    KKStartupConfig = 
    {
        -- load first scene from a class with this name, or from a Lua script with this name with .lua appended
        FirstSceneClassName = "GameLayer",

        -- set the director type, and the fallback in case the first isn't available
        DirectorType = DirectorType.DisplayLink,
        DirectorTypeFallback = DirectorType.NSTimer,

        MaxFrameRate = 60,
        DisplayFPS = YES,

        EnableUserInteraction = YES,
        EnableMultiTouch = NO,

        -- Render settings
        DefaultTexturePixelFormat = TexturePixelFormat.RGBA8888,
        GLViewColorFormat = GLViewColorFormat.RGB565,
        GLViewDepthFormat = GLViewDepthFormat.DepthNone,
        GLViewMultiSampling = NO,
        GLViewNumberOfSamples = 0,

        Enable2DProjection = NO,
        EnableRetinaDisplaySupport = YES,
        EnableGLViewNodeHitTesting = NO,
        EnableStatusBar = NO,

        -- Orientation & Autorotation
        DeviceOrientation = DeviceOrientation.LandscapeLeft,
        AutorotationType = Autorotation.None,
        ShouldAutorotateToLandscapeOrientations = NO,
        ShouldAutorotateToPortraitOrientations = NO,
        AllowAutorotateOnFirstAndSecondGenerationDevices = NO,

        -- Ad setup
        EnableAdBanner = NO,
        PlaceBannerOnBottom = YES,
        LoadOnlyPortraitBanners = NO,
        LoadOnlyLandscapeBanners = NO,
        AdProviders = "iAd, AdMob",	-- comma seperated list -> "iAd, AdMob" means: use iAd if available, otherwise AdMob
        AdMobRefreshRate = 15,
        AdMobFirstAdDelay = 5,
        AdMobPublisherID = "YOUR_ADMOB_PUBLISHER_ID", -- how to get an AdMob Publisher ID: http://developer.admob.com/wiki/PublisherSetup
        AdMobTestMode = YES,

        -- Mac OS specific settings
        AutoScale = NO,
        AcceptsMouseMovedEvents = NO,
        WindowFrame = RectMake(1024-640, 768-480, 640, 480),
        EnableFullScreen = NO,
    },
}

return config

Step 3 – GameLayer.h

No changes from Lesson 3. Complete GameLayer.h file is included here for your copy convenience.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "kobold2d.h"

@interface GameLayer : CCLayer
{
    CCSprite* player;
    CGPoint playerVelocity;
    CCSprite* movingTarget;
	float movingTargetMoveDuration;
}
@end

[ad name=”Google Adsense”]
Step 4 – Reduce Moving Target Animation Duration

Complete GameLayer.m file is included here for your copy convenience. We will just look at the code change up ahead for the endMovingTargetSequence method.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
@end

// Velocity deceleration
const float deceleration = 0.4f;
// Accelerometer sensitivity (higher = more sensitive)
const float sensitivity = 6.0f;
// Maximum velocity
const float maxVelocity = 100.0f;

@implementation GameLayer

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 4"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}
-(void) dealloc
{
#ifndef KK_ARC_ENABLED
	[super dealloc];
#endif // KK_ARC_ENABLED
}
#pragma mark Player Movement
-(void) acceleratePlayerWithX:(double)xAcceleration
{
    // Adjust velocity based on current accelerometer acceleration
    playerVelocity.x = (playerVelocity.x * deceleration) + (xAcceleration * sensitivity);
    // Limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    if (playerVelocity.x > maxVelocity)
    {
        playerVelocity.x = maxVelocity;
    }
    else if (playerVelocity.x < -maxVelocity)
    {
        playerVelocity.x = -maxVelocity;
    }
}
#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
}  
#pragma mark MovingTarget

-(void) initMovingTarget
{
    NSLog(@"initMovingTarget");
    // This is the image
	movingTarget = [CCSprite spriteWithFile:@"red_ball.png"];
    // Add CCSprite for movingTarget
    [self addChild:movingTarget z:0 tag:2];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
    movingTargetMoveDuration = 4.0f;
   	// Unschedule the selector just in case. If it isn't scheduled it won't do anything.
	[self unschedule:@selector(movingTargetUpdate:)];
	// Schedule the movingTarget update logic to run at the given interval.
	[self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) movingTargetUpdate:(ccTime)delta
{
    // CCSprite->CCNode no sequence of actions running.
    if ([movingTarget numberOfRunningActions] == 0)
    {
        NSLog(@"movingTargetUpdate");
        // Determine below screen position.
        CGPoint belowScreenPosition = CGPointMake(movingTarget.position.x, - ( [movingTarget texture].contentSize.height));
        // CCAction to move a CCNode object to the position x,y based on position. 
        CCMoveTo* moveEnd = [CCMoveTo actionWithDuration:movingTargetMoveDuration position:belowScreenPosition];
        // Call back function for the action.
        CCCallFuncN* callEndMovingTargetSequence = [CCCallFuncN actionWithTarget:self selector:@selector(endMovingTargetSequence)];
        // Create a sequence, add the actions: the moveEnd CCMoveTo and the call back function for the end position.
        CCSequence* sequence = [CCSequence actions:moveEnd,callEndMovingTargetSequence, nil];
        // Run the sequence.
        [movingTarget runAction:sequence];
    }
}
-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Decrease the moving target move duration to increase the speed.
    movingTargetMoveDuration -= 0.1f;
    // Moving target move duration is below 2 then hold at 2.
    if (movingTargetMoveDuration < 2.0f)
    {
        movingTargetMoveDuration = 2.0f;
    }
    NSLog(@"movingTargetMoveDuration: %f",movingTargetMoveDuration);
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
}

@end

The endMovingTargetSequence method is called when the moving target exits the bottom of the screen. That is set in the movingTargetUpdate method.

Line 173 of the endMovingTargetSequence method reduces the movingTargetMoveDuration class property by 0.1.

The movingTargetMoveDuration property starts at 4.0 in the initMovingTarget method.

Line 175 – 178 add a governor to prevent the duration from becoming less than 2.0.

As I mentioned earlier, you can be creative with ways to vary the moving target speed.

-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Decrease the moving target move duration to increase the speed.
    movingTargetMoveDuration -= 0.1f;
    // Moving target move duration is below 2 then hold at 2.
    if (movingTargetMoveDuration < 2.0f)
    {
        movingTargetMoveDuration = 2.0f;
    }
    NSLog(@"movingTargetMoveDuration: %f",movingTargetMoveDuration);
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
}



<== Lesson 3 || Lesson 5 ==>

[ad name=”Google Adsense”]

Categories
Articles

Kobold2D XCode 4 Introduction Tutorial Lesson 3 – Add Random Falling Target


<== Lesson 2 || Lesson 4 ==>

We have not discussed much about the nature of the game. Believe me it is relatively routine. The green player will try to intercept a falling red target.

Learn cocos2D Game Development
Learn cocos2D Game Development

The falling red target will start at random horizontal positions at the top of the screen and drop to the bottom of the screen. Only one red target falls at a time. The goal is to get the green player to intercept the falling red target.

In this lesson, we will add the falling red target but will not handle the collision. However, you can get some practice trying for later when you add a collision with sound and points.

Finished Lesson Running on IPad2 Simulator

[ad name=”Google Adsense”]
Lesson Downloads

  1. Game piece images for project
  2. IPhone images for project. Icons and splash screen.
  3. Completed Project. This is built in Kobold2d 1.0.1.

Step 1 – Build an Empty-Project Template

You can continue with the Lesson 2 project and make changes noted here. Otherwise the steps for creating this project from an Empty-Project template are the same as Lesson 1 except to substitute Lesson 3 for Lesson 1 in the project name and to include the red_ball.png and red_ball-hd.png files when you add the game pieces. Then you can use the code presented here.

Step 2 – Set Properties in config.lua

There are no new property changes from lesson 2. All are included here for you to follow along.

--[[
* Kobold2D™ --- http://www.kobold2d.org
*
* Copyright (c) 2010-2011 Steffen Itterheim. 
* Released under MIT License in Germany (LICENSE-Kobold2D.txt).
--]]


--[[
* Need help with the KKStartupConfig settings?
* ------ http://www.kobold2d.com/x/ygMO ------
--]]


local config =
{
    KKStartupConfig = 
    {
        -- load first scene from a class with this name, or from a Lua script with this name with .lua appended
        FirstSceneClassName = "GameLayer",

        -- set the director type, and the fallback in case the first isn't available
        DirectorType = DirectorType.DisplayLink,
        DirectorTypeFallback = DirectorType.NSTimer,

        MaxFrameRate = 60,
        DisplayFPS = YES,

        EnableUserInteraction = YES,
        EnableMultiTouch = NO,

        -- Render settings
        DefaultTexturePixelFormat = TexturePixelFormat.RGBA8888,
        GLViewColorFormat = GLViewColorFormat.RGB565,
        GLViewDepthFormat = GLViewDepthFormat.DepthNone,
        GLViewMultiSampling = NO,
        GLViewNumberOfSamples = 0,

        Enable2DProjection = NO,
        EnableRetinaDisplaySupport = YES,
        EnableGLViewNodeHitTesting = NO,
        EnableStatusBar = NO,

        -- Orientation & Autorotation
        DeviceOrientation = DeviceOrientation.LandscapeRight,
        AutorotationType = Autorotation.None,
        ShouldAutorotateToLandscapeOrientations = NO,
        ShouldAutorotateToPortraitOrientations = NO,
        AllowAutorotateOnFirstAndSecondGenerationDevices = NO,

        -- Ad setup
        EnableAdBanner = NO,
        PlaceBannerOnBottom = YES,
        LoadOnlyPortraitBanners = NO,
        LoadOnlyLandscapeBanners = NO,
        AdProviders = "iAd, AdMob",	-- comma seperated list -> "iAd, AdMob" means: use iAd if available, otherwise AdMob
        AdMobRefreshRate = 15,
        AdMobFirstAdDelay = 5,
        AdMobPublisherID = "YOUR_ADMOB_PUBLISHER_ID", -- how to get an AdMob Publisher ID: http://developer.admob.com/wiki/PublisherSetup
        AdMobTestMode = YES,

        -- Mac OS specific settings
        AutoScale = NO,
        AcceptsMouseMovedEvents = NO,
        WindowFrame = RectMake(1024-640, 768-480, 640, 480),
        EnableFullScreen = NO,
    },
}

return config

Step 3 – Add Moving Target Properties to GameLayer.h File

A CCSprite Cocos2d object is responsible for our target shown on line 14. CCSprite represents a 2d image and inherits from CCNode.

The movingTargetDuration float is the speed of the target fall from the top of the screen.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "kobold2d.h"

@interface GameLayer : CCLayer
{
    CCSprite* player;
    CGPoint playerVelocity;
    CCSprite* movingTarget;
	float movingTargetMoveDuration;
}
@end

Step 4 – Integrate Moving Target Into to GameLayer.m File

I am going to discuss specific sections of the GameLayer.m file in this post. Meantime for the impatient, like me, here here is the full GameLayer.m source code for your copy and paste convenience.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
@end

// Velocity deceleration
const float deceleration = 0.4f;
// Accelerometer sensitivity (higher = more sensitive)
const float sensitivity = 6.0f;
// Maximum velocity
const float maxVelocity = 100.0f;

@implementation GameLayer

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 3"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}
-(void) dealloc
{
#ifndef KK_ARC_ENABLED
	[super dealloc];
#endif // KK_ARC_ENABLED
}
#pragma mark Player Movement
-(void) acceleratePlayerWithX:(double)xAcceleration
{
    // Adjust velocity based on current accelerometer acceleration
    playerVelocity.x = (playerVelocity.x * deceleration) + (xAcceleration * sensitivity);
    // Limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    if (playerVelocity.x > maxVelocity)
    {
        playerVelocity.x = maxVelocity;
    }
    else if (playerVelocity.x < -maxVelocity)
    {
        playerVelocity.x = -maxVelocity;
    }
}
#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
}  
#pragma mark MovingTarget

-(void) initMovingTarget
{
    NSLog(@"initMovingTarget");
    // This is the image
	movingTarget = [CCSprite spriteWithFile:@"red_ball.png"];
    // Add CCSprite for movingTarget
    [self addChild:movingTarget z:0 tag:2];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
    movingTargetMoveDuration = 4.0f;
   	// Unschedule the selector just in case. If it isn't scheduled it won't do anything.
	[self unschedule:@selector(movingTargetUpdate:)];
	// Schedule the movingTarget update logic to run at the given interval.
	[self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

-(void) movingTargetUpdate:(ccTime)delta
{
    // CCSprite->CCNode no sequence of actions running.
    if ([movingTarget numberOfRunningActions] == 0)
    {
        NSLog(@"movingTargetUpdate");
        // Determine below screen position.
        CGPoint belowScreenPosition = CGPointMake(movingTarget.position.x, - ( [movingTarget texture].contentSize.height));
        // CCAction to move a CCNode object to the position x,y based on position. 
        CCMoveTo* moveEnd = [CCMoveTo actionWithDuration:movingTargetMoveDuration position:belowScreenPosition];
        // Call back function for the action.
        CCCallFuncN* callEndMovingTargetSequence = [CCCallFuncN actionWithTarget:self selector:@selector(endMovingTargetSequence)];
        // Create a sequence, add the actions: the moveEnd CCMoveTo and the call back function for the end position.
        CCSequence* sequence = [CCSequence actions:moveEnd,callEndMovingTargetSequence, nil];
        // Run the sequence.
        [movingTarget runAction:sequence];
    }
}
-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
}

@end

These methods are private and so we are declaring them in the implementation file. They are for controlling your moving target. We will talk more about them one at time.

#import "GameLayer.h"

@interface GameLayer (PrivateMethods)
-(void) initMovingTarget;
-(void) movingTargetUpdate:(ccTime)delta;
-(void) startMovingTargetSequence;
-(void) endMovingTargetSequence;
@end

Step 5 – Integrate Moving Target into GameLayer.h File init method

To keep the eye clear which lesson you are viewing, change the subtitle on line 49.

You have the initMovingTarget method to handle creating our moving target. Line 58 is added to call the initMovingTarget.

You need to place the moving target at various horizontal positions on the top of the screen. We are using CCRANDOM_0_1() for the randomization. CCRANDOM_0_1() needs a one time seeding shown on line 56.

@implementation GameLayer

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 3"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Seed random number generator
        srandom((UInt32)time(NULL));
        // Initialize our moving target.
        [self initMovingTarget];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}

[ad name=”Google Adsense”]
Step 6 – Add the initMoving Target Method

You can see on line 123 the red_ball.png is the graphic image for the moving target.

On line 125 the moving target is added. The z order is zero for this lesson. The tag value I mentioned in Lesson 1 as another way to get references to sprite objects. We are not using tags but since the game player has a tag value of 1, the moving target needed a different number. How about 2?

One line 127 the startMovingTargetSequence method for the initial start position of the moving target is called.

Line 128 sets the moving target moving duration value. For now the value never changes. But to add some variety to the game in a future lesson, we will decrease the value to speed up the moving target drop speed. The point is there needs to be a place to set the initial value.

One line 130 a standard safety precaution cancels the movingTargetUpdate method from being called. This method gets the target moving and we will look at it in a bit. At this point it is not possible to call initiMovingTarget more than once. However later it might happen this initMovingTarget method is called more than once and we have stopped any sequence from running.

Finally line 132 gets the moving target into motion. The movingTargetUpdate method defines that motion.

-(void) initMovingTarget
{
    NSLog(@"initMovingTarget");
    // This is the image
	movingTarget = [CCSprite spriteWithFile:@"red_ball.png"];
    // Add CCSprite for movingTarget
    [self addChild:movingTarget z:0 tag:2];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
    movingTargetMoveDuration = 4.0f;
   	// Unschedule the selector just in case. If it isn't scheduled it won't do anything.
	[self unschedule:@selector(movingTargetUpdate:)];
	// Schedule the movingTarget update logic to run at the given interval.
	[self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

Step 7 – Add the startMovingTargetSequence Target Method
This method through line 144 provides the starting point for the moving target object.

Then on line 146, the movingTargetUpdate method is scheduled for call. That is where the animation sequence for the moving target is created.

-(void) startMovingTargetSequence
{
    NSLog(@"startMovingTargetSequence");
    // Get the window size
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Get the image size
    CGSize imageSize = [movingTarget texture].contentSize;
    // Generate a random x starting position with offsets for center registration point.
    int randomX = CCRANDOM_0_1() * (screenSize.width / imageSize.width);
    movingTarget.position = CGPointMake(imageSize.width * randomX  + imageSize.width * 0.5f, screenSize.height + imageSize.height);
    // Schedule the movingTarget update logic to run at the given interval.
    [self schedule:@selector(movingTargetUpdate:) interval:0.1f];
}

Step 8 – Add the movingTargetUpdate Target Method

The movingTargetIUpdate method creates two CCAction objects.

The first is CCMoveTo on line 158. This is the destination of the moving target. The current y position and the belowScreenPosition y are traversed over the movingTargetMoveDuration value.

Line 160 creates the second CCCallFuncN to identify calling the endMovingTargetSequence method to handle the end of the animation.

These two CCAction objects are then bundled on line 162 into a CCSequence CCAction. CCSequence is essentially an array.

Line 164 runs the actions in the CCSequence.

So the CCMoveTo CCAction is processed over the movingTargetMoveDuration and on its conclusion the CCCallFuncN CCAction invokes the endMovingTargetSequence method for cleanup.

-(void) movingTargetUpdate:(ccTime)delta
{
    // CCSprite->CCNode no sequence of actions running.
    if ([movingTarget numberOfRunningActions] == 0)
    {
        NSLog(@"movingTargetUpdate");
        // Determine below screen position.
        CGPoint belowScreenPosition = CGPointMake(movingTarget.position.x, - ( [movingTarget texture].contentSize.height));
        // CCAction to move a CCNode object to the position x,y based on position. 
        CCMoveTo* moveEnd = [CCMoveTo actionWithDuration:movingTargetMoveDuration position:belowScreenPosition];
        // Call back function for the action.
        CCCallFuncN* callEndMovingTargetSequence = [CCCallFuncN actionWithTarget:self selector:@selector(endMovingTargetSequence)];
        // Create a sequence, add the actions: the moveEnd CCMoveTo and the call back function for the end position.
        CCSequence* sequence = [CCSequence actions:moveEnd,callEndMovingTargetSequence, nil];
        // Run the sequence.
        [movingTarget runAction:sequence];
    }
}

[ad name=”Google Adsense”]
Step 9 – Add the endMovingTargetSequence Target Method

This is the end of the moving target animation. The movingTargetUpdate method is removed as a scheduled method on line 171,

Line 172 then sets up for the next moving target for a repeat performance.

-(void) endMovingTargetSequence
{
    NSLog(@"endMovingTargetSequence");
    // Terminate running the moveTargetUpdate interval.
    [self unschedule:@selector(movingTargetUpdate:)];
    // Set the starting position and start movingTarget play sequence
    [self startMovingTargetSequence];
}



<== Lesson 2 || Lesson 4 ==>


Categories
Articles

Kobold2D XCode 4 Introduction Tutorial Lesson 2 – Set Landscape Orientation


<== Lesson 1 || Lesson 3 ==>

The first lesson put into place all the accelerometer code for our game. However we want the game to run only in landscape orientation.

Learn cocos2D Game Development
Learn cocos2D Game Development

KKStartupConfig – Setting the device orientation in Kobold2D is done in the config.lua configuration file. We saw in the last lesson the use of FirstSceneClassName for the KKStartupConfig object. FirstSceneClassName is a property for KKStartupConfig. The documentation for KKStartupConfig lists all the properties and valid values.

DeviceOrientation – One of the KKStartupConfig properties is DeviceOrientation. This is an int data type and has four values: DeviceOrientation.Portrait, DeviceOrientation.PortraitUpsideDown, DeviceOrientation.LandscapeLeft, and DeviceOrientation.LandscapeRight.

You can think of them based on where the home button is located. For DeviceOrientation.Portrait the home button is on the bottom where you might expect. For DeviceOrientation.PortraitUpsideDown the home button on top. The home button is to the right for DeviceOrientation.LandscapeLeft to the left for DeviceOrientation.LandscapeRight.

AutoRotationType – Another KKStartupConfig property dealing with rotation is AutoRotationType. This is an int and has these values: Autorotation.None, Autorotation.CCDirector, and Autorotation.UIViewController.

The None setting will never rotate your app’s device orientation. This is the setting we want.

The CCDirector setting makes the CCDirector responsible for rotating the OpenGL view but it will not auto rotate UIKit Views.

The UIViewController setting allows both UIKit views and the OpenGL view to be auto rotated and provides a rotation animation. But it comes with a performance penalty on 1st and 2nd generation iOS Devices. For that reason you can explicitly disable auto rotation on those devices via the AllowAutorotateOnFirstAndSecondGenerationDevices KKStartiupConfig property. We set AllowAutorotateOnFirstAndSecondGenerationDevices to false as we are not supporting older devices.

ShouldAutorotateTo… – Two other rotation KKStartupConfig properties are ShouldAutorotateToLandscapeOrientations and ShouldAutorotateToPortraitOrientations both boolean values. They explicitly restrict auto rotation and are useful when your game does not have alternative views.

Finished Lesson Running on IPad2 Simulator

[ad name=”Google Adsense”]
Lesson Downloads

  1. Game piece images for project
  2. IPhone images for project. Icons and splash screen.
  3. Completed Project. This is built in Kobold2d 1.0.1.

Step 1 – Build an Empty-Project Template

You can continue with the Lesson 1 project and make changes noted here. Otherwise the steps for creating this project from an Empty-Project template are the same as Lesson 1 except to substitute Lesson 2 for Lesson 1 in the project name.

Step 2 – Set the Orientation Properties in config.lua

This is the config.lua file located in the Projectfiles->Resources group.

Lines 43- 47 show the KKStartupConfig properties set for this lesson. Essentially our settings are saying to set the DeviceOrientation to LandscapeRight so the home button appears on the left and prevent any other orientation change.

Also take note to line 18 updated as a developer choice for the first scene game class.

--[[
* Kobold2D™ --- http://www.kobold2d.org
*
* Copyright (c) 2010-2011 Steffen Itterheim. 
* Released under MIT License in Germany (LICENSE-Kobold2D.txt).
--]]


--[[
* Need help with the KKStartupConfig settings?
* ------ http://www.kobold2d.com/x/ygMO ------
--]]


local config =
{
    KKStartupConfig = 
    {
        -- load first scene from a class with this name, or from a Lua script with this name with .lua appended
        FirstSceneClassName = "GameLayer",

        -- set the director type, and the fallback in case the first isn't available
        DirectorType = DirectorType.DisplayLink,
        DirectorTypeFallback = DirectorType.NSTimer,

        MaxFrameRate = 60,
        DisplayFPS = YES,

        EnableUserInteraction = YES,
        EnableMultiTouch = NO,

        -- Render settings
        DefaultTexturePixelFormat = TexturePixelFormat.RGBA8888,
        GLViewColorFormat = GLViewColorFormat.RGB565,
        GLViewDepthFormat = GLViewDepthFormat.DepthNone,
        GLViewMultiSampling = NO,
        GLViewNumberOfSamples = 0,

        Enable2DProjection = NO,
        EnableRetinaDisplaySupport = YES,
        EnableGLViewNodeHitTesting = NO,
        EnableStatusBar = NO,

        -- Orientation & Autorotation
        DeviceOrientation = DeviceOrientation.LandscapeRight,
        AutorotationType = Autorotation.None,
        ShouldAutorotateToLandscapeOrientations = NO,
        ShouldAutorotateToPortraitOrientations = NO,
        AllowAutorotateOnFirstAndSecondGenerationDevices = NO,

        -- Ad setup
        EnableAdBanner = NO,
        PlaceBannerOnBottom = YES,
        LoadOnlyPortraitBanners = NO,
        LoadOnlyLandscapeBanners = NO,
        AdProviders = "iAd, AdMob",	-- comma seperated list -> "iAd, AdMob" means: use iAd if available, otherwise AdMob
        AdMobRefreshRate = 15,
        AdMobFirstAdDelay = 5,
        AdMobPublisherID = "YOUR_ADMOB_PUBLISHER_ID", -- how to get an AdMob Publisher ID: http://developer.admob.com/wiki/PublisherSetup
        AdMobTestMode = YES,

        -- Mac OS specific settings
        AutoScale = NO,
        AcceptsMouseMovedEvents = NO,
        WindowFrame = RectMake(1024-640, 768-480, 640, 480),
        EnableFullScreen = NO,
    },
}

return config

[ad name=”Google Adsense”]
Step 3 – Update the Lesson Subtitle in the GameLayer.m file

Line 45 of the Lesson 2 GameLayer.m file updates the subtitle and included here for completeness. But you can see no other programming changes to control the orientation.

Notice on line 33 and 88 that we did not change but still allows the game to know the dimensions of the screen despite the landscape orientation.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"

@interface GameLayer (PrivateMethods)
@end

// Velocity deceleration
const float deceleration = 0.4f;
// Accelerometer sensitivity (higher = more sensitive)
const float sensitivity = 6.0f;
// Maximum velocity
const float maxVelocity = 100.0f;

@implementation GameLayer

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 2"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}
-(void) dealloc
{
#ifndef KK_ARC_ENABLED
	[super dealloc];
#endif // KK_ARC_ENABLED
}

#pragma mark Player Movement
-(void) acceleratePlayerWithX:(double)xAcceleration
{
    // Adjust velocity based on current accelerometer acceleration
    playerVelocity.x = (playerVelocity.x * deceleration) + (xAcceleration * sensitivity);
    // Limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    if (playerVelocity.x > maxVelocity)
    {
        playerVelocity.x = maxVelocity;
    }
    else if (playerVelocity.x < -maxVelocity)
    {
        playerVelocity.x = -maxVelocity;
    }
}
#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
}  
@end

Step 4 – GameLayer.h file

There are no changes from Lesson 1 for the GameLayer.h file. It is included here for completeness.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "kobold2d.h"

@interface GameLayer : CCLayer
{
    CCSprite* player;
    CGPoint playerVelocity;
}
@end

<== Lesson 1 || Lesson 3 ==>


[ad name=”Google Adsense”]

Categories
Articles

Kobold2D XCode 4 Introduction Tutorial Lesson 1 – Basic Accelerometer


Lesson 2 ==>

This first lesson goes through the steps to set up a Kobold2D project in XCode and use the accelerometer in the IPhone or IPad.

Learn cocos2D Game Development
Learn cocos2D Game Development

I am going to focus on the IPad, but the example should work fine in an IPhone as well. The example is very simple and is not yet a game. As the lessons continue a basic game will emerge.

Kobold2D is based on Cocos2D for IPhone project. Kobold2D helps make developing games in Cocos2D easier.

The example works in portrait orientation. Later lessons you change the orientation. Their is a green circle graphic that sits at the bottom of the screen and you can move it left and right by tilting the device. The accelerometer data is read and using some basic velocity adjustments the image moves until it reaches an edge. Here is a screen shot of the completed project.

Finished Lesson Running on IPad2 Simulator

The frames per second indicator is an option you will see later in the config.lua file.

You need to download and install Kobold2D and XCode.

Lesson Downloads

  1. Images for project
  2. Completed Project. This is built in Kobold2d 1.0.1.

[ad name=”Google Adsense”]

Step 1: Start a New Kobold2D Project

Kobold2D handles the creating of XCode projects through its own utility. Currently the recommendation is to close XCode before creating a new Kobold2D project.

Find the folder you installed Kobold2D and then launch the Kobold2D Project Starter application.

Kobold2D Project Starter application

Step 2: Select the Empty-Project Template

You are going to start with a bare bones project. The project will actually run but only displays a default splash screen and title screen.

Select an XCode workspace. The default KBold2d.xcworkspace is fine. All Kobold2d projects must be in an XCode workspace. This will appear to be an overkill once you get into XCode such as the Run shows all workspace projects and each with their own targets. You will find launching the wrong project a common problem.

You can create a new workspace here by typing the name. For now we are staying with the default KBold2d.xcworkspace workspace.

Type a project name and click the Create Project from Template button.

Selecting the Empty-Project Template

Step 3: Add IOS Icons

This step is optional as there will be default Kobold2d icons ready in your project. However if you have your own set you need to copy over them outside of XCode since they are already referenced in the XCode project. I provided a download of the various IOS icons in case you want to practice this step. Copy them as per this image.

Replacing Default IOS Icons

Step 4: Add Game Player Images

The game pieces are provided in the download. There is a low and high resolution version named according to the XCode IOS conventions. They are going to be in the Project Files->Resources group. You must copy them from the Finder into the XCode project navigator shown as follows:

Adding Player Images

Here is the “Choose options for adding these files” dialog. Check “Copy items into destination group’s folder (if needed)”. “Create folder references for any added folders” option is selected although it does not apply as we are not copying folders.

Finally Kobold2D is set up to build your game for a Mac delivery if you choose. We are building for IOS and so you can choose -IOS choice which is proceeded by the name you gave to the project. In our case the “Intro_Tutorial_Lesson_01-IOS”

Choose options for adding these files Dialog

[ad name=”Google Adsense”]
Step 5: Naming the Game Layer Class Files

The default name for the Game Layer class is HelloWorldLayer. We can leave that and proceed without any issues. But most developers want more control over the name of classes.

Plus you get to see the first glimpse to the internal workings of Kobold2D with the Lua file used for game configuration choices. One configuration is the main game layer class name.

In the project explorer window rename the HelloWorldLayer.h and HelloWorldLayer.m files to GameLayer.h and GameLayer.m files respectively.

Renaming HelloWorldLayer Class Files

Now open the config.lua file. You find this in the ProjectFiles->Resources folder. It is a text file. Change line 18 as you see below. You can look over some of the other potential configuration choices we can use as our work progress such as lines 42- 47 for controlling device orientation.

Line 25 for example contains the DisplayFPS to toggle the viewing of the frames per second. You might use this for testing when your game has performance issues. We will leave it on and you should see the 60 frames per second that is defined on line 24 most of the time for this lesson project.

As well you can add our own configuration variables to this file. A future lesson will provide an example.

--[[
* Kobold2D™ --- http://www.kobold2d.org
*
* Copyright (c) 2010-2011 Steffen Itterheim. 
* Released under MIT License in Germany (LICENSE-Kobold2D.txt).
--]]


--[[
* Need help with the KKStartupConfig settings?
* ------ http://www.kobold2d.com/x/ygMO ------
--]]


local config =
{
    KKStartupConfig = 
    {
        -- load first scene from a class with this name, or from a Lua script with this name with .lua appended
        FirstSceneClassName = "GameLayer",

        -- set the director type, and the fallback in case the first isn't available
        DirectorType = DirectorType.DisplayLink,
        DirectorTypeFallback = DirectorType.NSTimer,

        MaxFrameRate = 60,
        DisplayFPS = YES,

        EnableUserInteraction = YES,
        EnableMultiTouch = NO,

        -- Render settings
        DefaultTexturePixelFormat = TexturePixelFormat.RGBA8888,
        GLViewColorFormat = GLViewColorFormat.RGB565,
        GLViewDepthFormat = GLViewDepthFormat.DepthNone,
        GLViewMultiSampling = NO,
        GLViewNumberOfSamples = 0,

        Enable2DProjection = NO,
        EnableRetinaDisplaySupport = YES,
        EnableGLViewNodeHitTesting = NO,
        EnableStatusBar = NO,

        -- Orientation & Autorotation
        DeviceOrientation = DeviceOrientation.Portrait,
        AutorotationType = Autorotation.CCDirector,
        ShouldAutorotateToLandscapeOrientations = NO,
        ShouldAutorotateToPortraitOrientations = YES,
        AllowAutorotateOnFirstAndSecondGenerationDevices = YES,

        -- Ad setup
        EnableAdBanner = NO,
        PlaceBannerOnBottom = YES,
        LoadOnlyPortraitBanners = NO,
        LoadOnlyLandscapeBanners = NO,
        AdProviders = "iAd, AdMob",	-- comma seperated list -> "iAd, AdMob" means: use iAd if available, otherwise AdMob
        AdMobRefreshRate = 15,
        AdMobFirstAdDelay = 5,
        AdMobPublisherID = "YOUR_ADMOB_PUBLISHER_ID", -- how to get an AdMob Publisher ID: http://developer.admob.com/wiki/PublisherSetup
        AdMobTestMode = YES,

        -- Mac OS specific settings
        AutoScale = NO,
        AcceptsMouseMovedEvents = NO,
        WindowFrame = RectMake(1024-640, 768-480, 640, 480),
        EnableFullScreen = NO,
    },
}

return config

Next each GameLayer.h and GameLayer.m file needs to be edited to replace HelloWorldLayer with GameLayer. I will point out those changes as their code is explored for this lesson.

Step 6: The GameLayer.h File

Our header file is rather simple.

First be sure you change HelloWorldLayer to GameLayer on line 10.

On line 12 we define a CCSprite. This defines a 2d image game sprite for our game.

Line 13 defines the velocity for the player. This is a CGPoint object defined as “a structure that contains a point in a two-dimensional coordinate system”. In other words an x and y value.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "kobold2d.h"

@interface GameLayer : CCLayer
{
    CCSprite* player;
    CGPoint playerVelocity;
}
@end

[ad name=”Google Adsense”]
Step 7: The GameLayer.m File

First you need to rename HelloWorldLayer to GameLayer on lines 8, 10 , 20 highlighted so you do not miss them.

Constants defined here include the deceleration of the x velocity of our player at 40%. You can play with the responsiveness of of the accelerometer. The value of 6 seems to work well. Last is the fastest movement for the x velocity of our player.

/*
 * Kobold2D™ --- http://www.kobold2d.org
 *
 * Copyright (c) 2010-2011 Steffen Itterheim. 
 * Released under MIT License in Germany (LICENSE-Kobold2D.txt).
 */

#import "GameLayer.h"

@interface GameLayer (PrivateMethods)
@end

// Velocity deceleration
const float deceleration = 0.4f;
// Accelerometer sensitivity (higher = more sensitive)
const float sensitivity = 6.0f;
// Maximum velocity
const float maxVelocity = 100.0f;

@implementation GameLayer

The init method allows us to set up basic elements in the game.

Line 27 gets the accelerometer involved. Line 28 sets the filtering in a range of 0 to 1 to respond to movements of the device. Here you are looking for impact of spikes.

Line 30 and 31 adds our player sprite. The z argument is for layering. The tag argument is for developers to use to identify sprites in other Kobol2D methods without having to reference or create a CCSprite object. We do not have a use of the tag property at this point.

Lines 33-35 are for horizontally centering our player. The CCDirector class gives us access to the viewing size with the winSize property. The CCDirector is a Cocos2D class creates and handle the main Window and manages how and when to execute the Scenes.

Line 36 gives the bluish background color. The glClearColor function is clearing the graphics layer.

Lines 38 – 50 demonstrate how to add labels to the game. There are two lines of labels for this example.

The positioning takes advantage of the CCDirector screenCenter property for the first label. The second label uses screenCenter for the x property and offsets against the first label’s y position.

The ccCyan is the color for the text.

The scheduleUpdate method on line 52 calls the update method as often as possible up to the MaxFrameRate set in the config.lua file.

-(id) init
{
	if ((self = [super init]))
	{
        // Enable accelerometer input events.
		[KKInput sharedInput].accelerometerActive = YES;
		[KKInput sharedInput].acceleration.filteringFactor = 0.2f;
        // Graphic for player
        player = [CCSprite spriteWithFile:@"green_ball.png"];
		[self addChild:player z:0 tag:1];
        // Position player        
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
		float imageHeight = [player texture].contentSize.height;
		player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);
		glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
		// First line of title
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"Kobold2d Intro Tutorial" 
                                               fontName:@"Arial"  
                                               fontSize:30];
		label.position = [CCDirector sharedDirector].screenCenter;
		label.color = ccCYAN;
        [self addChild:label];
        // Second line of title
 		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Lesson 1"
                                                fontName:@"Arial"
                                                fontSize:24];
		label2.color = ccCYAN;
        label2.position = CGPointMake([CCDirector sharedDirector].screenCenter.x ,label.position.y - label.boundingBox.size.height);
        [self addChild:label2];
        // Start animation -  the update method is called.
        [self scheduleUpdate];;
	}
	return self;
}

These next lines handle the case of using automatic reference counting added with the XCode 4.2 compiler. Kobold2D has these constants to help with the alternative compiling choices.

-(void) dealloc
{
#ifndef KK_ARC_ENABLED
	[super dealloc];
#endif // KK_ARC_ENABLED
}

The acceleratePlayerWithX method adjust the player x velocity keeping it within the maximum limits.

Line 67 is degrading the velocity with the deceleration while increasing it based on the provided input coming from the accelerometer we will see in the update method.

#pragma mark Player Movement
-(void) acceleratePlayerWithX:(double)xAcceleration
{
    // Adjust velocity based on current accelerometer acceleration
    playerVelocity.x = (playerVelocity.x * deceleration) + (xAcceleration * sensitivity);
    // Limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    if (playerVelocity.x > maxVelocity)
    {
        playerVelocity.x = maxVelocity;
    }
    else if (playerVelocity.x < -maxVelocity)
    {
        playerVelocity.x = -maxVelocity;
    }
}

The update method on lines 82 and 83 uses KKInput acceleration smoothedX value to call the acceleratePlayerWithX method. The smoothing helps take out spikes in device data input.

The remaining lines 84 to 108 are positioning the player but keeping it withing the left and right boundaries.

#pragma mark update
-(void) update:(ccTime)delta
{
    // Gain access to the user input devices / states
    KKInput* input = [KKInput sharedInput];
    [self acceleratePlayerWithX:input.acceleration.smoothedX];
    // Accumulate up the playerVelocity to the player's position
    CGPoint pos = player.position;
    pos.x += playerVelocity.x;
    // The player constrainted to inside the screen
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    // Half the player image size player sprite position is the center of the image
    float imageWidthHalved = [player texture].contentSize.width * 0.5f;
    float leftBorderLimit = imageWidthHalved;
    float rightBorderLimit = screenSize.width - imageWidthHalved;
    // Hit left boundary
    if (pos.x < leftBorderLimit)
    {
        pos.x = leftBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Hit right boundary
    else if (pos.x > rightBorderLimit)
    {
        pos.x = rightBorderLimit;
        // Set velocity to zero
        playerVelocity = CGPointZero;
    }
    // Move the player
    player.position = pos; 
}  
@end

Lesson 2 ==>


Categories
Articles

HTML5 Canvas Based Animation Asteriods Space Ship Movement

This is an example of an asteroids game space ship using the HTML5 canvas and JQuery.

Demo

Download Source

The goal here was to reproduce the same example i posted for Actionscript 3 here: Actionscript 3 Animation Following the Mouse to Show Velocity at an Angle

Foundation HTML5 Animation with JavaScript
Learn More

That example as well as this is not a full asteroids game. I am posting it for the basic principles of velocity, acceleration and force. In this case the velocity, direction with speed, is computed for x, y and rotation.

JQuery is used for keystroke capture. Our space ship has a thruster for forward motion and ability to turn left or right. The up key is used for thrusting forward and the left and right keys for the turning as you should expect. The ship enters the opposite side of the canvas if it leaves the canvas.

The ship is a basic triangle and also has a flame when thrusting.

[ad name=”Google Adsense”]
You can view the full html file at the end of the post. To explain what is going on inside excerpts of the code are broken out.

The Canvas and Basic Animation Framework

This snippet defines basic constants and variables to simplify code later.

	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points.
	var canvasCenterX = CANVAS_WIDTH / 2;
	var canvasCenterY = CANVAS_HEIGHT / 2;

The canvas element is created dynamically in JavaScript and appended to the body element. I like to name the canvas drawing context canvas as you can see on line 29.

	// Create html syntax for canvas element.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	

These next few snippets represent the animation framework. I have used this in other HTML5 canvas examples.

Three framework functions are init, update and draw. The init function is the initialization of the animation framework and any components. The only components are the canvas and the ship.

For the init method you see some keys being identified for the document to process via JQuery. You could call JQuery that part of the framework.

The init method also initializes the ship object. Without the bounds set, the ship will leave the canvas and may never be seen again.

	// Initialize game
	function init()
	{
		// Set keys for play
		$(document).bind('keyup', KEY_NAME_THRUSTER, keyEvent); 
		$(document).bind('keydown', KEY_NAME_THRUSTER, keyEvent); 
		// Initialize the ship start position and boundaries.
		ship.x = canvasCenterX;
		ship.y = canvasCenterY;
		ship.bounds.left = 0;
		ship.bounds.right = CANVAS_WIDTH;
		ship.bounds.top = 0;
		ship.bounds.bottom = CANVAS_HEIGHT;
	}

Here is the key processing function. I am taking the JQuery key names and converting them to name I set in constants for more readable code.

The keyEvent function also would call animation object keyEvent methods and here we call the ship object;s keyEvent method.

	// Evaluate key input
	function keyEvent(evt)
	{
		// Animation name for the key pressed assumed to be the char name.
		var keyName = String.fromCharCode(evt.keyCode).toLowerCase();
		// Left arrow key
		if (evt.keyCode == 37)
		{
			keyName = KEY_NAME_LEFT;
		}
		// Right arrow key
		if (evt.keyCode == 39)
		{
			keyName = KEY_NAME_RIGHT;
		}
		// Up arrow key
		if (evt.keyCode == 38)
		{
			keyName = KEY_NAME_THRUSTER;	
		}
		// Call animation object keyEvent methods.
		ship.keyEvent(evt.type,keyName);
	}

The animation framework update method is called for updating model data. The draw method refreshes the view.

These two methods are called for each frame in the animation.

	// Update the model data.
	function update() 
	{
		ship.update();
	}
	// Draw the view.
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "blue";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "black";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		ship.draw();
	}

This is the heart of the animation. Once JQuery loads the animation framework is initialized and then the timer starts to fire for each frame in the animation.

	// JQuery ready - start here
	$(function() 
	{
		// Initialization.
		init();
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
	});

[ad name=”Google Adsense”]
The Ship Object
The ship object has all the needed data and logic to run in the animation framework.

This first snippet of code show the public and private properties. The underscore is indicate a private property. They should be easy to follow with the comment included or their name.

	var ship = 
	{
		shipStrokeColor: "#8ED6FF",
		shipFillColor: "#0000ff",
		flameStrokeColor: "#ff0000",
		flameFillColor: "#ff0000",
		x: 0,  			// Canvas x.
		y: 0,			// Canvas y.
		leftThrust: -5, // Number of degrees for each left turn request.
		rightThrust: 5, // Number of degrees for each right turn request.
		bounds: {top:-1,bottom:-1,left:-1,right:-1}, // Boundaries. -1 = no boundary.
		_drawStartX: 10,// Drawing starting x.
		_drawStartY: 0,	// Drawing starting y.
		_radians:0,		// Rotation value required for the canvas rotate method.
		_vx:0,			// Velocity for x.
		_vy:0,			// Velocity for y.
		_vr:0,			// Velocity for rotation.
		thrust:.2,		// Incremental force added to acceleration.
		_thrust:0,		// Force added to acceleration.
		rotation:0,		// Degrees of rotation.
		_radians:0,		// Degrees of rotation converted to radians for canvas.rotate method.

This is the ship object’s update method required by the animation framework. Here we get into trigonometry and physics. Its fairly easy to follow when you think we need to compute a velocity for the x and y properties and a rotation for direction. Velocity is direction with speed. Then there is force applied to the x and y velocity when the ship’s thruster is used.

First the direction of the ship is computed on line 57. However we added a velocity for rotation. This is the _vr property and basically represents how much we turn with the left and right keys. The leftThrust and rightThrust properties provide an external interface to _vr. We do not have acceleration with rotation, so you can assume the ship automatically compensates for turning to avoid spinning like a top.

Our x and y velocities are represented by _vx and _vy. They are adjusted by force represented by the thrust property. Lines 61 and 62 compute the acceleration based on thrust for both x and y. Then we apply the acceleration to the x and y velocities on lines 64 and 65.

The last step is to set the drawing x and y properties for the ship. Lines 67 – 70 are for the x position and you can see there is a boundary check. This is repeated for the y property on lines 72 – 75.

		update: function() // Update model data
		{
			// Increase rotation by rotation velocity
			this.rotation += this._vr;
			// Calculate rotation in radians
			this._radians = this.rotation * Math.PI / 180;
			// Acceleration for x and y velocity increases
			var ax = Math.cos(this._radians) * this._thrust;
			var ay = Math.sin(this._radians) * this._thrust;
			// Velocity for x and y
			this._vx += ax;
			this._vy += ay;
			// New x position.
			this.x += this._vx;
			// Check for x boundaries
			if (this.x > this.bounds.right && this.bounds.right >= 0) this.x = this.bounds.left;
			if (this.x < this.bounds.left && this.bounds.left >= 0) this.x = this.bounds.right;
			// New y position.
			this.y += this._vy;
			// Check for y boundaries
			if (this.y > this.bounds.bottom && this.bounds.bottom >= 0) this.y = this.bounds.top;
			if (this.y < this.bounds.top && this.bounds.top >= 0) this.y = this.bounds.bottom;
		},

The draw method is the next animation framework requirement. Here we get into the HTML5 canvas method and techniques to draw the ship with and without a thruster flame.

The procedure is simplified by drawing the ship without regard to its position on the canvas and then translating that to the canvas position. The canvas save and restore methods on lines 80 and 112 respectively give us that ability.

In between the work is done by first specifying the translation point on line 82. Then canvas rotation is done. Then we draw the ship. Line 86 determines if we need to draw a flame for thrusters firing. Finally the ship, a basic triangle, is draw.

Note the beginPath methods on lines 88 and 98. These keep the drawing fresh and distinct. Without them you would see the colors and lines overlap and each time the ship is rotated, you would see the previous ship.

		draw: function() // Draw.
		{
			// Draw off canvas
			canvas.save();
			//Translate canvas next draw position
			canvas.translate(this.x,this.y);
			// Rotate
			canvas.rotate(this._radians);
			// Thrusters on draw flame
			if (this._thrust > 0)
			{
				canvas.beginPath();
				canvas.moveTo(this._drawStartX - 12.5, this._drawStartY - 5);	
				canvas.lineTo(this._drawStartX - 25 , this._drawStartY);	
				canvas.lineTo(this._drawStartX - 12.5 , this._drawStartY + 5);	
				canvas.fillStyle = this.flameStrokeColor;
				canvas.fill();
				canvas.strokeStyle = this.flameFillColor;		
				canvas.stroke();
			}
			// Begin drawing the ship
			canvas.beginPath();
			// Start.
			canvas.moveTo(this._drawStartX, this._drawStartY);	
			// 	Ship body	
			canvas.lineTo(this._drawStartX -20 , this._drawStartY + 10);		
			canvas.lineTo(this._drawStartX -15 , this._drawStartY);		
			canvas.lineTo(this._drawStartX -20 , this._drawStartY - 10);	
			// Close.			
			canvas.lineTo(this._drawStartX, this._drawStartY);	
			canvas.fillStyle = this.shipStrokeColor;
			canvas.fill();
			canvas.strokeStyle = this.shipFillColor;		
			canvas.stroke();
			// Put it on the canvas
			canvas.restore();
	  	},

The last integrating for the ship object into the animation framework is handling the keystrokes.

First take a look back at line 18 and you see we set up constants for the key names and key types. These are translated from the keys pressed in the main framework keyEvent function on line 159 shown earlier.

	// Keyname constants
	var KEY_NAME_THRUSTER = "up";
	var KEY_NAME_LEFT = "left";
	var KEY_NAME_RIGHT = "right";
	// Constants to match JQuery Hotkeys event value type.
	var KEY_TYPE_UP = "keyup";
	var KEY_TYPE_DOWN = "keydown";

Then the ship keyEvent method needs to evaluate the key name and key type to update data. For example when the left or right key is released, the rotation velocity is set to zero on lines 136 to 140 to zero. This is our automatic adjustment for acceleration when turning. Consider it an advanced feature.

For turning left or right, the rotation velocity is set to the leftThrust and rightThrust properties in lines 126 to 135. This only happens when the key type is down.

For forward motion is covered on lines 121 to 125. Simply the internal thrust property is set with the public interface thrust property. Here we are applying force.

To stop applying forward force, lines 117 to 121 set the internal thrust back to zero.

You can see the

		keyEvent: function(keyType, keyName)
		{
			// Thruster is off
			if (keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_UP)
			{
				this._thrust = 0;
				this._vr = 0;	
			}
			// Thruster off
			else if(keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_DOWN)
			{
				this._thrust = this.thrust;
			}
			// Turning left
			if (keyName == KEY_NAME_LEFT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = this.leftThrust;
			}
			// Turning right
			if (keyName == KEY_NAME_RIGHT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = this.rightThrust;
			}
			// Stop turning
			if ((keyName == KEY_NAME_RIGHT || keyName == KEY_NAME_LEFT ) && keyType == KEY_TYPE_UP)
			{
				this._vr = 0;
			}
		}

[ad name=”Google Adsense”]
The entire HTML document for your convenience to view and copy.

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<title>HTML5 Canvas Based Animation Asteriods Space Ship Movement</title>
</head>
<body>
<div><a href="https://www.lonhosford.com/lonblog/2011/10/22/html5-canvas-bobbing-circle-animation-demonstrating-sine-wave-movement/">Article Posted By Lon Hosford - HTML5 Canvas Based Animation Asteriods Space Ship Movement</a>
<p>Use up key to move. Left and right keys to turn.</div>
<script type='text/javascript'>
	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points.
	var canvasCenterX = CANVAS_WIDTH / 2;
	var canvasCenterY = CANVAS_HEIGHT / 2;
	// Keyname constants
	var KEY_NAME_THRUSTER = "up";
	var KEY_NAME_LEFT = "left";
	var KEY_NAME_RIGHT = "right";
	// Constants to match JQuery Hotkeys event value type.
	var KEY_TYPE_UP = "keyup";
	var KEY_TYPE_DOWN = "keydown";
	// Create html syntax for canvas element.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	
	// The ship
	var ship = 
	{
		shipStrokeColor: "#8ED6FF",
		shipFillColor: "#0000ff",
		flameStrokeColor: "#ff0000",
		flameFillColor: "#ff0000",
		x: 0,  			// Canvas x.
		y: 0,			// Canvas y.
		bounds: {top:-1,bottom:-1,left:-1,right:-1}, // Boundaries. -1 = no boundary.
		_drawStartX: 10,// Drawing starting x.
		_drawStartY: 0,	// Drawing starting y.
		_radians:0,		// Rotation value required for the canvas rotate method.
		_vx:0,			// Velocity for x.
		_vy:0,			// Velocity for y.
		_vr:0,			// Velocity for rotation.
		thrust:.2,		// Incremental force added to acceleration.
		_thrust:0,		// Force added to acceleration.
		rotation:0,		// Degrees of rotation.
		_radians:0,		// Degrees of rotation converted to radians for canvas.rotate method.
		update: function() // Update model data
		{
			// Increase rotation by rotation velocity
			this.rotation += this._vr;
			// Calculate rotation in radians
			this._radians = this.rotation * Math.PI / 180;
			// Acceleration for x and y velocity increases
			var ax = Math.cos(this._radians) * this._thrust;
			var ay = Math.sin(this._radians) * this._thrust;
			// Velocity for x and y
			this._vx += ax;
			this._vy += ay;
			// New x position.
			this.x += this._vx;
			// Check for x boundaries
			if (this.x > this.bounds.right && this.bounds.right >= 0) this.x = this.bounds.left;
			if (this.x < this.bounds.left && this.bounds.left >= 0) this.x = this.bounds.right;
			// New y position.
			this.y += this._vy;
			// Check for y boundaries
			if (this.y > this.bounds.bottom && this.bounds.bottom >= 0) this.y = this.bounds.top;
			if (this.y < this.bounds.top && this.bounds.top >= 0) this.y = this.bounds.bottom;
		},
		draw: function() // Draw.
		{
			// Draw off canvas
			canvas.save();
			//Translate canvas next draw position
			canvas.translate(this.x,this.y);
			// Rotate
			canvas.rotate(this._radians);
			// Thrusters on draw flame
			//if (this._thrust > 0)
			//{
				canvas.beginPath();
				canvas.moveTo(this._drawStartX - 12.5, this._drawStartY - 5);	
				canvas.lineTo(this._drawStartX - 25 , this._drawStartY);	
				canvas.lineTo(this._drawStartX - 12.5 , this._drawStartY + 5);	
				canvas.fillStyle = this.flameStrokeColor;
				canvas.fill();
				canvas.strokeStyle = this.flameFillColor;		
				canvas.stroke();
			//}
			// Begin drawing the ship
			canvas.beginPath();
			// Start.
			canvas.moveTo(this._drawStartX, this._drawStartY);	
			// 	Ship body	
			canvas.lineTo(this._drawStartX -20 , this._drawStartY + 10);		
			canvas.lineTo(this._drawStartX -15 , this._drawStartY);		
			canvas.lineTo(this._drawStartX -20 , this._drawStartY - 10);	
			// Close.			
			canvas.lineTo(this._drawStartX, this._drawStartY);	
			canvas.fillStyle = this.shipStrokeColor;
			canvas.fill();
			canvas.strokeStyle = this.shipFillColor;		
			canvas.stroke();
			// Put it on the canvas
			canvas.restore();
	  	},
		keyEvent: function(keyType, keyName)
		{
			// Thruster is off
			if (keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_UP)
			{
				this._thrust = 0;
				this._vr = 0;	
			}
			// Thruster off
			else if(keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_DOWN)
			{
				this._thrust = this.thrust;
			}
			// Turning left
			if (keyName == KEY_NAME_LEFT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = -5;
			}
			// Turning right
			if (keyName == KEY_NAME_RIGHT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = 5;
			}
			// Stop turning
			if ((keyName == KEY_NAME_RIGHT || keyName == KEY_NAME_LEFT ) && keyType == KEY_TYPE_UP)
			{
				this._vr = 0;
			}
		}
	};	
	// Initialize game
	function init()
	{
		// Set keys for play
		$(document).bind('keyup', KEY_NAME_THRUSTER, keyEvent); 
		$(document).bind('keydown', KEY_NAME_THRUSTER, keyEvent); 
		// Initialize the ship start position and boundaries.
		ship.x = canvasCenterX;
		ship.y = canvasCenterY;
		ship.bounds.left = 0;
		ship.bounds.right = CANVAS_WIDTH;
		ship.bounds.top = 0;
		ship.bounds.bottom = CANVAS_HEIGHT;
	}
	// Evaluate key input
	function keyEvent(evt)
	{
		// Animation name for the key pressed assumed to be the char name.
		var keyName = String.fromCharCode(evt.keyCode).toLowerCase();
		// Left arrow key
		if (evt.keyCode == 37)
		{
			keyName = KEY_NAME_LEFT;
		}
		// Right arrow key
		if (evt.keyCode == 39)
		{
			keyName = KEY_NAME_RIGHT;
		}
		// Up arrow key
		if (evt.keyCode == 38)
		{
			keyName = KEY_NAME_THRUSTER;	
		}
		// Call animation object keyEvent methods.
		ship.keyEvent(evt.type,keyName);
	}
	// Update the model data.
	function update() 
	{
		ship.update();
	}
	// Draw the view.
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "blue";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "black";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		ship.draw();
	}
	// JQuery ready - start here
	$(function() 
	{
		// Initialization.
		init();
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
	});
</script>
</body>
</html>