Current state and plans for ECS integration?

Here is a tweaked editor script for the authoring component. Seems to work for my purposes but haven’t tested all the features. Its basically just a tweaked FollowerEntityEditor script.

using UnityEditor;
using UnityEngine;
using Pathfinding.RVO;
using Pathfinding.ECS;
using System.Linq;
namespace Pathfinding
{
	[CustomEditor(typeof(FollowerEntityAuthoring), true)]
	[CanEditMultipleObjects]
	public class FollowerEntityAuthoringEditor : EditorBase
	{
		//bool debug = false;
		bool tagPenaltiesOpen;

		protected override void OnDisable()
		{
			//base.OnDisable();
			EditorPrefs.SetBool("FollowerEntityAuthoring.tagPenaltiesOpen", tagPenaltiesOpen);
		}

		protected override void OnEnable()
		{
			//base.OnEnable();
			tagPenaltiesOpen = EditorPrefs.GetBool("FollowerEntityAuthoring.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 Application.isPlaying;
		}

		protected void AutoRepathInspector()
		{
			var mode = FindProperty("autoRepathBacking.mode");

			PropertyField(mode, "Recalculate Paths Automatically");
			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--;
			}
		}

		void PathfindingSettingsInspector()
		{
			bool anyCustomTraversalProvider = this.targets.Any(s => (s as FollowerEntityAuthoring).managedState.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 FollowerEntityAuthoring).managedState.pathfindingSettings.traversableTags).ToArray();
				SeekerEditor.TagsEditor(FindProperty("managedState.pathfindingSettings.tagPenalties"), traversableTags);
				for (int i = 0; i < targets.Length; i++)
				{
					(targets[i] as FollowerEntityAuthoring).managedState.pathfindingSettings.traversableTags = traversableTags[i];
				}
				EditorGUI.indentLevel--;
			}
		}

		protected override void Inspector()
		{
			Undo.RecordObjects(targets, "Modify FollowerEntityAuthoring 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");

			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);

			if (PropertyField("managedState.enableGravity", "Gravity"))
			{
				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)
			{
				var possiblySpherical = AstarPath.active.data.navmesh != null && !AstarPath.active.data.navmesh.RecalculateNormals;
				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);
				}
			}

			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 FollowerEntityAuthoring;
					script.SyncWithEntity();
				}
			}*/
		}
	}
}
2 Likes

Hey there. Aron pointed me at this post in response to my question about RVO in an ECS: RVO + Pathfinding in ECS - #2 by aron_granberg

This looks promising but I’m struggling to follow what you’re doing without the component definitions. Is there any chance you could post them so I can try to reconstruct what you’re doing here in my ECS?

The components are all included in the A* package. AstarPathfindingProject/Core/ECS/Components.

Oh awesome, thanks!

This script is just a pure Entity version of FollowerEntity. So instead of normal Gameobject prefabs you would add this to an Entity prefab that you instantiate with EntityManager or have placed in a subscene.

RVO seems to work for me both with FollowerEntity and this FollowerEntityAuthoring. Just have to enable LocalAvoidance and add an RVOSimulator to your scene.

2 Likes

Fyi. The next version of the package will have a built in baker for the follower entity component.

2 Likes

This is excellent news! :partying_face::confetti_ball::piñata::tada::beers::clinking_glasses::beers::clinking_glasses::tada::piñata::confetti_ball::partying_face::beers:

Cool that’s great!

Looking at the original topic, are there any plans yet to support ECS physics for the navmesh creation?
Right now, we still need for all navmesh related objects (terrain, obstacles, navmesh updates etc) hybrid entities and a duplicate physics scene (one using ECS physics, one with UnityEngine physics).

It could even be a separate package like an add-on, granted that it would be quite some work…

I don’t have any concrete plans right now, but it’s on my todo list. It would require quite a lot of changes.

1 Like