Monday 15 August 2011

How to make a local leaderboard

If your not up for implementing Game Center into your app, you can easily just create a simple local score system using plists.

Create a plist like this
The way I have my levels formated is, there are 3 "cups"; easy, medium and hard. Each cup has a certain amount of levels in them, and then each level will have its scores.

To read/write to the plist, I created a class which handles everything for us. Here is the code
@implementation scoreAndSaveClass

NSString* pathOfPlist;

+(void) getPathOfScoreList{
    
    NSError *error;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; 
    pathOfPlist = [documentsDirectory stringByAppendingPathComponent:@"scoresFile.plist"]; 
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    if (![fileManager fileExistsAtPath: pathOfPlist]) 
    {
        NSString * bundle = [[NSBundle mainBundle] pathForResource: @"scoresFile" ofType:@"plist"];
        
        [fileManager copyItemAtPath:bundle toPath: pathOfPlist error:&error]; 
    }
}

+(void) setLevelScore:(float) inScore andTheDifficulty:(NSString*) difficulty andTheLevel:(int) level{
     //get path of plist
    [self getPathOfScoreList];
    
    NSMutableDictionary * indexOfPlist = [[[NSMutableDictionary alloc] initWithContentsOfFile:pathOfPlist] autorelease];

    //gets dictionary of levels from this difficulty
    NSMutableDictionary * indexOfLevels = [indexOfPlist objectForKey:difficulty];
    
    //gets the array of scores for this level
    NSMutableArray *arrayOfLevelScores = [indexOfLevels objectForKey:[NSString stringWithFormat:@"Level%i", level]];

    //adds the users newest score to the array
    [arrayOfLevelScores addObject:[NSNumber numberWithFloat:inScore]];
    
    //updates the cup's dictionary to be the array with the latest values
    [indexOfLevels setValue:arrayOfLevelScores forKey:difficulty];

    //saves changes
    [indexOfPlist writeToFile: pathOfPlist atomically:YES];

    
}

