Rigidbodies as agent

  • A* version: 5.2.5
  • Unity version: 2022.3.55f1

Hi i am trying to figure out how to best move agents. I have created a nice controller for my game based on Rigidbody and I did not use the CharacterController ( was this a mistake )?

Now that i want to make AiAgents from this controller i am having trouble making it move…

I have tried what was outlined here:

Should i rewrite my controller to use CharacterController? Are rigidbodies no use with A* Pathfinding?

using Pathfinding.Util;
using Pathfinding;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding.ECS;

public class AiAvatarRb : MonoBehaviour
{
public Transform targetPosition;

private Seeker seeker;
private Rigidbody rb;

public Path path;

public float speed = 2;

public float nextWaypointDistance = 3;

[SerializeField]
private int currentWaypoint = 0;

public bool reachedEndOfPath;

public float stopDistance = 0.2f;

public float repathRate = 0.5f;
private bool enableRotation;
private float lastDeltaTime;
private float rotationSpeed;
private Vector2 velocity2D;
private Quaternion rotation;

/// <summary>
/// Plane which this agent is moving in.
/// This is used to convert between world space and a movement plane to make it possible to use this script in
/// both 2D games and 3D games.
/// </summary>
public IMovementPlane movementPlane = GraphTransform.identityTransform;

/// <summary>
/// Determines which direction the agent moves in.
/// For 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games.
/// For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games.
///
/// Using the YAxisForward option will also allow the agent to assume that the movement will happen in the 2D (XY) plane instead of the XZ plane
/// if it does not know. This is important only for the point graph which does not have a well defined up direction. The other built-in graphs (e.g the grid graph)
/// will all tell the agent which movement plane it is supposed to use.
///
/// [Open online documentation to see images]
/// </summary>
[UnityEngine.Serialization.FormerlySerializedAs("rotationIn2D")]
public OrientationMode orientation = OrientationMode.ZAxisForward;

/// <summary>
/// Rotation of the agent.
/// If <see cref="updateRotation"/> is true then this value will be synchronized every frame with Transform.rotation.
/// </summary>
protected Quaternion simulatedRotation;

[SerializeField]
private Vector3 velocity;

public void Start()
{
    seeker = GetComponent<Seeker>();
    // If you are writing a 2D game you should remove this line
    // and use the alternative way to move sugggested further below.
    //avatarController = GetComponent<AvatarCharacter>();

    // Start a new path to the targetPosition, call the the OnPathComplete function
    // when the path has been calculated (which may take a few frames depending on the complexity)
    seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);

    // If you are writing a 2D game you should remove this line
    // and use the alternative way to move sugggested further below.
    rb = GetComponent<Rigidbody>();
}

public void OnPathComplete(Path p)
{
    Debug.Log("A path was calculated. Did it fail with an error? " + p.error);

    if (!p.error)
    {
        path = p;

        // Reset the waypoint counter so that we start to move towards the first point in the path
        currentWaypoint = 0;

        // The path is calculated. The agent can move.
        reachedEndOfPath = false;
    }
}

public void CalculatePath()
{
    Debug.Log("seeker is StartPath...");
    seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);
}

