Categories
Articles

PhoneGap Cordova Camera XCode Example


This is a bare bones example of using Cordova or Phonegap to take a photo with your IPhone or IPad camera.

Learn cocos2D Game Development
PhoneGap Essentials: Building Cross-Platform Mobile Apps

The project uses XCode 4.3.2 and was tested using iOS 5.1 on a IPhone 4. There are no frills. I did make the button large enough for IPhone human guideline standards. I also added a simple console area to display debugging messages from the Javascript.

I also stripped out most of the extra Cordova comments that were not relevant.

Each time you take a picture another file is added. This example does not provide a way to delete the files. If you remove the app from the phone, the images are deleted. You may need to study the Cordova File API for how to manage your app’s local storage.

Here are the screens for the app.

The left screen is the app before any photo has been taken. It shows the integrated debugging console with the logged message that the Cordova onDeviceReady() method was called.

The middle screen is the IPhone Camera app with a photo just taken.

The right screen shows the app with a photo taken. You also see in the integrated console messages logged from the Javascript. The second line is the method called by touching the “Take Picture” button. The final message is the callback method from the Cordova getPicture camera method for a successful photo take. In particular you can see the file path and name of the photo.

Download XCode Project Files

Step 1 – Install Cordova for XCode

Instructions for setting up your development environment are located at the PhoneGap site Getting Started with iOS.

Step 2 – Create New Cordova-based Application

Step 3 – Set XCode Project Options


Set the XCode project options as follows:

  • Product name: Here I used CordovaCamera as the project name. You can use a name of your own choosing.
  • Company identifier: Provide your own reverse domain.
  • Use Automatic Reference Counting: Uncheck

Step 4 – Choose A File Location

Once you select a file location on your computer you will have a folder structure as follows:

And in XCode you will see a project window as follows:

You may notice the project has a warning. Ignore this for now.

Step 5 – Create and Add the www Folder and Files to the Project

These are the normal setup instructions for a Cordova alias PhoneGap XCode project. You can skip this step if you are used to creating Cordova XCode projects.

The process is creating a www folder by running the app once in the IPhone simulator and then add to the project explorer.

First run the app in the IPhone simulator.

The app runs but complains of the missing index.html file.

A www folder with this file and one other js file were created on your file system as this the app launched in the simulator. Here you see them and you need to drag the www folder into the Project Explorer. Do not drag to or copy to the XCode folders outside of XCode.

Fill out the “Choose options for adding these files” dialog as follows.

  • Destination: Unchecked
  • Folders: “Created folder references for added folders” selected.
  • Add to targets: CordovaCamera checked.

And this is the final results you see in the Project Explorer window.

Run the app in the IPhone simulator one more time.

This time you will be greeted with an Alert dialog with the “Cordova is working” message.

[ad name=”Google Adsense”]

Step 7 – Code the index.html File

I removed code comments and commented code that is included in the index.html file.

Here is the full index.html file completed for your copy convenience.

<!DOCTYPE html>
<html>
  <head>
  <title>Camera</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
	<meta charset="utf-8">
	<script type="text/javascript" charset="utf-8" src="cordova-1.5.0.js"></script>
    <script type="text/javascript">
    // Debugging div on device
    var console_log;
	
	function onBodyLoad()
	{		
		document.addEventListener("deviceready", onDeviceReady, false);
	}
	
	/*  Cordova has been initialized and is ready to roll */
	function onDeviceReady()
	{
        console_log = document.getElementById('console_div');
        console_log.innerHTML += "onDeviceReady()<br/>";
	}
        
    /*  Open the device camera app */
    function capturePhoto() 
    {
        console_log.innerHTML += "capturePhoto()<br/>";
        // navigator.camera.getPicture( cameraSuccess, cameraError, [ cameraOptions ] );
        navigator.camera.getPicture(getPhoto, 
                                    onFail, 
                                    { 
                                    quality: 50,
                                    destinationType: Camera.DestinationType.FILE_URI, // // Return image file URI
                                    sourceType:Camera.PictureSourceType.CAMERA,
                                    targetWidth:120,  // Width in pixels to scale image. Aspect ratio is maintained. Required targetHeight.
                                    targetHeight:180  // Height in pixels to scale image. Aspect ratio is maintained. Required targetWidth.
                                    });
    }
   /* navigator.camera.getPicture success function */
    function getPhoto(imageData) 
    {
        var cameraImage = document.getElementById('cameraImage');
        cameraImage.src = imageData;
        console_log.innerHTML += "getPhoto() - cameraImage.src: " + cameraImage.src + "<br/>";
    }
    /* navigator.camera.getPicture fail function */
    function onFail(message) 
    {
        alert('Failed because: ' + message);
    }       
    </script>
  </head>
  <body onload="onBodyLoad()" style = "text-align:center;background-color:#ccc;padding:0px;">
      <div>
          <h1 style = "margin-bottom:0px;">Cordova Camera</h1>
          <button style = "font-size:20px;width:200px;height:44px;;margin-bottom:5px;" onclick="capturePhoto();">Take Picture</button> 
          <br>
          <img style="width:120px;height:180px;;background-color:#fff;" id="cameraImage" src="" />
          <div id="console_div" style = "text-align:left;border:1px solid black;background-color:#fff;height:150px;overflow:auto;"></div>
      </div>
  </body>