+(NSArray*) getSortedScore:(NSString*) difficulty andTheLevel:(int) level{
    //get path of plist
    [self getPathOfScoreList];
    
    NSMutableDictionary * indexOfPlist = [[[NSMutableDictionary alloc] initWithContentsOfFile:pathOfPlist] autorelease];

    //gets the array of score depends on what cup is selected
    NSMutableDictionary * indexOfLevels = [indexOfPlist objectForKey:difficulty];
    
    //gets the array of score depends on what level is selected
    NSMutableArray *arrayOfLevelScores = [indexOfLevels objectForKey:[NSString stringWithFormat:@"Level%i", level]];
    
    //this sorted the array in ascending order
    NSSortDescriptor * temp= [[NSSortDescriptor alloc] initWithKey:@"doubleValue" ascending:NO];
    [arrayOfLevelScores sortUsingDescriptors:[NSArray arrayWithObject:temp]];
        
    return arrayOfLevelScores;
    
@end

 
To read/write to the plist, you call the required function. At the start of each function it calls the "get path" function which will set the string to be the location of the plist. Because each plist is structured as dictionary, we use dictionaries to browse through the plist.

Whenever you call the function to read/write a score you must pass a string into the function, which you will set to be either "Easy", "Medium" or "Hard" (at least it is for me, depends on how you structured your plist).

To display the latest score I created just a simple loop that loops the top 10 score, or if there is less than 10 scores, it lists all the scores it can.

int maxNumOfScores = MIN([scoreArray count], 15);
        
        for (int i = 0; i <maxNumOfScores; i++) {
         
            CCLabelTTF * scoreLabel = [CCLabelTTF labelWithString:[NSString stringWithFormat:@"Score %i", [[scoreArray objectAtIndex:i] intValue]] fontName:@"Marker Felt" fontSize:20];
            [self addChild:scoreLabel];
            scoreLabel.position = ccp(240, (280 - i*20));
            
        }

Wednesday 10 August 2011

Updated, how to scroll and pinch-zoom a tiled map

So, silly me, I didn't realise the original code I posted earlier in this blog doesn't work for big maps. This is because in big maps, the layer slides down to the bottom left, which would ruin the boundaries.

 So instead I came up with a method where you get the top right and the bottom left corners of the tiled map, finds their co-ordinates, then converts it to world space.

From here, the boundaries are drawn up where in the code below, I have taken into account a 40x320 UI bar running up the right side of the screen. My code is tried and tested on tiled maps of varying size, hope it will help someone.

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

{
    
    
    if (hasPickedButton == NO) {
        
        // "allObjects" returns an NSArray of all the objects in the set
        NSArray *touchArray = [touches allObjects];
        
        
        
        // Only run the following code if there is more than one touch, wanting to resize
        if ([touchArray count] > 1)
        {
            
            
            // We're going to track the first two touches (i.e. first two fingers)
            // Create "UITouch" objects representing each touch
            UITouch *fingerOne = [touchArray objectAtIndex:0];
            UITouch *fingerTwo = [touchArray objectAtIndex:1];
            
            // Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
            CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
            CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];
            
            CGPoint pointOnePrev = [fingerOne previousLocationInView:[fingerOne view]];
            CGPoint pointTwoPrev = [fingerTwo previousLocationInView:[fingerTwo view]];
            
            // The touch points are always in "portrait" coordinates
            // You will need to convert them if in landscape (which we are)
            pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
            pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];
            
            pointOnePrev = [[CCDirector sharedDirector] convertToGL:pointOnePrev];
            pointTwoPrev = [[CCDirector sharedDirector] convertToGL:pointTwoPrev];
            
            CGPoint point1Map = [mapLayer convertToNodeSpace:pointOne];
            CGPoint point2Map = [mapLayer convertToNodeSpace:pointTwo];
            CGPoint midPoint = ccpMidpoint(point1Map, point2Map);
            
            mapLayer.anchorPoint = ccp(midPoint.x/mapWidth, midPoint.y/mapWidth);
            
            
            //this statement finds the difference in the pinches that the player is doing now, and the pinch the player was doing
            
            //if they were pinching in, subtract the distance of the pinch from the distance of the previous pinch
            
            if ((sqrt(pow(pointOnePrev.x - pointTwoPrev.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) > sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) {
                distance -= sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))/100;
                distance = fabsf(distance);
            }
            //otherwise they are pinching out and so add on more to the distance
            else  if ((sqrt(pow(pointOnePrev.x - pointTwoPrev.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) < sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) {
                distance += sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))/100;
                distance = fabsf(distance);
            }
            
            // Get the distance between the touch points
            
            // Scale the distance based on the overall width of the screen (multiplied by a constant, just for effect)
            mapScale = distance / [CCDirector sharedDirector].winSize.width * 3;
            
            
            //we don't want the player to be able to zoom out to the point where the map is smaller than the screen
            float minFactor = MAX(440.0/mapWidth, 320.0/mapHeight);
            
            
            if (mapScale <= minFactor) {
                //set this here, so if the player keeps zooming out out out, even though it doesn't change the scale factor, the distance is always decremented. this line keeps it to the last distance that had any effect, stops jumping camera
                distance = lastGoodDistance;
            }
            //we don't want him zooming in larger than the original image
            
            if (mapScale>1.0) {
                distance = lastGoodDistance;
            }
            
            if ((mapScale< 1.0) && (mapScale > minFactor)){
                
                //set the last distance which had any affect to be the current one
                
                lastGoodDistance= distance;

                //allow the zoom to take place, but don't permentatly apply it just yet, we want to make sure the final scaled map is within the bounds of the view before moving it (otherwise it appears shakey)
                float oldscale = mapLayer.scale;
                [mapLayer setScale:mapScale];
                          //get the co-ordinates of the BL and TR tiles for this scale in wordspace, then put scale back to normal for now
                
                CGSize mapTiles = [levelMap mapSize];
                float y = mapTiles.height;
                float x = mapTiles.width;
                
                CCSprite * grid = [mapBgLayer tileAt:ccp(0, y-1)];
                CGPoint bottomLeftGrid = grid.position;
                bottomLeftGrid = [levelMap convertToWorldSpace:bottomLeftGrid];
                
                grid = [mapBgLayer tileAt:ccp(x-1, 0)];
                CGPoint topRightGrid = grid.position;
                
                topRightGrid = [levelMap convertToWorldSpace:topRightGrid];
                
                          //restore the old mapscale until we sort out the position
                mapLayer.scale = oldscale;
                
                //check to see if the layer is drifting off the screen while the zoom is taking place
                
                CGPoint difference = ccp(0, 0);
                
                float degreeOfAwkard = (1- mapScale)*10;
                
                float awkardOffsetX = degreeOfAwkard *3.51;
                float awkardOffsetY = degreeOfAwkard *3.5;
                
                if (bottomLeftGrid.x > 0) {
                    
                    float offBy = 0 - bottomLeftGrid.x;
                    difference.x += offBy;
                    
                }
                
                if (bottomLeftGrid.y > 0) {
                    float offBy = 0 - bottomLeftGrid.y;
                    difference.y += offBy;
                    
                }
                
                if (topRightGrid.x < 400 + awkardOffsetX) {
                    
                    float offBy = 400 - topRightGrid.x + awkardOffsetX;
                    difference.x += offBy;
                }
                
                if (topRightGrid.y < 280 + awkardOffsetY) {
                    float offBy = 280 - topRightGrid.y + awkardOffsetY;
                    difference.y += offBy;
                    
                }
                
                
                mapLayer.position = ccpAdd(mapLayer.position, difference);
                //      [mapLayer runAction:[CCMoveBy actionWithDuration:0.05 position:difference]];
                mapLayer.scale = mapScale;
                
                
            }
            
        }  
        
        
        if ([touchArray count] ==1){
            
            CGSize mapTiles = [levelMap mapSize];
            float y = mapTiles.height;
            float x = mapTiles.width;
            
            CCSprite * grid = [mapBgLayer tileAt:ccp(0, y-1)];
            CGPoint bottomLeftGrid = grid.position;
            bottomLeftGrid = [levelMap convertToWorldSpace:bottomLeftGrid];
            
            grid = [mapBgLayer tileAt:ccp(x-1, 0)];
            CGPoint topRightGrid = grid.position;
            
            topRightGrid = [levelMap convertToWorldSpace:topRightGrid];
            
            
            UITouch * fingerOne = [touchArray objectAtIndex:0];
            
            CGPoint newTouchLocation = [fingerOne locationInView:[fingerOne view]];
            newTouchLocation = [[CCDirector sharedDirector] convertToGL:newTouchLocation];
            
            CGPoint oldTouchLocation = [fingerOne previousLocationInView:fingerOne.view];
            oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation];
            
            //get the difference in the finger touches when the player was dragging
            CGPoint difference = ccpSub(newTouchLocation, oldTouchLocation);
            
            //adds this on to the layers current position, effectively moving it
            CGPoint bottomLeft = ccpAdd(mapLayer.position, difference);
            
            //check to see if the map edges of the map are showing in the screen, if so bringing them back on the view so no black space can be seen
            
            bottomLeft = ccpAdd(mapLayer.position, difference);
            
            bottomLeftGrid = ccpAdd(bottomLeftGrid, difference);
            
            
            topRightGrid = ccpAdd(topRightGrid, difference);
            //don't ask why, but the boundary changes by about 3pxls per 0.1 scale
            float degreeOfAwkard = (1- mapScale)*10;
            
            float awkardOffsetX = degreeOfAwkard *3.51;
            float awkardOffsetY = degreeOfAwkard *3.5;
            
            
            if (bottomLeftGrid.x > 0) {
                //finds how much the map is off the boundary
                float offBy = 0 - bottomLeftGrid.x;
                difference.x += offBy;
                
            }
            
            if (bottomLeftGrid.y > 0) {
                float offBy = 0 - bottomLeftGrid.y;
                difference.y += offBy;
                
            }
            
            if (topRightGrid.x < 400 + awkardOffsetX) {
                
                float offBy = 400 - topRightGrid.x + awkardOffsetX;
                difference.x += offBy;
            }
            
            if (topRightGrid.y < 280 + awkardOffsetY) {
                float offBy = 280 - topRightGrid.y + awkardOffsetY;
                difference.y += offBy;
                
            }
            //adjusts the map so it is within the boundary just
            mapLayer.position = ccpAdd(mapLayer.position, difference);
            
        }
        
    }
}

