diff options
author | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
commit | 8722a9920c1f6119bf6e769cba270e63097f8e25 (patch) | |
tree | 2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs | |
parent | 3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff) |
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs new file mode 100644 index 0000000..a0d6fd3 --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/TargetMover.cs @@ -0,0 +1,189 @@ +#pragma warning disable 649 +using UnityEngine; +using System.Linq; +using System.Collections; +using System.Collections.Generic; +using Pathfinding.Util; + +namespace Pathfinding { + /// <summary> + /// Moves the target in example scenes. + /// This is a simple script which has the sole purpose + /// of moving the target point of agents in the example + /// scenes for the A* Pathfinding Project. + /// + /// It is not meant to be pretty, but it does the job. + /// </summary> + [HelpURL("https://arongranberg.com/astar/documentation/stable/targetmover.html")] + public class TargetMover : VersionedMonoBehaviour { + /// <summary>Mask for the raycast placement</summary> + public LayerMask mask; + + public Transform target; + + /// <summary>Determines if the target position should be updated every frame or only on double-click</summary> + bool onlyOnDoubleClick; + public Trigger trigger; + public GameObject clickEffect; + public bool use2D; + public PathUtilities.FormationMode formationMode = PathUtilities.FormationMode.SinglePoint; + + Camera cam; + + public enum Trigger { + Continuously, + SingleClick, + DoubleClick + } + + public void Start () { + // Cache the Main Camera + cam = Camera.main; + } + + public void OnGUI () { + if (trigger != Trigger.Continuously && cam != null && Event.current.type == EventType.MouseDown) { + if (Event.current.clickCount == (trigger == Trigger.DoubleClick ? 2 : 1)) { + UpdateTargetPosition(); + } + } + } + + /// <summary>Update is called once per frame</summary> + void Update () { + if (trigger == Trigger.Continuously && cam != null) { + UpdateTargetPosition(); + } + } + + public void UpdateTargetPosition () { + Vector3 newPosition = Vector3.zero; + bool positionFound = false; + Transform hitObject = null; + + // If the game view has never been rendered, the mouse position can be infinite + if (!float.IsFinite(Input.mousePosition.x)) return; + + if (use2D) { + newPosition = cam.ScreenToWorldPoint(Input.mousePosition); + newPosition.z = 0; + positionFound = true; + var collider = Physics2D.OverlapPoint(newPosition, mask); + if (collider != null) hitObject = collider.transform; + } else { + // Fire a ray through the scene at the mouse position and place the target where it hits + if (cam.pixelRect.Contains(Input.mousePosition) && Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out var hit, Mathf.Infinity, mask)) { + newPosition = hit.point; + hitObject = hit.transform; + positionFound = true; + } + } + + if (positionFound) { + if (target != null) target.position = newPosition; + + if (trigger != Trigger.Continuously) { + // Slightly inefficient way of finding all AIs, but this is just an example script, so it doesn't matter much. + // FindObjectsByType does not support interfaces unfortunately. + var ais = UnityCompatibility.FindObjectsByTypeSorted<MonoBehaviour>().OfType<IAstarAI>().ToList(); + StopAllCoroutines(); + + if (hitObject != null && hitObject.TryGetComponent<Pathfinding.Examples.Interactable>(out var interactable)) { + // Pick the first AI to interact with the interactable + if (ais.Count > 0) interactable.Interact(ais[0]); + } else { + if (clickEffect != null) { + GameObject.Instantiate(clickEffect, newPosition, Quaternion.identity); + } + + // This will calculate individual destinations for each agent, like in a formation pattern. + // The simplest mode, FormationMode.SinglePoint, just assigns newPosition to all entries of the 'destinations' list. + var destinations = PathUtilities.FormationDestinations(ais, newPosition, formationMode, 0.5f); + for (int i = 0; i < ais.Count; i++) { +#if MODULE_ENTITIES + var isFollowerEntity = ais[i] is FollowerEntity; +#else + var isFollowerEntity = false; +#endif + if (ais[i] != null) { + ais[i].destination = destinations[i]; + + // Make the agents recalculate their path immediately for slighly increased responsiveness. + // The FollowerEntity is better at doing this automatically. + if (!isFollowerEntity) ais[i].SearchPath(); + } + } + + StartCoroutine(OptimizeFormationDestinations(ais, destinations)); + } + } + } + } + + /// <summary> + /// Swap the destinations of pairs of agents if it reduces the total distance they need to travel. + /// + /// This is a simple optimization algorithm to make group movement smoother and more efficient. + /// It is not perfect and may not always find the optimal solution, but it is very fast and works well in practice. + /// It will not work great for large groups of agents, as the optimization becomes too hard for this simple algorithm. + /// + /// See: https://en.wikipedia.org/wiki/Assignment_problem + /// </summary> + IEnumerator OptimizeFormationDestinations (List<IAstarAI> ais, List<Vector3> destinations) { + // Prevent swapping the same agents multiple times. + // This is because the distance measurement is only an approximation, and agents + // may temporarily have to move away from their destination before they can move towards it. + // Allowing multiple swaps could make the agents move back and forth indefinitely as the targets shift around. + var alreadySwapped = new HashSet<(IAstarAI, IAstarAI)>(); + + const int IterationsPerFrame = 4; + + while (true) { + for (int i = 0; i < IterationsPerFrame; i++) { + var a = Random.Range(0, ais.Count); + var b = Random.Range(0, ais.Count); + if (a == b) continue; + if (b < a) Memory.Swap(ref a, ref b); + var aiA = ais[a]; + var aiB = ais[b]; + + if ((MonoBehaviour)aiA == null) continue; + if ((MonoBehaviour)aiB == null) continue; + + if (alreadySwapped.Contains((aiA, aiB))) continue; + + var pA = aiA.position; + var pB = aiB.position; + var distA = (pA - destinations[a]).sqrMagnitude; + var distB = (pB - destinations[b]).sqrMagnitude; + + var newDistA = (pA - destinations[b]).sqrMagnitude; + var newDistB = (pB - destinations[a]).sqrMagnitude; + var cost1 = distA + distB; + var cost2 = newDistA + newDistB; + if (cost2 < cost1 * 0.98f) { + // Swap the destinations + var tmp = destinations[a]; + destinations[a] = destinations[b]; + destinations[b] = tmp; + + aiA.destination = destinations[a]; + aiB.destination = destinations[b]; + + alreadySwapped.Add((aiA, aiB)); + } + } + yield return null; + } + } + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + if (migrations.TryMigrateFromLegacyFormat(out var legacyVersion)) { + if (legacyVersion < 2) { + trigger = onlyOnDoubleClick ? Trigger.DoubleClick : Trigger.Continuously; + } + } + base.OnUpgradeSerializedData(ref migrations, unityThread); + } + } +} |