2024-06-03 18:26:03 +00:00
#if MODULE_ENTITIES
using UnityEditor ;
using UnityEngine ;
using Pathfinding.RVO ;
using Pathfinding.ECS ;
using System.Linq ;
namespace Pathfinding {
[CustomEditor(typeof(FollowerEntity), true)]
[CanEditMultipleObjects]
public class FollowerEntityEditor : EditorBase {
bool debug = false ;
bool tagPenaltiesOpen ;
protected override void OnDisable ( ) {
base . OnDisable ( ) ;
EditorPrefs . SetBool ( "FollowerEntity.debug" , debug ) ;
EditorPrefs . SetBool ( "FollowerEntity.tagPenaltiesOpen" , tagPenaltiesOpen ) ;
}
protected override void OnEnable ( ) {
base . OnEnable ( ) ;
debug = EditorPrefs . GetBool ( "FollowerEntity.debug" , false ) ;
tagPenaltiesOpen = EditorPrefs . GetBool ( "FollowerEntity.tagPenaltiesOpen" , false ) ;
}
public override bool RequiresConstantRepaint ( ) {
// When the debug inspector is open we want to update it every frame
// as the agent can move
return debug & & Application . isPlaying ;
}
protected void AutoRepathInspector ( ) {
var mode = FindProperty ( "autoRepathBacking.mode" ) ;
2024-11-28 23:07:50 +00:00
PropertyField ( mode , "Recalculate Paths Automatically" ) ;
2024-06-03 18:26:03 +00:00
if ( ! mode . hasMultipleDifferentValues ) {
var modeValue = ( AutoRepathPolicy . Mode ) mode . enumValueIndex ;
EditorGUI . indentLevel + + ;
var period = FindProperty ( "autoRepathBacking.period" ) ;
if ( modeValue = = AutoRepathPolicy . Mode . EveryNSeconds | | modeValue = = AutoRepathPolicy . Mode . Dynamic ) {
FloatField ( period , min : 0f ) ;
}
if ( modeValue = = AutoRepathPolicy . Mode . Dynamic ) {
EditorGUILayout . HelpBox ( "The path will be recalculated at least every " + period . floatValue . ToString ( "0.0" ) + " seconds, but more often if the destination changes quickly" , MessageType . None ) ;
}
EditorGUI . indentLevel - - ;
}
}
protected void DebugInspector ( ) {
debug = EditorGUILayout . Foldout ( debug , "Debug info" ) ;
if ( debug ) {
EditorGUI . indentLevel + + ;
EditorGUI . BeginDisabledGroup ( true ) ;
if ( targets . Length = = 1 ) {
var ai = target as FollowerEntity ;
EditorGUILayout . Toggle ( "Reached Destination" , ai . reachedDestination ) ;
EditorGUILayout . Toggle ( "Reached End Of Path" , ai . reachedEndOfPath ) ;
EditorGUILayout . Toggle ( "Has Path" , ai . hasPath ) ;
EditorGUILayout . Toggle ( "Path Pending" , ai . pathPending ) ;
if ( ai . isTraversingOffMeshLink ) {
2024-11-28 23:07:50 +00:00
EditorGUILayout . Toggle ( "Traversing Off-Mesh Link" , true ) ;
}
if ( ai . isStopped ) {
EditorGUILayout . Toggle ( "IsStopped (user controlled)" , ai . isStopped ) ;
2024-06-03 18:26:03 +00:00
}
EditorGUI . EndDisabledGroup ( ) ;
var newDestination = EditorGUILayout . Vector3Field ( "Destination" , ai . destination ) ;
if ( ai . entityExists ) ai . destination = newDestination ;
EditorGUI . BeginDisabledGroup ( true ) ;
2024-11-28 23:07:50 +00:00
EditorGUILayout . LabelField ( "Remaining Distance" , ai . remainingDistance . ToString ( "0.00" ) ) ;
2024-06-03 18:26:03 +00:00
EditorGUILayout . LabelField ( "Speed" , ai . velocity . magnitude . ToString ( "0.00" ) ) ;
} else {
int nReachedDestination = 0 ;
int nReachedEndOfPath = 0 ;
int nPending = 0 ;
for ( int i = 0 ; i < targets . Length ; i + + ) {
var ai = targets [ i ] as IAstarAI ;
if ( ai . reachedDestination ) nReachedDestination + + ;
if ( ai . reachedEndOfPath ) nReachedEndOfPath + + ;
if ( ai . pathPending ) nPending + + ;
}
EditorGUILayout . LabelField ( "Reached Destination" , nReachedDestination + " of " + targets . Length ) ;
EditorGUILayout . LabelField ( "Reached End Of Path" , nReachedEndOfPath + " of " + targets . Length ) ;
EditorGUILayout . LabelField ( "Path Pending" , nPending + " of " + targets . Length ) ;
}
EditorGUI . EndDisabledGroup ( ) ;
EditorGUI . indentLevel - - ;
}
}
void PathfindingSettingsInspector ( ) {
bool anyCustomTraversalProvider = this . targets . Any ( s = > ( s as FollowerEntity ) . pathfindingSettings . traversalProvider ! = null ) ;
if ( anyCustomTraversalProvider ) {
EditorGUILayout . HelpBox ( "Custom traversal provider active" , MessageType . None ) ;
}
PropertyField ( "managedState.pathfindingSettings.graphMask" , "Traversable Graphs" ) ;
tagPenaltiesOpen = EditorGUILayout . Foldout ( tagPenaltiesOpen , new GUIContent ( "Tags" , "Settings for each tag" ) ) ;
if ( tagPenaltiesOpen ) {
EditorGUI . indentLevel + + ;
var traversableTags = this . targets . Select ( s = > ( s as FollowerEntity ) . pathfindingSettings . traversableTags ) . ToArray ( ) ;
SeekerEditor . TagsEditor ( FindProperty ( "managedState.pathfindingSettings.tagPenalties" ) , traversableTags ) ;
for ( int i = 0 ; i < targets . Length ; i + + ) {
( targets [ i ] as FollowerEntity ) . pathfindingSettings . traversableTags = traversableTags [ i ] ;
}
EditorGUI . indentLevel - - ;
}
}
protected override void Inspector ( ) {
Undo . RecordObjects ( targets , "Modify FollowerEntity settings" ) ;
EditorGUI . BeginChangeCheck ( ) ;
Section ( "Shape" ) ;
FloatField ( "shape.radius" , min : 0.01f ) ;
FloatField ( "shape.height" , min : 0.01f ) ;
Popup ( "orientationBacking" , new [ ] { new GUIContent ( "ZAxisForward (for 3D games)" ) , new GUIContent ( "YAxisForward (for 2D games)" ) } , "Orientation" ) ;
2024-11-28 23:07:50 +00:00
var orientationProperty = FindProperty ( "orientationBacking" ) ;
bool is2D = ( OrientationMode ) orientationProperty . enumValueIndex = = OrientationMode . YAxisForward ;
2024-06-03 18:26:03 +00:00
Section ( "Movement" ) ;
FloatField ( "movement.follower.speed" , min : 0f ) ;
FloatField ( "movement.follower.rotationSpeed" , min : 0f ) ;
var maxRotationSpeed = FindProperty ( "movement.follower.rotationSpeed" ) ;
FloatField ( "movement.follower.maxRotationSpeed" , min : maxRotationSpeed . hasMultipleDifferentValues ? 0f : maxRotationSpeed . floatValue ) ;
if ( ByteAsToggle ( "movement.follower.allowRotatingOnSpotBacking" , "Allow Rotating On The Spot" ) ) {
EditorGUI . indentLevel + + ;
FloatField ( "movement.follower.maxOnSpotRotationSpeed" , min : 0f ) ;
FloatField ( "movement.follower.slowdownTimeWhenTurningOnSpot" , min : 0f ) ;
EditorGUI . indentLevel - - ;
}
Slider ( "movement.positionSmoothing" , left : 0f , right : 0.5f ) ;
Slider ( "movement.rotationSmoothing" , left : 0f , right : 0.5f ) ;
FloatField ( "movement.follower.slowdownTime" , min : 0f ) ;
FloatField ( "movement.stopDistance" , min : 0f ) ;
FloatField ( "movement.follower.leadInRadiusWhenApproachingDestination" , min : 0f ) ;
FloatField ( "movement.follower.desiredWallDistance" , min : 0f ) ;
2024-11-28 23:07:50 +00:00
if ( ! is2D & & PropertyField ( "managedState.enableGravity" , "Gravity" ) ) {
2024-06-03 18:26:03 +00:00
EditorGUI . indentLevel + + ;
PropertyField ( "movement.groundMask" , "Raycast Ground Mask" ) ;
EditorGUI . indentLevel - - ;
}
var movementPlaneSource = FindProperty ( "movementPlaneSourceBacking" ) ;
PropertyField ( movementPlaneSource , "Movement Plane Source" ) ;
if ( AstarPath . active ! = null & & AstarPath . active . data . graphs ! = null ) {
2024-11-28 23:07:50 +00:00
var possiblySpherical = AstarPath . active . data . navmeshGraph ! = null & & ! AstarPath . active . data . navmeshGraph . RecalculateNormals ;
2024-06-03 18:26:03 +00:00
if ( ! possiblySpherical & & ! movementPlaneSource . hasMultipleDifferentValues & & ( MovementPlaneSource ) movementPlaneSource . intValue = = MovementPlaneSource . Raycast ) {
EditorGUILayout . HelpBox ( "Using raycasts as the movement plane source is only recommended if you have a spherical or otherwise non-planar world. It has a performance overhead." , MessageType . Info ) ;
}
if ( ! possiblySpherical & & ! movementPlaneSource . hasMultipleDifferentValues & & ( MovementPlaneSource ) movementPlaneSource . intValue = = MovementPlaneSource . NavmeshNormal ) {
EditorGUILayout . HelpBox ( "Using the navmesh normal as the movement plane source is only recommended if you have a spherical or otherwise non-planar world. It has a performance overhead." , MessageType . Info ) ;
}
}
2024-11-28 23:07:50 +00:00
this . Popup ( "syncPosition" , new [ ] { new GUIContent ( "Move independently of transform" ) , new GUIContent ( "Move agent with transform" ) } , "Position Sync" ) ;
this . Popup ( "syncRotation" , new [ ] { new GUIContent ( "Rotate independently of transform" ) , new GUIContent ( "Rotate agent with transform" ) } , "Rotation Sync" ) ;
2024-06-03 18:26:03 +00:00
Section ( "Pathfinding" ) ;
PathfindingSettingsInspector ( ) ;
AutoRepathInspector ( ) ;
if ( SectionEnableable ( "Local Avoidance" , "managedState.enableLocalAvoidance" ) ) {
if ( Application . isPlaying & & RVOSimulator . active = = null & & ! EditorUtility . IsPersistent ( target ) ) {
EditorGUILayout . HelpBox ( "There is no enabled RVOSimulator component in the scene. A single global RVOSimulator component is required for local avoidance." , MessageType . Warning ) ;
}
FloatField ( "managedState.rvoSettings.agentTimeHorizon" , min : 0f , max : 20.0f ) ;
FloatField ( "managedState.rvoSettings.obstacleTimeHorizon" , min : 0f , max : 20.0f ) ;
PropertyField ( "managedState.rvoSettings.maxNeighbours" ) ;
ClampInt ( "managedState.rvoSettings.maxNeighbours" , min : 0 , max : SimulatorBurst . MaxNeighbourCount ) ;
PropertyField ( "managedState.rvoSettings.layer" ) ;
PropertyField ( "managedState.rvoSettings.collidesWith" ) ;
Slider ( "managedState.rvoSettings.priority" , left : 0f , right : 1.0f ) ;
PropertyField ( "managedState.rvoSettings.locked" ) ;
}
Section ( "Debug" ) ;
PropertyField ( "movement.debugFlags" , "Movement Debug Rendering" ) ;
PropertyField ( "managedState.rvoSettings.debug" , "Local Avoidance Debug Rendering" ) ;
DebugInspector ( ) ;
if ( EditorGUI . EndChangeCheck ( ) ) {
for ( int i = 0 ; i < targets . Length ; i + + ) {
var script = targets [ i ] as FollowerEntity ;
script . SyncWithEntity ( ) ;
}
}
}
}
}
#else
using UnityEditor ;
using UnityEngine ;
using Pathfinding.ECS ;
using UnityEditor.PackageManager ;
using UnityEditor.PackageManager.Requests ;
namespace Pathfinding {
// This inspector is only used if the Entities package is not installed
[CustomEditor(typeof(FollowerEntity), true)]
[CanEditMultipleObjects]
public class FollowerEntityEditor : EditorBase {
static AddRequest addRequest ;
protected override void Inspector ( ) {
if ( addRequest ! = null ) {
if ( addRequest . Status = = StatusCode . Success ) {
addRequest = null ;
// If we get this far, unity did not successfully reload the assemblies.
// Who knows what went wrong. Quite possibly restarting Unity will resolve the issue.
EditorUtility . DisplayDialog ( "Installed Entities package" , "The entities package has been installed. You may have to restart the editor for changes to take effect." , "Ok" ) ;
} else if ( addRequest . Status = = StatusCode . Failure ) {
EditorGUILayout . HelpBox ( "Failed to install the Entities package. Please install it manually using the Package Manager." + ( addRequest . Error ! = null ? "\n" + addRequest . Error . message : "" ) , MessageType . Error ) ;
} else {
EditorGUILayout . HelpBox ( "Installing entities package..." , MessageType . None ) ;
}
} else {
2024-11-28 23:07:50 +00:00
EditorGUILayout . HelpBox ( "This component requires the Entities package (1.1.0+) to be installed. Please install it using the Package Manager." , MessageType . Error ) ;
2024-06-03 18:26:03 +00:00
if ( GUILayout . Button ( "Install entities package" ) ) {
addRequest = Client . Add ( "com.unity.entities" ) ;
}
}
}
}
}
#endif