2D Platform Games Part 5: Surface Dynamics (slippery and sticky platforms)
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 4: Moving Platforms and Crush Detection. 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.
So far we’ve covered static platforms and moving platforms and provided accurate collision detection for each. We’ve also started to create a framework which lets us configure the behavioral properties of individual platforms. This puts us in a good position to introduce new types of platforms, like the classic ‘slippery ice’ and ‘sticky’ platforms which are the examples I shall show below. These types of platforms which merely change the movement physics of the player don’t require any changes to the collision detection system, whereas ladders and pass-through platforms (where you can jump onto them from below but are solid on top) do, so we defer these to subsequent articles.
Setting up your platforms
Let us begin by creating a list of possible platform types. We’ll start with three – normal platforms, frictionless platforms (“ice”) and platforms with high friction (“sticky”):
// Possible platform types typedef enum { Normal = 0, Frictionless = 1, Sticky = 2 } SurfaceType;
We will then create a new struct
which describes the properties of each type of surface:
// Properties of each platform type struct Surface { // The image to use as a tile ImageBrush tile; // X-acceleration when accelerating in the current direction of movement float accXf; // X-acceleration when accelerating away from the current direction of movement (turning around) float accXb; // X-deceleration to be applied when player does not attempt to move left or right float decX; // The maximum allowed X and Y movement speeds float maxSpeedX; float maxSpeedY; };
First we allow each platform type to have its own sprite (image brush). This lets each platform type have a distinct visual appearance to make it obvious to the player what kind of platform we are standing on. Next, we define how the player should move when on the surface. These replace the global defaults we have used until now. Note that I have de-composed accX
into two new variables accXf
and accXb
. While completely optional, this gives greater flexibility over the movement of the player, enabling control of how difficult it is for him or her to stop and begin moving in the opposite direction.
In the definition of each individual Platform
, we add a member indicating the surface type of the platform:
// An individual platform or surface struct Platform { // The surface type SurfaceType surface; ... };
In the main game class definition, we add an array defining each surface and change the definition of accX
to use accXf
and accXb
instead. Note that we still keep these variables (along with maxSpeedX
and maxSpeedY
) and they will now refer to the physics model currently in use. The reason we need this is because we won’t always be on a platform; sometimes we’ll be in mid-air, but we want to keep using the same physics until the player lands on a different platform, however we can’t check which settings to use when we’re not standing on a platform, so we must keep a copy of them for when the player leaves the platform. If we don’t do this, players can cheat on ice and sticky platforms by jumping up to slow themselves down or speed themselves up.
We also get rid of the global image to use for platforms since we will now select a different image for each one:
/* Remove this */ // The bitmap brush to use to draw the platforms ImageBrush tile; /* */ ... // Platform type definitions Surface surfaces[3]; ... // The amount of X acceleration to apply when the player moves forward (current direction of travel) or backwards (opposite direction) // The amount of X deceleration to apply when the player does not attempt to move left or right // These are updated depending on the type of the last surface stood on float accXf, accXb, decX; ... }
In the class constructor, we replace the assignment of tile
with loaders for images for each platform type:
surfaces[Normal].tile = MakeBrush(MakeImage(L"tile.png"), Custom, D2D1_EXTEND_MODE_WRAP); surfaces[Frictionless].tile = MakeBrush(MakeImage(L"ice-tile.png"), Custom, D2D1_EXTEND_MODE_WRAP); surfaces[Sticky].tile = MakeBrush(MakeImage(L"glue-tile.png"), Custom, D2D1_EXTEND_MODE_WRAP);
We remove the assignments to accX
, decX
, maxSpeedX
and maxSpeedY
as these will be set to defaults whenever the player re-spawns (in case the player dies on an ice or sticky platform, we need to reset the physics model when the player re-spawns).
Next we define the properties of each surface type:
// Define surface types surfaces[Normal].accXf = 12.f * mScale; surfaces[Normal].accXb = 12.f * mScale; surfaces[Normal].decX = 15.f * mScale; surfaces[Normal].maxSpeedX = 5.0f * mScale; surfaces[Normal].maxSpeedY = 10.0f * mScale; surfaces[Frictionless].accXf = 24.f * mScale; surfaces[Frictionless].accXb = 24.f * mScale; surfaces[Frictionless].decX = 0.f * mScale; surfaces[Frictionless].maxSpeedX = 12.0f * mScale; surfaces[Frictionless].maxSpeedY = 30.0f * mScale; surfaces[Sticky].accXf = 1.f * mScale; surfaces[Sticky].accXb = 15.f * mScale; surfaces[Sticky].decX = 20.f * mScale; surfaces[Sticky].maxSpeedX = 2.0f * mScale; surfaces[Sticky].maxSpeedY = 10.0f * mScale;
These figures can take a bit of tweaking to get them to behave as you want; I have found the above values are fairly representative of most video games. The Normal surface uses the same settings we have used globally until now, and are the settings used by default.
As you can see, FrictionLess platforms cause acceleration twice as fast (24.f vs 12.f), have no deceleration at all in decX
which removes all friction, and your maximum speed is considerably faster, to allow the player to shoot back and forth on the ice.
In sharp contrast, Sticky platforms decelerate the player very fast when they are not trying to move; they also allow the player to stop and turn around very quickly, but acceleration and top speed are very slow, making it difficult to walk on the platform.
In SetupResources()
we add a line in the object creation loop to set the default surface type of each platform:
for (int o = 0; o < worldObjectCount; o++) { ... p.surface = Normal; ... }
and then at the end of the function, we configure a few platforms to use the new surface types:
// Slippery ice platform worldObjects[15].surface = Frictionless; worldObjects[16].surface = Frictionless; worldObjects[17].surface = Frictionless; // Glue tiles worldObjects[19].surface = Sticky; worldObjects[20].surface = Sticky;
In spawnPlayer()
, add code to reset the physics when the player spawns:
// Set defaults before we are standing on any platform accXf = surfaces[Normal].accXf; accXb = surfaces[Normal].accXb; decX = surfaces[Normal].decX; maxSpeedX = surfaces[Normal].maxSpeedX; maxSpeedY = surfaces[Normal].maxSpeedY;
Finally, in the rendering code, change the line which uses the old global tile to select an image depending on the object’s surface type:
SetBrush(surfaces[it->surface].tile);
So far, so good. This is all pretty standard stuff and there shouldn’t really be any surprises so far. If you run the program now you should see the visual appearance of each platform type (the ice and sticky platforms are on the right of the first screen in our example game world), but the player’s behaviour remains the same as before.
Altering Player Physics
The physics applied to the player should depend on the type of the platform we are standing on. So as soon as we land or walk onto a new platform, we update the current physics settings with the settings from that platform’s surface type as follows (this code goes immediately after the player’s position and platform currently standing on are updated, and right before checking the keyboard input):
// The physics applied to the player depend on the type of surface we are standing on if (standingPlatform) { accXf = surfaces[standingPlatform->surface].accXf; accXb = surfaces[standingPlatform->surface].accXb; decX = surfaces[standingPlatform->surface].decX; maxSpeedX = surfaces[standingPlatform->surface].maxSpeedX; maxSpeedY = surfaces[standingPlatform->surface].maxSpeedY; }
Note that if we are not standing on a platform (in mid-air), the settings are not updated or set to the normal defaults, but the current settings are retained instead. This prevents the ‘jumping cheat’ I mentioned earlier. This behaviour is optional and if you don’t want the player to be able to go whizzing off ice platforms at high speed, you may wish to reset the physics settings when the player enters flight.
We also need to change the processing of moving left and right with the correct amount of acceleration or deceleration:
// Move (accelerate) leftwards if (GetAsyncKeyState(VK_LEFT)) { /* Remove this line */ speedX -= LinearMovement(accX, updateTick); // How much acceleration to apply depends on whether we are trying to move // in the same direction as now, or the opposite way if (speedX <= 0) speedX -= LinearMovement(accXf, updateTick); else speedX -= LinearMovement(accXb, updateTick); moveRequest = true; } // Move (accelerate) rightwards if (GetAsyncKeyState(VK_RIGHT)) { /* Remove this line */ speedX += LinearMovement(accX, updateTick); if (speedX >= 0) speedX += LinearMovement(accXf, updateTick); else speedX += LinearMovement(accXb, updateTick); moveRequest = true; }
This code simply ensures that if we try moving in the current direction of travel, accXf
is applied, otherwise accXb
is applied.
That’s All She Wrote
The demo code linked at the top of the page includes all of these changes so you can see the surface types in action.
In Part 6 we will examine a true nightmare of collision detection and player physics handling as we introduce ladders to our platform game. Until next time!
-
January 21, 2013 at 21:432D Platform Games Part 4: Moving Platforms and Crush Detection « Katy's Code
-
January 28, 2013 at 21:292D Platform Games Part 6: Creating Platforms and Geometry the Player can be Inside (Ladders, Ropes and Water) « Katy's Code
-
February 19, 2013 at 05:422D Platform Games Part 11: Collision Detection Edge Cases for The Uninitiated « Katy's Code
-
January 4, 2014 at 00:372D Platform Games Part 12: A Framework for Interactive Game Objects | Katy's Code