In this short tutorial, we'll extend our platformer pathfinder so that it can deal with one-way platforms: blocks that the character can jump through and also step on. (Technically, these are two-way platforms, since you can jump through them from either direction, but let's not split hairs!)
Demo
You can play the Unity demo, or the WebGL version (100MB+), to see the final result in action. Use WASD to move the character, left-click on a spot to find a path you can follow to get there, right-click a cell to toggle the ground at that point, and middle-click to place a one-way platform.
Changing the Map to Accommodate One-Way Platforms
To handle the one-way platforms, we need to add a new tile type to the map:
public enum TileType { Empty, Block, OneWay }
One-way platforms have the same pathfinder weight as empty tiles—that is, 1
. That's because the player can always go through them when jumping up; they only stop him when he's falling, and that in no way impairs the character's movement.
We also need a function that lets us know if the tile at a given position is specifically a one-way platform:
public bool IsOneWayPlatform(int x, int y) { if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) return false; return (tiles[x, y] == TileType.OneWay); }
Finally, we need to change Map.IsGround
to return true
if a tile is either a solid block or a one way platform:
public bool IsGround(int x, int y) { if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) return false; return (tiles[x, y] == TileType.OneWay || tiles[x, y] == TileType.Block); }
That's the map part of the code sorted; now we can work on the pathfinder itself.
Adding New Node Filtering Conditions
We also need to add two new node filtering conditions to our list. Remember, our list currently looks like this:
- It is the start node.
- It is the end node.
- It is a jump node.
- It is a first in-air node in a side jump (a node with jump value equal to
3
). - It is the landing node (a node that had a non-zeo jump value becomes
0
). - It is the high point of the jump (the node between moving upwards and and falling downwards).
- It is a node that goes around an obstacle.
We want to add these two conditions:
- The node is on a one-way platform.
- The node is on the ground and the previous node was on a one-way platform (or vice-versa).
Including Nodes That Are One-Way Platforms
The first point: we always want to include a node if it's on a one-way platform:
if ((mClose.Count == 0) || (mMap.IsOneWayPlatform(fNode.x, fNode.y - 1)) ... mClose.Add(fNode);
Include Ground Nodes if Previous Node Was a One-Way Platform
The second point: we need to include a node if it is on the ground and the previous node is on a one-way platform:
if ((mClose.Count == 0) || (mMap.IsOneWayPlatform(fNode.x, fNode.y - 1)) || (mGrid[fNode.x, fNode.y - 1] == 0 && mMap.IsOneWayPlatform(fPrevNode.x, fPrevNode.y - 1)) ... mClose.Add(fNode);
All Together, Then...
Here's a refreshed list of node filter conditions; the algorithm will let through any node that fulfils any of the following requirements:
- It is the start node.
- It is the end node.
- The node is on a one-way platform.
- The node is on the ground and the previous node was on a one-way platform (or vice-versa).
- It is a jump node.
- It is a first in-air node in a side jump (a node with jump value equal to
3
). - It is the landing node (a node that had a non-zero jump value becomes
0
). - It is the high point of the jump (the node between moving upwards and and falling downwards).
- It is a node that goes around an obstacle.
And here's the code checking all these conditions:
if ((mClose.Count == 0) || (mMap.IsOneWayPlatform(fNode.x, fNode.y - 1)) || (mGrid[fNode.x, fNode.y - 1] == 0 && mMap.IsOneWayPlatform(fPrevNode.x, fPrevNode.y - 1)) || (fNodeTmp.JumpLength == 3) || (fNextNodeTmp.JumpLength != 0 && fNodeTmp.JumpLength == 0) //mark jumps starts || (fNodeTmp.JumpLength == 0 && fPrevNodeTmp.JumpLength != 0) //mark landings || (fNode.y > mClose[mClose.Count - 1].y && fNode.y > fNodeTmp.PY) || (fNode.y < mClose[mClose.Count - 1].y && fNode.y < fNodeTmp.PY) || ((mMap.IsGround(fNode.x - 1, fNode.y) || mMap.IsGround(fNode.x + 1, fNode.y)) && fNode.y != mClose[mClose.Count - 1].y && fNode.x != mClose[mClose.Count - 1].x)) mClose.Add(fNode);
How Filtering Looks With One-Way Platforms
Finally, here's an example of filtering with one-way platforms.
Conclusion
That's all there is to it! It's a simple addition, really. In the next tutorial in this series, we'll add a slightly more complicated (but still fairly straightforward) extension, allowing the pathfinding algorithm to deal with characters that are larger than 1x1 blocks.