From 6ee263cfac7586850f8a5cadfbb86c95fe3f0e81 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 25 Nov 2020 11:39:20 -0600 Subject: [PATCH] animation state handling (stopping/start/pausing/reloading) while editing, add cleanup code to algorithm, path generation and iteration, move gizmos from manager --- Paths/Assets/Scripts/Algorithms/AStar.cs | 19 +++ Paths/Assets/Scripts/Algorithms/Node.cs | 10 ++ Paths/Assets/Scripts/Algorithms/NodeGrid.cs | 9 +- Paths/Assets/Scripts/UIController.cs | 168 +++++++++++++++++--- 4 files changed, 187 insertions(+), 19 deletions(-) diff --git a/Paths/Assets/Scripts/Algorithms/AStar.cs b/Paths/Assets/Scripts/Algorithms/AStar.cs index 2a289ce..aafc771 100644 --- a/Paths/Assets/Scripts/Algorithms/AStar.cs +++ b/Paths/Assets/Scripts/Algorithms/AStar.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace Algorithms { @@ -92,5 +93,23 @@ namespace Algorithms { return _path; } + /// + /// Attempts to clean the NodeGrid of all edits made to heuristic values and such, fast. + /// + public void Cleanup() { + while (_openList.Count > 0) { + Node node = _openList[0]; + _openList.RemoveAt(0); + + node.Reset(); + } + + while (_closedList.Count > 0) { + Node node = _closedList[0]; + _closedList.RemoveAt(0); + + node.Reset(); + } + } } } \ No newline at end of file diff --git a/Paths/Assets/Scripts/Algorithms/Node.cs b/Paths/Assets/Scripts/Algorithms/Node.cs index faab057..ae79d90 100644 --- a/Paths/Assets/Scripts/Algorithms/Node.cs +++ b/Paths/Assets/Scripts/Algorithms/Node.cs @@ -37,6 +37,16 @@ namespace Algorithms { Walkable = walkable; } + /// + /// Resets this Node back to it's assumed default values. + /// + public void Reset() { + Parent = null; + DistanceToTarget = null; + Cost = 1; + State = NodeState.None; + } + public override bool Equals(object obj) { return obj is Node node && Position.Equals(node.Position); } diff --git a/Paths/Assets/Scripts/Algorithms/NodeGrid.cs b/Paths/Assets/Scripts/Algorithms/NodeGrid.cs index b3bf83f..12a0287 100644 --- a/Paths/Assets/Scripts/Algorithms/NodeGrid.cs +++ b/Paths/Assets/Scripts/Algorithms/NodeGrid.cs @@ -147,7 +147,7 @@ namespace Algorithms { } } - public GridNodeType[,] RenderNodeTypes() { + public GridNodeType[,] RenderNodeTypes(Vector2Int? start = null, Vector2Int? end = null) { GridNodeType[,] nodeTypeGrid = new GridNodeType[Grid.GetLength(0), Grid.GetLength(1)]; for (int x = 0; x < Grid.GetLength(0); x++) { @@ -156,6 +156,13 @@ namespace Algorithms { } } + // Start / End node addition + if (start.HasValue) + nodeTypeGrid[start.Value.x, start.Value.y] = GridNodeType.Start; + + if (end.HasValue) + nodeTypeGrid[end.Value.x, end.Value.y] = GridNodeType.End; + return nodeTypeGrid; } } diff --git a/Paths/Assets/Scripts/UIController.cs b/Paths/Assets/Scripts/UIController.cs index 76dded3..c2111be 100644 --- a/Paths/Assets/Scripts/UIController.cs +++ b/Paths/Assets/Scripts/UIController.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using Algorithms; +using UnityEditor; using UnityEngine; using UnityEngine.UI; @@ -22,6 +24,7 @@ public enum ClickType { /// public enum AnimationState { Stopped, + Paused, Started, Reloading } @@ -31,23 +34,41 @@ public enum AnimationState { /// All UI elements are referenced and controlled here. /// public class UIController : MonoBehaviour { + // UI & important App references public Slider progressSlider; public GridController gridController; public Manager manager; + // Animation State, Click Management private Vector2Int _lastClickLocation; private ClickType _modify; - private AnimationState _animating; + private AnimationState _animationState; + private AnimationState _previousAnimationState; + private bool EditShouldReload => + _animationState == AnimationState.Started || _animationState == AnimationState.Paused; + + // Grid State & Pathfinding private NodeGrid _grid; private Vector2Int _start; private Vector2Int _end; + private IPathfinding _algorithm; + private Stack _path; + private ChangeController _state; + + // Animation speed & indexing + public float clampIncrement; + private float _runtime; + public int CurrentIndex => (int) _runtime; + public float speed; private void Start() { _grid = new NodeGrid(gridController.width, gridController.height); - _animating = AnimationState.Stopped; + _animationState = AnimationState.Stopped; + _previousAnimationState = _animationState; _start = _grid.RandomPosition(); _end = _grid.RandomPosition(); + _runtime = 0; } private void Update() { @@ -65,6 +86,8 @@ public class UIController : MonoBehaviour { Node node = _grid.GetNode(position); _modify = node.Walkable ? ClickType.Add : ClickType.Remove; node.Walkable = !node.Walkable; + if (EditShouldReload) + _animationState = AnimationState.Reloading; } _lastClickLocation = position; @@ -79,25 +102,27 @@ public class UIController : MonoBehaviour { // Note: Wall toggling instantly reloads, but only real start/end node movement reloads. case ClickType.Add: node.Walkable = false; - _animating = AnimationState.Reloading; + if (EditShouldReload) + _animationState = AnimationState.Reloading; break; case ClickType.Remove: node.Walkable = true; - _animating = AnimationState.Reloading; + if (EditShouldReload) + _animationState = AnimationState.Reloading; break; case ClickType.Start: if (node.Walkable) { _start = position; - if (_animating == AnimationState.Started) - _animating = AnimationState.Reloading; + if (EditShouldReload) + _animationState = AnimationState.Reloading; } break; case ClickType.End: if (node.Walkable) { _end = position; - if (_animating == AnimationState.Started) - _animating = AnimationState.Reloading; + if (EditShouldReload) + _animationState = AnimationState.Reloading; } break; @@ -108,23 +133,130 @@ public class UIController : MonoBehaviour { } } - if (_animating == AnimationState.Reloading) { - } - else if (_animating == AnimationState.Started) { + // Handle user start/stopping + if (Input.GetKeyDown(KeyCode.Space)) { + switch (_animationState) { + case AnimationState.Stopped: + _animationState = AnimationState.Reloading; + break; + case AnimationState.Paused: + _animationState = AnimationState.Started; + break; + case AnimationState.Started: + _animationState = AnimationState.Paused; + break; + case AnimationState.Reloading: + break; + default: + throw new ArgumentOutOfRangeException(); + } } - gridController.LoadGridState(_grid.RenderNodeTypes(_start, _end)); + switch (_animationState) { + case AnimationState.Reloading: + if (!Input.GetMouseButton(0)) { + GeneratePath(); + LoadNextState(); + _animationState = AnimationState.Started; + } + else + gridController.LoadGridState(_grid.RenderNodeTypes(_start, _end)); + + break; + case AnimationState.Started: + // Calculate how much to move forward + var increment = Time.deltaTime * speed * CurrentMultiplier(); + if (clampIncrement > 0) + increment = Mathf.Clamp(increment, 0, _state.Count * Time.deltaTime / clampIncrement); + _runtime += increment; + + if (CurrentIndex < _state.Count) + LoadNextState(); + break; + case AnimationState.Stopped: + gridController.LoadGridState(_grid.RenderNodeTypes(_start, _end)); + break; + case AnimationState.Paused: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (_animationState != _previousAnimationState) { + Debug.Log($"Animation State {_previousAnimationState} -> {_animationState}"); + _previousAnimationState = _animationState; + } + } + + private void GeneratePath() { + if (_algorithm != null) + _algorithm.Cleanup(); + + _runtime = 0f; + _algorithm = new AStar(_grid); + _path = _algorithm.FindPath(_start, _end); + // Debug.Log($"{_state.Count} changs made"); + _state = _algorithm.ChangeController; + } + + private void LoadNextState() { + // Move to the new calculated index + _state.MoveTo(Math.Max(1, CurrentIndex)); // use Math.max to ensure both start/end nodes are always rendered + gridController.LoadDirtyGridState(_state.Current, _state.DirtyFlags); + + string pathCount = _path != null ? $"{_path.Count}" : "N/A"; + manager.debugText.text = $"{_state.CurrentRuntime * 1000.0:F1}ms\n" + + $"{CurrentIndex:000} / {_state.Count:000}\n" + + $"Path: {pathCount} tiles"; } /// - /// Generates the path and sets up the UI Controller to begin animation. + /// Returns the current time multiplier, based on the latest change in the path. /// - private void StartAnimation() { + /// A positive non-zero float representing how fast the current frame should be processed. + private float CurrentMultiplier() { + if (_state.Index == -1) + return 1; + + switch (_state.CurrentChange.New) { + case GridNodeType.Path: + return 1 / 5f; + case GridNodeType.Empty: + break; + case GridNodeType.Wall: + break; + case GridNodeType.Start: + break; + case GridNodeType.End: + break; + case GridNodeType.Seen: + break; + case GridNodeType.Expanded: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return 1; } - /// - /// Stops the path animation and readies the UI Controller for grid editing. - /// - private void StopAnimation() { + public void OnDrawGizmos() { + if (!Application.isPlaying) return; + + Vector3 mouse = manager.mainCamera.ScreenToWorldPoint(Input.mousePosition); + Vector3 localScale = manager.gridObject.transform.localScale; + Vector2Int gridPosition = gridController.GetGridPosition(mouse); + + var style = new GUIStyle(); + style.normal.textColor = Color.blue; + Gizmos.color = Color.blue; + + Gizmos.DrawWireCube(gridController.GetWorldPosition(gridPosition), localScale / (Vector2) gridController.Size); + Handles.Label(mouse, String.Format("{0}{1}", + gridPosition, + _algorithm.NodeGrid.IsValid(gridPosition) + ? $"\n{_state.Current[gridPosition.x, gridPosition.y]}" + : "" + ), style); } } \ No newline at end of file