Tuesday 9 August 2011

How to easily keep track of save games

This is just a simple method on how to create local save files for a level system. For the app I'm making, there are several levels and the player only unlocks the next level if the previous one is complete. To do this I'm just using plists.

Create plists with this sort of structure. There will be 1 plist for saves.




Now create a class which will be used to manage this data.

//
//  scoreAndSaveClass.h
//  Ant Run
//
//  Created by Peter Lockhart on 05/08/2011.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>


@interface scoreAndSaveClass : NSObject {

}
+(void) getPathOfSaveList;
+(BOOL) checkCompletedLevels:(int) thisIndex andTheDifficulty:(NSString*) difficulty;
+(void) setLevelCompleted:(int) thisIndex andTheDifficulty:(NSString*) difficulty;


@end

//
//  scoreAndSaveClass.m
//  Ant Run
//
//  Created by Peter Lockhart on 05/08/2011.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "scoreAndSaveClass.h"


@implementation scoreAndSaveClass

NSString* pathOfPlist;

+(void) getPathOfSaveList{
    
    NSError *error;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; 
    pathOfPlist = [documentsDirectory stringByAppendingPathComponent:@"saveFiles.plist"]; 
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    if (![fileManager fileExistsAtPath: pathOfPlist]) 
    {
        NSString * bundle = [[NSBundle mainBundle] pathForResource: @"saveFiles" ofType:@"plist"];
        
        [fileManager copyItemAtPath:bundle toPath: pathOfPlist error:&error]; 
    }
}


