Cocos2d Game Development Blueprints
上QQ阅读APP看书,第一时间看更新

Implementing the parallax effect in Cocos2d

The parallax effect is a scrolling technique used in 2D games that simulates depth and displacement by moving the foreground layers faster than background layers. This technique was developed initially to be used in traditional animation in the 1930s and was first used in computer games in the early 80s.

This effect consists of making our brain think that we are looking at displacement similar to what happens when we are traveling on a train: we can see the trees near the railway passing fast, but if we look to the houses placed further away, they pass a little slower, and if you look at the mountains in the background, they look almost immobile. If we place our scientist over a ground layer that will displace slowly and above this layer we scroll a clouds layer from the top to the bottom of the screen, our brain will think that Dr. Fringe is flying over the clouds thanks to his amazing backpack-reactor.

CCParallaxNode

As the parallax effect is a very common technique used in 2D games, Cocos2d includes CCParallaxNode, a class inherited from CCNode that will facilitate our work.

Thanks to this class's method addChild:z:parallaxRatio:positionOffset we can specify the nodes (children) that will take part in the parallax scrolling, which will move faster or slower depending on the ratio configured. We can also indicate the z-order and the parallax offset to use. Don't worry if you don't understand how it works right now, it's better when you can see the effect running.

First of all, you will need to add some images to your project as they will be used to create the different layers, so follow these steps:

  1. In the project navigator, select the Resources group.
  2. Right-click and select Add Files to "ExplosionsAndUFOs"….
  3. Select the background1.png, background2.png, background3.png, background4.png, clouds1.png, and clouds2.png files from the Resources folder.
  4. Be sure that Copy items into destination group's folder (if needed) is selected and click on Add.

These are the background images you just added:

CCParallaxNode

As you can see, the four background images are components of a whole image that will simulate us flying over the Earth. I cut it into four subdivisions because we can't load images bigger than 4096 x 4096 pixels.

The clouds1.png and clouds2.png images are two different layers that we will combine to achieve the parallax effect.

Now add the following line at the end of the init method, just before return self;:

// Configure parallax effect
[self configureParallaxEffect];

We will implement the method by adding the following code in GameScene.m:

-(void) configureParallaxEffect{
    // Create the layers that will take part in the parallax effect
    CCSprite *parallaxBackground1 = [CCSprite spriteWithImageNamed:@"background1.png"];
    CCSprite *parallaxBackground2 = [CCSprite spriteWithImageNamed:@"background2.png"];
    CCSprite *parallaxBackground3 = [CCSprite spriteWithImageNamed:@"background3.png"];
    CCSprite *parallaxBackground4 = [CCSprite spriteWithImageNamed:@"background4.png"];
    CCSprite *parallaxBackground5 = [CCSprite spriteWithImageNamed:@"background1.png"];
    
    CCSprite *parallaxClouds1 = [CCSprite spriteWithImageNamed:@"clouds1.png"];
    CCSprite *parallaxClouds2 = [CCSprite spriteWithImageNamed:@"clouds2.png"];
    CCSprite *parallaxClouds3 = [CCSprite spriteWithImageNamed:@"clouds1.png"];
    CCSprite *parallaxClouds4 = [CCSprite spriteWithImageNamed:@"clouds2.png"];
    
    CCSprite *parallaxLowerClouds1 = [CCSprite spriteWithImageNamed:@"clouds2.png"];
    CCSprite *parallaxLowerClouds2 = [CCSprite spriteWithImageNamed:@"clouds1.png"];
    CCSprite *parallaxLowerClouds3 = [CCSprite spriteWithImageNamed:@"clouds2.png"];
    CCSprite *parallaxLowerClouds4 = [CCSprite spriteWithImageNamed:@"clouds1.png"];
    
}

A parallax effect needs several layers to move at different speeds in order to simulate depth and displacement; that's why we need to create a CCSprite instance for each of them and why we're using the images we just added to the project. As we want to simulate that Dr. Fringe is flying over the whole planet, we need a very large background, but we have to deal with the restriction that we can't load images bigger than 4096 x 4096; that's why we divided the background into four smaller images. We're going to concatenate them one after the other until we fly all around the world. Sometimes, you will need to add an extra parallax sprite (as we're doing with parallaxBackground5) to cover the blank spaces between the first background and the last to achieve endless scrolling.

Before doing anything else, we will modify the sprites' anchor point to the bottom-left corner so we can align the sprites with the screen easily. To achieve this, add the following lines just at the end of configureParallaxEffect:

    // Modify the sprites anchor point
    parallaxBackground1.anchorPoint = CGPointMake(0, 0);
    parallaxBackground2.anchorPoint = CGPointMake(0, 0);
    parallaxBackground3.anchorPoint = CGPointMake(0, 0);
    parallaxBackground4.anchorPoint = CGPointMake(0, 0);
    parallaxBackground5.anchorPoint = CGPointMake(0, 0);
    
    parallaxClouds1.anchorPoint = CGPointMake(0, 0);
    parallaxClouds2.anchorPoint = CGPointMake(0, 0);
    parallaxClouds3.anchorPoint = CGPointMake(0, 0);
    parallaxClouds4.anchorPoint = CGPointMake(0, 0);
    
    parallaxLowerClouds1.anchorPoint = CGPointMake(0, 0);
    parallaxLowerClouds2.anchorPoint = CGPointMake(0, 0);
    parallaxLowerClouds3.anchorPoint = CGPointMake(0, 0);
    parallaxLowerClouds4.anchorPoint = CGPointMake(0, 0);

