2D Platform Games Part 3: Scrolling and Parallax Backgrounds
IMPORTANT! All of the articles in this series require you to have either Visual Studio 2010 or the Visual C++ 2010 Redistributable Package (4.8MB) installed in order to be able to try the pre-compiled EXEs provided with the examples. The Redistributable Package can be freely bundled with your own applications.
This article builds upon the demo project created in 2D Platform Games Part 2: Collision Detection Tweaks. Start with 2D Platform Games Part 1: Collision Detection for Dummies if you just stumbled upon this page at random!
Download source code and compiled EXE for the code in this article.
While it is perfectly acceptable to have a platform game with each level or section based on a single screen, some games require a larger world, and to display this we need to allow the world to scroll (from side to side, up and down or both) on-screen. In this article we will learn how to add scrolling to our project, as well as so-called parallax backgrounds which are background images which also scroll, but slower than the foreground level, giving a “2.5D” impression of depth.
As usual we’ll be using Simple2D to cut down on messy DirectX drawing code, and as usual the maths and concepts apply to all game engines.
In The Bad Old Days
If you don’t want a historical lesson, feel free to skip to the next section.
Scrolling was really rough on the nerves of programmers back in the days of 8 and 16-bit home computers. Coders for machines like the Archimedes were lucky: by cunningly altering the start address of video memory by 1, 2 or 4 bytes (depending on the number of colours per pixel – 256, 65536 or 16.7 million), you could give the instant impression of scrolling left or right by 1 pixel. To scroll up and down, you added or subtracted the number of bytes a single row of pixels occupied in screen memory.
Other platform programmers were not so lucky. To scroll right for example, you might have to copy every column of pixels one column to the left, leaving a junk column on the right-most side of the screen which could be replaced with new content. This of course means that every pixel in every polygon and sprite has to be calculated and plotted individually, which is a nightmare. On top of that, bulk memory copies are very expensive on old computers, so this could use up a significant amount of the total processing time available to draw the scene; it may also be so slow that obvious screen tearing is apparent as the screen refreshes part-way through a copy (if you know what back-buffering is, this was an option together with V-sync to solve that problem, but doubled the amount of video memory required – something not available on all systems of the time).
Still other platform programmers were even more unlucky. Some computers like the ZX Spectrum had video memory which was organized very strangely. Rather than each byte or two comprising the colour of a pixel and ordered in rows from left to right and top to bottom, the Spectrum’s video memory layout consisted of chunks of memory each representing an 8×8 block on the screen – from left to right and top to bottom (if I remember correctly). The next byte in memory after the bottom-right corner of one block represented the top-left corner of the next block, making trivial memory copies to scroll the screen impossible.
Such old-school scrolling is a fascinating topic, and I encourage you to read more about it around the web if you’re interested.
DirectX
With modern GPUs and DirectX, we are held under no such constraints. All rendering is done to an off-screen render target, which is essentially a memory buffer – usually in the graphics card itself – and when rendering is completed, the current on-screen display is switched with the buffer. On the next frame, the memory that was being used to display the screen on the last frame is now the off-screen buffer which is rendered to. At the end of rendering, the buffers are switched again, and so on. This is called back-buffer flipping and the technique of using two memory buffers for the current and next frame is called double buffering.
On top of this, when you render geometry or pixels to the render target, DirectX automatically clips (ignores) any which are outside its dimensions, so there is no need to worry about having to calculate how to plot half an object. DirectX does the clipping for you.
All of this is a boon as far as scrolling goes, because we don’t have to worry about partial object clipping or screen tear, with the downside that the entire screen must be rendered every frame (as opposed to the old techniques where only the scrolled in row or column needs to be plotted). In practice, this is generally irrelevant, but it’s worth knowing how technology and techniques have changed.
As a final insult to traditional methods, Direct2D allows us to apply an arbitrary transformation to any geometry – which occurs automatically when you render it, once the transformation has been set. So if we want to scroll the game world 200 pixels to the left (giving the impression of the player moving to the right), all we have to do is set a transform which specifies that everything to be rendered should be rendered 200 pixels left of the specified co-ordinates. Therefore, we don’t even have to re-calculate the on-screen co-ordinates of any objects when we scroll!
What this all boils down to, then, is that all we have to do to enable scrolling is to calculate the transformation we need to apply to objects to be rendered when the scene is drawn each frame. But first, we’ll need some game world objects to test it with.
Adding world geometry beyond the screen extents
There are no special constraints on where geometry can be, so if we want it to lie outside the starting screen we just have to specify co-ordinates less than 0 or greater than the screen resolution. Let’s create a new level which we can use over the next several articles to demonstrate a number of features. Replace the landscape definition in SetupResources()
with:
worldObjectCount = 22; // Create the landscape definition ObjectDefinition objdef[] = { // Gently sloping straight surface { 80*6, 80*1, Rectangle, .5f, .5f, PointBottomLeft, 0.f, -10.f, PointBottomLeft, 0.f, PointBottomLeft, 80, ResolutionY - 200 }, // Block to help climb up steep sloping wall { 80*1, 80*1, Rectangle, .5f, .5f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointBottomLeft, 250, ResolutionY - 60 }, // Steep sloping wall { 80*8, 80*1, Rectangle, .6f, .6f, PointBottomLeft, 0.f, 0.f, PointBottomLeft, 75.f, PointTopLeft, 240, ResolutionY - 250 }, // Edge walls // (32 = one screen width, 24 = one screen height, when scaled at .25f) { 80*96, 80*1, Rectangle, .25f, .25f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointBottomLeft, 0, ResolutionY - 20 }, { 80*48, 80*1, Rectangle, .25f, .25f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 90.f, PointTopLeft, 20, -ResolutionY }, { 80*48, 80*1, Rectangle, .25f, .25f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 90.f, PointTopLeft, ResolutionX*3, -ResolutionY }, // Gently sloping curved surface { 150, 35, Ellipse, 1.f, 1.f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 456, 378 }, // Square blocks ('stairs') { 80*1, 80*1, Rectangle, .75f, .75f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 20, ResolutionY - 80 }, { 80*1, 80*1, Rectangle, .4f, .4f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 20, ResolutionY - 110 }, // Two sloping platforms on left-hand side to give access to a long fall through a thin platform { 80*5, 80*1, Rectangle, .25f, .25f, PointTopLeft, 0.f, 10.f, PointBottomLeft, 0.f, PointTopLeft, 20, 200 }, { 80*20, 80*1, Rectangle, .25f, .25f, PointTopLeft, 0.f, -15.f, PointBottomLeft, 0.f, PointTopLeft, 100, 150 }, // Very thin platform which moves left and right (to test fall-through and relative movement) { 80*20, 80*1, Rectangle, .1f, .05f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 420, 250 }, // Platform which moves up and down { 80*6, 80*1, Rectangle, .3f, .3f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 250, 180 }, // First object not on the first screen { 80*20, 80*1, Rectangle, .25f, .25f, PointTopLeft, 0.f, -10.f, PointBottomLeft, 0.f, PointTopLeft, 500, ResolutionY-20 }, { 80*20, 80*1, Rectangle, .25f, .25f, PointTopLeft, 0.f, 10.f, PointBottomLeft, 0.f, PointTopLeft, 500+80*20/4, 390 }, // Frictionless (slippery/ice) platform { 80*9, 80*1, Rectangle, .3f, .3f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 5.f, PointTopLeft, 637, 286 }, { 80*20, 80*1, Rectangle, .3f, .3f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 850, 305 }, { 80*20, 80*1, Rectangle, .3f, .3f, PointTopLeft, 0.f, 0.f, PointBottomLeft, -10.f, PointTopLeft, 1325, 305 }, // Ladder { 20*1, 20*18, Rectangle, 1.f, 1.f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 500, -200 }, // Sticky (glue) tiles { 80*8, 80*1, Rectangle, .5f, .5f, PointTopLeft, 0.f, 0.f, PointBottomLeft, 0.f, PointTopLeft, 930, 140 }, { 80*6, 80*1, Rectangle, .5f, .5f, PointTopLeft, 0.f, -15.f, PointBottomLeft, 0.f, PointTopLeft, 692, 204 }, // Platform above screen height { 80*20, 80*1, Rectangle, .3f, .3f, PointTopLeft, 0.f, 15.f, PointBottomLeft, 0.f, PointTopLeft, 520, -200 } };
You can see the final intended use for each of these platforms in the comments, but here we are only interested in scrolling around to make them visible (or hidden).
World layout
The maths for calculating how much to scroll is a little abstract so I have drawn a diagram to illustrate the concept, see Figure 1.