+(BOOL) checkCompletedLevels:(int) thisIndex andTheDifficulty:(NSString*) difficulty{
    //set the string  of plathofPlist to be that of the level plist
    [self getPathOfSaveList];
    
    //this is the root of the plist
    NSMutableDictionary * indexOfPlist = [[[NSMutableDictionary alloc] initWithContentsOfFile:pathOfPlist] autorelease];
    
    //get the array under the level category (defined by the string "difficulty", which is set when the method is called)
    NSMutableArray *arrayOfLevelSaves = [indexOfPlist objectForKey:difficulty];
    //get the boolean for the level that needs checked
    BOOL test = [[arrayOfLevelSaves objectAtIndex:thisIndex] boolValue];
    
    //check if level is completed
    if (test == YES) {
        return YES;
    }
    return NO;
}

+(void) setLevelCompleted:(int) thisIndex andTheDifficulty:(NSString*) difficulty{
    [self getPathOfSaveList];
    
    NSMutableDictionary * indexOfPlist = [[[NSMutableDictionary alloc] initWithContentsOfFile:pathOfPlist] autorelease];
    
    NSMutableArray *arrayOfLevelSaves = [indexOfPlist objectForKey:difficulty];
    
    //create a temp boolean to replace the original value of the array of saves
    BOOL done = YES;
    //make the level specified be completed
    [arrayOfLevelSaves replaceObjectAtIndex:thisIndex withObject:[NSNumber numberWithBool:done]];

    //update the dictionary in the plist so that its array of save files is the newest version
    [indexOfPlist setValue:arrayOfLevelSaves forKey:difficulty];
    
    //write the plist to memory
    [indexOfPlist writeToFile: pathOfPlist atomically:YES];
    
}
@end

Don't be put off by it, its really not too bad.

I've declared a string at the top of the .m file, which will hold the path of the save file. This will change depending on if you are accessing the save file plist or the scores plist (which will be in a later tutorial). The getPathOfSaveList function will not get called by you, so don't worry about remembering to call it (saves headache when we implement local scores later).

The code in the function is pretty self explanatory. It searches in the iphones memory to find the specified file and if it doesn't exists then it creates the file (but it should always exist because we created the plist in xcode in the project file).


 