For the moment, we're just setting up layers to ease layers movement. Then add these lines in the same method:

    // Modify opacity
    parallaxLowerClouds1.opacity = 0.3;
    parallaxLowerClouds2.opacity = 0.3;
    parallaxLowerClouds3.opacity = 0.3;
    parallaxLowerClouds4.opacity = 0.3;
    
    parallaxClouds1.opacity = 0.8;
    parallaxClouds2.opacity = 0.8;
    parallaxClouds3.opacity = 0.8;
    parallaxClouds4.opacity = 0.8;

We're modifying the opacity for the clouds layers. We want to place both cloud layers between the scientist and the ground, each of them with a different opacity as we want to simulate that they're different types of clouds and we want to see what is behind them. The opacity of a node is a value that may vary from 0 to 1, which means that we're setting 80 and 30 percent of the original opacity to parallaxClouds# and parallaxLowerClouds# respectively.

Then we add the following lines at the end of configureParallaxEffect:

    // Define start positions
    CGPoint backgroundOffset1 = CGPointZero;
    CGPoint backgroundOffset2 = CGPointMake(0, parallaxBackground1.contentSize.height);
    CGPoint backgroundOffset3 = CGPointMake(0, parallaxBackground1.contentSize.height + parallaxBackground2.contentSize.height);
    CGPoint backgroundOffset4 = CGPointMake(0, parallaxBackground1.contentSize.height + parallaxBackground2.contentSize.height + parallaxBackground3.contentSize.height);
    CGPoint backgroundOffset5 = CGPointMake(0, parallaxBackground1.contentSize.height + parallaxBackground2.contentSize.height + parallaxBackground3.contentSize.height + parallaxBackground4.contentSize.height);

    CGPoint lowerClouds1Offset = CGPointMake(0, _screenSize.height);
    CGPoint lowerClouds2Offset = CGPointMake(0, _screenSize.height + 3 * parallaxBackground1.contentSize.height);
    CGPoint lowerClouds3Offset = CGPointMake(0, _screenSize.height + 6 * parallaxBackground1.contentSize.height);
    CGPoint lowerClouds4Offset = CGPointMake(0, _screenSize.height + 9 * parallaxBackground1.contentSize.height);
    
    CGPoint clouds1Offset = CGPointMake(0, _screenSize.height);
    CGPoint clouds2Offset = CGPointMake(0, _screenSize.height + 3 * parallaxBackground1.contentSize.height);
    CGPoint clouds3Offset = CGPointMake(0, _screenSize.height + 6 * parallaxBackground1.contentSize.height);
    CGPoint clouds4Offset = CGPointMake(0, _screenSize.height + 9 * parallaxBackground1.contentSize.height);

With these lines, we're initializing the different offsets that we will need for each layer of the parallax node; in other words, we are specifying its position on the screen. As the background will cover the screen initially, we give a CGPointZero value to the first background layer's offset that is equivalent to CGPointMake(0, 0). Then, we prepare the rest of parallaxBackground# (parallaxBackground1, parallaxBackground2, parallaxBackground3, and parallaxBackground4) to be placed one after the other and then we set backgroundOffset5 at the end to cover the blank space the backgrounds leave behind as they scroll down.

Cloud's offsets don't need to cover the whole space; that's why we set them at a higher position so they will appear as we fly.

