Twice a month, we revisit some of our readers’ favorite posts from throughout the history of Activetuts+. This tutorial was first published in October, 2009 (and led to my inviting Michael to be Activetuts+ Tech Editor!)
This is a really simple technique which you can use to add the illusion of depth to any side-scroller game. While I'm explaining how it's achieved, we'll also take a look at making infinitely repeating backgrounds.
Introduction
You can see the basic parallax scrolling effect at work in the demo. By adjusting the speeds at which certain objects scroll, we can change how near or far away they appear to be.
This tutorial will explain how to code the effect, as well as how to make the game's camera appear to follow the car. Lastly, it'll explain how to create infinitely repeating backgrounds, just like in Scooby Doo.
Step 1: The Setup
If you're using Flash, create a new ActionScript 3.0 Flash file. Set the size of the stage to be whatever you like; I've chosen the default 550 by 400 pixels.
If you're not using the Flash IDE, don't worry; just create a new AS3 project. The zip file contains a SWC with all my graphics, so you can use those just by adding it to your library. Skip all the drawing steps if you do so.
If you are using the Flash IDE but you don't want to draw anything, the zip also contains a FLA file containing all my MovieClips :)
Step 2: Draw a Car
Create a new MovieClip symbol and draw a car. You can animate it if you like. Here's mine:
Try to centre the car so that its registration point (the little cross) is about half-way along. This will make it easier for the camera to follow it later.
Step 3: Export Your Car for ActionScript
Right-click your car symbol in the Library and select Properties:
Give your car a Class (Car will do) and check the Export for ActionScript box (this lets us access the car using code). Also, check the Export in first frame box (otherwise, we'll have to make a preloader).
Step 4: Draw a Road
Create another new symbol, but this time draw a road:
Make it wider than the stage, but unlike the car, align the registration point with the left edge of the road. This will help later on, when we need to turn it into a repeating pattern.
Just like you did for the car, give the road a Class, export it for ActionScript and export it in the first frame.
Step 5: Create the Document Class
Create a new AS file and paste the following code into it:
package { import flash.display.MovieClip; public class ParallaxDemo extends MovieClip { public function ParallaxDemo() { } } }
Save this as ParallaxDemo.as, in the same folder as your FLA (or as your project, if you're not using the IDE).
If you're using the IDE, be sure to set this as your document class in the Properties Panel of your document:
Not sure what we're doing here? Check out my Quick Tip on using a document class.
Step 6: Set Up the Car and the Road
Create new instances of your car and road in the AS file: (lines 6, 7, 11, 12)
package { import flash.display.MovieClip; public class ParallaxDemo extends MovieClip { public var car:Car; public var road:Road; public function ParallaxDemo() { car = new Car(); road = new Road(); } } }
Step 7: Position the Car and the Road
If you're using my graphics, you can just copy the following code (lines 14-17):
package { import flash.display.MovieClip; public class ParallaxDemo extends MovieClip { public var car:Car; public var road:Road; public function ParallaxDemo() { car = new Car(); road = new Road(); car.x = 275.0; car.y = 235.0; road.x = 0.0; road.y = 294.0; } } }
Otherwise, you'll need to figure out where your car and road should be placed at the start. Create a new Layer in your FLA's timeline, then make it into a Guide Layer by right-clicking it and selecting Guide. Flash will ignore anything you do in this layer when it creates a SWF, so drag your car and road symbols here.
Make sure the left edge of your road is aligned with the left edge of the stage, and that the car is roughly in the centre (horizontally). Then adjust them so that they fit together:
Now take my code from above and adjust it to match the x- and y- coordinates of your car and road. By clicking on your car or road, you'll be able to see these values in the Properties panel.
Step 8: Add your Symbols to the Stage
If you test your movie now, you'll see nothing. We need to addChild() the car and road to the stage:
public function ParallaxDemo() { car = new Car(); road = new Road(); car.x = 275.0; car.y = 235.0; road.x = 0.0; road.y = 294.0; stage.addChild( road ); stage.addChild( car ); }
(Make sure you add the road first, or it'll cover the car!)
Test your movie now, and it should look like this:
Great! Well, OK, it's nothing spectacular yet. But now the setup's out of the way, we can make this more interesting. For starters, let's get this car moving...
Step 9: Add an ENTER_FRAME Event Listener
Alter your document class to add an event listener to be triggered on each frame: (lines 4, 23, 26-29)
package { import flash.display.MovieClip; import flash.events.Event; public class ParallaxDemo extends MovieClip { public var car:Car; public var road:Road; public function ParallaxDemo() { car = new Car(); road = new Road(); car.x = 275.0; car.y = 235.0; road.x = 0.0; road.y = 294.0; stage.addChild( road ); stage.addChild( car ); addEventListener( Event.ENTER_FRAME, onEnterFrame ); } public function onEnterFrame( evt:Event ):void { } } }
If you kept the default frame rate of 12fps, the onEnterFrame() function will be called every 1/12th of a second.
Step 10: Move That Car!
If we keep increasing the car's x-position...
public function onEnterFrame( evt:Event ):void { car.x = car.x + 10; //if you're up for a challenge, try adding basic //keyboard controls to let the player accelerate //and decelerate. }
...we can make the car move forwards...
...right off the edge of the screen!
Step 11: Follow That Car!
This is hardly ideal; after a few seconds we can't even see the car any more. So let's make the "camera" appear to follow the car.
What exactly does this mean? Well, basically we need the car to stay in the same place, while the road appears to move backwards.
That means we could do something like this:
public function onEnterFrame( evt:Event ):void { road.x = road.x - 10; }
...but this would just complicate things later. For example, imagine if we wanted to add other cars to the road, or powerups, or oil slicks, or anything at all; we'd have to move every single one of them backwards in the onEnterFrame() function.
No, there is a much simpler technique we can use. Instead of addChild()-ing the car and road to the stage, we create a new object, addChild() them to that, and then move this object backwards in the onEnterFrame() function.
It sounds more complicated than it is. Let me show you the actual code:
package { import flash.display.MovieClip; import flash.events.Event; public class ParallaxDemo extends MovieClip { public var car:Car; public var road:Road; public var roadContainer:MovieClip; public function ParallaxDemo() { car = new Car(); road = new Road(); car.x = 275.0; car.y = 235.0; road.x = 0.0; road.y = 294.0; roadContainer = new MovieClip(); roadContainer.addChild( road ); roadContainer.addChild( car ); stage.addChild( roadContainer ); addEventListener( Event.ENTER_FRAME, onEnterFrame ); } public function onEnterFrame( evt:Event ):void { car.x = car.x + 10; roadContainer.x = roadContainer.x - 10; } } }
In lines 9 and 21 we create a new, blank MovieClip called roadContainer. Flash automatically sets its x and y values to 0.
In lines 22 and 23 we add the road and the car to the roadContainer, instead of the stage. In line 25 we add the roadContainer itself to the stage - and since the car and road are now added to the roadContainer, this lets us see them on the stage.
Line 32 is the most important part. Here, we move the roadContainer backwards by the same amount that we just moved the car forwards. This means that everything inside the roadContainer gets moved left by 10 pixels, but since the car has just been moved 10 pixels to the right, it stays in the centre of the screen.
It's a bit like running up the down escalator. If you walk up at the same speed that it's moving down, then to a person standing on the stairs next to you, you don't seem to be moving.
The overall effect:
The car stays in the centre! Great. Well, great apart from the gaping white hole. But we'll get to that. Now if you want to add more cars to the road, all you have to do is addChild() them to the roadContainer.
Step 12: Improve the Following
The trouble with just moving the whole container backwards a little bit every frame is that it's not very flexible. What if the player uses a powerup to teleport the car 100 pixels forwards instead of 10? What if we want to make the camera centre on a different car?
When the SWF first loads, the car's x-position is 275, and the roadContainer's x-position is 0. How do they each change over time?
- Start: car.x is 275, roadContainer.x is 0
- Frame 1: car.x is 285, roadContainer.x is -10
- Frame 2: car.x is 295, roadContainer.x is -20
- Frame 3: car.x is 305, roadContainer.x is -30
Do you see a general rule connecting the two? If not, check this out:
- Start: car.x is 275, roadContainer.x is 275 - 275
- Frame 1: car.x is 285, roadContainer.x is 275 - 285
- Frame 2: car.x is 295, roadContainer.x is 275 - 295
- Frame 3: car.x is 305, roadContainer.x is 275 - 305
The connection is a little more obvious now! Let's put it into code:
public function onEnterFrame( evt:Event ):void { car.x = car.x + 10; roadContainer.x = 275 - car.x; }
You can do what you like with the car now. Speed it up, teleport it forwards a random number of pixels, stop it moving - whatever! The camera will still follow it.
And if you want to make the camera follow a different object, just replace car.x with otherObject.x in the line we just changed.
Step 13: Extend the Road
Time to fix the endless white void of nothingness at the end of the road.
The simplest way to make the road longer is just to add another instance of the road symbol to the right of our existing symbol, like so: (lines 9, 22, 23, 27)
package { import flash.display.MovieClip; import flash.events.Event; public class ParallaxDemo extends MovieClip { public var car:Car; public var road:Road; public var road2:Road; public var roadContainer:MovieClip; public function ParallaxDemo() { car = new Car(); road = new Road(); road2 = new Road(); car.x = 275.0; car.y = 235.0; road.x = 0.0; road.y = 294.0; road2.x = road.x + road.width; road2.y = road.y; roadContainer = new MovieClip(); roadContainer.addChild( road ); roadContainer.addChild( road2 ); roadContainer.addChild( car ); stage.addChild( roadContainer ); addEventListener( Event.ENTER_FRAME, onEnterFrame ); } public function onEnterFrame( evt:Event ):void { car.x = car.x + 10; roadContainer.x = 275 - car.x; } } }
Here's how mine looks when it's run:
Oh dear. Better fix that gap.
Step 14: Mind the Gap
(If you're not drawing your own graphics, skip to Step 17.)
The problem is in line 22 of the above code, road2.x = road.x + road.width. The road's width value must be slightly larger than my road actually appears to be.
Even if your road doesn't have the same problem, it still might not fit together perfectly. So head back to your FLA and drag another Road symbol from the library to your Guide layer.
Make sure it's got the same y-position as the first road segment, then move it along horizontally until there's no gap:
Step 15: Tweak the Join
If the edges of your two symbols don't quite join together neatly, double-click one of them. You'll be able to edit it and immediately see how the changes you make affect the other:
Use this trick to adjust the edges of the symbol so that the join is clean.
Step 16: Work Out the Breadth
Instead of using road.width to figure out where the second road segment should be placed, we'll use a number I call the breadth.
To find this number for your road, just take the x-position of your right-most road symbol (in your Guide layer), and subtract the x-position of your left-most road symbol.
All you're doing here is figuring out how many pixels apart your two road segments have to be in order to get the same perfect join you just created in Flash.
Step 17: Add a Breadth Variable
Create a new Number variable, roadBreadth, and set its value to the number you worked out in the previous step: (If you're using my graphics, that number is 653.7.)
public class ParallaxDemo extends MovieClip { public var car:Car; public var road:Road; public var road2:Road; public var roadContainer:MovieClip; public var roadBreadth:Number; public function ParallaxDemo() { car = new Car(); road = new Road(); road2 = new Road(); roadBreadth = 653.7;
Step 18: Replace Width with Breadth
Replace the line:
road2.x = road.x + road.width;
with:
road2.x = road.x + roadBreadth;
Now test it out. There should be no gap:
Great! We still run into the endless white void, though...
Step 19: Make the Background Repeat Infinitely
We could create a road3 and a road4 and a road5 and so on, positioning them each to the right of the one before them, but no matter how many segments we created, the car would reach the end of them eventually.
For a better solution, think back to when you were a child. Did you ever play that game where you pretend the floor is made of lava, but you have to get to the other end of the room somehow? (If not, heck, go play it now, it's great fun.)
I don't know about you, but in my house, sofa cushions were considered to be lava-resistant, able to be used as stepping-stones. We only had a couple, which wasn't enough to reach the end of the room - but eventually I figured out how to make them reach further.
I'd lay the two cushions down to make a short path, and walk over to the second one. Then, I'd pick up the one behind me, drop it in front of me, and step across to it. By repeatedly picking up the one behind me and moving it in front of me, I could get to anywhere I pleased.
We can use the same technique to make the road last forever, without having to use more than two segments. All we have to do is detect when a road segment is "behind" the camera, and move it in front of it.
What do I mean by "behind" the camera? I mean that the right-hand edge of the segment is off the left-hand edge of the stage. We can use this if-statement to check that:
if ( road.x + roadBreadth + roadContainer.x < 0 ) { //road is behind the camera }
Curious as to why this works? If not, skip to the next step. Otherwise, let me explain:
- road.x is how many pixels to the right the left-hand edge of road is from the left-hand edge of roadContainer
- road.x + roadBreadth is how many pixels to the right the right-hand edge of road is from the left-hand edge of roadContainer
- roadContainer.x is how many pixels to the right the left-hand edge of roadContainer is from the left-hand edge of the stage (since roadContainer is constantly moving to the left, this will usually be negative)
- So, ( road.x + roadBreadth + roadContainer.x ) is how many pixels to the right the right-hand edge of road is from the left-hand edge of the stage.
Phew! OK, I'll admit, that's pretty confusing. If you'd like a deeper explanation, feel free to ask in the comments :)
Step 20: Move the Road in Front of the Camera
Now that we can tell when the road segment is behind the camera, we need to move it in front of the camera again.
If we moved the road to the right by roadBreadth number of pixels, it would be in the exact same place as the other road segment. So, we need to move it to the right by twice that amount:
if ( road.x + roadBreadth + roadContainer.x < 0 ) { road.x = road.x + (2 * roadBreadth); }
Put that in your onEnterFrame() function, and test it out:
As you can see, one road segment is repeating, but the other isn't yet.
Step 21: Move the Other Road Segment
We can just copy the above code for our other road segment, road2:
public function onEnterFrame( evt:Event ):void { car.x = car.x + 25; roadContainer.x = 275 - car.x; if ( road.x + roadBreadth + roadContainer.x < 0 ) { road.x = road.x + (2 * roadBreadth); } if ( road2.x + roadBreadth + roadContainer.x < 0 ) { road2.x = road2.x + (2 * roadBreadth); } }
Test it out again:
Fantastic! An infinitely looping, side-scrolling background :) Now to create the actual parallax effect...
Step 22: Create Rolling Hills
(Skip this step if you're using my graphics.)
We're going to need a repeating background to show off the parallax scrolling. I've chosen hills, but you could make buildings, forests, alien sculptures - anything you like!
There are a few tricks you can use to make what you draw look like it's further away than the car:
- Use duller colours (for instance, a darker shade of green for your grass)
- Draw less detail (no individual tufts of grass)
- Add a "blurred" effect to the edges (because the camera is focused on the car)
Follow the same basic steps we used for drawing the road:
- Make the symbol wider than the stage
- Align the symbol so that the registration point is at the left-hand edge
- Give it a class name, export it for ActionScript, and export it in the first frame
- Tweak the join to make sure two symbols fit together neatly
- Figure out the "breadth" of the symbol
Here's mine:
Step 23: Code the Hills
The code regarding the hills is almost exactly the same as the code we just wrote regarding the roads. Have a go at writing it yourself. I've pasted my AS file with all the new additions below, so you can refer to it if you like:
package { import flash.display.MovieClip; import flash.events.Event; public class ParallaxDemo extends MovieClip { public var car:Car; public var road:Road; public var road2:Road; public var roadContainer:MovieClip; public var roadBreadth:Number; public var hills:Hills; public var hills2:Hills; public var hillsBreadth:Number; public var hillsContainer:MovieClip; public function ParallaxDemo() { car = new Car(); road = new Road(); road2 = new Road(); roadBreadth = 653.7; hills = new Hills(); hills2 = new Hills(); hillsBreadth = 890.5; car.x = 275.0; car.y = 235.0; road.x = 0.0; road.y = 294.0; road2.x = road.x + roadBreadth; road2.y = road.y; hills.x = 0; hills.y = 14.5; hills2.x = hills.x + hillsBreadth; hills2.y = hills.y; roadContainer = new MovieClip(); roadContainer.addChild( road ); roadContainer.addChild( road2 ); roadContainer.addChild( car ); hillsContainer = new MovieClip(); hillsContainer.addChild( hills ); hillsContainer.addChild( hills2 ); stage.addChild( hillsContainer ); stage.addChild( roadContainer ); addEventListener( Event.ENTER_FRAME, onEnterFrame ); } public function onEnterFrame( evt:Event ):void { car.x = car.x + 10; roadContainer.x = 275 - car.x; if ( road.x + roadBreadth + roadContainer.x < 0 ) { road.x = road.x + (2 * roadBreadth); } if ( road2.x + roadBreadth + roadContainer.x < 0 ) { road2.x = road2.x + (2 * roadBreadth); } hillsContainer.x = 275 - car.x; if ( hills.x + hillsBreadth + hillsContainer.x < 0 ) { hills.x = hills.x + (2 * hillsBreadth); } if ( hills2.x + hillsBreadth + hillsContainer.x < 0 ) { hills2.x = hills2.x + (2 * hillsBreadth); } } } }
(The new lines are 12-15, 24-26, 35-38, 45-47, 49, and 67-75. How did you do?)
Here is the result:
You might be wondering why I've bothered creating a hillsContainer. If so, then nicely spotted! We could just addChild() the hills to the roadContainer - but creating a new container for the background is what lets us create the actual parallax effect.
Step 24: The Actual Parallax Effect
The effect only requires changing one line of code:
hillsContainer.x = 275 - car.x;
into this:
hillsContainer.x = (275 - car.x) * 1/5;
This makes the hills scroll at 1/5th the speed of the road and car.
It looks like this:
You don't have to use 1/5; make this value larger or smaller until the speed feels right to you.
Why does this work? Well, remember that we see things in a cone of vision; the further away something is the more of it we can see. So if we walk past two objects of the same size, but one is further away, the closer of the two will appear to move faster, like so:
Let's add another background layer, even further away than the hills.
Step 25: Create Mountains
This is exactly the same as creating the road and the hills, so I'm not even going to paste the code this time! All I'll do is post a picture of my mountains..
..tell you that my mountains' breadth is 751.5, x is 0 and y is 63.0; remind you to create a new mountainContainer MovieClip; and let you know that my mountains scroll at 1/16th the speed of my road.
Oh, and show you the result:
Step 26: Create the Sky
The sky is nice and easy. Since it's really far away, it scrolls so slowly that it looks like it's barely scrolling at all. Clouds and birds move, of course, and the Sun rises and sets, but none of this is due to any parallax scrolling effect. This means we don't have to make anything in the sky scroll!
(The exception to this is if the camera is travelling really, really fast - like, the speed of an aeroplane or rocket. Even then, be sure to make it scroll very slowly.)
So, no need to worry about breadth here, or creating an infinitely repeating image. It's still a good idea to make a skyContainer, though, just to keep things consistent. My sky is just a blue rectangle:
If you place it at x=0, y=0 it'll cover the whole stage. Here's what it looks like in the SWF:
Step 27: Create a Big Tree in the Foreground
We've created a lot of background objects, but nothing closer to the camera than the car. As I'm sure you realise, such an object would have to scroll faster than the roadContainer, so let's try this.
For my foreground object, I've drawn a tree:
The tree's a little different from the other objects we've made so far because it's not made to loop - it stands alone, it won't join to another tree standing next to it. This means that we only ever need one tree on screen at any one time (especially since it's so big!)
So we only need one Tree object in the code as well. Write the code for this object. If you're using my graphics, the starting x-position will be 780.0 and the y-position will be 175.0.
Since the tree will scroll, we still need a treeContainer, and we still need a treeBreadth. However, this time, the treeBreadth just controls the number of pixels between each tree. I've used a nice round 1000.0 for mine.
Step 28: Scroll the Tree
Since there's only one tree, the scrolling code is much simpler:
treeContainer.x = (275 - car.x) * 3; if ( tree.x + treeBreadth + treeContainer.x < 0 ) { tree.x = tree.x + (2 * treeBreadth); }
Nothing complicated :) Just note that it scrolls three times faster than the road. Here is the final result:
Congratulations! You've created a dynamically scrolling camera, infinitely repeating backgrounds, and a pseudo-3D parallax effect :)
Further Ideas to Try
Here are few more things you can do with the same code:
If you're creating a shoot-'em-up and you'd like all your explosions to appear closer to the camera than your enemies, simply create a new explosionsContainer, addChild() any explosions to that, and make it scroll at the same speed as the enemiesContainer.
Put the player's score, their lives counter, the mute and pause buttons, and any other parts of your game's interface into a single container. Place this container in front of all other containers, but don't make it scroll. This is an easy way to keep a game's camera and assets separate from its interface.
Try having one container stay still while making the containers in front of it and behind it scroll in opposite directions. This creates a cool rotation effect, as seen about five minutes into this clip from Disney's Snow White!
Conclusion
Thanks for reading this tutorial; I hope you enjoyed it. If anything was unclear at all, or if you'd like to ask any questions about the effect, please post a comment below.
Speaking of comments, if you create anything using this tutorial, I'd love it if you posted a link so I could see it :)
Comments