</html>

First look at the html starting on line 53. To simplify, the css is placed inline with tags versus the better approach of using an external css file.

  <body onload="onBodyLoad()" style = "text-align:center;background-color:#ccc;padding:0px;">
      <div>
          <h1 style = "margin-bottom:0px;">Cordova Camera</h1>
          <button style = "font-size:20px;width:200px;height:44px;;margin-bottom:5px;" onclick="capturePhoto();">Take Picture</button> 
          <br>
          <img style="width:120px;height:180px;;background-color:#fff;" id="cameraImage" src="" />
          <div id="console_div" style = "text-align:left;border:1px solid black;background-color:#fff;height:150px;overflow:auto;"></div>
      </div>
  </body>

Line 54 is the container div.

Line 55 provides a simple page heading.

Line 56 is the “Take Picture” button. It calls the capturePhoto() method when touched. Notice that you use the onClick handler, Cordova does the translation to touch up event for you.

Line 57 is the img tag for the photo once we take a picture.

Line 58 is a debugging div for output from your javascript. There are other debugging console solutions that may better serve your needs.

Back to the top of index.html, line 10 declares the console_log variable. It will globally reference the console_div div tag in the html. You can then see on lines 20 and 21 getting the reference from the DOM for console_div div tag.

<!DOCTYPE html>
<html>
  <head>
  <title>Camera</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
	<meta charset="utf-8">
	<script type="text/javascript" charset="utf-8" src="cordova-1.5.0.js"></script>
    <script type="text/javascript">
    // Debugging div on device
    var console_log;
	
	function onBodyLoad()
	{		
		document.addEventListener("deviceready", onDeviceReady, false);
	}
	
	/*  Cordova has been initialized and is ready to roll */
	function onDeviceReady()
	{
        console_log = document.getElementById('console_div');
        console_log.innerHTML += "onDeviceReady()<br/>";
	}

The onBodyLoad function on line 12 is called from the DOM body tag onload event. It then provides a listener to the deviceready event from Cordova.

The deviceready event calls the onDeviceReady function to get the ball rolling.

This boot strapping is all standard for Cordova apps as it has since it was called PhoneGap.

Moving down to line 24 the capturePhoto function was added and is called by the “Take Picture” button in the html.

The function logs to our makeshift console on line 27.

The key item is the Cordova Camera API being used on line 29.

You see the Cordova API is attached to the DOM navigator object.

In this case we are using the camera.getPicture method.

The first argument is the function to call for a successful use of the device camera and the second is for failed usage of the device camera.

    /*  Open the device camera app */
    function capturePhoto() 
    {
        console_log.innerHTML += "capturePhoto()<br/>";
        // navigator.camera.getPicture( cameraSuccess, cameraError, [ cameraOptions ] );
        navigator.camera.getPicture(getPhoto, 
                                    onFail, 
                                    { 
                                    quality: 50,
                                    destinationType: Camera.DestinationType.FILE_URI, // // Return image file URI
                                    sourceType:Camera.PictureSourceType.CAMERA,
                                    targetWidth:120,  // Width in pixels to scale image. Aspect ratio is maintained. Required targetHeight.
                                    targetHeight:180  // Height in pixels to scale image. Aspect ratio is maintained. Required targetWidth.
                                    });
    }

