environments & tiles

Coding the perfect slope

This is very different from the other stuff i’ve posted so far. This is a technical post on how I made 45º slopes in my game.

First of all, this post is intended for those who are coding their own platformer physics. If you’re using an engine, the hard work might have already be done for you.

What the hell is a “perfect slope” anyways?

When I finished implementing square tiles in my game I was ready to make slopes, when I remembered countless games that had wonky slope physics. I didn’t want something like that for my game.

In those games the player would do things like these:

  • Slide down the slope when idle
  • Not be able jump off a slope
  • “Fall” off a slope when descending it
  • Stand on a slope with only the corner of their feet

Would you play a game if the player couln’t jump off a slope?

Me neither.

I decided i would have to either drop slopes or adress all those problems. I did the latter and it turned out pretty well (after about 2 weeks).

How my collision works

My collision is done in two passes, horizontal and vertical.

First i update the player’s x position and detect horizontal collisions, then I update the player’s y and detect vertical collisions. Here’s pseudocode:

“px” and “py” are the player’s positions, and “pSpeedX” and “pSpeedY” are the player’s velocities. the parameter passed to “collisionDetect” is the pass. 0 for horizontal, 1 for vertical.


Detecting collisions with slopes (45º only)

With square tiles it’s pretty trivial, since both are box-shaped. This is called an AABB (Axis Aligned Bounding Box) collision detection and it’s done like this:

Detecting collisions with square tiles are done in both passes.

With slopes it’s a little bit different:

First of all you need to know which direction the slope is “facing”. I do that with a boolean called “slopeDirection”.

You start by checking if the player is inside the slope’s bounding box before making further checks. You do that the same way we did with square tiles. Then, we have variables “collisionX” and “yTop” (forgive my bad naming conventions).

collisionX is the horizontal position where we will be checking for collisions. If the slope is facing right (slopeDirection == 1), we know the collision will only happen with the player’s bottom-right corner. When the slope is facing left, it happens with the bottom-left corner. We set this variable based on the slope’s direction.

yTop is the slope’s topmost position where collisionX is. This image explains it better:

Then we do a second check to determine if the player is intersecting an AABB where the topmost position is yTop. Since yTop changes when collisionX changes, this AABB’s height will change too.

This is important: While collisions with square tiles are detected and resolved on both passes, collisions with slopes are detected and resolved only on the second (vertical) pass!

Resolving the collision

OK, we know when the player’s inside the slope, but how do we make him react to it?

Easy:

We set the player’s topmost vertical position to yTop minus his height. This pushes the player up to the point where his feet is barely touching the slope.

Now the player reacts correctly to slopes, but we have a problem:

The “Heel/Toe Stand”

This one is particulary infuriating. Take a look:

What’s happening here is that since the player’s hitbox is rectangle-shaped, it stands on slopes with only one corner, leaving the player’s feet floating. Hitboxes shown below.

I fixed this by making a special slope-only collision “box” for the player. The word “box” is between quotes because it’s actually a line, not a box:

Now the player collides with square tiles using the green box and collides with slopes using the blue line. This ensures his feet are centered on the slope.

To achieve this we change the slope detection code to this:

Now “collisionX” is set to the center of the player’s collision box regardless of the slope’s direction, and instead of detecting intersections using the player’s box, we check intersections using the player’s line, with “collisionX”.

At this moment i thought i fixed it. Unfortunately i was wrong.

Inside-ground collision

Since the player collides with slopes using the blue line, some part of the green box remains inside the ground, and upon walking up this slope, that green part inside the ground collides with the side of A, preventing the player to walk up the slope.

Solution:

This is a tile-based environment, so when loading the level, i disable collision with the left side of all the tiles that have a slope to it’s left, and disable collision with the right side of all that have a slope to it’s right.

Other problems arised.

“Falling” off a slope

I thought everything was fixed and working, until i tried descending a slope. This happened:

Instead of smoothly descending the slope, i fell off it.

The fix was rather complicated, but worth it.

The player has a boolean “onGround”, determining if he is “grounded” and able to jump. This is set everytime the player collides with something on the vertical pass and his vertical speed is greater than zero (meaning he’s falling).

The slopes were only detected on the vertical pass, but I changed that and made the slope’s horizontal pass set a boolean called “descendingSlope” if the player is “grounded” and colliding with an enlarged version of it’s bounding box:

Notice I set “descendingSlope” to true if

  • It’s the horizontal pass
  • The player is “grounded”
  • The player is colliding with an enlarged version of it’s bounding box

What does this boolean do then?

On the update loop that runs every frame, if this boolean is set and if the player’s vertical speed is greater or equal to zero (means he’s falling), the player’s vertical speed will be set to same value as the horizontal, meaning the player will move downwards and horizontally with the same speed, making him move in a 45º angle and “stick” to the slope.

