Make App Pie

Training for Developers and Artists

The Slippy Flippy Challenge: Make Marquee Style Scrolling Backgrounds

To give a sense of motion in a game one can use parallax backparalaxgrounds. Such backgrounds are in constant motion, but at a different rate than the foreground motion.

In Slippy Flippy, we use ice floating by on the water for such an effect. While this seems rather complicated, it is not as difficult as one might expect.

Make The Background Tiles

First we’ll make background tiles. Tiles have edges that placed edge to edge appear seamless.  We can compose tiles in an image processing application like GIMP or Photoshop. I’ll use GIMP for this, but this will work in Photoshop. I’ll mention a few differences in Photoshop later.

Open GIMP, click the background color and set it to the a background color of  8070aa:

Select a background color in GIMP
Select a background color in GIMP

Use white or a slight bluish gray like f0f0ff for the foreground. Now select File>New… and make a 64×64 pixel image:

64px x 64px tile
64px x 64px tile

Zoom in at least 400% and select the pencil with a width of 2 pixels

400% zoom,  2 pixel pencil tool
400% zoom, 2 pixel pencil tool

Now scribble a few ice floes into the water with the pencil set to your white or gray color.

Scribbled ice flows
Scribbled ice flows

Our next step is to prevent seams showing in the tile. We use the offset filter to prevent this. In GIMP, open the offset filter at Layer>Transform>Offset… in the drop down menu.

The offset tool used for making tiles.
The offset tool used for making tiles.

You will get a dialog box like this:

The offset dialog. GIMP has the nifty x/2,y/2 button.
The offset dialog. GIMP has the nifty x/2,y/2 button.

We need to offset by half the tile size. Press the x/2 y/2 button then change back the y value to 0. Alternatively place 32 in the x value. Select wrap around then Offset.

The two edges of our original image are next to each other on the center of the image. I’ve put a guide line to show where that edge is.

Tile with visible seam.  Guide line marks location of seam.
Tile with visible seam. Guide line marks location of seam.

Edit the image until there is no seam showing.

Finished tile
Finished tile

You now have a seamless image. You can apply the offset again to get back to our original then save, but you can also just save it. In GIMP, use the File>Export selection in the drop down and save as a .PNG file named iceflow somewhere you can find it. Drag and drop into the Xcode project.

In Photoshop, making tiles uses a similar process. You will find the offset filter in Filter>Other>Offset…:
Screenshot 2014-06-16 09.44.32

Just like GIMP, change the x-value to half the width of your image.
Screenshot 2014-06-16 09.45.55

While this is a good exercise, it is better to use two different images in different sizes,  The boundaries will not look as mechanical that way.  To save you from creating them, here are the two images you can use for this. Save them with the names in the captions:

Name one topwater2.png and the othere bottomwater2.png
bottomwater2.png
topWater2
topwater2.png

Since we are using a relative system for everything, the images do not have to be the same size. Our calculation later on will figure everything out for us.

Create a MakeBoundaries method

We already have in the demo program a make boundaries method. We’ll have to start from the beginning for our animated background.
Add this method to the scene code for the slippyflippyPenguinDemo.

-(SKSpriteNode *)boundarySpriteSettings:(SKSpriteNode *)sprite{
    sprite.zPosition = 20;
    sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
    sprite.physicsBody.dynamic = NO;
    sprite.physicsBody.categoryBitMask = obstacleCategory;
    sprite.physicsBody.contactTestBitMask = penguinCategory;
    sprite.physicsBody.collisionBitMask = penguinCategory;
    return sprite;
}

This code takes much of the repetitive code for our top and bottom sprites and sets up a common method for setting them.

Create the Actions

To create a marquee effect, we take a sprite, move it double its length then snap it back to the original position forever. We can make a SKAction for this with the following code:

-(SKAction *)marqueeForever:(SKTexture *)texture{
//action 1 - moving double the width of the texture
SKAction* moveTopSprite = [SKAction moveByX:-texture.size.width*2 y:0 duration:0.02 * texture.size.width*2];
//action 2 -- reset the sprite by moving it backwards immediately
SKAction* resetTopSprite = [SKAction moveByX:texture.size.width*2 y:0 duration:0];
//combine action 1 and 2
return [SKAction repeatActionForever:[SKAction sequence:@[moveTopSprite, resetTopSprite]]];

}