public void Update()
{
    // Repath if the target has moved more than a specific distance since the last path was calculated.
    if (Time.time - repathRate > 0.5f)
    {
        CalculatePath();
    }

    if (path == null)
    {
        // We have no path to follow yet, so don't do anything
        Debug.Log("There is no path...");
        CalculatePath();
        return;
    }

    // Check in a loop if we are close enough to the current waypoint to switch to the next one.
    // We do this in a loop because many waypoints might be close to each other and we may reach
    // several of them in the same frame.
    reachedEndOfPath = false;
    // The distance to the next waypoint in the path
    float distanceToWaypoint;
    while (true)
    {
        // If you want maximum performance you can check the squared distance instead to get rid of a
        // square root calculation. But that is outside the scope of this tutorial.
        distanceToWaypoint = Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]);
        if (distanceToWaypoint < nextWaypointDistance)
        {
            // Check if there is another waypoint or if we have reached the end of the path
            if (currentWaypoint + 1 < path.vectorPath.Count)
            {
                currentWaypoint++;
            }
            else
            {
                // Set a status variable to indicate that the agent has reached the end of the path.
                // You can use this to trigger some special code if your game requires that.
                reachedEndOfPath = true;
                break;
            }
        }
        else
        {
            break;
        }
    }

    // Slow down smoothly upon approaching the end of the path
    // This value will smoothly go from 1 to 0 as the agent approaches the last waypoint in the path.
    var speedFactor = reachedEndOfPath ? Mathf.Sqrt(distanceToWaypoint / nextWaypointDistance) : 1f;

    // Direction to the next waypoint
    // Normalize it so that it has a length of 1 world unit
    Vector3 dir = (path.vectorPath[currentWaypoint] - transform.position).normalized;
    // Multiply the direction by our desired speed to get a velocity
    velocity = dir * speed * speedFactor;
    velocity.y -= Physics.gravity.y * Time.deltaTime;

    // Move the agent using the CharacterController component
    // Note that SimpleMove takes a velocity in meters/second, so we should not multiply by Time.deltaTime
    // controller.SimpleMove(velocity);
    // someone one on the forums said that i should use rb.MovePosition instead of controller.SimpleMove and put it in FixedUpdate.

    this.transform.LookAt(path.vectorPath[currentWaypoint]);

    // If you are writing a 2D game you should remove the CharacterController code above and instead move the transform directly by uncommenting the next line
    // transform.position += velocity * Time.deltaTime;
}

private void FixedUpdate()
{
    rb.velocity = velocity;
    //rb.AddForce(velocity, ForceMode.VelocityChange);
    //rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
}

protected virtual void CalculateNextRotation(float slowdown, out Quaternion nextRotation)
{
    if (lastDeltaTime > 0.00001f && enableRotation)
    {
        Vector2 desiredRotationDirection;
        desiredRotationDirection = velocity2D;

        // Rotate towards the direction we are moving in.
        // Don't rotate when we are very close to the target.
        var currentRotationSpeed = rotationSpeed * Mathf.Max(0, (slowdown - 0.3f) / 0.7f);
        nextRotation = SimulateRotationTowards(desiredRotationDirection, currentRotationSpeed * lastDeltaTime);
    }
    else
    {
        // TODO: simulatedRotation
        nextRotation = rotation;
    }
}

/// <summary>
/// Simulates rotating the agent towards the specified direction and returns the new rotation.
///
/// Note that this only calculates a new rotation, it does not change the actual rotation of the agent.
///
/// See: <see cref="orientation"/>
/// See: <see cref="movementPlane"/>
/// </summary>
/// <param name="direction">Direction in the movement plane to rotate towards.</param>
/// <param name="maxDegrees">Maximum number of degrees to rotate this frame.</param>
protected Quaternion SimulateRotationTowards(Vector2 direction, float maxDegrees)
{
    if (direction != Vector2.zero)
    {
        Quaternion targetRotation = Quaternion.LookRotation(movementPlane.ToWorld(direction, 0), movementPlane.ToWorld(Vector2.zero, 1));
        // This causes the character to only rotate around the Z axis
        if (orientation == OrientationMode.YAxisForward) targetRotation *= Quaternion.Euler(90, 0, 0);
        return Quaternion.RotateTowards(simulatedRotation, targetRotation, maxDegrees);
    }
    return simulatedRotation;
}

}

Thanks All.

What issues are you seeing with your Rigidbody agents? Are they ignoring gravity or jittering, or something else?

They are jittering or not moving towards the waypoints properly… do you have Rigidbody agents?

i figured that instead of moving the rigidbody by doing something like:

private void FixedUpdate()
{
    //rb.velocity = velocity;
    //rb.AddForce(velocity, ForceMode.VelocityChange);
    //rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
}

I used the ai

    // Move the agent using the CharacterController component
    // Note that SimpleMove takes a velocity in meters/second, so we should not multiply by Time.deltaTime
    // controller.SimpleMove(velocity);
    // someone one on the forums said that i should use rb.MovePosition instead of controller.SimpleMove and put it in FixedUpdate.
    ai.destination = transform.position + velocity;

ai being: IAstarAI

private IAstarAI ai;

not sure if i should be using this or using the rb directly?

No, I don’t usually have a need for Rigidbody agents myself.

I just tried adding rigidbodies to my agent and I didn’t get any jittering- can you post your agent and rigidbody settings and I’ll try and recreate it?