Figure 1. Our game world split into screen-sized grid rectangles, with some important areas highlighted
In the previous demo code, the game world occupied the bottom-left black square in Figure 1. Our new geometry spans 3 screen widths and 2 screen heights. We have considered the bottom-left corner of the first screen where we initially placed objects to be the bottom-left corner of the game world, ie. (0, 480) when using a resolution of 640×480 (x increases as you move right, y increases as you move down). The top-right corner is therefore 3 screen widths more and 2 screen widths less than this, ie. (1920, -480).
NOTE: Before we continue, please note that using the screen resolution as a basis for level size is not a smart choice, as most games allow the resolution to be changed. Also note that your choice for the location of the world origin and extents (width and height) is entirely arbitrary, as is which co-ordinates represent the bottom, top, left and right-most position in the game world. The illustration above simply shows our layout so you can follow the maths below. Normally, the world origin should be the top or bottom-left corner of the world, or its centre point (so that level designers can expand in all four directions), and the size of the game world should be measured in arbitrary distance units of your choice, rather than screen pixels. When using arbitrary distance units you can just substitute the extents in your distance units for ResolutionX and ResolutionY below, but you will need to adjust the calculation accordingly if you move the world origin.
NOTE: We use both horizontal and vertical scrolling below. If you only want your level to scroll one way or the other and not both, you can simply omit the code for the axis of movement you’re not going to use.
Keeping the player in the centre of the screen
A good place to start with the scrolling calculation is just to adjust the scroll amount such that the centre point of the player is always the centre point of the screen. Where playerX
and playerY
are the top-left co-ordinates of the player’s current position, playerW
and playerH
are the player’s width and height and ResolutionX
and ResolutionY
are the screen dimensions – all in pixels – we can keep the player in the centre of the screen as follows:
float scrollX = ResolutionX/2 -playerW/2 - playerX; float scrollY = ResolutionY/2 - playerH/2 - playerY;
We simply take the desired on-screen top-left co-ordinate of the player, which is half the width and height of the screen subtracted by half the width and height of the player (so that the player centre is the screen centre), then subtract the player’s position in the game world. Note, subtraction, not addition. When the player moves to the right, all of the objects on the screen should scroll to the left (a negative X co-ordinate translation of the geometry), so the further right the player is, the further left (negatively in X) the geometry should be scrolled.
Clamping the scroll at the level boundaries
The technique above works, but when the player is outside the blue area shown in Figure 1, part of the screen rendered will be outside the game world and therefore empty. Modern games work around this by only scrolling when doing so guarantees the entire screen will be inside the game world, and plotting the player at a non-central position if he or she is near the edge.
Consider again the blue region in Figure 1. When the player is inside it, we can be sure that if we plot the player in the centre of the screen, the rest of the screen will be part of the game world too. Outside the blue area, we need to cap the amount of scrolling (horizontally if outside the left or right-hand edges, and vertically if outside the top or bottom edges), and draw the player at the correct position.
Expanding on this, look at the bottom-left screen rectangle in Figure 1. When the player is to the left of the left-hand edge of the blue region (regardless of his or her vertical position), no horizontal scrolling occurs. When the player is below the bottom edge of the blue region (regardless of his or her horizontal position), no vertical scrolling occurs. In other words, when the player is in the bottom-left quarter of this screen, there should be no scrolling at all and the player should be plotted somewhere left and down of the screen centre.
The mathematics for horizontal scrolling to satisfy all these conditions is as follows:
float scrollX = ResolutionX/2 - playerW/2 - playerX; float levelExtentX = static_cast<float>(ResolutionX*3); if (playerX < ResolutionX/2 - playerW/2) scrollX = 0.0f; if (playerX > levelExtentX - ResolutionX/2 - playerW/2) scrollX = -(levelExtentX - ResolutionX);
First we take the total width of the level in levelExtentX
(3 screen widths here). If the player is to the left of the left-hand edge of the blue region in Figure 1, the amount to scroll in X is set to zero so that the left-hand screen pixel column will be the left-hand pixel column of the game world. If the player is to the right of the right-hand edge of the blue region, the amount to scroll in X is set to the width of the level minus one screen width (remember it is set negative because scrolling to the right needs us to move all the geometry to the left).
The code for vertical scrolling is similar:
float scrollY = ResolutionY/2 - playerH/2 - playerY; float levelExtentY = static_cast<float>(ResolutionY) * 2.f; if (playerY >= ResolutionY/2 - playerH/2) scrollY = 0.0f; if (playerY < -levelExtentY + ResolutionY + ResolutionY/2 - playerH/2) scrollY = levelExtentY - ResolutionY;
If the player is below the bottom edge of the blue region in Figure 1, the amount to scroll in Y is set to zero so that the bottom-most screen pixel row will be the bottom-most pixel row of the game world (recalling that the Y extent of our level goes from -480 to 480 so the point at which we need to stop scrolling downwards is 240 minus half the height of the player). If the player is above the upper edge of the blue region (calculated as minus the Y extent of the level plus one screen height – giving -480 for the top of the game world – plus half the screen height – giving -240 which is a point on the upper edge of the blue region – minus half the player height to correct for the size of the player’s sprite), the amount to scroll in Y is set to the height of the level minus one screen height. This time the scroll amount is set positive rather than negative, as when we scroll upwards, we have to move all the geometry downwards – a positive translation in the Y co-ordinates of the geometry.
Performing the scroll
Now we have calculated how much to displace the geometry by, implementing it is a breeze with Direct2D. If you add the above code to the start of DrawScene()
, you can make it take effect with one additional line before you render anything:
Screen->SetTransform(D2D1::Matrix3x2F::Translation(scrollX, scrollY));
(where Screen
is an instance of ID2D1RenderTarget
).
IMPORTANT! Once you have drawn the geometry and the player, it is critical to reset the world transform on the render target, otherwise computations on the geometry on the next frame (such as fetching bounding boxes) will return results relative to the transform. Reset at the end of DrawScene()
as follows:
Screen->SetTransform(D2D1::Matrix3x2F::Identity());
Note that if you want to draw a HUD (heads-up display) of scores, lives etc. you will want to do this before rendering the HUD so that it always appears in the same fixed location on the screen.
If you now run the modified source code, you should have a beautifully scrolling game world!
Clipping
At the beginning of the article I mentioned that DirectX handles clipping of partially visible objects for us. However, for objects we know are completely off-screen, even sending them to DirectX at all is a waste of time, and it is easy to figure out which objects are completely outside the bounds of the display by comparing the bounding box of the visible area with the bounding box of each object, then discarding any which don’t overlap.

