Home > Game Development Walkthroughs > 2D Platform Games Part 5: Surface Dynamics (slippery and sticky platforms)

2D Platform Games Part 5: Surface Dynamics (slippery and sticky platforms)

January 21, 2013 Leave a comment Go to comments

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!

Advertisement

Share your thoughts! Note: to post source code, enclose it in [code lang=...] [/code] tags. Valid values for 'lang' are cpp, csharp, xml, javascript, php etc. To post compiler errors or other text that is best read monospaced, use 'text' as the value for lang.

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 )

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: