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{//mapCCTMXTiledMap * testMap;CCTMXLayer * testLayer;CCLayer * mapLayer;//uiCCLayer * UILayer;CCSprite* UIBackground;//declarations for return to main menuCCMenu * 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 scenereturn 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 valueif( (self=[super init])) {self.isTouchEnabled = YES;//declare the map and add to a layermapLayer = [CCLayer node];testMap = [CCTMXTiledMap tiledMapWithTMXFile:@"testMap.tmx"];testLayer = [testMap layerNamed:@"Test Layer 1"];[mapLayer addChild:testMap];[self addChild:mapLayer];//get map size in pixelsmapHeight = testMap.contentSize.height;mapWidth = testMap.contentSize.width;//set up defaultsmapLayer.position= ccp(0, 0);//this value works well for the calculation later, trial and error reallydistance = 150;lastGoodDistance = 150;mapScale = 1;//set up a layer for the UIUILayer = [CCLayer node];UIBackground = [CCSprite spriteWithFile:@"UIbackgroundImage.png"];NSLog(@"%@", UIBackground);UIBackground.position= ccp(460, 160);[UILayer addChild:UIBackground];//set up labels for return menureturnMenuImage = [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 mapint 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 setNSArray *touchArray = [touches allObjects];// Only run the following code if there is more than one touch, wanting to resizeif ([touchArray count] > 1){// We're going to track the first two touches (i.e. first two fingers)// Create "UITouch" objects representing each touchUITouch *fingerOne = [touchArray objectAtIndex:0];UITouch *fingerTwo = [touchArray objectAtIndex:1];// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually useCGPoint 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 pinchif ((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 distanceelse 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 screenfloat 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 cameradistance = lastGoodDistance;}//we don't want him zooming in larger than the original imageif (mapScale> 1.0){mapScale = 1.0;distance = lastGoodDistance;}//set the last distance which had any affect to be the current onelastGoodDistance= distance;//allow the zoom to take placemapLayer.scale=mapScale;CGPoint bottomLeft = mapLayer.position;//check to see if the layer is drifting off the screen while the zoom is taking placeif (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 layermapLayer.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 draggingCGPoint difference = ccpSub(newTouchLocation, oldTouchLocation);//adds this on to the layers current position, effectively moving itCGPoint 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 seenif (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
Awesome. Just what i needed.
ReplyDeleteQuestion though, 2 fingers are only being picked up as one (on simulator, haven't done phone cause it's 5.0 right now)
Any ideas?