summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/RVO/RVOController.cs
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-05-23 10:08:29 +0800
committerchai <215380520@qq.com>2024-05-23 10:08:29 +0800
commit8722a9920c1f6119bf6e769cba270e63097f8e25 (patch)
tree2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/RVO/RVOController.cs
parent3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff)
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/RVO/RVOController.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/RVO/RVOController.cs601
1 files changed, 601 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/RVO/RVOController.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/RVO/RVOController.cs
new file mode 100644
index 0000000..d97befa
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/RVO/RVOController.cs
@@ -0,0 +1,601 @@
+using UnityEngine;
+using System.Collections.Generic;
+using UnityEngine.Serialization;
+
+namespace Pathfinding.RVO {
+ using Pathfinding.Util;
+ using Pathfinding.Drawing;
+
+ /// <summary>
+ /// RVO Character Controller.
+ /// Similar to Unity's CharacterController. It handles movement calculations and takes other agents into account.
+ /// It does not handle movement itself, but allows the calling script to get the calculated velocity and
+ /// use that to move the object using a method it sees fit (for example using a CharacterController, using
+ /// transform.Translate or using a rigidbody).
+ ///
+ /// <code>
+ /// public void Update () {
+ /// // Just some point far away
+ /// var targetPoint = transform.position + transform.forward * 100;
+ ///
+ /// // Set the desired point to move towards using a desired speed of 10 and a max speed of 12
+ /// controller.SetTarget(targetPoint, 10, 12, targetPoint);
+ ///
+ /// // Calculate how much to move during this frame
+ /// // This information is based on movement commands from earlier frames
+ /// // as local avoidance is calculated globally at regular intervals by the RVOSimulator component
+ /// var delta = controller.CalculateMovementDelta(transform.position, Time.deltaTime);
+ /// transform.position = transform.position + delta;
+ /// }
+ /// </code>
+ ///
+ /// For documentation of many of the variables of this class: refer to the Pathfinding.RVO.IAgent interface.
+ ///
+ /// Note: Requires a single RVOSimulator component in the scene
+ ///
+ /// See: Pathfinding.RVO.IAgent
+ /// See: RVOSimulator
+ /// See: local-avoidance (view in online documentation for working links)
+ /// </summary>
+ [AddComponentMenu("Pathfinding/Local Avoidance/RVO Controller")]
+ [UniqueComponent(tag = "rvo")]
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/rvocontroller.html")]
+ public class RVOController : VersionedMonoBehaviour {
+ [SerializeField][FormerlySerializedAs("radius")]
+ internal float radiusBackingField = 0.5f;
+
+ [SerializeField][FormerlySerializedAs("height")]
+ float heightBackingField = 2;
+
+ [SerializeField][FormerlySerializedAs("center")]
+ float centerBackingField = 1;
+
+ /// <summary>
+ /// Radius of the agent in world units.
+ /// Note: If a movement script (AIPath/RichAI/AILerp, anything implementing the IAstarAI interface) is attached to the same GameObject, this value will be driven by that script.
+ /// </summary>
+ public float radius {
+ get {
+ if (ai != null) return ai.radius;
+ return radiusBackingField;
+ }
+ set {
+ if (ai != null) ai.radius = value;
+ radiusBackingField = value;
+ }
+ }
+
+ /// <summary>
+ /// Height of the agent in world units.
+ /// Note: If a movement script (AIPath/RichAI/AILerp, anything implementing the IAstarAI interface) is attached to the same GameObject, this value will be driven by that script.
+ /// </summary>
+ public float height {
+ get {
+ if (ai != null) return ai.height;
+ return heightBackingField;
+ }
+ set {
+ if (ai != null) ai.height = value;
+ heightBackingField = value;
+ }
+ }
+
+ /// <summary>A locked unit cannot move. Other units will still avoid it but avoidance quality is not the best.</summary>
+ [Tooltip("A locked unit cannot move. Other units will still avoid it. But avoidance quality is not the best")]
+ public bool locked;
+
+ /// <summary>
+ /// Automatically set <see cref="locked"/> to true when desired velocity is approximately zero.
+ /// This prevents other units from pushing them away when they are supposed to e.g block a choke point.
+ ///
+ /// When this is true every call to <see cref="SetTarget"/> or <see cref="Move"/> will set the <see cref="locked"/> field to true if the desired velocity
+ /// was non-zero or false if it was zero.
+ /// </summary>
+ [Tooltip("Automatically set #locked to true when desired velocity is approximately zero")]
+ public bool lockWhenNotMoving = false;
+
+ /// <summary>How far into the future to look for collisions with other agents (in seconds)</summary>
+ [Tooltip("How far into the future to look for collisions with other agents (in seconds)")]
+ public float agentTimeHorizon = 2;
+
+ /// <summary>How far into the future to look for collisions with obstacles (in seconds)</summary>
+ [Tooltip("How far into the future to look for collisions with obstacles (in seconds)")]
+ public float obstacleTimeHorizon = 0.5f;
+
+ /// <summary>
+ /// Max number of other agents to take into account.
+ /// A smaller value can reduce CPU load, a higher value can lead to better local avoidance quality.
+ /// </summary>
+ [Tooltip("Max number of other agents to take into account.\n" +
+ "A smaller value can reduce CPU load, a higher value can lead to better local avoidance quality.")]
+ public int maxNeighbours = 10;
+
+ /// <summary>
+ /// Specifies the avoidance layer for this agent.
+ /// The <see cref="collidesWith"/> mask on other agents will determine if they will avoid this agent.
+ /// </summary>
+ public RVOLayer layer = RVOLayer.DefaultAgent;
+
+ /// <summary>
+ /// Layer mask specifying which layers this agent will avoid.
+ /// You can set it as CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ...
+ ///
+ /// This can be very useful in games which have multiple teams of some sort. For example you usually
+ /// want the agents in one team to avoid each other, but you do not want them to avoid the enemies.
+ ///
+ /// This field only affects which other agents that this agent will avoid, it does not affect how other agents
+ /// react to this agent.
+ ///
+ /// See: bitmasks (view in online documentation for working links)
+ /// See: http://en.wikipedia.org/wiki/Mask_(computing)
+ /// </summary>
+ [Pathfinding.EnumFlag]
+ public RVOLayer collidesWith = (RVOLayer)(-1);
+
+ /// <summary>
+ /// An extra force to avoid walls.
+ /// This can be good way to reduce "wall hugging" behaviour.
+ ///
+ /// Deprecated: This feature is currently disabled as it didn't work that well and was tricky to support after some changes to the RVO system. It may be enabled again in a future version.
+ /// </summary>
+ [HideInInspector]
+ [System.Obsolete]
+ public float wallAvoidForce = 1;
+
+ /// <summary>
+ /// How much the wallAvoidForce decreases with distance.
+ /// The strenght of avoidance is:
+ /// <code> str = 1/dist*wallAvoidFalloff </code>
+ ///
+ /// See: wallAvoidForce
+ ///
+ /// Deprecated: This feature is currently disabled as it didn't work that well and was tricky to support after some changes to the RVO system. It may be enabled again in a future version.
+ /// </summary>
+ [HideInInspector]
+ [System.Obsolete]
+ public float wallAvoidFalloff = 1;
+
+ /// <summary>\copydocref{Pathfinding.RVO.IAgent.Priority}</summary>
+ [Tooltip("How strongly other agents will avoid this agent")]
+ [UnityEngine.Range(0, 1)]
+ public float priority = 0.5f;
+
+ /// <summary>
+ /// Priority multiplier.
+ /// This functions identically to the <see cref="priority"/>, however it is not exposed in the Unity inspector.
+ /// It is primarily used by the <see cref="Pathfinding.RVO.RVODestinationCrowdedBehavior"/>.
+ /// </summary>
+ [System.NonSerialized]
+ public float priorityMultiplier = 1.0f;
+
+ /// <summary>\copydocref{Pathfinding.RVO.IAgent.FlowFollowingStrength}</summary>
+ [System.NonSerialized]
+ public float flowFollowingStrength = 0.0f;
+
+ GraphNode obstacleQuery;
+
+ /// <summary>
+ /// Center of the agent relative to the pivot point of this game object.
+ /// Note: If a movement script (AIPath/RichAI/AILerp, anything implementing the IAstarAI interface) is attached to the same GameObject, this value will be driven by that script.
+ /// </summary>
+ public float center {
+ get {
+ // With an AI attached, this will always be driven to height/2 because the movement script expects the object position to be at its feet
+ if (ai != null) return ai.height/2;
+ return centerBackingField;
+ }
+ set {
+ centerBackingField = value;
+ }
+ }
+
+ /// <summary>\details Deprecated:</summary>
+ [System.Obsolete("This field is obsolete in version 4.0 and will not affect anything. Use the LegacyRVOController if you need the old behaviour")]
+ public LayerMask mask { get { return 0; } set {} }
+
+ /// <summary>\details Deprecated:</summary>
+ [System.Obsolete("This field is obsolete in version 4.0 and will not affect anything. Use the LegacyRVOController if you need the old behaviour")]
+ public bool enableRotation { get { return false; } set {} }
+
+ /// <summary>\details Deprecated:</summary>
+ [System.Obsolete("This field is obsolete in version 4.0 and will not affect anything. Use the LegacyRVOController if you need the old behaviour")]
+ public float rotationSpeed { get { return 0; } set {} }
+
+ /// <summary>\details Deprecated:</summary>
+ [System.Obsolete("This field is obsolete in version 4.0 and will not affect anything. Use the LegacyRVOController if you need the old behaviour")]
+ public float maxSpeed { get { return 0; } set {} }
+
+ public MovementPlane movementPlaneMode => simulator?.MovementPlane ?? RVOSimulator.active?.movementPlane ?? MovementPlane.XZ;
+
+ /// <summary>Determines if the XY (2D) or XZ (3D) plane is used for movement</summary>
+ public SimpleMovementPlane movementPlane {
+ get {
+ var mode = simulator?.MovementPlane ?? RVOSimulator.active?.movementPlane;
+ if (mode != null) {
+ if (mode.Value == MovementPlane.Arbitrary) return movementPlaneBackingField;
+ if (mode.Value == MovementPlane.XY) return SimpleMovementPlane.XYPlane;
+ }
+ return SimpleMovementPlane.XZPlane;
+ }
+ set {
+ var mode = simulator?.MovementPlane ?? RVOSimulator.active?.movementPlane;
+ if (mode != null && mode.Value != MovementPlane.Arbitrary) throw new System.InvalidOperationException("Cannot set the movement plane unless the RVOSimulator's movement plane setting is set to Arbitrary.");
+ movementPlaneBackingField = value;
+ }
+ }
+
+ /// <summary>Reference to the internal agent</summary>
+ public IAgent rvoAgent { get; private set; }
+
+ /// <summary>Reference to the rvo simulator</summary>
+ SimulatorBurst simulator { get; set; }
+
+ /// <summary>Cached tranform component</summary>
+ protected Transform tr;
+
+ [SerializeField]
+ [FormerlySerializedAs("ai")]
+ IAstarAI aiBackingField;
+
+ internal SimpleMovementPlane movementPlaneBackingField = GraphTransform.xzPlane.ToSimpleMovementPlane();
+
+ /// <summary>Cached reference to a movement script (if one is used)</summary>
+ protected IAstarAI ai {
+ get {
+#if UNITY_EDITOR
+ if (aiBackingField == null && !Application.isPlaying) aiBackingField = GetComponent<IAstarAI>();
+#endif
+ // Note: have to cast to MonoBehaviour to get Unity's special overloaded == operator.
+ // If we didn't do this then this property could return a non-null value that pointed to a destroyed component.
+ if ((aiBackingField as MonoBehaviour) == null) aiBackingField = null;
+ return aiBackingField;
+ }
+ set {
+ aiBackingField = value;
+ }
+ }
+
+ /// <summary>Enables drawing debug information in the scene view</summary>
+ public AgentDebugFlags debug;
+
+ /// <summary>
+ /// Current position of the agent.
+ /// Note that this is only updated every local avoidance simulation step, not every frame.
+ /// </summary>
+ public Vector3 position {
+ get {
+ simulator.BlockUntilSimulationStepDone();
+ return rvoAgent.Position;
+ }
+ }
+
+ /// <summary>
+ /// Current calculated velocity of the agent.
+ /// This is not necessarily the velocity the agent is actually moving with
+ /// (that is up to the movement script to decide) but it is the velocity
+ /// that the RVO system has calculated is best for avoiding obstacles and
+ /// reaching the target.
+ ///
+ /// See: CalculateMovementDelta
+ ///
+ /// You can also set the velocity of the agent. This will override the local avoidance input completely.
+ /// It is useful if you have a player controlled character and want other agents to avoid it.
+ ///
+ /// Setting the velocity using this property will mark the agent as being externally controlled for 1 simulation step.
+ /// Local avoidance calculations will be skipped for the next simulation step but will be resumed
+ /// after that unless this property is set again.
+ ///
+ /// Note that if you set the velocity the value that can be read from this property will not change until
+ /// the next simulation step.
+ ///
+ /// See: <see cref="Pathfinding::RVO::IAgent::ForceSetVelocity"/>
+ /// See: ManualRVOAgent.cs (view in online documentation for working links)
+ /// </summary>
+ public Vector3 velocity {
+ get {
+ // For best accuracy and to allow other code to do things like Move(agent.velocity * Time.deltaTime)
+ // the code bases the velocity on how far the agent should move during this frame.
+ // Unless the game is paused (timescale is zero) then just use a very small dt.
+ var dt = Time.deltaTime > 0.0001f ? Time.deltaTime : 0.02f;
+ return CalculateMovementDelta(dt) / dt;
+ }
+ set {
+ simulator.BlockUntilSimulationStepDone();
+ rvoAgent.ForceSetVelocity(value);
+ }
+ }
+
+ /// <summary>
+ /// Direction and distance to move in a single frame to avoid obstacles.
+ ///
+ /// The position of the agent is taken from the attached movement script's position (see <see cref="Pathfinding.IAstarAI.position)"/> or if none is attached then transform.position.
+ /// </summary>
+ /// <param name="deltaTime">How far to move [seconds].
+ /// Usually set to Time.deltaTime.</param>
+ public Vector3 CalculateMovementDelta(float deltaTime) => CalculateMovementDelta(ai != null ? ai.position : tr.position, deltaTime);
+
+ /// <summary>
+ /// Direction and distance to move in a single frame to avoid obstacles.
+ ///
+ /// <code>
+ /// public void Update () {
+ /// // Just some point far away
+ /// var targetPoint = transform.position + transform.forward * 100;
+ ///
+ /// // Set the desired point to move towards using a desired speed of 10 and a max speed of 12
+ /// controller.SetTarget(targetPoint, 10, 12, targetPoint);
+ ///
+ /// // Calculate how much to move during this frame
+ /// // This information is based on movement commands from earlier frames
+ /// // as local avoidance is calculated globally at regular intervals by the RVOSimulator component
+ /// var delta = controller.CalculateMovementDelta(transform.position, Time.deltaTime);
+ /// transform.position = transform.position + delta;
+ /// }
+ /// </code>
+ /// </summary>
+ /// <param name="position">Position of the agent.</param>
+ /// <param name="deltaTime">How far to move [seconds].
+ /// Usually set to Time.deltaTime.</param>
+ public Vector3 CalculateMovementDelta (Vector3 position, float deltaTime) {
+ if (rvoAgent == null) return Vector3.zero;
+ var delta = movementPlane.ToPlane(rvoAgent.CalculatedTargetPoint - position);
+ return movementPlane.ToWorld(Vector2.ClampMagnitude(delta, rvoAgent.CalculatedSpeed * deltaTime), 0);
+ }
+
+ /// <summary>\copydocref{Pathfinding.RVO.IAgent.AvoidingAnyAgents}</summary>
+ public bool AvoidingAnyAgents {
+ get {
+ if (rvoAgent == null) return false;
+ return rvoAgent.AvoidingAnyAgents;
+ }
+ }
+
+ /// <summary>\copydocref{Pathfinding.RVO.IAgent.SetCollisionNormal}</summary>
+ public void SetCollisionNormal (Vector3 normal) {
+ simulator.BlockUntilSimulationStepDone();
+ rvoAgent.SetCollisionNormal(normal);
+ }
+
+ /// <summary>\copydocref{Pathfinding.RVO.IAgent.SetObstacleQuery}</summary>
+ public void SetObstacleQuery (GraphNode sourceNode) {
+ obstacleQuery = sourceNode;
+ }
+
+ /// <summary>
+ /// \copydocref{Pathfinding.RVO.IAgent.ForceSetVelocity}.
+ /// Deprecated: Set the <see cref="velocity"/> property instead
+ /// </summary>
+ [System.Obsolete("Set the 'velocity' property instead")]
+ public void ForceSetVelocity (Vector3 velocity) {
+ this.velocity = velocity;
+ }
+
+ /// <summary>
+ /// Converts a 3D vector to a 2D vector in the movement plane.
+ /// If movementPlane is XZ it will be projected onto the XZ plane
+ /// otherwise it will be projected onto the XY plane.
+ /// </summary>
+ public Vector2 To2D (Vector3 p) {
+ return movementPlane.ToPlane(p);
+ }
+
+ /// <summary>
+ /// Converts a 3D vector to a 2D vector in the movement plane.
+ /// If movementPlane is XZ it will be projected onto the XZ plane
+ /// and the elevation coordinate will be the Y coordinate
+ /// otherwise it will be projected onto the XY plane and elevation
+ /// will be the Z coordinate.
+ /// </summary>
+ public Vector2 To2D (Vector3 p, out float elevation) {
+ return movementPlane.ToPlane(p, out elevation);
+ }
+
+ /// <summary>
+ /// Converts a 2D vector in the movement plane as well as an elevation to a 3D coordinate.
+ /// See: To2D
+ /// See: movementPlane
+ /// </summary>
+ public Vector3 To3D (Vector2 p, float elevationCoordinate) {
+ return movementPlane.ToWorld(p, elevationCoordinate);
+ }
+
+ void OnDisable () {
+ if (simulator == null) return;
+
+ // Remove the agent from the simulation but keep the reference
+ // this component might get enabled and then we can simply
+ // add it to the simulation again
+ simulator.RemoveAgent(rvoAgent);
+ simulator = null;
+ rvoAgent = null;
+ }
+
+ void OnEnable () {
+ tr = transform;
+ ai = GetComponent<IAstarAI>();
+
+ // Make sure the AI finds this component
+ // This is useful if the RVOController was added during runtime.
+ if (ai is AIBase aiBase) aiBase.FindComponents();
+
+ if (RVOSimulator.active == null) {
+ Debug.LogError("No RVOSimulator component found in the scene. Please add one.");
+ enabled = false;
+ } else {
+ simulator = RVOSimulator.active.GetSimulator();
+ rvoAgent = simulator.AddAgent(Vector3.zero);
+ rvoAgent.PreCalculationCallback = UpdateAgentProperties;
+ rvoAgent.DestroyedCallback = OnAgentDestroyed;
+ }
+ }
+
+ void OnAgentDestroyed () {
+ // This can happen if the RVOSimulator component is destroyed.
+ if (gameObject.activeInHierarchy) {
+ // We clear the fields to avoid calling simulator.RemoveAgent during OnDisable
+ simulator = null;
+ rvoAgent = null;
+ enabled = false;
+ } else {
+ // If the gameObject is not active, we do not want to set this.enabled to false
+ // as that will also disable this component.
+ // If the user used e.g. a pooling system, they might inactivate the gameObject,
+ // but would expect the RVOController component to be enabled when they activate it again.
+ }
+ }
+
+ protected void UpdateAgentProperties () {
+ var scale = tr.localScale;
+
+ rvoAgent.Radius = Mathf.Max(0.001f, radius * Mathf.Abs(scale.x));
+ rvoAgent.AgentTimeHorizon = agentTimeHorizon;
+ rvoAgent.ObstacleTimeHorizon = obstacleTimeHorizon;
+ rvoAgent.Locked = locked;
+ rvoAgent.MaxNeighbours = maxNeighbours;
+ rvoAgent.DebugFlags = debug;
+ rvoAgent.Layer = layer;
+ rvoAgent.CollidesWith = collidesWith;
+
+ var plane = movementPlane;
+ rvoAgent.MovementPlane = plane;
+
+ // Use the position from the movement script if one is attached
+ // as the movement script's position may not be the same as the transform's position
+ // (in particular if IAstarAI.updatePosition is false).
+ var pos = plane.ToPlane(ai != null ? ai.position : tr.position, out float elevation);
+
+ if (movementPlaneMode == MovementPlane.XY) {
+ // In 2D it is assumed the Z coordinate differences of agents is ignored.
+ rvoAgent.Height = 1;
+ rvoAgent.Position = plane.ToWorld(pos, 0);
+ } else {
+ rvoAgent.Height = height * scale.y;
+ rvoAgent.Position = plane.ToWorld(pos, elevation + (center - 0.5f * height) * scale.y);
+ }
+
+ // TODO: Move this to a separate file
+ var reached = rvoAgent.CalculatedEffectivelyReachedDestination;
+ var prio = priority * priorityMultiplier;
+ var flow = flowFollowingStrength;
+ if (reached == ReachedEndOfPath.Reached) {
+ flow = 1.0f;
+ prio *= 0.3f;
+ } else if (reached == ReachedEndOfPath.ReachedSoon) {
+ flow = 1.0f;
+ prio *= 0.45f;
+ }
+ rvoAgent.Priority = prio;
+ rvoAgent.FlowFollowingStrength = flow;
+
+ // Note: We need to set this during UpdateAgentProperties to avoid a race condition.
+ // The HierarchicalNodeIndex, which is what is stored in the rvoAgent, can be invalidated by graph updates.
+ // So we must ensure that there cannot be any graph updates between when we set this, and when the simulation step happens.
+ // So setting it here, which is right before the simulation step, is a good option.
+ rvoAgent.SetObstacleQuery(obstacleQuery);
+ obstacleQuery = null;
+ }
+
+ /// <summary>
+ /// Set the target point for the agent to move towards.
+ /// Similar to the <see cref="Move"/> method but this is more flexible.
+ /// It is also better to use near the end of the path as when using the Move
+ /// method the agent does not know where to stop, so it may overshoot the target.
+ /// When using this method the agent will not overshoot the target.
+ /// The agent will assume that it will stop when it reaches the target so make sure that
+ /// you don't place the point too close to the agent if you actually just want to move in a
+ /// particular direction.
+ ///
+ /// The target point is assumed to stay the same until something else is requested (as opposed to being reset every frame).
+ ///
+ /// See: Also take a look at the documentation for <see cref="IAgent.SetTarget"/> which has a few more details.
+ /// See: <see cref="Move"/>
+ /// </summary>
+ /// <param name="pos">Point in world space to move towards.</param>
+ /// <param name="speed">Desired speed in world units per second.</param>
+ /// <param name="maxSpeed">Maximum speed in world units per second.
+ /// The agent will use this speed if it is necessary to avoid collisions with other agents.
+ /// Should be at least as high as speed, but it is recommended to use a slightly higher value than speed (for example speed*1.2).</param>
+ /// <param name="endOfPath">Point in world space which is the agent's final desired destination on the navmesh.
+ /// This is typically the end of the path the agent is following.
+ /// May be set to (+inf,+inf,+inf) to mark the agent as not having a well defined end of path.
+ /// If this is set, multiple agents with roughly the same end of path will crowd more naturally around this point.
+ /// They will be able to realize that they cannot get closer if there are many agents trying to get closer to the same destination and then stop.</param>
+ public void SetTarget (Vector3 pos, float speed, float maxSpeed, Vector3 endOfPath) {
+ if (rvoAgent == null) return;
+
+ simulator.BlockUntilSimulationStepDone();
+ rvoAgent.SetTarget(pos, speed, maxSpeed, endOfPath);
+
+ if (lockWhenNotMoving) {
+ locked = speed < 0.001f;
+ }
+ }
+
+ /// <summary>
+ /// Set the desired velocity for the agent.
+ ///
+ /// This is assumed to stay the same until something else is requested (as opposed to being reset every frame).
+ ///
+ /// Note: In most cases the <see cref="SetTarget"/> method is better to use.
+ /// What this will actually do is call <see cref="SetTarget"/> with (position + velocity).
+ /// See the note in the documentation for IAgent.SetTarget about the potential
+ /// issues that this can cause (in particular that it might be hard to get the agent
+ /// to stop at a precise point).
+ ///
+ /// See: <see cref="SetTarget"/>
+ /// </summary>
+ /// <param name="velocity">Velocity in units/second that you want the agent to move with.</param>
+ public void Move (Vector3 velocity) {
+ if (rvoAgent == null) return;
+
+ simulator.BlockUntilSimulationStepDone();
+ var speed = movementPlane.ToPlane(velocity).magnitude;
+ rvoAgent.SetTarget((ai != null ? ai.position : tr.position) + velocity, speed, speed, new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity));
+
+ if (lockWhenNotMoving) {
+ locked = speed < 0.001f;
+ }
+ }
+
+ /// <summary>
+ /// Teleport the agent to a new position.
+ /// Deprecated: Use transform.position instead, the RVOController can now handle that without any issues.
+ /// </summary>
+ [System.Obsolete("Use transform.position instead, the RVOController can now handle that without any issues.")]
+ public void Teleport (Vector3 pos) {
+ tr.position = pos;
+ }
+
+ public override void DrawGizmos () {
+ tr = transform;
+ // The AI script will draw similar gizmos
+ if (ai == null) {
+ var color = AIBase.ShapeGizmoColor * (locked ? 0.5f : 1.0f);
+ var pos = transform.position;
+
+ var scale = tr.localScale;
+ if (movementPlaneMode == MovementPlane.XY) {
+ Draw.WireCylinder(pos, Vector3.forward, 0, radius * scale.x, color);
+ } else {
+ Draw.WireCylinder(pos + To3D(Vector2.zero, center - height * 0.5f) * scale.y, To3D(Vector2.zero, 1), height * scale.y, radius * scale.x, color);
+ }
+ }
+ }
+
+ [System.Flags]
+ enum RVOControllerMigrations {
+ MigrateScale,
+ }
+
+ protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) {
+ if (migrations.TryMigrateFromLegacyFormat(out var legacyVersion)) {
+ if (legacyVersion > 1) migrations.MarkMigrationFinished((int)RVOControllerMigrations.MigrateScale);
+ }
+ if (migrations.AddAndMaybeRunMigration((int)RVOControllerMigrations.MigrateScale, unityThread)) {
+ if (transform.localScale.y != 0) centerBackingField /= Mathf.Abs(transform.localScale.y);
+ if (transform.localScale.y != 0) heightBackingField /= Mathf.Abs(transform.localScale.y);
+ if (transform.localScale.x != 0) radiusBackingField /= Mathf.Abs(transform.localScale.x);
+ }
+ }
+ }
+}