Zoom Layer centered on a Sprite

Go To StackoverFlow.com

0

I am in process of developing a small game where a space-ship travels through a layer (doh!), in some situations the spaceship comes close to an enemy, and the whole layer is zoomed in on the space-ship with the zoom level being dependent on the distance between the ship and the enemy. All of this works fine.

The main question, however, is how do I keep the zoom being centered on the space-ship?

Currently I control the zooming in the GameLayer object through the update method, here is the code:

-(void) prepareLayerZoomBetweenSpaceship{
    CGPoint mainSpaceShipPosition  = [mainSpaceShip position];
    CGPoint enemySpaceShipPosition = [enemySpaceShip position];

    float distance = powf(mainSpaceShipPosition.x - enemySpaceShipPosition.x, 2) + powf(mainSpaceShipPosition.y - enemySpaceShipPosition.y,2);
    distance = sqrtf(distance);

    /*
        Distance > 250 --> no zoom
        Distance < 100 --> maximum zoom
     */
    float myZoomLevel = 0.5f;
    if(distance < 100){ //maximum zoom in
        myZoomLevel = 1.0f;
    }else if(distance > 250){
        myZoomLevel = 0.5f;
    }else{
        myZoomLevel = 1.0f - (distance-100)*0.0033f;
    }

    [self zoomTo:myZoomLevel];
}

-(void) zoomTo:(float)zoom {
    if(zoom > 1){
        zoom = 1;
    }

    // Set the scale.
    if(self.scale != zoom){
        self.scale = zoom;
    }
}

Basically my question is: How do I zoom the layer and center it exactly between the two ships? I guess this is like a pinch zoom with two fingers!

2012-04-05 17:20
by clops
there is nothing in this code that suggests the zoom is focused on the ship.Also...is the ship free to move on the screen through user interaction or it's fixed and you move the layer - skytz 2012-04-05 19:37
Hi, yes the ship is controlled by a joystick. My question is actually how to focus the layer on the ship when it is being zoomed : - clops 2012-04-06 07:41
I would need to know more to give a definitive answer but in general: get the position of the center point, then set the position to the negative of those coords. Then multiply those coords by the scale - tustvold 2012-04-06 17:47
Tustvold, thanks for the headsup :) but can you pretty-please elaborate on this? I'd love to accept your answer - clops 2012-04-11 10:04


1

Below is some code that should get it working for you. Basically you want to:

  1. Update your ship positions within the parentNode's coordinate system
  2. Figure out which axis these new positions will cause the screen will be bound by.
  3. Scale and re-position the parentNode

I added some sparse comments, but let me know if you have any more questions/issues. It might be easiest to dump this in a test project first...

ivars to put in your CCLayer:

CCNode *parentNode;
CCSprite *shipA;
CCSprite *shipB;
CGPoint destA, deltaA;
CGPoint destB, deltaB;
CGPoint halfScreenSize;
CGPoint fullScreenSize;

init stuff to put in your CCLayer:

CGSize size = [[CCDirector sharedDirector] winSize];
fullScreenSize = CGPointMake(size.width, size.height);
halfScreenSize = ccpMult(fullScreenSize, .5f);
parentNode = [CCNode node];
[self addChild:parentNode];

shipA = [CCSprite spriteWithFile:@"Icon-Small.png"]; //or whatever sprite
[parentNode addChild:shipA];

shipB = [CCSprite spriteWithFile:@"Icon-Small.png"];
[parentNode addChild:shipB];

//schedules update for every frame... might not run great.
//[self schedule:@selector(updateShips:)]; 

//schedules update for 25 times a second
[self schedule:@selector(updateShips:) interval:0.04f]; 

Zoom / Center / Ship update method:

-(void)updateShips:(ccTime)timeDelta {
    //SHIP POSITION UPDATE STUFF GOES HERE
    ...

    //1st: calc aspect ratio formed by ship positions to determine bounding axis
    float shipDeltaX = fabs(shipA.position.x - shipB.position.x);
    float shipDeltaY = fabs(shipA.position.y - shipB.position.y);
    float newAspect = shipDeltaX / shipDeltaY;

    //Then: scale based off of bounding axis

    //if bound by x-axis OR deltaY is negligible
    if (newAspect > (fullScreenSize.x / fullScreenSize.y) || shipDeltaY < 1.0) { 
        parentNode.scale = fullScreenSize.x / (shipDeltaX + shipA.contentSize.width);
    } 
    else { //else: bound by y-axis or deltaX is negligible
        parentNode.scale = fullScreenSize.y / (shipDeltaY + shipA.contentSize.height);
    }


    //calculate new midpoint between ships AND apply new scale to it
    CGPoint scaledMidpoint = ccpMult(ccpMidpoint(shipA.position, shipB.position), parentNode.scale);
    //update parent node position (move it into view of screen) to scaledMidpoint
    parentNode.position = ccpSub(halfScreenSize, scaledMidpoint);
}

Also, I'm not sure how well it'll perform with a bunch of stuff going on -- but thats a separate problem!

2012-04-14 03:06
by MechEthan
Thanks for a thorough answer, I will test it tonight, but looks promising : - clops 2012-04-16 08:35
I have another ~20 lines of code in the updateShips method that moves them around randomly for easy demo that you could drop into a sample project to see it work. Let me know if you want those - MechEthan 2012-04-16 17:22
EDITED ANSWER: I broke up the last line of code into 2 lines + 2 comments for clarity, but its functionally identical to original - MechEthan 2012-04-16 17:34
I really appreciate your help, I have awarded you the bounty... I did not get to testing your solution yet, I shall do this asap! thanks : - clops 2012-04-17 09:10
Basically the last two lines of your example added to my code did the job, thanks - clops 2012-04-17 13:34
Glad I could help. I was just about to start digging into this question for myself when I saw your post, hah - MechEthan 2012-04-17 18:08


0

Why don't you move the entire view, & position it so the ship is in the centre of the screen? I haven't tried it with your example, but it should be straight forward. Maybe something like this -

CGFloat x = (enemySpaceShipPosition.x - mainSpaceShipPosition.x) / 2.0 - screenCentreX;
CGFloat y = (enemySpaceShipPosition.y - mainSpaceShipPosition.y) / 2.0 - screenCentreY;


CGPoint midPointForContentOffset = CGPointMake(-x, -y);

[self setContentOffset:midPointForContentOffset];

...where you've already set up screenCentreX & Y. I haven't used UISCrollView for quite a while (been working on something in Unity so I'm forgetting all by Obj-C), & I can't remember how the contentOffset is affected by zoom level. Try it & see! (I'm assuming you're using a UIScrollView, maybe you could try that too if you're not)

2012-04-11 10:14
by SomaMan
It looks like the correct way to handle things --> move the layer to match the center point between the two ships. It has to scale however, as if it were a pinch zoom. My maths lets me down here : - clops 2012-04-11 10:24
Ads