The details are in the third argument which is an array of properties called cameraOptions.

For the quality option on line 32 I choose the value of 50. This is a blatant copy from the documentation. But also I read that values over 50 may become a factor in memory issues. Something for you to research if you need a quality value over 50.

On line 33 the destinationType option takes values defined by Camera.DestinationType.

For the destinationType option I choose Camera.DestinationType.FILE_URI. The Camera.DestinationType.FILE_URI sends a file url of the picture taken to the success function. The only other choice for the destinationType property as of this writing is Camera.DestinationType.DATA_URL which returns an image as base64 encoded string to the success function.

Line 34 sets the sourceType option. It uses values from Camera.PictureSourceType. The official documentation leaves the sourceType option values up to your interpretation based on naming. The sourceType option I used is Camera.PictureSourceType.CAMERA since my plan is to use the device camera. The other two values are Camera.PictureSourceType.PHOTOLIBRARY and Camera.PictureSourceType.SAVEDPHOTOALBUM.

Finally on lines 35 and 36 I included the targetWidth and targetHeight options.

I found if you omit the targetWidth and targetHeight options you may have aspect ratio issues with displaying correct scaling. I discovered this when taking a picture using portrait and using landscape orientations in the camera app. The landscape orientation would have a distorted aspect ratio.

The html I set the img tag height and width to 120px and 180px respectively. As you see I used the same values for targetWidth and targetHeight respectively and that resolved my scaling issue. You see I copied the API documentation comments for these two options and added my own interpretation.

The last part of the javascript contains the navigator.camera.getPicture method success and fail functions named getPhoto and onFail respectively.

   /* navigator.camera.getPicture success function */
    function getPhoto(imageData) 
    {
        var cameraImage = document.getElementById('cameraImage');
        cameraImage.src = imageData;
        console_log.innerHTML += "getPhoto() - cameraImage.src: " + cameraImage.src + "<br/>";
    }
    /* navigator.camera.getPicture fail function */
    function onFail(message) 
    {
        alert('Failed because: ' + message);
    }       

For getPhoto the image url is passed in the imageData argument in line 40. All is needed is to assign the url to img tag src property. In the html the img tag has the id of cameraImage and on line 42 we get a reference from the DOM and on line 43 the url assignment to the img tag is complete.

Line 44 displays the url in our makeshift console window for observation.

The navigator.camera.getPicture fail callback is the onFail function on line 47. Line 49 displays feedback using the argument which has a message. I have not yet hit a problem and do not know how to simulate one.

Step 8 – Run the App

When you run the app, you need to use your IPhone or IPad. The camera cannot be simulated. Here is the first screen. At the beginning of the blog are all the screens for your reference.

Good luck!

[ad name=”Google Adsense”]

Categories
Articles

Tap, Move, Shake: Turning Your Game Ideas into iPhone & iPad Apps


Todd Moore’s new book, Tap, Move, Shake: Turning Your Game Ideas into iPhone & iPad Apps, is currently the best choice for getting started with writing and publishing IOS games.

A key feature is that the apps in the book are IN THE APP STORE. Never saw that trick before and I think is a standard for anyone writing IOS books. I actually followed the steps of creating an app I could download suffice it had a tad more flash in the store.

Tap, Move, Shake: Turning Your Game Ideas into iPhone & iPad Apps
Tap, Move, Shake: Turning Your Game Ideas into iPhone & iPad Apps

This is the first book I have seen in XCode/ObjectiveC that takes the submission to the App store as important a learning step as is the process of writing code. I loved the give and take App store rejection emails that provided a lot of insight for anyone who is going to submit an app the first time.

Also insightful is the special effort in giving the coder who is light on media creation great chapters on creating graphics and sound. Links provided are well researched.

On the wish list for me was not to dismiss ARC (Automatic Reference Counting) at the onset. Gee the book kinda assumes the beginner and ARC leans to that. But I suspect a case of examples completed at a time Apple was busy upsetting the cart for writers in progress.

The chapters are extremely well thought out especially the progression of development with the sections in each chapter.

