cocos2d-iphoneiphone-5parallaxkobold2d

Parallax scrolling on iPhone 4 and 5 screens


I am trying to implement landscape parallax scrolling which works both on iPhone 4 and the new iPhone 5. I started with a sprite which is 1136px in width (HD) and thought that I could use the same for the iPhone 4 as well. The problem is that it won't work on iPhone 4 anymore. If you're using an iPhone 5, screensize and sprite size are the same. Not so on the iPhone 4 which will result in awkward replacing of the sprite after you reached 1136px sidewards motion (i.e. the length of the sprite/iPhone 5's screen).

How can I implement endless parallax scrolling independent of the screen size / sprite size ratios?

Here is the code which updates the sprites so that they go ad infinitum (based on the code of the new Cocos2D 2 book by Itterheim):

for (CCSprite* sprite in spriteBatch.children)
{
    NSNumber* factor = [speedFactors objectAtIndex:sprite.zOrder];

    CGPoint pos = sprite.position;
    pos.x -= (scrollSpeed * factor.floatValue) * (delta * 50);

    // Reposition stripes when they're out of bounds
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    if (pos.x < -screenSize.width)
    {
        pos.x += (screenSize.width * 2) - 2;
    }

    sprite.position = pos;
}

Here is its context:

 @implementation ParallaxBackground

-(id) init
{
    if ((self = [super init]))
    {
        CGSize screenSize = [[CCDirector sharedDirector] winSize];

        // Get the game's texture atlas texture by adding it. Since it's added already it will simply return 
        // the CCTexture2D associated with the texture atlas.
        CCTexture2D* gameArtTexture = [[CCTextureCache sharedTextureCache] addImage:@"game-art.pvr.ccz"];

        // Create the background spritebatch
        spriteBatch = [CCSpriteBatchNode batchNodeWithTexture:gameArtTexture];
        [self addChild:spriteBatch];

        bgLayerTotal = 3;

        // Add the 6 different layer objects and position them on the screen
        for (int i = 0; i < bgLayerTotal; i++)
        {
            NSString* frameName = [NSString stringWithFormat:@"bg%i.png", i];
            CCSprite* sprite = [CCSprite spriteWithSpriteFrameName:frameName];
            sprite.anchorPoint = CGPointMake(0, 0.5f);
            sprite.position = CGPointMake(0, screenSize.height / 2);
            [spriteBatch addChild:sprite z:i];
        }

        // Add 7 more stripes, flip them and position them next to their neighbor stripe
        for (int i = 0; i < bgLayerTotal; i++)
        {
            NSString* frameName = [NSString stringWithFormat:@"bg%i.png", i];
            CCSprite* sprite = [CCSprite spriteWithSpriteFrameName:frameName];

            // Position the new sprite one screen width to the right
            sprite.anchorPoint = CGPointMake(0, 0.5f);

            sprite.position = CGPointMake(screenSize.width - 1, screenSize.height / 2);

            // Flip the sprite so that it aligns perfectly with its neighbor
            sprite.flipX = YES;

            // Add the sprite using the same tag offset by numStripes
            [spriteBatch addChild:sprite z:i tag:i + bgLayerTotal];
        }

        // Initialize the array that contains the scroll factors for individual stripes.
        speedFactors = [NSMutableArray arrayWithCapacity:bgLayerTotal];
        [speedFactors addObject:[NSNumber numberWithFloat:0.1f]];
        [speedFactors addObject:[NSNumber numberWithFloat:3.0f]];
        [speedFactors addObject:[NSNumber numberWithFloat:4.0f]];
        NSAssert(speedFactors.count == (unsigned int)bgLayerTotal, @"speedFactors count does not match bgLayerTotal!");

        scrollSpeed = 1.0f;

        [self scheduleUpdate];
    }
    return self;
}

-(void) update:(ccTime)delta
{
    for (CCSprite* sprite in spriteBatch.children)
    {
        NSNumber* factor = [speedFactors objectAtIndex:sprite.zOrder];

        CGPoint pos = sprite.position;
        pos.x -= (scrollSpeed * factor.floatValue) * (delta * 50);

        // Reposition stripes when they're out of bounds
        CGSize screenSize = [CCDirector sharedDirector].winSize;
        if (pos.x < -screenSize.width)
        {
            pos.x += (screenSize.width * 2) - 2;
        }

        sprite.position = pos;
    }
}

Solution

  • Rather than using the screensize to repeat the background, just use the width of the largest background sprite (or the sum of the widths of the bg pieces, if they are broken up). You could also just hard-code the max width to 1136.

    So, change:

    CGSize screenSize = [CCDirector sharedDirector].winSize;
    if (pos.x < -screenSize.width)
    {
        pos.x += (screenSize.width * 2) - 2;
    }
    

    To something like:

    CCSprite *bg = (CCSprite*)[spriteBatch getChildByTag:0];
    if (pos.x < -bg.contentSize.width)
    {
        pos.x += (bg.contentSize.width * 2) - 2;
    }