> | | | | | | | | | | | > All About Slopes

All About Slopes

Posted on Friday, July 27, 2012 | 7 Comments

I recently received a very threatening letter warning me about the laws I was breaking by not adhering to the regulations set forth by the state regarding handicap accessibility in my privately-owned tile-based game.  Apparently, inclusion of sloped tiles is mandatory for any tile-based game, privately owned or not.  The letter, which contained a generous helping of the word "lawsuit", was sent by a lawyer that contacted me on behalf of a "Mr. Maton."  Could it be...?  I think perhaps my own creation is threatening me with lawsuits for not including sloped tiles in my game!

A sinister Otto demanding sloped tiles.
Just implement sloped tiles and nobody gets hurt.

Who could blame poor Mr. Maton?  It's not like he wants to live his life on top of a set of wheels, but he has no other choice.  His life must be hard enough as it is; the least we can do is make it a little easier by providing him with sloped tiles.  Besides, don't you think he will be overwhelmed with joy once he sees the hard work we put in to accommodate his needs?

If you've played any sort of platforming game, which is pretty likely considering you're reading my blog, you've probably come across slopes or ramps.  As a game developer, you may not think slopes are important to have.  It might just seem like a lot of extra work for no real benefit when the player can just jump up what would otherwise be sloped surfaces, but it really is important to have.

Slopes break up the monotony of a level so that it is not just completely flat and square.  They also allow for more interesting level design.  Plus, it just makes sense to include slopes because the player is not going to want to jump all the time, nor should he be expected to!  You can do a lot of cool things with slopes, especially if your game has simulated physics, but for now let's just focus on their main purpose, which is to move the player up or down in addition to left or right.

Before getting started with the code, I needed to make some new graphics first because I didn't have any for sloped tiles.   I wasn't interested in spending a whole lot of time on this part, so I just made the slopes in one color, based off the brown tiles.  I made both 45° and 22.5° slopes during this time because I knew I was going to be using both in my game.  22.5° slopes actually consist of two tiles and it was a little tricky figuring out where to place the pixels.

With my freshly-drawn slopes, I was ready to start coding.  Like most things in programming, there are many ways to approach the issue.  Ultimately, you need to do what works best for your particular project and for how your code is going to be used.  Since I am using the Flixel framework, I have to approach the issue a certain way.  I can't just test for collision between the level and the player using "FlxG.collide()" because that Flixel method only tests the bounding boxes and thus slopes wouldn't work.  Therefore, I need to write my own code that runs when the player is on a sloped tile.

This is the time in a programming project where you feel like you're starting to get into the deep end of the pool.  You're leaving the "I just need to change a few things in this code" part and entering the "I need to write an entire large section of code from scratch" part.  This can be pretty daunting at first, but every problem can be overcome by breaking it down into smaller problems.

There are a lot of things to think about when writing a new function, like how it's going to be used and what is going to use it.  In addition to Otto, I will have enemies that move along the level, including slopes, and also projectiles that will inevitably collide with a slope.  Basically, every object in my game that can collide with a slope will need to use this code.  I am eventually going to want to structure my code so that any object can use it, but for now I just wanted to focus on getting a working version of this function for just one object -- the player.

I only want to use the slope code when the player is on a sloped tile, so I need a way to check whether or not the player is on a sloped tile!  I figured the easiest way would be to give each tile a "slope" property, which determines if a tile is sloped or not and which kind of slope.  This property would be set whenever the tile is generated.  When I first started working on this, I tried adding a "slope" property to the FlxTilemap class and renaming it to FlxTilemapOtto, but I started getting errors from paths not matching and whatnot, so I scrapped that idea for the time being and moved on to something else.

My next idea was to check the documentation on FlxTilemap for anything about grabbing a specific tile.  Some of the method names sounded promising, like "getTileCoords()" and "getTile()", but it was still not quite as easy as I had hoped.  None of the methods return the actual tile object itself like I wanted.  My best bet was using the "getTile()" method, which returns the value of the tile.  This value actually turns out to be the number of the tile in the tilemap array, which corresponds to the specific tile image that gets drawn on the map.  The only thing to remember with this approach is that you need to keep your tile images in a certain order -- I will explain this more shortly.

The "getTile()" method takes two parameters, x and y, and returns the value of the tile located at that point on your map.  X and y in the method are measured in tiles, not pixels, so keep that in mind when you use it.  I can use this method by passing it the x and y coordinates of the player sprite divided by the tile width, like so:

Actionscript 3 Flixel FlxTilemap "getTile()" code.

You'll notice I use the Math.floor() function, which is just to ensure that there are no decimals.  I think it may also avoid a potential bug where a value gets rounded up and gets counted as the next tile over, but I'm not sure.  This code works just fine and that's all I care about.

