mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交
This commit is contained in:
@@ -0,0 +1,526 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Pathfinding.RVO {
|
||||
using Pathfinding.Util;
|
||||
|
||||
/// <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);
|
||||
///
|
||||
/// // 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")]
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_r_v_o_1_1_r_v_o_controller.php")]
|
||||
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 = 2;
|
||||
|
||||
/// <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>\copydoc Pathfinding::RVO::IAgent::Priority</summary>
|
||||
[Tooltip("How strongly other agents will avoid this agent")]
|
||||
[UnityEngine.Range(0, 1)]
|
||||
public float priority = 0.5f;
|
||||
|
||||
/// <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 {} }
|
||||
|
||||
/// <summary>Determines if the XY (2D) or XZ (3D) plane is used for movement</summary>
|
||||
public MovementPlane movementPlane {
|
||||
get {
|
||||
if (simulator != null) return simulator.movementPlane;
|
||||
else if (RVOSimulator.active) return RVOSimulator.active.movementPlane;
|
||||
else return MovementPlane.XZ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reference to the internal agent</summary>
|
||||
public IAgent rvoAgent { get; private set; }
|
||||
|
||||
/// <summary>Reference to the rvo simulator</summary>
|
||||
public Simulator simulator { get; private set; }
|
||||
|
||||
/// <summary>Cached tranform component</summary>
|
||||
protected Transform tr;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("ai")]
|
||||
IAstarAI aiBackingField;
|
||||
|
||||
/// <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 bool 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 {
|
||||
return To3D(rvoAgent.Position, rvoAgent.ElevationCoordinate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 {
|
||||
rvoAgent.ForceSetVelocity(To2D(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) {
|
||||
if (rvoAgent == null) return Vector3.zero;
|
||||
return To3D(Vector2.ClampMagnitude(rvoAgent.CalculatedTargetPoint - To2D(ai != null ? ai.position : tr.position), rvoAgent.CalculatedSpeed * deltaTime), 0);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
///
|
||||
/// // 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) {
|
||||
return To3D(Vector2.ClampMagnitude(rvoAgent.CalculatedTargetPoint - To2D(position), rvoAgent.CalculatedSpeed * deltaTime), 0);
|
||||
}
|
||||
|
||||
/// <summary>\copydoc Pathfinding::RVO::IAgent::SetCollisionNormal</summary>
|
||||
public void SetCollisionNormal (Vector3 normal) {
|
||||
rvoAgent.SetCollisionNormal(To2D(normal));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// \copydoc 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) {
|
||||
float dummy;
|
||||
|
||||
return To2D(p, out dummy);
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
if (movementPlane == MovementPlane.XY) {
|
||||
elevation = -p.z;
|
||||
return new Vector2(p.x, p.y);
|
||||
} else {
|
||||
elevation = p.y;
|
||||
return new Vector2(p.x, p.z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
if (movementPlane == MovementPlane.XY) {
|
||||
return new Vector3(p.x, p.y, -elevationCoordinate);
|
||||
} else {
|
||||
return new Vector3(p.x, elevationCoordinate, p.y);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
tr = transform;
|
||||
ai = GetComponent<IAstarAI>();
|
||||
|
||||
var aiBase = ai as AIBase;
|
||||
// Make sure the AI finds this component
|
||||
// This is useful if the RVOController was added during runtime.
|
||||
if (aiBase != null) 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();
|
||||
|
||||
// We might already have an rvoAgent instance which was disabled previously
|
||||
// if so, we can simply add it to the simulation again
|
||||
if (rvoAgent != null) {
|
||||
simulator.AddAgent(rvoAgent);
|
||||
} else {
|
||||
rvoAgent = simulator.AddAgent(Vector2.zero, 0);
|
||||
rvoAgent.PreCalculationCallback = UpdateAgentProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateAgentProperties () {
|
||||
var scale = tr.localScale;
|
||||
|
||||
rvoAgent.Radius = Mathf.Max(0.001f, radius * scale.x);
|
||||
rvoAgent.AgentTimeHorizon = agentTimeHorizon;
|
||||
rvoAgent.ObstacleTimeHorizon = obstacleTimeHorizon;
|
||||
rvoAgent.Locked = locked;
|
||||
rvoAgent.MaxNeighbours = maxNeighbours;
|
||||
rvoAgent.DebugDraw = debug;
|
||||
rvoAgent.Layer = layer;
|
||||
rvoAgent.CollidesWith = collidesWith;
|
||||
rvoAgent.Priority = priority;
|
||||
|
||||
float elevation;
|
||||
// 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).
|
||||
rvoAgent.Position = To2D(ai != null ? ai.position : tr.position, out elevation);
|
||||
|
||||
if (movementPlane == MovementPlane.XZ) {
|
||||
rvoAgent.Height = height * scale.y;
|
||||
rvoAgent.ElevationCoordinate = elevation + (center - 0.5f * height) * scale.y;
|
||||
} else {
|
||||
rvoAgent.Height = 1;
|
||||
rvoAgent.ElevationCoordinate = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public void SetTarget (Vector3 pos, float speed, float maxSpeed) {
|
||||
if (simulator == null) return;
|
||||
|
||||
rvoAgent.SetTarget(To2D(pos), speed, maxSpeed);
|
||||
|
||||
if (lockWhenNotMoving) {
|
||||
locked = speed < 0.001f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the desired velocity for the agent.
|
||||
/// Note that this is a velocity (units/second), not a movement delta (units/frame).
|
||||
///
|
||||
/// This is assumed to stay the same until something else is requested (as opposed to being reset every frame).
|
||||
///
|
||||
/// Note: In most cases the SetTarget method is better to use.
|
||||
/// What this will actually do is call 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>
|
||||
public void Move (Vector3 vel) {
|
||||
if (simulator == null) return;
|
||||
|
||||
var velocity2D = To2D(vel);
|
||||
var speed = velocity2D.magnitude;
|
||||
|
||||
rvoAgent.SetTarget(To2D(ai != null ? ai.position : tr.position) + velocity2D, speed, speed);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void OnDrawGizmos () {
|
||||
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 (movementPlane == MovementPlane.XY) {
|
||||
Draw.Gizmos.Cylinder(pos, Vector3.forward, 0, radius * scale.x, color);
|
||||
} else {
|
||||
Draw.Gizmos.Cylinder(pos + To3D(Vector2.zero, center - height * 0.5f) * scale.y, To3D(Vector2.zero, 1), height * scale.y, radius * scale.x, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override int OnUpgradeSerializedData (int version, bool unityThread) {
|
||||
if (version <= 1) {
|
||||
if (!unityThread) return -1;
|
||||
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);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 560112f876f5746c780eca0d404e7139
|
||||
timeCreated: 1490876162
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -200
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,115 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Pathfinding.RVO {
|
||||
using Pathfinding.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a navmesh as RVO obstacles.
|
||||
/// Add this to a scene in which has a navmesh or grid based graph, when scanning (or loading from cache) the graph
|
||||
/// it will be added as RVO obstacles to the RVOSimulator (which must exist in the scene).
|
||||
///
|
||||
/// Warning: You should only have a single instance of this script in the scene, otherwise it will add duplicate
|
||||
/// obstacles and thereby increasing the CPU usage.
|
||||
///
|
||||
/// If you update a graph during runtime the obstacles need to be recalculated which has a performance penalty.
|
||||
/// This can be quite significant for larger graphs.
|
||||
///
|
||||
/// In the screenshot the generated obstacles are visible in red.
|
||||
/// [Open online documentation to see images]
|
||||
/// </summary>
|
||||
[AddComponentMenu("Pathfinding/Local Avoidance/RVO Navmesh")]
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_r_v_o_1_1_r_v_o_navmesh.php")]
|
||||
public class RVONavmesh : GraphModifier {
|
||||
/// <summary>
|
||||
/// Height of the walls added for each obstacle edge.
|
||||
/// If a graph contains overlapping regions (e.g multiple floor in a building)
|
||||
/// you should set this low enough so that edges on different levels do not interfere,
|
||||
/// but high enough so that agents cannot move over them by mistake.
|
||||
/// </summary>
|
||||
public float wallHeight = 5;
|
||||
|
||||
/// <summary>Obstacles currently added to the simulator</summary>
|
||||
readonly List<ObstacleVertex> obstacles = new List<ObstacleVertex>();
|
||||
|
||||
/// <summary>Last simulator used</summary>
|
||||
Simulator lastSim;
|
||||
|
||||
public override void OnPostCacheLoad () {
|
||||
OnLatePostScan();
|
||||
}
|
||||
|
||||
public override void OnGraphsPostUpdate () {
|
||||
OnLatePostScan();
|
||||
}
|
||||
|
||||
public override void OnLatePostScan () {
|
||||
if (!Application.isPlaying) return;
|
||||
|
||||
Profiler.BeginSample("Update RVO Obstacles From Graphs");
|
||||
RemoveObstacles();
|
||||
NavGraph[] graphs = AstarPath.active.graphs;
|
||||
RVOSimulator rvosim = RVOSimulator.active;
|
||||
if (rvosim == null) throw new System.NullReferenceException("No RVOSimulator could be found in the scene. Please add one to any GameObject");
|
||||
|
||||
// Remember which simulator these obstacles were added to
|
||||
lastSim = rvosim.GetSimulator();
|
||||
|
||||
for (int i = 0; i < graphs.Length; i++) {
|
||||
RecastGraph recast = graphs[i] as RecastGraph;
|
||||
INavmesh navmesh = graphs[i] as INavmesh;
|
||||
GridGraph grid = graphs[i] as GridGraph;
|
||||
if (recast != null) {
|
||||
foreach (var tile in recast.GetTiles()) {
|
||||
AddGraphObstacles(lastSim, tile);
|
||||
}
|
||||
} else if (navmesh != null) {
|
||||
AddGraphObstacles(lastSim, navmesh);
|
||||
} else if (grid != null) {
|
||||
AddGraphObstacles(lastSim, grid);
|
||||
}
|
||||
}
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
protected override void OnDisable () {
|
||||
base.OnDisable();
|
||||
RemoveObstacles();
|
||||
}
|
||||
|
||||
/// <summary>Removes all obstacles which have been added by this component</summary>
|
||||
public void RemoveObstacles () {
|
||||
if (lastSim != null) {
|
||||
for (int i = 0; i < obstacles.Count; i++) lastSim.RemoveObstacle(obstacles[i]);
|
||||
lastSim = null;
|
||||
}
|
||||
|
||||
obstacles.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Adds obstacles for a grid graph</summary>
|
||||
void AddGraphObstacles (Pathfinding.RVO.Simulator sim, GridGraph grid) {
|
||||
bool reverse = Vector3.Dot(grid.transform.TransformVector(Vector3.up), sim.movementPlane == MovementPlane.XY ? Vector3.back : Vector3.up) > 0;
|
||||
|
||||
GraphUtilities.GetContours(grid, vertices => {
|
||||
// Check if the contour is traced in the wrong direction from the one we want it in.
|
||||
// If we did not do this then instead of the obstacles keeping the agents OUT of the walls
|
||||
// they would keep them INSIDE the walls.
|
||||
if (reverse) System.Array.Reverse(vertices);
|
||||
obstacles.Add(sim.AddObstacle(vertices, wallHeight, true));
|
||||
}, wallHeight*0.4f);
|
||||
}
|
||||
|
||||
/// <summary>Adds obstacles for a navmesh/recast graph</summary>
|
||||
void AddGraphObstacles (Pathfinding.RVO.Simulator simulator, INavmesh navmesh) {
|
||||
GraphUtilities.GetContours(navmesh, (vertices, cycle) => {
|
||||
var verticesV3 = new Vector3[vertices.Count];
|
||||
for (int i = 0; i < verticesV3.Length; i++) verticesV3[i] = (Vector3)vertices[i];
|
||||
// Pool the 'vertices' list to reduce allocations
|
||||
ListPool<Int3>.Release(vertices);
|
||||
obstacles.Add(simulator.AddObstacle(verticesV3, wallHeight, cycle));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52e10183f1ec74d27ba5e19d5a2242c0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@@ -0,0 +1,314 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pathfinding.RVO {
|
||||
/// <summary>
|
||||
/// Base class for simple RVO colliders.
|
||||
///
|
||||
/// This is a helper base class for RVO colliders. It provides automatic gizmos
|
||||
/// and helps with the winding order of the vertices as well as automatically updating the obstacle when moved.
|
||||
///
|
||||
/// Extend this class to create custom RVO obstacles.
|
||||
///
|
||||
/// See: writing-rvo-colliders (view in online documentation for working links)
|
||||
/// See: RVOSquareObstacle
|
||||
/// </summary>
|
||||
public abstract class RVOObstacle : VersionedMonoBehaviour {
|
||||
/// <summary>
|
||||
/// Mode of the obstacle.
|
||||
/// Determines winding order of the vertices
|
||||
/// </summary>
|
||||
public ObstacleVertexWinding obstacleMode;
|
||||
|
||||
public RVOLayer layer = RVOLayer.DefaultObstacle;
|
||||
|
||||
/// <summary>
|
||||
/// RVO Obstacle Modes.
|
||||
/// Determines winding order of obstacle vertices
|
||||
/// </summary>
|
||||
public enum ObstacleVertexWinding {
|
||||
/// <summary>Keeps agents from entering the obstacle</summary>
|
||||
KeepOut,
|
||||
/// <summary>Keeps agents inside the obstacle</summary>
|
||||
KeepIn,
|
||||
}
|
||||
|
||||
/// <summary>Reference to simulator</summary>
|
||||
protected Pathfinding.RVO.Simulator sim;
|
||||
|
||||
/// <summary>All obstacles added</summary>
|
||||
private List<ObstacleVertex> addedObstacles;
|
||||
|
||||
/// <summary>Original vertices for the obstacles</summary>
|
||||
private List<Vector3[]> sourceObstacles;
|
||||
|
||||
/// <summary>
|
||||
/// Create Obstacles.
|
||||
/// Override this and add logic for creating obstacles.
|
||||
/// You should not use the simulator's function calls directly.
|
||||
///
|
||||
/// See: AddObstacle
|
||||
/// </summary>
|
||||
protected abstract void CreateObstacles();
|
||||
|
||||
/// <summary>
|
||||
/// Enable executing in editor to draw gizmos.
|
||||
/// If enabled, the CreateObstacles function will be executed in the editor as well
|
||||
/// in order to draw gizmos.
|
||||
/// </summary>
|
||||
protected abstract bool ExecuteInEditor { get; }
|
||||
|
||||
/// <summary>If enabled, all coordinates are handled as local.</summary>
|
||||
protected abstract bool LocalCoordinates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Static or dynamic.
|
||||
/// This determines if the obstacle can be updated by e.g moving the transform
|
||||
/// around in the scene.
|
||||
/// </summary>
|
||||
protected abstract bool StaticObstacle { get; }
|
||||
|
||||
protected abstract float Height { get; }
|
||||
/// <summary>
|
||||
/// Called in the editor.
|
||||
/// This function should return true if any variables which can change the shape or position of the obstacle
|
||||
/// has changed since the last call to this function. Take a look at the RVOSquareObstacle for an example.
|
||||
/// </summary>
|
||||
protected abstract bool AreGizmosDirty();
|
||||
|
||||
/// <summary>Enabled if currently in OnDrawGizmos</summary>
|
||||
private bool gizmoDrawing = false;
|
||||
|
||||
/// <summary>Vertices for gizmos</summary>
|
||||
private List<Vector3[]> gizmoVerts;
|
||||
|
||||
/// <summary>
|
||||
/// Last obstacle mode.
|
||||
/// Used to check if the gizmos should be updated
|
||||
/// </summary>
|
||||
private ObstacleVertexWinding _obstacleMode;
|
||||
|
||||
/// <summary>
|
||||
/// Last matrix the obstacle was updated with.
|
||||
/// Used to check if the obstacle should be updated
|
||||
/// </summary>
|
||||
private Matrix4x4 prevUpdateMatrix;
|
||||
|
||||
/// <summary>Draws Gizmos</summary>
|
||||
public void OnDrawGizmos () {
|
||||
OnDrawGizmos(false);
|
||||
}
|
||||
|
||||
/// <summary>Draws Gizmos</summary>
|
||||
public void OnDrawGizmosSelected () {
|
||||
OnDrawGizmos(true);
|
||||
}
|
||||
|
||||
/// <summary>Draws Gizmos</summary>
|
||||
public void OnDrawGizmos (bool selected) {
|
||||
gizmoDrawing = true;
|
||||
|
||||
Gizmos.color = new Color(0.615f, 1, 0.06f, selected ? 1.0f : 0.7f);
|
||||
var movementPlane = RVOSimulator.active != null ? RVOSimulator.active.movementPlane : MovementPlane.XZ;
|
||||
var up = movementPlane == MovementPlane.XZ ? Vector3.up : -Vector3.forward;
|
||||
|
||||
if (gizmoVerts == null || AreGizmosDirty() || _obstacleMode != obstacleMode) {
|
||||
_obstacleMode = obstacleMode;
|
||||
|
||||
if (gizmoVerts == null) gizmoVerts = new List<Vector3[]>();
|
||||
else gizmoVerts.Clear();
|
||||
|
||||
CreateObstacles();
|
||||
}
|
||||
|
||||
Matrix4x4 m = GetMatrix();
|
||||
|
||||
for (int i = 0; i < gizmoVerts.Count; i++) {
|
||||
Vector3[] verts = gizmoVerts[i];
|
||||
for (int j = 0, q = verts.Length-1; j < verts.Length; q = j++) {
|
||||
Gizmos.DrawLine(m.MultiplyPoint3x4(verts[j]), m.MultiplyPoint3x4(verts[q]));
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
for (int j = 0, q = verts.Length-1; j < verts.Length; q = j++) {
|
||||
Vector3 a = m.MultiplyPoint3x4(verts[q]);
|
||||
Vector3 b = m.MultiplyPoint3x4(verts[j]);
|
||||
|
||||
if (movementPlane != MovementPlane.XY) {
|
||||
Gizmos.DrawLine(a + up*Height, b + up*Height);
|
||||
Gizmos.DrawLine(a, a + up*Height);
|
||||
}
|
||||
|
||||
Vector3 avg = (a + b) * 0.5f;
|
||||
Vector3 tang = (b - a).normalized;
|
||||
if (tang == Vector3.zero) continue;
|
||||
|
||||
Vector3 normal = Vector3.Cross(up, tang);
|
||||
|
||||
Gizmos.DrawLine(avg, avg+normal);
|
||||
Gizmos.DrawLine(avg+normal, avg+normal*0.5f+tang*0.5f);
|
||||
Gizmos.DrawLine(avg+normal, avg+normal*0.5f-tang*0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gizmoDrawing = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's the matrix to use for vertices.
|
||||
/// Can be overriden for custom matrices.
|
||||
/// Returns: transform.localToWorldMatrix if LocalCoordinates is true, otherwise Matrix4x4.identity
|
||||
/// </summary>
|
||||
protected virtual Matrix4x4 GetMatrix () {
|
||||
return LocalCoordinates ? transform.localToWorldMatrix : Matrix4x4.identity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the obstacle.
|
||||
/// Do not override this function
|
||||
/// </summary>
|
||||
public void OnDisable () {
|
||||
if (addedObstacles != null) {
|
||||
if (sim == null) throw new System.Exception("This should not happen! Make sure you are not overriding the OnEnable function");
|
||||
|
||||
for (int i = 0; i < addedObstacles.Count; i++) {
|
||||
sim.RemoveObstacle(addedObstacles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enabled the obstacle.
|
||||
/// Do not override this function
|
||||
/// </summary>
|
||||
public void OnEnable () {
|
||||
if (addedObstacles != null) {
|
||||
if (sim == null) throw new System.Exception("This should not happen! Make sure you are not overriding the OnDisable function");
|
||||
|
||||
for (int i = 0; i < addedObstacles.Count; i++) {
|
||||
// Update height and layer
|
||||
var vertex = addedObstacles[i];
|
||||
var start = vertex;
|
||||
do {
|
||||
vertex.layer = layer;
|
||||
vertex = vertex.next;
|
||||
} while (vertex != start);
|
||||
|
||||
sim.AddObstacle(addedObstacles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates obstacles</summary>
|
||||
public void Start () {
|
||||
addedObstacles = new List<ObstacleVertex>();
|
||||
sourceObstacles = new List<Vector3[]>();
|
||||
prevUpdateMatrix = GetMatrix();
|
||||
CreateObstacles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates obstacle if required.
|
||||
/// Checks for if the obstacle should be updated (e.g if it has moved)
|
||||
/// </summary>
|
||||
public void Update () {
|
||||
Matrix4x4 m = GetMatrix();
|
||||
|
||||
if (m != prevUpdateMatrix) {
|
||||
for (int i = 0; i < addedObstacles.Count; i++) {
|
||||
sim.UpdateObstacle(addedObstacles[i], sourceObstacles[i], m);
|
||||
}
|
||||
prevUpdateMatrix = m;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds a simulator in the scene.
|
||||
///
|
||||
/// Saves found simulator in <see cref="sim"/>.
|
||||
///
|
||||
/// Throws: System.InvalidOperationException When no RVOSimulator could be found.
|
||||
/// </summary>
|
||||
protected void FindSimulator () {
|
||||
if (RVOSimulator.active == null) throw new System.InvalidOperationException("No RVOSimulator could be found in the scene. Please add one to any GameObject");
|
||||
sim = RVOSimulator.active.GetSimulator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an obstacle with the specified vertices.
|
||||
/// The vertices array might be changed by this function.
|
||||
/// </summary>
|
||||
protected void AddObstacle (Vector3[] vertices, float height) {
|
||||
if (vertices == null) throw new System.ArgumentNullException("Vertices Must Not Be Null");
|
||||
if (height < 0) throw new System.ArgumentOutOfRangeException("Height must be non-negative");
|
||||
if (vertices.Length < 2) throw new System.ArgumentException("An obstacle must have at least two vertices");
|
||||
if (sim == null) FindSimulator();
|
||||
|
||||
if (gizmoDrawing) {
|
||||
var v = new Vector3[vertices.Length];
|
||||
WindCorrectly(vertices);
|
||||
System.Array.Copy(vertices, v, vertices.Length);
|
||||
gizmoVerts.Add(v);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (vertices.Length == 2) {
|
||||
AddObstacleInternal(vertices, height);
|
||||
return;
|
||||
}
|
||||
|
||||
WindCorrectly(vertices);
|
||||
AddObstacleInternal(vertices, height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an obstacle.
|
||||
/// Winding is assumed to be correct and very little error checking is done.
|
||||
/// </summary>
|
||||
private void AddObstacleInternal (Vector3[] vertices, float height) {
|
||||
addedObstacles.Add(sim.AddObstacle(vertices, height, GetMatrix(), layer));
|
||||
sourceObstacles.Add(vertices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Winds the vertices correctly.
|
||||
/// Winding order is determined from <see cref="obstacleMode"/>.
|
||||
/// </summary>
|
||||
private void WindCorrectly (Vector3[] vertices) {
|
||||
int leftmost = 0;
|
||||
float leftmostX = float.PositiveInfinity;
|
||||
|
||||
var matrix = GetMatrix();
|
||||
|
||||
for (int i = 0; i < vertices.Length; i++) {
|
||||
var x = matrix.MultiplyPoint3x4(vertices[i]).x;
|
||||
if (x < leftmostX) {
|
||||
leftmost = i;
|
||||
leftmostX = x;
|
||||
}
|
||||
}
|
||||
|
||||
var p1 = matrix.MultiplyPoint3x4(vertices[(leftmost-1 + vertices.Length) % vertices.Length]);
|
||||
var p2 = matrix.MultiplyPoint3x4(vertices[leftmost]);
|
||||
var p3 = matrix.MultiplyPoint3x4(vertices[(leftmost+1) % vertices.Length]);
|
||||
|
||||
MovementPlane movementPlane;
|
||||
if (sim != null) movementPlane = sim.movementPlane;
|
||||
else if (RVOSimulator.active) movementPlane = RVOSimulator.active.movementPlane;
|
||||
else movementPlane = MovementPlane.XZ;
|
||||
|
||||
if (movementPlane == MovementPlane.XY) {
|
||||
p1.z = p1.y;
|
||||
p2.z = p2.y;
|
||||
p3.z = p3.y;
|
||||
}
|
||||
|
||||
if (VectorMath.IsClockwiseXZ(p1, p2, p3) != (obstacleMode == ObstacleVertexWinding.KeepIn)) {
|
||||
System.Array.Reverse(vertices);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5c46b71c11fa41a08c1d9b79aabd500
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@@ -0,0 +1,195 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Pathfinding.RVO {
|
||||
using Pathfinding.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Unity front end for an RVO simulator.
|
||||
/// Attached to any GameObject in a scene, scripts such as the RVOController will use the
|
||||
/// simulator exposed by this class to handle their movement.
|
||||
/// In pretty much all cases you should only have a single RVOSimulator in the scene.
|
||||
///
|
||||
/// You can have more than one of these, however most scripts which make use of the RVOSimulator
|
||||
/// will use the <see cref="active"/> property which just returns the first simulator in the scene.
|
||||
///
|
||||
/// This is only a wrapper class for a Pathfinding.RVO.Simulator which simplifies exposing it
|
||||
/// for a unity scene.
|
||||
///
|
||||
/// See: Pathfinding.RVO.Simulator
|
||||
/// See: local-avoidance (view in online documentation for working links)
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Pathfinding/Local Avoidance/RVO Simulator")]
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_r_v_o_1_1_r_v_o_simulator.php")]
|
||||
public class RVOSimulator : VersionedMonoBehaviour {
|
||||
/// <summary>First RVOSimulator in the scene (usually there is only one)</summary>
|
||||
public static RVOSimulator active { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desired FPS for rvo simulation.
|
||||
/// It is usually not necessary to run a crowd simulation at a very high fps.
|
||||
/// Usually 10-30 fps is enough, but it can be increased for better quality.
|
||||
/// The rvo simulation will never run at a higher fps than the game
|
||||
/// </summary>
|
||||
[Tooltip("Desired FPS for rvo simulation. It is usually not necessary to run a crowd simulation at a very high fps.\n" +
|
||||
"Usually 10-30 fps is enough, but can be increased for better quality.\n"+
|
||||
"The rvo simulation will never run at a higher fps than the game")]
|
||||
public int desiredSimulationFPS = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Number of RVO worker threads.
|
||||
/// If set to None, no multithreading will be used.
|
||||
/// Using multithreading can significantly improve performance by offloading work to other CPU cores.
|
||||
/// </summary>
|
||||
[Tooltip("Number of RVO worker threads. If set to None, no multithreading will be used.")]
|
||||
public ThreadCount workerThreads = ThreadCount.Two;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate local avoidance in between frames.
|
||||
/// If this is enabled and multithreading is used, the local avoidance calculations will continue to run
|
||||
/// until the next frame instead of waiting for them to be done the same frame. This can increase the performance
|
||||
/// but it can make the agents seem a little less responsive.
|
||||
///
|
||||
/// This will only be read at Awake.
|
||||
/// See: Pathfinding.RVO.Simulator.DoubleBuffering
|
||||
/// </summary>
|
||||
[Tooltip("Calculate local avoidance in between frames.\nThis can increase jitter in the agents' movement so use it only if you really need the performance boost. " +
|
||||
"It will also reduce the responsiveness of the agents to the commands you send to them.")]
|
||||
public bool doubleBuffering;
|
||||
|
||||
/// <summary>\copydoc Pathfinding::RVO::Simulator::symmetryBreakingBias</summary>
|
||||
[Tooltip("Bias agents to pass each other on the right side.\n" +
|
||||
"If the desired velocity of an agent puts it on a collision course with another agent or an obstacle " +
|
||||
"its desired velocity will be rotated this number of radians (1 radian is approximately 57°) to the right. " +
|
||||
"This helps to break up symmetries and makes it possible to resolve some situations much faster.\n\n" +
|
||||
"When many agents have the same goal this can however have the side effect that the group " +
|
||||
"clustered around the target point may as a whole start to spin around the target point.")]
|
||||
[Range(0, 0.2f)]
|
||||
public float symmetryBreakingBias = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the XY (2D) or XZ (3D) plane is used for movement.
|
||||
/// For 2D games you would set this to XY and for 3D games you would usually set it to XZ.
|
||||
/// </summary>
|
||||
[Tooltip("Determines if the XY (2D) or XZ (3D) plane is used for movement")]
|
||||
public MovementPlane movementPlane = MovementPlane.XZ;
|
||||
|
||||
/// <summary>
|
||||
/// Draw obstacle gizmos to aid with debugging.
|
||||
///
|
||||
/// In the screenshot the obstacles are visible in red.
|
||||
/// [Open online documentation to see images]
|
||||
/// </summary>
|
||||
public bool drawObstacles;
|
||||
|
||||
/// <summary>Reference to the internal simulator</summary>
|
||||
Pathfinding.RVO.Simulator simulator;
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal simulator.
|
||||
/// Will never be null when the game is running
|
||||
/// </summary>
|
||||
public Simulator GetSimulator () {
|
||||
if (simulator == null) {
|
||||
OnSyncLoad();
|
||||
}
|
||||
return simulator;
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
active = this;
|
||||
}
|
||||
|
||||
public override void OnSyncLoad()
|
||||
{
|
||||
base.OnSyncLoad();
|
||||
active = this;
|
||||
if (simulator == null && Application.isPlaying) {
|
||||
int threadCount = AstarPath.CalculateThreadCount(workerThreads);
|
||||
simulator = new Pathfinding.RVO.Simulator(threadCount, doubleBuffering, movementPlane);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
|
||||
if (desiredSimulationFPS < 1) desiredSimulationFPS = 1;
|
||||
|
||||
var sim = GetSimulator();
|
||||
sim.DesiredDeltaTime = 1.0f / desiredSimulationFPS;
|
||||
sim.symmetryBreakingBias = symmetryBreakingBias;
|
||||
sim.Update();
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
active = null;
|
||||
if (simulator != null) {
|
||||
simulator.OnDestroy();
|
||||
simulator = null;
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
gizmos.ClearCache();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[System.NonSerialized]
|
||||
RetainedGizmos gizmos = new RetainedGizmos();
|
||||
|
||||
static Color ObstacleColor = new Color(255/255f, 60/255f, 15/255f, 1.0f);
|
||||
void OnDrawGizmos () {
|
||||
// Prevent interfering with scene view picking
|
||||
if (Event.current.type != EventType.Repaint) return;
|
||||
|
||||
if (drawObstacles && simulator != null && simulator.obstacles != null) {
|
||||
var hasher = new RetainedGizmos.Hasher();
|
||||
var obstacles = simulator.obstacles;
|
||||
int numEdges = 0;
|
||||
for (int i = 0; i < obstacles.Count; i++) {
|
||||
var vertex = obstacles[i];
|
||||
do {
|
||||
hasher.AddHash(vertex.position.GetHashCode() ^ vertex.height.GetHashCode());
|
||||
numEdges++;
|
||||
vertex = vertex.next;
|
||||
} while (vertex != obstacles[i] && vertex != null);
|
||||
}
|
||||
|
||||
if (!gizmos.Draw(hasher)) {
|
||||
Profiler.BeginSample("Rebuild RVO Obstacle Gizmos");
|
||||
using (var helper = gizmos.GetGizmoHelper(null, hasher)) {
|
||||
var up = movementPlane == MovementPlane.XY ? Vector3.back : Vector3.up;
|
||||
var vertices = new Vector3[numEdges*6];
|
||||
var colors = new Color[numEdges*6];
|
||||
int edgeIndex = 0;
|
||||
for (int i = 0; i < obstacles.Count; i++) {
|
||||
var start = obstacles[i];
|
||||
var c = start;
|
||||
do {
|
||||
vertices[edgeIndex*6 + 0] = c.position;
|
||||
vertices[edgeIndex*6 + 1] = c.next.position;
|
||||
vertices[edgeIndex*6 + 2] = c.next.position + up*c.next.height;
|
||||
vertices[edgeIndex*6 + 3] = c.position;
|
||||
vertices[edgeIndex*6 + 4] = c.next.position + up*c.next.height;
|
||||
vertices[edgeIndex*6 + 5] = c.position + up*c.height;
|
||||
edgeIndex++;
|
||||
c = c.next;
|
||||
} while (c != start && c != null && c.next != null);
|
||||
}
|
||||
|
||||
for (int i = 0; i < colors.Length; i++) {
|
||||
colors[i] = ObstacleColor;
|
||||
}
|
||||
|
||||
helper.DrawTriangles(vertices, colors, numEdges * 2);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
gizmos.FinalizeDraw();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1764de1de43cc4d19af52679d3eaae06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -10100
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding.RVO {
|
||||
/// <summary>Square Obstacle for RVO Simulation.</summary>
|
||||
[AddComponentMenu("Pathfinding/Local Avoidance/Square Obstacle")]
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_r_v_o_1_1_r_v_o_square_obstacle.php")]
|
||||
public class RVOSquareObstacle : RVOObstacle {
|
||||
/// <summary>Height of the obstacle</summary>
|
||||
public float height = 1;
|
||||
|
||||
/// <summary>Size of the square</summary>
|
||||
public Vector2 size = Vector3.one;
|
||||
|
||||
/// <summary>Center of the square</summary>
|
||||
public Vector2 center = Vector3.zero;
|
||||
|
||||
protected override bool StaticObstacle { get { return false; } }
|
||||
protected override bool ExecuteInEditor { get { return true; } }
|
||||
protected override bool LocalCoordinates { get { return true; } }
|
||||
protected override float Height { get { return height; } }
|
||||
|
||||
//If UNITY_EDITOR to save a few bytes, these are only needed in the editor
|
||||
#if UNITY_EDITOR
|
||||
private Vector2 _size;
|
||||
private Vector2 _center;
|
||||
private float _height;
|
||||
#endif
|
||||
|
||||
protected override bool AreGizmosDirty () {
|
||||
#if UNITY_EDITOR
|
||||
bool ret = _size != size || _height != height || _center != center;
|
||||
_size = size;
|
||||
_center = center;
|
||||
_height = height;
|
||||
return ret;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void CreateObstacles () {
|
||||
size.x = Mathf.Abs(size.x);
|
||||
size.y = Mathf.Abs(size.y);
|
||||
height = Mathf.Abs(height);
|
||||
|
||||
var verts = new [] { new Vector3(1, 0, -1), new Vector3(1, 0, 1), new Vector3(-1, 0, 1), new Vector3(-1, 0, -1) };
|
||||
for (int i = 0; i < verts.Length; i++) {
|
||||
verts[i].Scale(new Vector3(size.x * 0.5f, 0, size.y * 0.5f));
|
||||
verts[i] += new Vector3(center.x, 0, center.y);
|
||||
}
|
||||
|
||||
AddObstacle(verts, height);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21ffa94b775f54641bd8c73c930db320
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
Reference in New Issue
Block a user