I think you need a basic skill in XCode and Objective C to follow the book. XCode is introduced nicely for beginners and Objective C is learned more by example and less by explanation. I could not have solved some bugs without a basic skill in Objective C. The architecture of IOS app is explained well. I loved the clarity of explaining the bootstrap of an IOS app. I finally got it!

The book covers EVERYTHING you need from setting up for development, coding, basic testing, resource creation, app submission and even app marketing. All at a very clear to the point approach. The book examples make you feel you are starting at the beginning because they are from the gaming industry beginnings tuned to the phone.

This is 254 page book that has a good number of images taking up pages. Compared to 3 and 4 inch opus magnum IOS books out there that are OMGs difficult to hold open on the desk or in a lounge chair, Todd gets a great deal done as a focused writer. Cutting content is key. I rather buy more books than have big monsters.

Want to get started in IOS gaming without a gaming engine or better understand your gaming engine, then do this book.

[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

XCode 4 IPhone Mountains of the USA Tutorial: Lesson 9 – Add Annotation to MapView


<== Lesson 8 || Overview ||

This lesson adds an annotation and pin on the map to better show the location of the mountain.

Detail View with MapView Annotation

MapAnnotation class is all that you need to get a pin on the map with an annotation above it.

However, you may want the annotation and pin to “drop in” or you might want to control the pin color. This requires a MKMapViewDelegate class and the viewForAnnotation method. In this method a MKPinAnnotationView object is created to embellish the annotation with more functionality as well as the “drop in” effect. The DetailViewController class will serve as the MKMapViewDelegate.

As you proceed through the steps, the code items not needed to “just show an annotation and pin” are identified for you so you can try an example without the special drop in effect. To summarize that approach in advance, you do not need to add the MKMapViewDelegate protocol to your DetailViewController.h file, you exclude the [mapView setDelegate:self]; line from the viewWillAppear method in the DetailViewContoller.m files and you will not need the viewForAnnotation method added in step 3.

Source Download

  1. Starting XCode 4 Project. This is the lesson 8 project completed.
  2. PHP and CSV Files. Script to read data file and selects by elevation and returns XML. See Lesson 2.
  3. Completed XCode 4 Project

[ad name=”Google Adsense”]

Step 1: DetailViewController.h – Add the MKMapViewDelegate Protocol
Download and uncompress the Starting XCode Project file and open in XCode.

Open the DetailViewController.h class and add line 3 to include the MapAnnotation header.

On line 6 you need to edit in the MKMapViewDelegate protocol. This line is needed only if you want more functionality over the annotation such as pin color or a “drop in” effect by including the viewForAnnotation method.

#import &amp;amp;lt;UIKit/UIKit.h&amp;amp;gt;
#import &amp;amp;lt;MapKit/Mapkit.h&amp;amp;gt;
#import "MapAnnotation.h"
#import "MountainItem.h"

@interface DetailViewController : UIViewController &amp;amp;lt;MKMapViewDelegate&amp;amp;gt;{
    MKMapView *mapView;

    MountainItem *mountainItem;
}

@property (nonatomic, retain) IBOutlet MKMapView *mapView;

@property (nonatomic, retain) MountainItem *mountainItem;

@end

Step 2: DetailViewController.m – Create the Annotation
Open the DetailViewController.m file.

The first 51 lines of code are not changing but included here for online reference:

#import "DetailViewController.h"

@implementation DetailViewController
@synthesize mapView;
@synthesize mountainItem;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)dealloc
{
    [mapView dealloc];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.mapView = nil;

}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

Add the highlighed line in the next code view.

Line 68 sets this class to receive MKMapViewDelegate messages. In the last step you defined this class as MKMapViewDelegate.

Line 68 is not needed if all you want to show the annotation and pin without any special view or effects. A MKMapViewDelegate is not needed for that.

Line 70 clears all annotations from the map. You can leave this line out and the previous pins and their annotations will stay on the map. If you try some mountains in the same range or zoom way out, you will see the previous pins and when you touch the pin the annotation will appear.

Lines 71 to 73 create the annotation object, give it a title and position in the center of the view region created in the last lesson on lines 61 to 66.

The annotation is added to the map on line 75.

The annotation is selected on line 77. When you test the app, you should touch the annotation and the pin. You will see the selected and unselected state. Unselected, just a pin appears. Selected the title appears. Line 77 is achieving the selection process in code.