Friday 5 August 2011

How to sort an array in ascending/descending order, easily!

So many people create their own functions to loop through an array and comparing the values to each other and sorting it that way, which takes far too much effort for me! No one seems to know about this on google, cause I searched for ages trying to find an easy way to do this. Simply do;

NSMutableArray *arrayOfLevelSaves = //your array full of numbers;
    
NSSortDescriptor * temp= [[NSSortDescriptor alloc] initWithKey:@"doubleValue" ascending:NO];
[arrayOfLevelSaves sortUsingDescriptors:[NSArray arrayWithObject:temp]];

Friday 29 July 2011

Just beware of...

..your textures not being included in your tmxtiledmap whenever you are using cocos2d. I was using the java and it had the option to included all tilesets in the tmx file, however this does not work and you need to have the .png of your tileset in the xcode resources with the map itself. 

Also, if your tileset files weren't part of the same root folder as the map when you were making it, it won't know where to look for the right file. To correct this, whenever you import the tmx map into xcode resources, click on it and it should show xml code and edit the 4th line to read;

  <image source="yourspritesheet" width="NUMBER" height="NUMBER"/>.  

You would get the error:
exec_arithmetic error on rect.origin.x = (gid % max_x) * (tileSize_.width + spacing_) + margin_;

Thursday 28 July 2011

How to scroll and zoom in/out of a map using motions

So for my first cocos2d game I'm making a tiled map based game. I wanted the player to be able to zoom in and out freely, and to drag and move the level around the camera if it was too big to fit all on the screen.

There was little google help on this, and after days of torture I've got the code working for it, which I hope will help someone.

The code is fully commented and hopefully people can read through it and understand it. The player can pinch-zoom and scroll across the map, as long as the map isn't smaller than the actual screen (otherwise the map would show black spacing).

Everything is just maths to do with the scaling of the original size of the layer and the scaled layer. However when the layer is fully scaled out, there is a slight off set, which I think is just down to fliddly bits in xcode and it can't be helped. To solve this, I did an offset which should fix the offset and make the level have correct boundries: if (bottomLeft.x + mapWidth*mapScale +differenceX/2 < 440+ (1-mapScale)*33) {

where "+ (1-mapScale)*33)" is the fiddly bit.
http://twitter.com/#!/ant_run


//
//
//  Created by petemyster on 21/07/2011.
//  Copyright petemyster. All rights reserved.
//

// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"

@interface easyLevel1 : CCLayer
{
    //map
    CCTMXTiledMap * testMap;
    CCTMXLayer * testLayer;
    CCLayer * mapLayer;
    //ui
    CCLayer * UILayer;
    CCSprite* UIBackground;
    
    //declarations for return to main menu
    CCMenu * returnMenu;
    CCMenuItemImage * returnMenuImage;
    
}
+(CCScene *) scene;
-(void) goBackToMain;
-(void) update:(ccTime) dt;

@property (nonatomic, retain) CCSprite * testSprite;

//map
@property (nonatomic, retain) CCTMXTiledMap * testMap;
@property (nonatomic, retain) CCTMXLayer * testLayer;
@property (nonatomic, retain) CCLayer * mapLayer;
@property int mapWidth;
@property int mapHeight;
@property float mapScale;
@property float distance;
@property float lastGoodDistance;
//UI
@property (nonatomic, retain) CCLayer * UILayer;
@property (nonatomic, retain) CCSprite* UIBackground;

//properties for the return to main menu screen
@property (nonatomic, retain) CCMenu * returnMenu;
@property (nonatomic, retain) CCMenuItemImage * returnMenuImage;

@end