This variable needs to be reset to ‘false’ every frame after checking it!

Why do we check if the player is falling before “sticking” him to the slope?

If we don’t perform this check, the player will stick to the slope even when he’s going up, meaning he can’t jump off the slope.

Also, have you noticed that “descendingSlope” is only set to true in the detection code when the player is “grounded”? This is because we don’t want the player sticking to the slope before he even touches it.

Wait, what? More problems?

The “Ultimate Heel/Toe Stand”

You thought we already squished this bug. You were wrong.

The blue line should be touching the slope, but it isn’t, because the player is heel/toe standing on the square tile on the side of the slope.

Fix: Disable collision with all square tiles if the player is inside a slope’s bounding box.

If the player is inside a slope’s bounding box, we set a (yet another) boolean called “onSlope”.

This boolean is reset to 'false’ every frame along with “descendingSlope” and “onGround”.

Now that we have this boolean, we can disable collision with square tiles if it’s set. Slopes and squares must be checked separately though: all slopes must be checked, then all the squares must be checked, but only if onSlope == false.

Now for the final touch:

Resultant speed on slopes

If slope collision resolving only changes the player’s y position and speed, it means the horizontal speed stays intact. And if he’s going up or down at the same time his horizontal speed stays the same, it means his resultant speed is greater when on slopes than when on ground.

There’s an easy fix on the update loop though:

Before “maxSpeed” is changed, it represents the maximum horizontal speed he can achieve on a flat ground. If the player is on a slope and he’s grounded, we change that so that he doesn’t go faster on a slope than on ground.

We basically want the maximum resultant speed when on slopes (diagonal) to be equal to “maxSpeed”. But since “maxSpeed” is only horizontal, we need to find the horizontal component of this diagonal vector.

We need to find 'x’.

x*x + x*x = maxSpeed*maxSpeed
(x*x)2 = maxSpeed*maxSpeed
x
*x = (maxSpeed*maxSpeed)/2.0
x = sqrt((maxSpeed*maxSpeed)/2.0)
x = maxSpeed/sqrt(2.0)
x = maxSpeed*(1.0/sqrt(2.0))

Now the player runs on slopes with the same speed as when on ground.

This took about 2 hours to type, so reblogs are appreciated. I hope i helped someone with this huge post.

Also, would you like more technical posts like this, or only gameplay/progress posts from now on?

6

With over 200 elements this pixel style 3d western town construction kit offers a wide variety of options for city planners. It also comes with the option to kit bash your own buildings by combining windows, doors and beams with plane buildings to make your own variations. The set also contains environment tiles for immediate surroundings.

Models and textures by Csaba Baity

I've got a secret for y'all

If you don’t use loose substrate at all, there is an absolutely 0 chance your animal will become impacted unless you feed it a fuckton of mealworms/superworms/prey items that are too big.

Holy crap

I mean, my mind is reeling from this information.

No sand, no impaction… huh… never thought of it that way.

Remove the danger, there is no danger. 

I mean, part of being a reptile owner is keeping your animal in a safe environment, right?

Go with tile. Paper towels. Anything but loose substrate.

Tile can be literally cut to your cage size for less than $20 at lowes or home depot. It’ll last your animal a lifetime providing you don’t drop it. The most EASY thing to clean. Pick it out. Wipe it off. Put it back in. Holy shit it’s so simple, a caveman could do it!

Seriously, stop endangering your animals health with loose substrate. You’re spending a lot of money on sand, providing you clean it out as you should.

Tile is cheap, easy to clean, and it looks bitchin’. I’m honestly thinking of taking a chisel and carving little shapes into the tile.

*she has a water dish it was just being cleaned at the time

6

every now and again i go back to messing around with my WW style test map in unreal 4.

i tried to do the ocean as my first unreal material but i couldn’t get it right. tried it again last night with everything i’ve learnt since then and it turns out recreating the original WW ocean is actually rly easy 

updating it tho is a lot harder. i’m not happy with the refraction and i spent forever trying to figure out how to make procedural wind waker waves instead of using a single texture to fix tiling but maaaaaan, you just cant proceduralify WW’s waves (or do the foam that way. i already know thats gunna need a seperate mesh and material everywhere the water collides with geo :| )

i’ve been having trouble with normals too. WW’s obviously a very flat game and too much detail in the normals looks off. i’ve been hand painting them which works ok for tiled environment textures but i really want to incorporate zbrush into the work flow since thats such an easier way of making normals

Looking for environment pixel artist!

I’m putting out the call for a talented pixel artist with skill in environment and object design, as we’ll be needing a bit of freelance supplemental help on Hyper Light in the coming months. Please send your environment + tile + object examples (under 5 images) to alx@heart-machine.com BEFORE July 1st and we can talk!