Line 78 is clean up.

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:NO animated:YES];
    [self setTitle:mountainItem.name];

    [mapView setMapType:MKMapTypeStandard];
    [mapView setZoomEnabled:YES];
    [mapView setScrollEnabled:YES];
    MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };
    region.center.latitude = [mountainItem.latitude doubleValue] ;
    region.center.longitude = [mountainItem.longitude doubleValue] ;
    region.span.longitudeDelta = 1.0f;
    region.span.latitudeDelta = 1.0f;
    [mapView setRegion:region animated:YES];

    [mapView setDelegate:self];

    [self.mapView removeAnnotations:self.mapView.annotations];
    MapAnnotation *ann = [[MapAnnotation alloc] init];
    ann.title = mountainItem.name;
    ann.coordinate = region.center;

    [mapView addAnnotation:ann];

    [mapView selectAnnotation:ann animated:YES];
    [ann release];
}

[ad name=”Google Adsense”]

Step 3: DetailViewController.m – Add Annotation View and Drop In Effect

The viewForAnnotation method is called to develop the view for an annotation.

This implementation is simple because we only have one annotation. The method is called for all annotations. With multiple annotation, you may need to treat certain annotations differently, so you might need to identify which is calling this method. As well if you are using a unpredictable number of annotations and some annotations persist, this is a place to determine how to reuse MKPinAnnotationView objects. All of this is beyond the scope of the tutorial, but this explains why you will see more lines of code in other examples and why this example appears simpler.

Line 83 creates the MKPinAnnotationView annView object. You are going to reuse the same MKPinAnnotationView, so the identifier MyPin is created to help that happen.

The pin “drop in” effect occurs because of line 84.

Line 85 is needed to show the pin annotation title. There are more items that can be added to the annotation such as a subtitle and left and right views.

The pin color is set on line 87. You can use MKPinAnnotationColorGreen and MKPinAnnotationColorPurple. These and MKPinAnnotationColorRed were added in IOS 3.0.

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id &amp;amp;lt;MKAnnotation&amp;amp;gt;) annotation

{
    MKPinAnnotationView *annView = [[[MKPinAnnotationView alloc ] initWithAnnotation:annotation reuseIdentifier:@"MyPin"] autorelease] ;
    annView.animatesDrop=TRUE;
    annView.canShowCallout = YES;

	annView.pinColor = MKPinAnnotationColorRed;

    return annView;
}

Try in the simulator. Tap the annotations and the pins to get the feel for them.

Categories
Articles

XCode 4 IPhone Mountains of the USA Tutorial: Lesson 8 – Add MapView


<== Lesson 7 || Overview || Lesson 9 ==>

Now you have the second view added in the last lesson, you can add a map to show where the mountain is located. You can use the longitude and latitude that are part of the MountainItem data passed to the second view.

Detail View Screen with MapView

To work with a map, you need to include the MapKit framework. There are frameworks included automatically with a new IOS XCode project. They are UIKit, Foundation and CoreGraphics. You have probably noticed the import statement for UIKit in the header for MainViewController and DetailViewController.

In XCode 4 a group called Frameworks contains the frameworks you include with your project.

Typical Frameworks Group

It is a good practice to assure any new ones you add are in that group for easy reference.

Source Download

  1. Starting XCode 4 Project. This is the lesson 7 project completed.
  2. PHP and CSV Files. Script to read data file and selects by elevation and returns XML. See Lesson 2.
  3. Completed XCode 4 Project

[ad name=”Google Adsense”]

Step 1: Add the MapKit Framework
Download and uncompress the Starting XCode Project file and open in XCode.

In the project explorer select the top node.

Select Project Node

In the center of XCode follow these steps:

Steps to Open Frameworks and Libraries Dialog

Type map into the search text field and you should see the list narrow down to the MapKit.framework choice and then select the Add button.

Frameworks and Libraries Dialog

Locate the MapKit.framework file in the project explorer and drag it into the Frameworks group. You could leave MapKit.framework where it was grouped, but keeping the frameworks together makes sense.

Drag MapKit Framework To Frameworks Group

Step 2: DetailViewController.h – Replace TextView with MapView

Open the DetailViewController.h file in the project explorer.

You are going to replace the UITextView with a MKMapView. Lines that are being replaced are included here for convenience.