Build a Loop of Sprites

This will work great, but like our top sprite above, our sprites may not be as wide as our frame. We therefore will need to tile the sprites. What we will do is make a sequence of tiles next to one another. Add this code to make the top boundary:

-(void) makeTopBoundary{

    //----make the top boundary sprites
    //get our texture
    SKTexture* topTexture = [SKTexture textureWithImageNamed:@"topWater2"];
    //define the actions of the ground sprite
    SKAction *moveTopSpritesForever = [self marqueeForever:topTexture];
    // Create the  top sprites

    int spriteCount = 2 + (self.frame.size.width/topTexture.size.width) * 2; //how many do I need?
    for( int i = 0; i < spriteCount; ++i ) {
        SKSpriteNode* topSprite = [SKSpriteNode spriteNodeWithTexture:topTexture];
        topSprite.position = CGPointMake(i * topSprite.size.width, self.frame.size.height - (topSprite.size.height / 2) );
        topSprite = [self boundarySpriteSettings:topSprite];
        topSprite.name= @"top";
        [topSprite runAction:moveTopSpritesForever];
        [self addChild:topSprite];
    }
}

In line 5 we load the sprite texture directly from an .png image in our bundle. In line 7, we get our endless marquee action.

In line 10, we figure out how many sprites we will need to cover the frame. We go through the loop to make enough sprites to overlap the frame. That is double the frame width divided by the sprite width since the sprite needs to move double its width and something has to be there at the time so as not to show gaps. We then loop to create that many sprites.

In line 13, we position the sprite based on the count i. Instead of changing anchor here, we left the default anchor and calculated a relative value based on frame position and sprite size. Since we are positioning this relative to the frame, we subtract the frame height from the sprite height, which with a center anchor is half the height.

We can do the same thing for the bottom sprite boundary. Add this code:

-(void) makeBottomBoundary{
    //----make the bottom boundary sprites
    //define the texture
    SKTexture* bottomTexture = [SKTexture textureWithImageNamed:@"bottomWater2"];
    //bottomTexture.filteringMode = SKTextureFilteringNearest;
    SKAction *moveBottomSpritesForever = [self marqueeForever:bottomTexture];
    // create the bottom sprites
    int spriteCount = 2 + (self.frame.size.width/bottomTexture.size.width) * 2;
    for( int i = 0; i < spriteCount; ++i ) {
        SKSpriteNode* bottomSprite = [SKSpriteNode spriteNodeWithTexture:bottomTexture ];
        [bottomSprite setScale:2.0]; //adjust for the small bitmap -- better to make a bigger bitmap.
        bottomSprite.position = CGPointMake(i * bottomSprite.size.width, bottomSprite.size.height / 2);
        bottomSprite = [self boundarySpriteSettings:bottomSprite];
        bottomSprite.name = @"bottom";
        [bottomSprite runAction:moveBottomSpritesForever];
        [self addChild:bottomSprite];
    }
}

Note this code is almost identical to the top one, except in line 12. Since We calculate the y position from the bottom of the frame, no subtraction of the frame height is necessary.

Use the New Methods

In initWithSize: change the boundary code by commenting out our old methods and using our two new ones:

//make boundaries
        /*
        SKSpriteNode *topBoundary= [self makeWorldBoundaryWithName:@"top"];
        topBoundary.anchorPoint = CGPointMake(0.5, 1.0); //top center anchor
        topBoundary.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame));
        [self addChild:topBoundary];

        SKSpriteNode *bottomBoundary = [self makeWorldBoundaryWithName:@"bottom"];
        bottomBoundary.anchorPoint = CGPointMake(0.5, 0.0); //bottom center anchor
        bottomBoundary.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinX(self.frame));
        [self addChild:bottomBoundary];
        */
        [self makeTopBoundary];
        [self makeBottomBoundary];

Build and run. You should now have animated boundaries.

While we used it for boundaries,  if you have a repetitive background, you can use this marquee trick to make backgrounds of many different sizes.

We have most of our game elements in place. Next time we’ll set up some scoring for the game.

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: