Making an enemy that only moves in cardinal directions

I’m trying to make an enemy that only moves up/down/left/right and never on diagonals. I found this old thread and had a read through it and tried a few things: What would it take to get orthogonal (non-diagonal) movement?

The end goal is to create a robot character that moves in a straight line at a constant speed, stops dead, turns 90 degrees slowly, then moves in another straight line. I’ve incorporated 0 slowdown distance in AIPath to do help with the movement, but the path is no good, with many zig-zags.

I can’t use the Manhattan heuristic, since this will only be for a small set of enemies. I’ve read through “How to create a Custom Movement Script,” but I’m not sure that’s right for me; it looks like the movement script is only processing the movement after the path is created, but I want to create a more specific path.

I’ve tried playing with a custom ITraversalProvider, but that didn’t provide much value: killing off diagonal movement via CanTraverse is clumsy, and GetTraversalCost doesn’t provide enough information about parent nodes (as far as I can tell) to see if the requested traversal is going to involve a turn.

I feel like the smartest way to do this would simply be to introduce a massive penalty for turning when calculating the path (thereby minimizing the number of turns), but I can’t figure out how to do that. Is that possible?

Hmmm…I believe I have an idea of what can be done here, specifically for a use-case where the unit is supposed to follow a more linear path, but also behave like other units. This may be a little convoluted, but I don’t know of any other way to get this to work (that said, I’ll keep looking)

Here’s my idea:

Start by calculating a typical path, like you have in the screenshot. Right after the path is calculated )in the callback for creating a path), create a new path, that we’ll call angledPath. I think the play here would be to take the normal path and get it’s points from vectorPath or similar. Start at the first index of vectorPath, and increment through the following paths, until the dot product of the first index is over a certain threshold against the selected index. If the threshold is never met for any point (as it would in your screenshot as I’ve decribed), then use the first and last index. Find the right angle between the two points and ammend them to the angledPath’s points. You’d also have to do some checking to make sure that path wouldn’t pass through obstacles but that’s not too much more actually.

Here’s an MSPaint example of what I mean. Black is the original path, Blue are the points that are 1) at either 90 degrees towards the next point or 2) The two points where a 90 degree turn goes towards. Red is the connection for those path points, creating a new cardinal direction only path.

This might be overkill but if you want it for just one single type of unit, I don’t see anything that can be changed per unit. Sorta like a custom modifier.

Thanks for your response. I’ve had in mind something like that, but it’s still kind of a compromise vs a solution. Is it possible to intercept where the path is calculated and simply calculate it differently? Like, is there an interface for that? We have a lot of path post-processing available, but I’m not sure how to get into the actual path creation algorithm there.

That’s a smart algorithm for correcting the path, though I think it might have trouble collapsing it into obstacles in the path. They’re not pictured above, but the enemies are going to be moving through a maze-like structure. I guess you mention this here:

You’d also have to do some checking to make sure that path wouldn’t pass through obstacles but that’s not too much more actually

How do you do the obstacle checking after the path is calculated?

I think this is a very good question, but I wouldn’t have an answer for it myself. I’ll tag @aron_granberg in this and see what he suggests for getting more hands-on and manual with path creation. I’m almost sure there’s a way to do what you’re asking but he’d know better than me :+1:

So in a post-processing fashion, I’d imagine it would look something like doing a raycast from one point to the next and verify there are no small obstacles in the path, getting the total length that needs to be walked around (you could probably get the Renderer.bounds for this), then you add three points intercepting that portion of the new path with the first point moving to the side, then forward, then back.

“If there is something in this leg of the path, get the bounds, and use those values to place points around the bounds (maybe +2f) to walk around it”

For large obstacles I doubt this would even be an issue though.

Hi

It’s not possible to find a path with the minimal number of turns in this package. This would require a different pathfinding algorithm. It’s not easy to just bolt on, I’m afraid.

However, you can get pretty good results using the manhattan heuristic.
You mention that you want to do this for only some enemies. Fortunately, you can set this value per-path.

// Disable the agent's built-in path searching routine
ai.canSearch = false;

var path = ABPath.Construct(...);
path.heuristic = Heuristic.Manhattan;
ai.SetPath(path);

OK so I’ve finally gotten around to trying this two months later. @aron_granberg, I’ve tried subbing in the manual path as you’ve specified here, but I’ve found the Manhattan heuristic doesn’t really seem to make a difference in the final output: the paths as seen here are still very tight and jagged, as opposed to nice minimal turns.

So, it sounds like this isn’t supported out of the box, which is fine. If I wanted to just calculate the path myself and send a list of vector3s to the AStar system so it moves the guy along that path, how could I do that? And, I assume if do that, I won’t be able to leverage stuff like RVOController and AlternativePath anymore?

Is your world by any chance not aligned to the global X and Z axes? Looks like it might be rotated?

It’s on XZ.

Is this not the expected result? Here’s the four different heuristics tested:

None

Manhattan

Diag Man

Euclid

My settings, for reference


If I can read the regular aipath after its calculated, I can trace it through my tiles and generate a simple orthogonal path based on that, I just need a pointer for the API I’d call to create and set a custom path like this.

Strange. Do you have any penalties/tags applied to this world? The Manhattan heuristic shouldn’t give that result on a standard graph.

Also. The euclid one doesn’t look like euclid at all. It looks more like diagonal manhattan…

Alright I realize now that setting Connections to Eight instead of Four is yielding a wildly different set of results. I kind of wanted my guys to stick to right angles as much as possible, which is why I’d chosen that, but maybe that was the wrong choice?

Even still, Manhattan is not yielding ideal results:

Hi

Here are some sample images of what you should expect for the different heuristics: Pathfinding - A* Pathfinding Project

Actually. I investigated this a bit more, and it’s a bug introduced in 5.0.
The Manhattan and None heuristic were yielding weird results, because it wasn’t handling ties properly.
I’ll include a fix in the next update.

1 Like

Awesome, I’ll hold out. Time to throw this back on the backburner, see you in 2025😅

As an aside, this project looks really good visually :slight_smile: Well done!

1 Like