#import &amp;lt;UIKit/UIKit.h&amp;gt;
#import "MountainItem.h"

@interface DetailViewController : UIViewController {
    UITextView *mountainInfoTextView;

    MountainItem *mountainItem;
}
@property (nonatomic, retain) IBOutlet UITextView *mountainInfoTextView;

@property (nonatomic, retain) MountainItem *mountainItem;
@end

Line 2 below show the inclusion of MapKit in this class.

Lines 6 and 11 give you an IBOutlet to the MapView you add later to the DetailViewController.xib.

#import &amp;lt;UIKit/UIKit.h&amp;gt;
#import &amp;lt;MapKit/Mapkit.h&amp;gt;
#import "MountainItem.h"

@interface DetailViewController : UIViewController &amp;lt;MKMapViewDelegate&amp;gt;{
    MKMapView *mapView;

    MountainItem *mountainItem;
}

@property (nonatomic, retain) IBOutlet MKMapView *mapView;

@property (nonatomic, retain) MountainItem *mountainItem;

@end

Step 3: DetailViewController.m – Add mapView Property to the Implementation

Open the DetailViewController.m file in the project explorer.

These are the standard lines to include the mapView property to the implementation file.

You need to remove the mountainInfoTextView property and for convenience the lines are shown here.

#import "DetailViewController.h"

@implementation DetailViewController
@synthesize mountainInfoTextView;
@synthesize mountainItem;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)dealloc
{
    [mountainInfoTextView dealloc];
    [mountainItem dealloc];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.mountainInfoTextView = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

Include the lines below to add the mapView object.

#import "DetailViewController.h"

@implementation DetailViewController
@synthesize mapView;
@synthesize mountainItem;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)dealloc
{
    [mapView dealloc];
    [mountainItem dealloc];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.mapView = nil;

}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

[ad name=”Google Adsense”]
Step 4: DetailViewController.m – Show the Mountain’s Location on the Map

Remove line 58 because you are replacing the mountainInfoTextView with the coding for the mapView.

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self setTitle:mountainItem.name];

    mountainInfoTextView.text = [NSString stringWithFormat: @"Name: %@\nElevation: %f\nLatitude: %f\nLongitude: %f",mountainItem.name, [mountainItem.elevation floatValue],  [mountainItem.latitude floatValue], [mountainItem.longitude floatValue]]; 

}
@end

Add the highlighted lines in the next code view.

The standard map type is set on line 59. You could also use MKMapTypeSatellite and MKMapTypeHybrid if you want to experiment.

Lines 60 and 61 are self explanatory.

The zoom and center point of the map is done on lines 62 to 67. Then center point is the longitude and latitude data passed into the class in the mountainItem object.

The span is an offset for the zoom. You can experiment with positive float numbers such as .01 which will zoom in tight. Because many of the mountains do not have any feature in standard map view, the value of 1 seems to show enough map details to keep from being disoriented.

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:NO animated:YES];
    [self setTitle:mountainItem.name];

    [mapView setMapType:MKMapTypeStandard];
    [mapView setZoomEnabled:YES];
    [mapView setScrollEnabled:YES];
    MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };
    region.center.latitude = [mountainItem.latitude doubleValue] ;
    region.center.longitude = [mountainItem.longitude doubleValue] ;
    region.span.longitudeDelta = 1.0f;
    region.span.latitudeDelta = 1.0f;
    [mapView setRegion:region animated:YES];

}

@end

Step 5: DetailViewController.xib – Replace the TextView with the MapView

Open the DetailViewController.xib.

Delete the TextView in the design window.

Delete TextView

Drag the MapView from the Objects panel to the view in the design window and fill the view.

Drag MapView

With the MapView still selected open the Connections Inspector and drag a “New Referencing Outlet” to the File’s Owner and when you release the mouse select mapView, the name your are using in our DetailViewController to control the MapView. The Connections Inspector should look as follows:

Connections Inspector For MapView

Then finally as a double check select the File’s Owner and the Connections Inspector should appear as follows:

Connections Inspector for File’s Owner

You should be good to check this out in the Simulator. Remember you need to hold down the Option button and drag the mouse to get the multi touch over the map for zooming. Scroll the map by dragging the mouse.

<== Lesson 7 || Overview || Lesson 9 ==>