Figure 2. The objects in the red bounding boxes should be rendered, but the objects in the green bounding boxes should be clipped.
In Figure 2 we consider the case of horizontal clipping only. The black rectangle is the visible area of the game world (that which will be rendered on the screen), and the other rectangles are the bounding boxes of various objects in the world. To determine whether an object should be clipped (for horizontal scrolling only), we compare the X co-ordinates of the left and right hand edges of its bounding box with the X co-ordinates of the left and right hand edges of the visible area (screen)’s bounding box. Let’s look at the 6 possible cases shown:
- Both edges are within the edges of the visible area. It should be rendered.
- The right-hand edge is in the visible area (greater than the screen left-hand edge). It should be rendered.
- The left-hand edge is in the visible area (less than the screen right-hand edge). It should be rendered.
- Both edges are outside the visible area but they are on opposite sides meaning the object is wider than the screen, so it should be rendered. Note, this is equivalent to: the right-hand edge is greater than the screen left-hand edge (as in case 2) and the left-hand edge is less than the screen right-hand edge (as in case 3).
- Both edges are outside the visible area on the same side. It should be clipped. Note, this is equivalent to: the right-hand edge is not greater than the screen left-hand edge (opposite of case 2) and the left-hand edge is less than the screen-right hand edge (same as case 3).
- Both edges are outside the visible area on the same side. It should be clipped. Note, this is equivalent to: the right-hand edge is greater than the screen left-hand edge (same as case 2) but the left-hand edge is not less then the screen-right hand edge (opposite of case 3).
Crunching it all down, this basically means that whenever the left-hand edge of the object’s bounding box is less than the screen’s right-hand edge, and the right-hand edge of the object’s bounding box is greater than the screen’s left-hand edge, we should render the object, otherwise we should clip it.
The principle for vertical clipping is identical and the two can be easily combined. Here is the code, which you can replace the original object drawing code with:
// Draw the landscape objects int clippedScenery = 0; for (int i = 0; i < worldObjectCount; i++) { Geometry &g = worldObjects[i]; D2D1_RECT_F bounds = g.GetBounds(D2D1::Matrix3x2F::Translation(-scrollX, -scrollY)); if (bounds.left < -scrollX + ResolutionX && bounds.right >= -scrollX && bounds.top < -scrollY + ResolutionY && bounds.bottom >= -scrollY) { SetBrush(tile); g.Fill(); } else clippedScenery++; }
Remember that since the geometry moves in the opposite direction to our perception of where the screen ‘moves’, we have to negate the scroll amounts to get the correct bounding box for the screen in the if
statement. Simple2D’s GetBounds()
function respects the current world transform so we must undo it by providing the reverse translation prior to getting each object’s bounding box. Note that if you use Direct2D directly, an ID2D1Geometry
object’s GetBounds()
function ignores the world transform so you don’t need to worry about this.
Since there is no visible difference when clipping is working correctly, we count up how many objects have been clipped in clippedScenery
to check that it is working. This variable is optional and only used for information. After resetting the world transform, you can display it with code like this:
Text(25, 25, "Clipped " + StringFactory(clippedScenery) + " off-screen objects");
Congratulations! You now have a scrolling game with clipping optimization!
Static backgrounds
Adding a background is a quick and easy way of making your project feel more like a game, and in Simple2D it is trivial. You can easily add a static background which remains fixed even in a scrolling level as follows:
In the class definition:
Image background;
In the class constructor:
background = MakeImage(L"bg.png");
In DrawScene()
(note: this must go before you set the scrolling transform otherwise the background will scroll with the level! It must also go before you draw the world objects so that it appears behind them and not in front; I placed it as the first line of code in DrawScene()
):
background->Draw(0, 0);
The parameters simply specify that the top-left corner of the image should be at the top-left corner of the screen. You can then make a background image with the same resolution as the screen resolution of your game to exactly cover the screen.
Parallax backgrounds
Parallax is an old-school term referring to creating a fake cartoon-like 3D illusion by having all your objects on the same 2D plane but moving some faster than others (and possibly drawing the slower ones in darker or less opaque colours) to give the illusion of depth. Parallax is only used as a visual effect and cannot be used for quasi-3D collision detection or any other such purpose.
By way of example, this video shows probably the most classic use of parallax of them all, the so-called parallax starfield:
The principle here is that all the stars are given only 2D co-ordinates and speeds. The faster-moving stars are rendered slightly larger (or in a brighter shade of white) to give the appearance of them being closer to the viewer.
In our demo project, we’ll make a background which sits on top of the static background but behind the platforms, which scrolls as the game world scrolls, but slower than the world geometry. If you are not quite sure how this will look, try the demo at the top of the page to see the effect we are aiming for.
We are just going to scroll the background horizontally in our demo (although there is no reason why it can’t scroll vertically as well), so to start with we’ll need an image which is the height of the screen, wider than the width of the screen but not as wide as the entire level. Given that our sample level is 3 screen widths, we’re going to arbitrarily use an image which is 2 screen widths wide. It’s important that you use transparency when creating the image so that the static background shows through non-solid parts of the parallax background, otherwise the static background will be completely occluded. I recommend using PNG format files for best results.
My friend coined the term ‘mediumground’ to describe the parallax background so I’ll stick with that for amusement value. Start by preparing the image:
In the class definition:
Image mediumground;
In the class constructor:
mediumground = MakeImage(L"mg.png");
In DrawScene()
, after the static background is drawn and the scroll amount calculated, but before the world transform is set:
// Draw a portion of the mediumground based on the scroll amount float scrollPc = -scrollX / (levelExtentX - ResolutionX); D2D1_SIZE_F mgSize = mediumground->GetImage()->GetSize(); float mgX = (mgSize.width - ResolutionX) * scrollPc; mediumground->DrawPartWH(0, 0, static_cast<int>(mgX), 0, ResolutionX, ResolutionY);
Uh oh, it looks like more maths!
First we calculate the fraction (from 0-1, ie. the decimal percentage) of how far horizontally the level is scrolled, relative to its total width minus one screen width. Since the level cannot scroll more than the level’s total width minus one screen width (because we clamped horizontal scrolling when the player is near the right-hand edge of the level earlier), this gives a value from 0-1.
This percentage is then used to determine which section of the mediumground to render. First we get the width of the mediumground, then we determine the left-hand X co-ordinate within it to render from. This is done by subtracting one screen width from the mediumground width and multiplying by the scroll percentage calculated above. In this way, when we are at the left-hand edge of the level, the left-hand X co-ordinate to render from will be zero, and when we are at the right-hand edge (or at least we have scrolled right as far as we are allowed to), the left-hand X co-ordinate to render from will be the width of the mediumground minus one screen width, which is exactly what we want, since we are always going to render one screen’s worth of mediumground.
Finally, we render a portion of the mediumground, specifying in the first two parameters that the top-left corner of the selected portion will be at the top-left pixel of the screen, then specifying the top-left X and Y co-ordinates and the width and height of the portion to render in the remaining parameters.
With the changes in place, we can now try the final product!
Cut Through, Like Butter
In Part 4 we’ll return to game mechanics and look at how to create moving platforms with working collision detection, and how to check if the player has been crushed by moving objects in the game world. Until next time…
Katy this is awesome… I don’t know of the difficulty or if it could be a future Part, but having a day/night cycle would be awesome!
3 months later… (sorry about that)
Day and night cycle is pretty simple to implement and there are a few ways of doing it. First you’ll need a timer running in your code which is trivial to implement.
If we talk only of the backgrounds, you can for a simple start use two backgrounds, one for day, one for night, render them both at the same location every frame, and as the day/night cycle proceeds, gradually adjust the transparency of each such that as night approaches, the night background becomes more opaque and the day background becomes more transparent etc.
If you want full daytime and full nighttime for a period, and only transition periodically, you can just build in a couple of extra timers for this and when those timers are running, render either the day or night background at full opaqueness, and the other one not at all.
For all of the above, if you’re using Simple2D, you can use either LinearMovement or the Animation classes to track the alpha (transparency) of each background. If you are using the technique directly in the above paragraph, you can use AnimationChain to handle it all for you.
If you want the game objects to change as the day/night cycle progresses, you can use the same technique and provide night-time textures for each game object.
There are other ways to do it too: you can use the Simple2D effects framework (or just render a partially transparent rectangle over the whole screen after you’ve rendered the game world) such that the rectangle’s colour/hue/saturation changes gradually. This will alter the colour of everything on the screen in the same way without you needing to have two graphics for every object and background, but you lose the flexibility of being able to alter the colour of each object individually as the cycle progresses. You can of course, also use a mixture of both techniques.
Hope that helps!