Integration with Easy Character Movement

Hi, I just started to use A* as my procedural world’s pathfinding solution.
I’m using Easy Character Movement (ECM) for my agents. This is a rigidbody based controller, so gravity simulation is not needed in controller scripts.
The ECM asset comes with a Navmesh Agent implementation. And I don’t find Base AI from A* useful for it uses unity’s Character Controller component (I don’t intend to use it).
What I want to do is to write my own implementation between ECM and A*, but don’t know where to start. Any suggestions on which class should I take as reference or which fields should I use during implementing?

Another question: I have agents does not require grounding (like fishes) but has a y position limit in world space, and they should be able to avoid rocks or corals in my under water scene. Is there any solution for them to find a safe path when following to or escaping from locations? Any help can A* provide with these kinds of agents?

Thanks!

Hi

There is a tutorial for how to write your own movement script here: https://arongranberg.com/astar/docs/custom_movement_script.html
However it is rather simple and does not have particularly good movement.
If you want a reference, the AIPath script is the best to use.

You could also subclass the AIPath script and override the FinalizeMovement method as all the code specific for the CharacterController class happens in that method (see AIBase.FinalizePosition more specifically).

Note that the AIPath script supports movement not only using CharacterControllers, but also with rigidbodies or just a raw Transform (using raycasting for grounding).

That sounds like it requires full 3D pathfinding. This is something that this package does not provide (well, technically you can do it with a point graph) because it is so ridiculously memory and CPU heavy so it is not practical in most use cases. Some kind of local avoidance is usually better suited for fishes (take a look at boids for example).

Thanks for the reply! I’ll derive a class from AIPath then override the FinalizeMovement methed and use my own controller to move the characters.

Regard to the fishes pathfinding, I don’t intend to use a true 3D pathfinding method for them, they’ll still search for routes in 2D, but I’ll override the height for them (instead of snapping to ground, I’ll lerp their y position from the start pos to the destination, these character’s destinations will be generated above terrain height).
The issue I’m having here is that I want to restric all fishes under sea level, and make them ignore positions higher than sea level. But still I’ll have ground animals which should ignore positions under sea level. And bird like animals which can walk on ground(using land animal logic and pathfinding) can fly in air (using only local avoidance) and swim on sea surface(they’ll got snapped to sea level but still need to know where a rock is poking through water, need to avoid them).
Just don’t know how can I achieve such complex behaviours with A*. I’m quite new to pathfinding. Any suggestions?

BTW, what does boids mean? Don’t quite understand it.

Hi