Once we have all we need to create a parallax effect, we can initialize the parallax node and append the children to it. Add the following lines just after the offset's initialization:

    // Initialize parallax node
    CCParallaxNode *parallaxNode = [CCParallaxNode node];
    
    // Add parallax children defining z-order, ratio and offset
    [parallaxNode addChild:parallaxBackground1 z:0 parallaxRatio:CGPointMake(0, 1) positionOffset:backgroundOffset1];
    [parallaxNode addChild:parallaxBackground2 z:0 parallaxRatio:CGPointMake(0, 1) positionOffset:backgroundOffset2];
    [parallaxNode addChild:parallaxBackground3 z:0 parallaxRatio:CGPointMake(0, 1) positionOffset:backgroundOffset3];
    [parallaxNode addChild:parallaxBackground4 z:0 parallaxRatio:CGPointMake(0, 1) positionOffset:backgroundOffset4];
    [parallaxNode addChild:parallaxBackground5 z:0 parallaxRatio:CGPointMake(0, 1) positionOffset:backgroundOffset5];
    
    [parallaxNode addChild:parallaxLowerClouds1 z:1 parallaxRatio:CGPointMake(0, 2) positionOffset:lowerClouds1Offset];
    [parallaxNode addChild:parallaxLowerClouds2 z:1 parallaxRatio:CGPointMake(0, 2) positionOffset:lowerClouds2Offset];
    [parallaxNode addChild:parallaxLowerClouds3 z:1 parallaxRatio:CGPointMake(0, 2) positionOffset:lowerClouds3Offset];
    [parallaxNode addChild:parallaxLowerClouds4 z:1 parallaxRatio:CGPointMake(0, 2) positionOffset:lowerClouds4Offset];

    [parallaxNode addChild:parallaxClouds1 z:2 parallaxRatio:CGPointMake(0, 3) positionOffset:clouds1Offset];
    [parallaxNode addChild:parallaxClouds2 z:2 parallaxRatio:CGPointMake(0, 3) positionOffset:clouds2Offset];
    [parallaxNode addChild:parallaxClouds3 z:2 parallaxRatio:CGPointMake(0, 3) positionOffset:clouds3Offset];
    [parallaxNode addChild:parallaxClouds4 z:2 parallaxRatio:CGPointMake(0, 3) positionOffset:clouds4Offset];

The preceding lines configure the behavior of the different layers by giving them the proper z-order value, ratio, and position offset. With z-order, we will configure which layers will be shown above or below, positionOffset receives a CGPoint instance that will multiply the speed of the node in the x and y axes, and the position offset sets the clouds' initial position.

We also need to modify the scientist's z-order so go to the line where we added the scientist's sprite to the scene:

[self addChild:_scientist];

Replace it with:

[self addChild:_scientist z:1];

Thanks to z-order, we place the ground layer on the lower level (z-order = 0), then the lower clouds layer (z-order = 1), and the top clouds layer (z-order = 2). These z-order values don't concern the scene hierarchy but they do concern parallax node's hierarchy, and will indicate the order in which parallax layers will be placed. We need to update the scientist z-order because we added the parallax node to the scene with its default z-order value, and if we don't change it, the scientist will appear behind the background layer and will be hidden to our eyes.

Once we've decided the order of the layers, we need to specify the ratio of the layer's movement. In our case, we are configuring parallaxBackground# to move at the same speed as CCParallaxNode, parallaxLowerClouds# to move at double the speed of CCParallaxNode, and parallaxClouds# to move at three times the speed of CCParallaxNode. You will realize that we just specified these parallaxRatio values for the y axis; that's because we're planning to only scroll vertically.

If you remember the concept the parallax effect is based on, we are going to simulate depth and movement by scrolling layers at different speeds; that's why we set different values, but you can modify the parallax ratio at your convenience to achieve the desired effect.

Now that the parallax node is configured, we can add it to the scene as a common node. Then there is only one thing left to do: create the movement actions. Add these lines at the end of configureParallaxEffect:

    [self addChild:parallaxNode];
    
    // Create a move action
    CCActionMoveBy *move1 = [CCActionMoveBy actionWithDuration:24 position:CGPointMake(0, -(parallaxBackground1.contentSize.height + parallaxBackground2.contentSize.height + parallaxBackground3.contentSize.height + parallaxBackground4.contentSize.height))];

    CCActionMoveBy *move2 = [CCActionMoveBy actionWithDuration:0 position:CGPointMake(0, parallaxBackground1.contentSize.height + parallaxBackground2.contentSize.height + parallaxBackground3.contentSize.height + parallaxBackground4.contentSize.height)];

    // Create a sequence with both movements
    CCActionSequence *sequence = [CCActionSequence actionWithArray:@[move1, move2]];
    
    // Create an infinite loop for the movement action
    CCActionRepeatForever *loop = [CCActionRepeatForever actionWithAction:sequence];

    // Run the action
    [parallaxNode runAction:loop];

As we want to move all the layers from the top to the bottom, we will need to configure a movement action that displaces the layers until they reach a desired point. In our case, we set the final point at a negative position equal to the sum of all the background's height; this way it will show all the background layers falling down one after the other, simulating that it's scrolling repeatedly.

As you know from the previous chapter, we can specify the duration of the movement, and the backpack-reactor is powerful enough to fly around the world in just 24 seconds.

We need to recover the original layer's position; that's why we create another movement action that will set the parallax node to the original position instantly. If we concatenate both movements in a sequence, it will look like nothing happened and if we run this sequence indefinitely using CCActionRepeatForever, this will achieve the endless parallax scrolling that we need. Then if parallaxNode runs the created loop action, magic will happen. Run the game on Xcode and enjoy your first parallax effect—don't you feel like Walt Disney?

CCParallaxNode

You can see that Dr. Nicholas Fringe looks like he is falling from the sky because he has nothing to make him fly, that is, his backpack-reactor is shut down. Don't worry, as we're about to give him a hand to save his life.