Integration with KCC

I’m trying to subclass AIpath to use with Kinemetic Character Controller. First, I had to virtual both FinalizeRotation and FinalizePosition:

void FinalizeRotation (Quaternion nextRotation) {
void FinalizePosition (Vector3 nextPosition) {`

Then I tried a simple script that passed the vector into KCC:

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 the subclass so I protected it:

protected Vector3 accumulatedMovementDelta = Vector3.zero;

I tried another method:

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;
		}
	}

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();
	}

Ultimate it just sends the KCC controller walking forwards endlessly, not pathing at all to any point. @aron_granberg can you suggest a better way of integrating A*PP with other character controller assets, 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?

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.

Oh, so CanMove is useful to disable for integrating with other controllers? So I should basically use the Update code that’s in the example link you gave and not worry about overriding anything in the original scripts?

Yes, I think that’s a better way to go about it.

Well, this at least gives the exact same result as I was getting before:

public class AIBridge : AIPath
{
	ForkedExampleCharacterController KCC;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
    }

	AICharacterInputs inputs = new AICharacterInputs();

	void Update () {
		canMove = false;
		
		Vector3 nextPosition;
		Quaternion nextRotation;
		
		MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
		inputs.MoveVector = nextPosition.normalized;
		
		KCC.SetInputs(ref inputs);
	}

}

I have no idea why the controller just walks in a straight line, it should at least be moving in the same direction towards the target as it does with the character controller script. This is using a fresh copy of A*PP, what else could I be missing?

MoveVector sounds like it should be a direction, but you are feeding in a position (well, a normalized position, but nevertheless). nextPosition is the position the agent should move to during this frame, not the direction it should move in.

Okay, I tried this:

public class AIBridge : RichAI
{
	ForkedExampleCharacterController KCC;
	Vector3 currentPosition;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
	    currentPosition = transform.position;
    }

	AICharacterInputs inputs = new AICharacterInputs();

	void Update () {
		canMove = false;
		
		Vector3 nextPosition;
		Quaternion nextRotation;
		
		MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
		inputs.MoveVector = nextPosition - currentPosition;
		//inputs.LookVector = nextRotation * Vector3.forward;
		
		KCC.SetInputs(ref inputs);
		
		currentPosition = transform.position;
	}

This should create a directional vector, but now the ai doesn’t move at all?

Then I tried this, and it just fires off into the distance as usual:

public class AIBridge : RichAI
{
	ForkedExampleCharacterController KCC;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
    }

	AICharacterInputs inputs = new AICharacterInputs();

	void Update () {
		canMove = false;
		
		Vector3 currentPosition = simulatedPosition;
		Vector3 nextPosition;
		Quaternion nextRotation;
		
		MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
		inputs.MoveVector = (nextPosition - currentPosition).normalized;
		//inputs.LookVector = nextRotation * Vector3.forward;
		
		KCC.SetInputs(ref inputs);
		
		currentPosition = tr.position;
		Debug.Log(nextPosition);
	}

}

Also, just a quick test using the player position works perfectly:

public class AIBridge : RichAI
{
	ForkedExampleCharacterController KCC;
	Vector3 currentPosition;
	Transform player;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
	    currentPosition = transform.position;
	    player = GameObject.FindGameObjectWithTag("Player").transform;
    }

	AICharacterInputs inputs = new AICharacterInputs();

	void Update () {
		canMove = false;
		
		Vector3 nextPosition;
		Quaternion nextRotation;
		
		MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
		inputs.MoveVector = (player.position - currentPosition).normalized;
		//inputs.LookVector = nextRotation * Vector3.forward;
		
		KCC.SetInputs(ref inputs);
		
		currentPosition = transform.position;
		Debug.Log(nextPosition);
	}

Hi

You are subclassing the RichAI script but you are not calling the base methods properly. The Start method should override the RichAI’s Start method and then you should call base.Start() as well. Otherwise things will break.

I would recommend that you do not subclass the RichAI script at all, instead you can do this in another script that calls the RichAI.

I tried this but it still doesn’t pathfind to the player, it does actually move now and sort of seems to try to pathfind but it’s still in a straight line ahead:

public class AIBridge : MonoBehaviour
{
	ForkedExampleCharacterController KCC;
	RichAI RAI;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
	    RAI = GetComponent<RichAI>();
    }

	AICharacterInputs inputs = new AICharacterInputs();

	void Update () {
		RAI.canMove = false;
		
		Vector3 currentPosition = transform.position;
		Vector3 nextPosition;
		Quaternion nextRotation;
		
		RAI.MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
		inputs.MoveVector = (nextPosition - currentPosition).normalized;
		//inputs.LookVector = nextRotation * Vector3.forward;
		
		KCC.SetInputs(ref inputs);
		
		currentPosition = transform.position;
	}

}

The simulatedPosition code had to go due to the protection level, should I override this in RichAI and use it as I was? Or am I getting some other part of this wrong?

Hi can you verify that everything works properly if you feed KCC for example keyboard input?

Yes, when I use this it flawlessly follows the player direction:

public class AIBridge : RichAI
{
	ForkedExampleCharacterController KCC;
	Vector3 currentPosition;
	Transform player;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
	    currentPosition = transform.position;
	    player = GameObject.FindGameObjectWithTag("Player").transform;
    }

	AICharacterInputs inputs = new AICharacterInputs();

	void Update () {
		canMove = false;
		
		Vector3 nextPosition;
		Quaternion nextRotation;
		
		MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
		inputs.MoveVector = (player.position - currentPosition).normalized;
		//inputs.LookVector = nextRotation * Vector3.forward;
		
		KCC.SetInputs(ref inputs);
		
		currentPosition = transform.position;
		Debug.Log(nextPosition);
	}

Are you also allowing the agent to rotate? The included movement scripts will usually try to move roughly in the forward direction and rotate to change direction. (you can make sure ‘slow when not facing target’ is set to false to reduce this behavior).

Here’s using player.position - currentPosition: https://imgur.com/KOAGz24
Here’s using my bridge script for RichAI, it’s sort of pathfinding: https://imgur.com/EJyTz5c

Yes, I have enable rotation on, but do I need to pass this into KCC in order for it to rotate correctly? I was going to try this, but it doesn’t seem to work: inputs.LookVector = nextRotation * Vector3.forward;

Ah, if I disable Enable Rotation in RichAI, it seems to move to the correct position. Is there a better I can feed KCC the correct rotation, since RichAI produces at Quaternion and KCC expects a Vector3.

I think I’ve got it, thanks for all of the suggestions @aron_granberg! Here’s my final script:

public class AIBridge : MonoBehaviour
{
	ForkedExampleCharacterController KCC;
	RichAI RAI;
	
    // Start is called before the first frame update
    void Start()
    {
	    KCC = GetComponent<ForkedExampleCharacterController>();
	    RAI = GetComponent<RichAI>();
    }

	AICharacterInputs inputs = new AICharacterInputs();

	void Update () {
		RAI.canMove = false;
		
		Vector3 currentPosition = transform.position;
		Vector3 nextPosition;
		Quaternion nextRotation;
		
		RAI.MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
		
		Vector3 moveDir = (nextPosition - currentPosition).normalized;
		Vector3 lookDir = moveDir;
		
		lookDir.y = 0;
		
		inputs.MoveVector = moveDir;
		inputs.LookVector = Quaternion.LookRotation(lookDir, Vector3.up) * Vector3.forward;
		
		KCC.SetInputs(ref inputs);
		
		currentPosition = transform.position;
	}

}

Enable Rotation seems to work okay, should it be disabled if I’m using my own controller and setting my own rotations like this or is it still useful?

Also, at the end of the movement the AI kind does a little face-away dance, is this because it isn’t quite reaching it’s destination or because I haven’t told it to do anything else when it reaches the destination? https://imgur.com/VSNm1M0

Zebbi,
Thank you for your work on this. I’ve been using your suggestions in some Astar, KCC, and Behavior Designer tasks.

Did you ever get all the kinks worked out? I have weird jerkiness when trying to get this to work in a wander task.

Thanks in advance.

I finally figure it out and as usual it was something simple. I just needed to move towards the agent.destination after it had been set. Thanks again for this super helpful thread. Vector3 moveDir = (agent.destination - transform.position).normalized;

1 Like