Cocos2d: Moving background on update: offsett issue

| | August 4, 2015

working with Objective C, iOS and Cocos2d I am developing a vertical scrolling shooter game for iPhone (retina display models with 640 width x 960 height pixel resolution).

My basic algorithm works as following:

  • I create two instances of an image that has exactly 640 width x 960
    height pixel of resolution, which we will call imageA and imageB
  • I then set the two imags with exactly 480.0f of offset from each other, as
    the screenSize of a CCScene is set by default to 480.0f.
  • At each update method call I move the two images by the same value. I make sure that their offsett stays to 480.0f

However when running the game I see a 1 pixel height line between the two images. This literally bugs me and would like to adjust this.

What am I doing wrong?

This is a zoom in on the background when the “offsett line” is visible. The white line you can see divides the two background images and is not meant to exist as both images are completely black :):

line

If I change the yPositionOfSecondElement value to 479.0f until the first loop the two images overlap correctly, but as soon as the loop starts the two images starts having an offsett of -1.0f.

Here is the initialization code:

-(void) init
{

//...

screenHeight = 480.0f;
yPositionOfSecondElement= screenHeight;//I tried subtracting an offsett of -1 but eventually the image would go wrong again
yPositionOfFirstElement = 0.0f;


 loopedBackgroundImageInstanceA = [BackgroundLoopedImage loopImageForLevel:levelName];
            loopedBackgroundImageInstanceA.anchorPoint = CGPointMake(0.5f, 0.0f);
            loopedBackgroundImageInstanceA.position = CGPointMake(160.0f, yPositionOfFirstElement);
            [node addChild:loopedBackgroundImageInstanceA z:zLevelBackground];

            //loopedBackgroundImageInstanceA.color= ccRED;

            loopedBackgroundImageInstanceB = [BackgroundLoopedImage loopImageForLevel:levelName];
            loopedBackgroundImageInstanceB.anchorPoint = CGPointMake(0.5f, 0.0f);
            loopedBackgroundImageInstanceB.position = CGPointMake(160.0f, yPositionOfSecondElement);
            [node addChild:loopedBackgroundImageInstanceB z:zLevelBackground];



  //....
}

And here is the move code called at each update:

-(void) moveBackgroundSprites:(BackgroundLoopedImage*)imageA  :(BackgroundLoopedImage*)imageB :(ccTime)delta
{
    isEligibleToMove=false;

    //This is done to avoid rounding errors
    float yStep = delta * [GameController sharedGameController].currentBackgroundSpeed;

    NSString* formattedNumber = [NSString stringWithFormat:@"%.02f", yStep];
    yStep = atof([formattedNumber UTF8String]);

    //First should adjust position of images
    [self adjustPosition:imageA :imageB];

    //The can get the actual image position

    CGPoint posA = imageA.position;
    CGPoint posB = imageB.position;

    //Here could verify if the checksum is equal to the required difference (should be 479.0f)
    if (![self verifyCheckSum:posA :posB]) {
        CCLOG(@"does not comply A");
    }

    //At this stage can compute the hypotetical new position
    CGPoint newPosA = CGPointMake(posA.x, posA.y - yStep);
    CGPoint newPosB = CGPointMake(posB.x, posB.y - yStep);

    // Reposition stripes when they're out of bounds
    if (newPosA.y <= -yPositionOfSecondElement)
    {
        newPosA.y = yPositionOfSecondElement;
        [imageA shuffle];
        if (timeElapsed>=endTime && hasReachedEndLevel==FALSE) {
            hasReachedEndLevel=TRUE;
            shouldMoveImageEnd=TRUE;
        }
    }
    else if (newPosB.y <= -yPositionOfSecondElement)
    {
       newPosB.y = yPositionOfSecondElement;
        [imageB shuffle];
        if (timeElapsed>=endTime && hasReachedEndLevel==FALSE) {
            hasReachedEndLevel=TRUE;
            shouldMoveImageEnd=TRUE;
        }
    }

    //Here should verify that the check sum is equal to 479.0f
    if (![self verifyCheckSum:posA :posB]) {
        CCLOG(@"does not comply B");
    }

    imageA.position = newPosA;
    imageB.position = newPosB;


    //Here could verify that the check sum is equal to 479.0f
    if (![self verifyCheckSum:posA :posB]) {
        CCLOG(@"does not comply C");
    }

    isEligibleToMove=true;
}

-(BOOL) verifyCheckSum:(CGPoint)posA :(CGPoint)posB
{
    BOOL comply = false;
    float sum = 0.0f;

    if (posA.y > posB.y) {
        sum = posA.y - posB.y;
    }
    else if (posB.y > posA.y){
        sum = posB.y - posA.y;
    }
    else{
        return false;
    }

    if (sum!=yPositionOfSecondElement) {
        comply= false;
    }
    else{
        comply=true;
    }
    return comply;
}

And here is what happens on the update:

if(shouldMoveImageA && shouldMoveImageB)
    {
        if (isEligibleToMove) {
            [self moveBackgroundSprites:loopedBackgroundImageInstanceA :loopedBackgroundImageInstanceB :delta];
        }

Forget about shouldMoveImageA and shouldMoveImageB, this is just for when the background reaches the end of level, this works.

2 Responses to “Cocos2d: Moving background on update: offsett issue”

  1. It looks like your image has an anti aliasing at it’s border. You can either fix this in photoshop or you could use a program like texturepacker that will extend your picture a pixel and remove this border.

  2. Don’t know if this will help but here is the code for a game where we have moving clouds in the background. Is very old but will get you an idea.

    -(id) init{
        if( (self=[super init] )) {
            first = [CCSprite spriteWithFile:@"clouds_1.png"];
            firstWidth = first.contentSize.width;
            height = first.contentSize.height;
            second = [CCSprite spriteWithFile:@"clouds_2.png"];
            secondWidth = second.contentSize.width;
            Yvalue = GO_CLOUDS_YVALUE;
            duration = GO_CLOUDS_DURATION;
            CGSize size = [[CCDirector sharedDirector] winSize];
            firstXf = -1 * (secondWidth - size.width + firstWidth/2);
            secondXf = -1 * (firstWidth + secondWidth/2);
            firstX0 = size.width + firstWidth/2;
            secondX0 = size.width + secondWidth/2;
    
            moveFirstDone = [CCCallFuncN actionWithTarget:self selector:@selector(callFirstDone:)];
            moveSecondDone = [CCCallFuncN actionWithTarget:self selector:@selector(callSecondDone:)];
    
            first.position = ccp(firstX0 + -1*firstX0,Yvalue);
            second.position = ccp(secondX0,Yvalue);
            [self addChild:first];
            [self addChild:second];
            [first runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration position:ccp(firstXf,Yvalue)],moveFirstDone,nil]];
            [second runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration*2 position:ccp(secondXf,Yvalue)],moveSecondDone,nil]];
        }
        return self;
    }
    
    -(void)callSecondDone:(id)sender{
        second.position = ccp(secondX0,Yvalue);
        [second runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration*2 position:ccp(secondXf,Yvalue)],moveSecondDone,nil]];
    }
    
    -(void)callFirstDone:(id)sender{
        first.position = ccp(firstX0,Yvalue);
        [first runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration*2 position:ccp(firstXf,Yvalue)],moveFirstDone,nil]];
    }
    

Leave a Reply