Post subject: Algorithms: pathfinding in generated platforming levels
Joined: 7/2/2007
Posts: 3960
This is a problem I've been mulling over for a bit; I'm interested in seeing how other people approach it. I have a program I've written that generates cave systems, like this: I'd like to use it for a Metroid-style game of exploring, finding powerups that let you get past barriers, etc., but to do that I need to be able to programatically figure out what parts of the map are accessible to the player. That map above has some areas that can't be reached (straight up from the middle, for example, or the long vertical cave on the right side) and many that can't be exited once entered (most of the rectangular rooms, for example). My starting assumptions: the player is 3 blocks tall and 1 block wide. He can jump 3 blocks into the air (i.e. if there is a wall that is as tall as he is, he can jump onto it). He can jump over a gap that is five blocks wide (total jump length 6 blocks). He cannot crawl, turn into a morph ball, slide, or anything like that. I started working on an algorithm that starts with a known-accessible block and tries to find out what blocks are reachable from it. The algorithm looks roughly like this:
Pop accessible block A off of queue
Foreach distance d < player jump length:
  Look at space B d to the side of A
  Is that space occupied by a block:
    B is accessible by walking/jumping. Enqueue it
    # Abstracted away: checking for ceiling room, horizontal barriers
  Else:
    Look above B for the closest floor C with enough headroom above it for the player to fit
    Is it lower than the player's max jump height:
      C is accessible by jumping. Enqueue it
      # Abstracted away: previously-jumped-to targets; decaying jump height with distance
    Look below B for the closest floor D:
    Is D far enough below C for the player to fit:
      D is accessible by dropping down. Enqueue it
      # Abstracted away: horizontal barriers
  Repeat
Repeat
All of the "abstracted away" notes are basically things that this "teleport horizontally over and then look up and down" algorithm doesn't handle well. There's also messiness with things like a tunnel that is reachable by jumping, but only if you start out above it (there's an example of that down and a bit left of center in the example map). Basically I think this approach is flawed, but I'm having difficulty thinking of one that would work better. If you think the problem's interesting, I'd love to hear your thoughts on it. Edit: I've come up with an alternative approach that I think is less error-prone. I've written a description with images here. Thoughts on that approach?
Pyrel - an open-source rewrite of the Angband roguelike game in Python.
Active player (348)
Joined: 3/21/2006
Posts: 940
Location: Toronto, Canada
Are you using this as a base level design (i.e. use the randomly generated layout and bugtest/change it) or are the caves going to be randomly generated every time you play? If you're placing item and enemy spawn points in one solid layout then you don't really need to write algorithms, just do a ton of bugtesting.
My current project: Something mysterious (oooooh!) My username is all lower-case letters. Please get it right :(
Joined: 7/2/2007
Posts: 3960
Not much point in writing a robust random map generator if I'm only going to use it once. :) The idea is to remake the map every time you play; I'd have stuck "procedurally" into the topic title if it would have fit.
Pyrel - an open-source rewrite of the Angband roguelike game in Python.
Editor, Active player (297)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Ooo. 2D pathfinding is very simple if you allow free movement (i.e. flying). . Just use djikstra's algorithm. For efficiency, only consider points that are in the corner of something (i.e. points that are in the transition of letting you see something new). However, when you add gravity... and the need to jump, it becomes much more complicated. Djikstra's algorithm is like this: (pseudocode)
struct point
  var loc = xy
  var distance = float
  var points_travelled = list of xy
end

var locations = list of point

locations.push(origin)

world.add_corner(goal)

while locations.not_empty do
  locations.sort_by_distance
  point spectator = locations.pop
  if spectator.xy = goal.xy then
    print "yay, found the shortest route: ", spectator.points_travelled
  end
  for all corners in the world, as corner, do
     if uninterrupted_sight(corner, spectator.xy) and spectator.points_travelled.has_no(corner) then
       point spectator_new = spectator
       spectator_new.points_travelled.push(corner)
       spectator_new.xy = corner
       spectator_new.distance += distance(corner, spectator.xy)
       locations.push(spectator_new)
     end
  end
end
Editor, Active player (297)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
For jumping & gravity, mayb e use the same algorithm, but choose the list of "corners" from those points that are currently reachable by jumping, rounded to a 16 pixel granularity or something. Those destinations can be found by simulating the jump curve, or maybe in some simpler manner...
Joined: 5/2/2006
Posts: 1020
Location: Boulder, CO
is there a good way of doing it that does not involve looping over all corners? I wrote most of an basic RTS at one point, and everything worked like it should have except I could not find a good way of dealing with movement around obstacles, especially when other units are in motion as movement starts. any idea how its done in commercial games? I suspect they calculate the whole map down to distinct zones, each with a simple process to get to the others, then something similar to what bisqwit posted for fine pathing within each area, but even this seems far too computationally expensive to be practical.
Has never colored a dinosaur.
Joined: 7/2/2007
Posts: 3960
Generally I suspect that commercial games use A* pathing to move units around, and recalculate the pathing if another unit gets in the way (or just have the unit that was obstructed wait for the obstructing unit to get out of the way). RTSes have the advantage of not dealing with remotely realistic jumping -- the jumping units in SC2 are basically short-range flyers, for example. It's really a totally different can of worms dealing with platforming levels. For the moment, I've given up on determining actual accessibility, and am looking into just trying to recognize situations that look like trouble (e.g. long steep walls, overhangs, and the like). That just involves walking along the perimeter and calculating the local slope at intervals, which is much more straightforward. Edit: guess BBCode doesn't like the * in the URL? Weird.
Pyrel - an open-source rewrite of the Angband roguelike game in Python.
Player (68)
Joined: 3/11/2004
Posts: 1058
Location: Reykjaví­k, Ísland
If you could apply "flags" to bricks, maybe a decent approach would be: Start with a known accessible brick. Flag it as accessible (duh). Find all bricks that are accessible from this brick (either by walking or jumping) and flag those as accessible too. Flag this brick as "calculated" so it won't be included in the next iteration. Repeat above for all bricks that are accessible but not yet calculated. Mind you, I have never programmed anything like this. But it seems to me like it would work. The hard part would be to make an algorithm that would calculate the accessibility. (you would have to take into account that the player can't pass through walls/ceilings, etc.)
Joined: 5/2/2006
Posts: 1020
Location: Boulder, CO
The real issue with that algorithm is that it would correctly measure what is accessible and what is not, but it would be hard to make it find the best path to each block. the order you checked newly labeled as accessible blocks would be key.
Has never colored a dinosaur.
Joined: 7/2/2007
Posts: 3960
Actually, the real issue with that algorithm is figuring out what blocks are accessible from a given block. Simulating jump trajectories is no fun. :) Anyway, I've given up on that approach and have replaced it with something that's much simpler: I walk the walls and look for angles that look problematic. I'm getting decent results so far, though it still requires some tweaking.
Pyrel - an open-source rewrite of the Angband roguelike game in Python.