Ah, I see. The easiest way to separate them is to simply create two graphs and then set the Seeker settings so that they can only traverse the relevant graph (see https://arongranberg.com/astar/docs/multipleagenttypes.html)
The other approach is to use a script to mark all nodes below sea level with a particular tag (see https://arongranberg.com/astar/docs/tags.php) and then set the Seeker settings so that the agents ignore some tags.

I would recommend a custom movement script for the fishes. Firstly because you will likely have a lot of them (might not be true, but usually there is a lot of fish) so performance is important, and because that would make it easier for you to add your y coordinate adjustment. You could tweak the AIPath script as well if you want though.

Boids: https://en.wikipedia.org/wiki/Boids, https://www.red3d.com/cwr/boids/

I considered to use two graphs, but since I’ll need to update graph in runtime, fear having two of them may cause significant spike. I tried to only update one graph every 10 meter movement with 200*200 nodes, 0.5f node size. I run it on multithreaded high priority(uses 24 threads on my pc) still will cause 10ms spike every time it refreshes the graph. Can’t imagine the cost when updating two graphs.

About marking nodes with tags, is this method faster than using two graphs?

Yes, it is faster. Using two graphs will be approximately twice as slow.

Note that having 24 cores does nothing to improve the performance unfortunately as the physics code (which the grid graph uses) has to be run in the main thread (possibly in the future this could be handled using the Unity job system though).

I’m not sure what you mean by ‘every 10 meter movement’ though. Weren’t your tags static? (just above/below sea level).

By 10 meter movement, its a setting in your sample script in Procedural Example scene that allow me to choose how often the grids to get updated.

If I want to tag nodes according to their world position’s y value in runtime, should I mark them in the grid mover script or just set them up in the settings?

FinalizeMovement is virtual:

public virtual void FinalizeMovement (Vector3 nextPosition, Quaternion nextRotation) {

But FinalizeRotation and FinalizePosition are not?

void FinalizeRotation (Quaternion nextRotation) {

void FinalizePosition (Vector3 nextPosition) {

1 Like

I’ve been trying something like this, but it doesn’t seem to work, I can’t even figure out how to get FinalizeMovement to work in my subclassed script:

public class AIBridge : AIPath
{
	ForkedExampleCharacterController KCC;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
    }
	    
	public override void FinalizePosition (Vector3 nextPosition) {
		Debug.Log("test");
		
		AICharacterInputs inputs = new AICharacterInputs();
		Vector3 currentPosition = simulatedPosition;
		
		inputs.MoveVector = currentPosition;
		KCC.SetInputs(ref inputs);
		}

}

I also noticed accumulatedMovementDelta wasn’t available in a subclass so I protected it:

protected Vector3 accumulatedMovementDelta = Vector3.zero;

This is timely for me. I’m also trying to integrate with ECM. ECM takes a “we control the agent” stance. If the agent has a path, it harvests the desiredVelocity, remainingDistance, etc. and then disables the agent’s ability to update itself.

                agent.updatePosition = false;
                agent.updateRotation = false;

                agent.updateUpAxis = false;

Then, it does all its own movement calculations and forces the Unity navmesh agent to conform during LateUpdate like this:

        protected void SyncAgent()
        {
            agent.speed = speed;
            agent.angularSpeed = angularSpeed;

            agent.acceleration = acceleration;
            agent.velocity = movement.velocity;

            agent.nextPosition = transform.position;
        }

I’m still trying to understand how to override this nav agent.

I’ve been using KCC, I imagine it’s a similar issue trying to integrate A*PP with it:

protected override void FixedUpdate () {
		if (canMove) {
			Vector3 nextPosition;
			Quaternion nextRotation;
			MovementUpdate(Time.fixedDeltaTime, out nextPosition, out nextRotation);
			FinalizeMovement(nextPosition, nextRotation);
				
		}
			
	}
	    
	public override void FinalizePosition (Vector3 nextPosition) {
		if (updatePosition) {
			AICharacterInputs inputs = new AICharacterInputs();
			
			inputs.MoveVector = nextPosition * Time.deltaTime;
			Debug.Log(inputs.MoveVector);
			KCC.SetInputs(ref inputs);
		}
		else{
			AICharacterInputs inputs = new AICharacterInputs();
			
			inputs.MoveVector = Vector3.zero;
		}
	}

@aron_granberg

I’ve tried overriding the entire function but it just moves my AI forward endlessly:

	public override void FinalizePosition (Vector3 nextPosition) {
		// Use a local variable, it is significantly faster
		Vector3 currentPosition = simulatedPosition;
		bool positionDirty1 = false;

		if (updatePosition) {
			tr.position = currentPosition;
			
			AICharacterInputs inputs = new AICharacterInputs();
			inputs.MoveVector = (nextPosition - currentPosition) + accumulatedMovementDelta;
			Debug.Log(inputs.MoveVector);
			KCC.SetInputs(ref inputs);
			
			currentPosition = tr.position;
			//if (Motor.GroundingStatus.IsStableOnGround) verticalVelocity = 0;
		}

		// Clamp the position to the navmesh after movement is done
		bool positionDirty2 = false;
		currentPosition = ClampToNavmesh(currentPosition, out positionDirty2);

		// Assign the final position to the character if we haven't already set it (mostly for performance, setting the position can be slow)
		if ((positionDirty1 || positionDirty2) && updatePosition) {
			// Note that rigid.MovePosition may or may not move the character immediately.
			// Check the Unity documentation for the special cases.
			if (rigid != null) rigid.MovePosition(currentPosition);
			else if (rigid2D != null) rigid2D.MovePosition(currentPosition);
			else tr.position = currentPosition;
		}

		accumulatedMovementDelta = Vector3.zero;
		simulatedPosition = currentPosition;
		UpdateVelocity();
	}

Maybe some sort of hook would be useful, so if a controller override script is added to the object, we can just hook into the part of FinalizePosition that sets the movement for our own custom controller?

Hi

If you want external movement logic, but you still want to use what the AIPath/RichAI scripts calculate, then I would recommend checking the documentation here: https://arongranberg.com/astar/docs/iastarai.html#MovementUpdate. Presumably KCC has its own movement logic, so you just have to supply a direction to it. Then you probably want to skip the call to FinalizeMovement completely.