However, this function doesn't give us exactly what we want.  We only really want to run the sloped tile code if Otto's bottom half is overlapping one.  If you recall, the default x and y coordinates of a FlxSprite are in the upper-left corner, so we need to add a little bit to both values.  When you think about it, all we're really doing is testing the value of a tile underneath a single point, so that single point in this instance needs to be the exact spot where we want Otto to start moving up or down a slope.

Since Otto's width is an even number, there isn't an exact center pixel to use for this test.  On my first version, I actually changed this pixel back and forth between the middle-left and middle-right pixels depending on which way you were heading on a slope, but as I've found out it's much better to just pick one point and deal with it.  This point needs to be on the very bottom row of the player sprite's pixels and as close to the middle as possible.

Also, to be completely sure that the points I used were accurate, I made a new FlxSprite, called "pointTest" which is essentially one red pixel.  When I assign the test point a value, I also assign it to the x and y values of "pointTest" to make sure that I am testing the right pixel.  My original version of the slope code was flawed because I updated the values of "pointTest" before calling "super.update()" and as a result the red pixel always seemed to lag behind the player.  Make sure to put all your collision code after "super.update()"!!

The red "pointTest" pixel in action!
The red pixel is where to check for collisions with slopes.

So, now we know how to check the value of the tile underneath the exact point we want to test, but the FlxTilemap still treats your sloped tiles as if they were blocks.  There are a few options at this point.  I could take all of the slopes tiles out of my main tilemap and then make a new map with only slopes in it.  With this method, I test for both collisions separately, instead of one or the other.  This seems like extra work to me, though, so I found a better way.

My solution is to take advantage of a FlxTilemap method called "setTileProperties()", because Tile objects have a property that determines whether or not they are solid.  Tiles that are not solid are ignored when checking for collisions.  So, when the level is first created in the code, I run a loop that sets the solid property to false for every tile that needs it.  I like this solution because it lets me keep my slopes as part of the level, which also helps me for when I make and export maps with Mappy.

Remember when I said you need to keep your tile images in a certain order?  This is because when we check for slopes, we're checking for tiles of a certain number.  In my tilesheet, tile 0 is nothing (no tile), tiles 1 through 15 are normal solid blocks, and tiles 16 through 21 are slopes.  By having this set-up, I can simply check to see if the value returned from "getTile()" is greater than 15.  If it is, that means there is a sloped tile at the point of the red pixel and we need to run some slope code!  In the future, when I have several sets of slopes with different graphics, I need to make sure to keep the same slope types together, even if it means separating the sets.

To check for slopes, we need to add an if...else statement to the current collision code.  If the value of "tileUnderneath" is greater than 15, run the slope code; otherwise, run the normal collision code.  I started by checking a single direction first -- moving left up a ramp -- because once you've got a single direction working, it's easy to get the others working.

Unfortunately, you'll have to wait until next time to get the juicy details of my slope code.  I told you we're in the deep end of the pool!  It might seriously even take me two more long posts to explain all of the slope code and this version is much different than what I'm working on now (which I will explain as well in the future).  Still, it is nice for me to re-think through all of my slope code while writing this post as it helps solidify my understanding of it.

In the meantime, I leave you with this, my first working version of sloped tiles!  Please note that jumping does not work while on sloped tiles in this version and neither does falling onto a sloped tile.  You may encounter other bugs.  I experienced a very, very rare bug where I would slip through two sloped tiles if I turned and started moving the other direction at a specific spot, but I suspect this had something to do with the fact that I used two different test points for the slope in this version.  Also, I encourage you to leave a comment if you have something you'd like to say or ask!

Thanks for reading!

Comments:7

  1. Watch out for the laws man!

    ReplyDelete
  2. Nice read! Really enjoying your blog

    ReplyDelete
  3. Neat stuff man, really useful for us who are kinda lost. I also like the way you write, not as mechanic and straight-forward as many tutorials like this.

    keep up the good work!

    ReplyDelete
    Replies
    1. I'm glad this has helped you! Thanks for your feedback, it means a lot to me!

      Delete
  4. Wow, I really took slopes for granted in retro games! This makes me think of how fun it was on Mario games when you got to run down a hill of some sort, or Donkey Kong Country. You'd get to gain speed and it really did sort of raise excitement in an otherwise square world.

    ReplyDelete
    Replies
    1. Exactly! Granted, the slopes I'm making don't offer resistance or extra acceleration, but they still break up the monotony of the level. I wish there were built-in support for slopes on the Flixel framework because I don't really know which approach is best in terms of programming them.

      Delete

Powered by Blogger.