Circular Staircase Causes Path to fail

Hi Aron.

I have an interesting bug.

I have an NPC traversing the level, going from the bottom floor, up a staircase that is shaped like 2 L’s facing each other (see images). He walks up fine, however, when he reaches the equivelant of where the stairs start again, but above, he tries to turn around and go up the stairs again, instead of moving forward.

If i put another point in the middle, causing the NPC to search just halfway, and then search the rest from the top, I don’t experience this issue.

Can you shed light on why this is happening? how can this be fixed? accounted for?
How can I get the seeker to search the path in parts?


Sorry for the late answer.
Could you post your graph settings and your AI settings (e.g what movement script you are using)?


The movement script is a modified version of the RichAI, Here is the relevant part.

                   LastWaypoint = false;
                   desiredSpeed = m_lastRequestedSpeed;
		if (m_currentPath != null)
			RichPathPart pt = m_currentPath.GetCurrentPart();
			RichFunnel fn = pt as RichFunnel;
			if (fn != null)
				//Clear buffers for reuse
				Vector3 position = UpdateTarget(fn, currentPosition);

				//Pick next waypoint if current is reached
				int tgIndex = 0;
				//Target point
				Vector3 tg = m_buffer[tgIndex];
				Vector3 dir = tg - position;
				dir.y = 0;

				bool passedTarget = Vector3.Dot(dir, m_currentTargetDirection) < 0;
				//Check if passed target in another way
				if (passedTarget && m_buffer.Count - tgIndex > 1)
					tg = m_buffer[tgIndex];

				if (tg != m_lastTargetPoint)
					m_currentTargetDirection = (tg - position);
					m_currentTargetDirection.y = 0;
					m_lastTargetPoint = tg;
					//Debug.DrawRay (tr.position, Vector3.down*2,,0.2f);

				//Direction to target
				dir = (tg - position);
				dir.y = 0;
				float magn = dir.magnitude;
				dir = magn == 0 ? : dir / magn;

                // Wall avoidance force vector
                Vector3 force =;

                if (DoAvoidance && m_rvoWallAvoidFalloff > 0 && m_rvoWallAvoidForce > 0)
                    List<ObstacleVertex> obst = m_rvoAgent.NeighbourObstacles;

                    if (obst != null) for (int i = 0; i < obst.Count; i++)
                            Vector3 a = obst[i].position;
                            Vector3 b = obst[i].next.position;

                            Vector3 closest = position - AstarMath.NearestPointStrict(a, b, position);

                            if (closest == a || closest == b) continue;

                            float dist = closest.sqrMagnitude;
                            closest /= dist * m_rvoWallAvoidFalloff;
                            force += closest;

				//Is the endpoint of the path (part) the current target point
				bool endPointIsTarget = m_lastCorner && m_buffer.Count - tgIndex == 1;

				if (endPointIsTarget)
                    LastWaypoint = true;
                    //Calculate desired Speed
                    float currentSpeed = m_locomotion.CurrentSpeed;
                    var ramped_speed = currentSpeed * (magn /     m_locomotion.CalculateSlowDownDistance(currentSpeed));
                    var clipped_speed = Mathf.Min(ramped_speed, desiredSpeed);
                    if (magn >= m_arrivedAtTargetDistance)
                        desiredSpeed = clipped_speed;
                        desiredSpeed = 0f;
						return m_locomotion.ForwardVector;
				if (force !=
					dir = (dir + (force)).normalized;

			return dir;


Hm… I am not sure what could be wrong…
If you try to set the repath rate on the RichAI to some very high value so that the character does not recalculate the path while following it, does that change anything?

There is no manual repeat rate set at all.

Due to the volume of characters, i just search the path once and follow it.

Hm… ok, then it is likely that it finds the wrong start node during the first path request.
If you position the character somewhere else in the level and try it, does the same thing happen?

I have a feeling this has to do with the difference in heights. I’m not really sure on how the internal workings of the buffer, funnel and path class are.

It just feels like it it picks the wrong next point, making the NPC try and go back up the stairs, even though he is already up.

Maybe you could add

Debug.DrawLine (transform.position, tg,;

to the script and see which point it tries to move to.

Good Idea.

Like I suspected, he tries going to a point on the stairs below again.

I did notice this time around (no updates have been done to code, perhaps some to the navmesh) it took a few runs around his patrol before he got stuck there.

Ok. When he travels up the stair (not yet stuck), does it look like he is moving towards the correct points (using the debug code you added previously) or is he always moving towards points one level down?

He moves properly until getting up to the second level. as soon as that happens, he tries going forward on the wrong level:

You can see the red line going from the top floor to the bottom.

@aron_granberg - any new ideas on this? can you explain perhaps how the target array works and what could be causing it to go back through the nodes?


After some thinking and experimentation I think I know what might be causing it.
The RichAI agent always tries to look for which node it is currently standing on, sometimes this node might not be what it expects, for example it might have been pushed off a cliff or something, and then it shouldn’t just continue as if nothing had happened. In this case I think it steps a very tiny amount outside the navmesh at the top, it then suddenly finds that it is inside a node one stair down (it is checking in the XZ space) so it will continue the path from there. This is obviously not what you want in this particular case.

I have modified the RichPath.cs script. I am not quite ready to release an new update to the package with it because there might be edge cases I haven’t thought about, but for your case I think it should work a lot better.
Here is the script:, it should be a drop in replacement.

Thanks Aron.

This did solve the issue.

1 Like