- 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.