// Import the interfaces
#import "easyLevel1.h"
#import "easyLevelSelect.h"
@implementation easyLevel1
@synthesize testMap,mapLayer, testLayer;
@synthesize  UILayer, UIBackground;
@synthesize returnMenu, returnMenuImage;
@synthesize mapWidth, mapHeight, mapScale, distance, lastGoodDistance;
+(CCScene *) scene;
{
    // 'scene' is an autorelease object.
    CCScene *scene = [CCScene node];
    
    // 'layer' is an autorelease object.
    easyLevel1 *layer = [easyLevel1 node];
    
    // add layer as a child to scene
    [scene addChild: layer];
    
    // return the scene
    return scene;
}
// on "init" you need to initialize your instance
-(id) init
{
    // always call "super" init
    // Apple recommends to re-assign "self" with the "super" return value
    if( (self=[super init])) {
        
        self.isTouchEnabled = YES;
        
        //declare the map and add to a layer
        mapLayer = [CCLayer node];
        testMap = [CCTMXTiledMap tiledMapWithTMXFile:@"testMap.tmx"];
        testLayer = [testMap layerNamed:@"Test Layer 1"];
        [mapLayer addChild:testMap];
        [self addChild:mapLayer];
        
        //get map size in pixels
        mapHeight = testMap.contentSize.height;
        mapWidth = testMap.contentSize.width;
        //set up defaults
        mapLayer.position= ccp(0, 0);
        //this value works well for the calculation later, trial and error really
        distance = 150;
        lastGoodDistance = 150;
        mapScale = 1;
        
        //set up a layer for the UI
        UILayer = [CCLayer node];
        UIBackground = [CCSprite spriteWithFile:@"UIbackgroundImage.png"];
        NSLog(@"%@", UIBackground);
        UIBackground.position= ccp(460, 160);
        [UILayer addChild:UIBackground];
        
        //set up labels for return menu
        returnMenuImage = [CCMenuItemImage itemFromNormalImage:@"backNavigationArrow.png" selectedImage:@"backNavigationArrowPressed.png" target:self selector:@selector(goBackToMain)];
        returnMenu = [CCMenu menuWithItems:returnMenuImage, nil];
        [self addChild:UILayer];
        [UILayer addChild:returnMenu];
        [returnMenu alignItemsVerticallyWithPadding:1];
        [returnMenu setPosition:ccp(460, 300)];
        
    }
    return self;
}
-(void) goBackToMain{
    
    [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:0.5 scene:[easyLevelSelect scene:gameMode]]];
    
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
// Override the "ccTouchesMoved:withEvent:" method to add your own logic
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    //finds the difference in the original map and the scaled map
    int differenceX = mapWidth - (mapWidth*mapScale);
    int differenceY = mapHeight - (mapHeight*mapScale);    
    
    // This method is passed an NSSet of touches called (of course) "touches"
    // "allObjects" returns an NSArray of all the objects in the set
    NSArray *touchArray = [touches allObjects];
    
    // Only run the following code if there is more than one touch, wanting to resize
    if ([touchArray count] > 1)
    {
        // We're going to track the first two touches (i.e. first two fingers)
        // Create "UITouch" objects representing each touch
        UITouch *fingerOne = [touchArray objectAtIndex:0];
        UITouch *fingerTwo = [touchArray objectAtIndex:1];
        
        // Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
        CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
        CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];
        
        CGPoint pointOnePrev = [fingerOne previousLocationInView:[fingerOne view]];
        CGPoint pointTwoPrev = [fingerTwo previousLocationInView:[fingerTwo view]];
        
        // The touch points are always in "portrait" coordinates
        // You will need to convert them if in landscape (which we are)
        pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
        pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];
        
        pointOnePrev = [[CCDirector sharedDirector] convertToGL:pointOnePrev];
        pointTwoPrev = [[CCDirector sharedDirector] convertToGL:pointTwoPrev];
        
        //this statement finds the difference in the pinches that the player is doing now, and the pinch the player was doing
        
        //if they were pinching in, subtract the distance of the pinch from the distance of the previous pinch
        
        if ((sqrt(pow(pointOnePrev.x - pointTwoPrev.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) > sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) {
            distance -= sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))/100;
            distance = fabsf(distance);
        }
        //otherwise they are pinching out and so add on more to the distance
        else  if ((sqrt(pow(pointOnePrev.x - pointTwoPrev.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) < sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))) {
            distance += sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0))/100;
            distance = fabsf(distance);
        }
        
        // Get the distance between the touch points
        
        // Scale the distance based on the overall width of the screen (multiplied by a constant, just for effect)
        mapScale = distance / [CCDirector sharedDirector].winSize.width * 3;
        
        //we don't want the player to be able to zoom out to the point where the map is smaller than the screen
        float minFactor = MAX(440.0/mapWidth, 320.0/mapHeight);
        
        if (mapScale < minFactor) {
            mapScale = minFactor;
            //set this here, so if the player keeps zooming out out out, even though it doesn't change the scale factor, the distance is always decremented. this line keeps it to the last distance that had any effect, stops jumping camera
            distance = lastGoodDistance;
        }
        //we don't want him zooming in larger than the original image
        if (mapScale> 1.0){
            mapScale = 1.0;
            distance = lastGoodDistance;
            
        }    
        
        //set the last distance which had any affect to be the current one
        lastGoodDistance= distance;
        //allow the zoom to take place
        mapLayer.scale=mapScale;
        
        CGPoint bottomLeft = mapLayer.position;
        
        //check to see if the layer is drifting off the screen while the zoom is taking place
        
        if (bottomLeft.x - differenceX/2 > 0 - (mapWidth * (1-mapScale))) {
            bottomLeft.x = differenceX/2- (mapWidth * (1-mapScale));
        }
        
        if (bottomLeft.y - differenceY/2 > 0 -(mapHeight * (1-mapScale))) {
            bottomLeft.y = differenceY/2-(mapHeight * (1-mapScale));
            
        }
        
        if (bottomLeft.x + mapWidth*mapScale +differenceX/2 < 440) {
            bottomLeft.x = -differenceX/2 - mapWidth*mapScale + 440;
            
        }
        if (bottomLeft.y + mapHeight*mapScale +differenceY/2 < 320) {
            bottomLeft.y =  -differenceY/2 - mapHeight*mapScale + 320;
            
        }
        //set the position of the layer
        mapLayer.position = bottomLeft;
        
    }
    
    if ([touchArray count] ==1){
        
        UITouch * fingerOne = [touchArray objectAtIndex:0];
        
        CGPoint newTouchLocation = [fingerOne locationInView:[fingerOne view]];
        newTouchLocation = [[CCDirector sharedDirector] convertToGL:newTouchLocation];
        
        CGPoint oldTouchLocation = [fingerOne previousLocationInView:fingerOne.view];
        oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation];
        
        //get the difference in the finger touches when the player was dragging
        CGPoint difference = ccpSub(newTouchLocation, oldTouchLocation);
        
        //adds this on to the layers current position, effectively moving it
        CGPoint newPosition = ccpAdd(mapLayer.position, difference);
        
        CGPoint bottomLeft = newPosition;
        
        //check to see if the map edges of the map are showing in the screen, if so bringing them back on the view so no black space can be seen
        if (bottomLeft.x - differenceX/2 > 0 - (mapWidth * (1-mapScale)) + (1-mapScale)*33) {
            bottomLeft.x = differenceX/2- (mapWidth * (1-mapScale))+(1-mapScale)*33;
        }
        
        if (bottomLeft.y - differenceY/2 > 0 -(mapHeight * (1-mapScale))) {
            bottomLeft.y = differenceY/2-(mapHeight * (1-mapScale));
            
        }
        
        if (bottomLeft.x + mapWidth*mapScale +differenceX/2 < 440+ (1-mapScale)*33) {
            bottomLeft.x = -differenceX/2 - mapWidth*mapScale + 440 + (1-mapScale)*33;
            
        }
        if (bottomLeft.y + mapHeight*mapScale +differenceY/2 < 320) {
            bottomLeft.y =  -differenceY/2 - mapHeight*mapScale + 320;
            
        }
        
        mapLayer.position = bottomLeft;
    }
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
    // in case you have something to dealloc, do it in this method
    // in this particular example nothing needs to be released.
    // cocos2d will automatically release all the children (Label)
    
    // don't forget to call "super dealloc"
    
    [super dealloc];
}
@end