mirror of
https://github.com/BobbyRafael31/Unity-MazeRunner-Pathfinding-Visualizer.git
synced 2025-08-13 08:52:21 +00:00
Initial commit
This commit is contained in:
8
Assets/Scripts/Editor.meta
Normal file
8
Assets/Scripts/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d42eae92c7176994eab3b43bf03f581d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
61
Assets/Scripts/Editor/MemoryProfilerSetup.cs
Normal file
61
Assets/Scripts/Editor/MemoryProfilerSetup.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public class MemoryProfilerSetup
|
||||
{
|
||||
[MenuItem("Tools/Pathfinding/Enable Memory Profiler")]
|
||||
public static void EnableMemoryProfiler()
|
||||
{
|
||||
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(
|
||||
EditorUserBuildSettings.selectedBuildTargetGroup);
|
||||
|
||||
if (!definesString.Contains("ENABLE_MEMORY_PROFILER"))
|
||||
{
|
||||
if (definesString.Length > 0)
|
||||
definesString += ";";
|
||||
|
||||
definesString += "ENABLE_MEMORY_PROFILER";
|
||||
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(
|
||||
EditorUserBuildSettings.selectedBuildTargetGroup, definesString);
|
||||
|
||||
Debug.Log("Memory Profiler enabled! (Added ENABLE_MEMORY_PROFILER symbol)");
|
||||
Debug.Log("Please restart the editor for this to take effect.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Memory Profiler is already enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Pathfinding/Disable Memory Profiler")]
|
||||
public static void DisableMemoryProfiler()
|
||||
{
|
||||
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(
|
||||
EditorUserBuildSettings.selectedBuildTargetGroup);
|
||||
|
||||
if (definesString.Contains("ENABLE_MEMORY_PROFILER"))
|
||||
{
|
||||
definesString = definesString.Replace("ENABLE_MEMORY_PROFILER", "");
|
||||
definesString = definesString.Replace(";;", ";"); // Fix double semicolons
|
||||
|
||||
// Remove leading or trailing semicolons
|
||||
if (definesString.StartsWith(";"))
|
||||
definesString = definesString.Substring(1);
|
||||
if (definesString.EndsWith(";"))
|
||||
definesString = definesString.Substring(0, definesString.Length - 1);
|
||||
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(
|
||||
EditorUserBuildSettings.selectedBuildTargetGroup, definesString);
|
||||
|
||||
Debug.Log("Memory Profiler disabled! (Removed ENABLE_MEMORY_PROFILER symbol)");
|
||||
Debug.Log("Please restart the editor for this to take effect.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Memory Profiler is already disabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
11
Assets/Scripts/Editor/MemoryProfilerSetup.cs.meta
Normal file
11
Assets/Scripts/Editor/MemoryProfilerSetup.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de037ed26926f9a43a584de8100ed5ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
13
Assets/Scripts/FrameLimiter.cs
Normal file
13
Assets/Scripts/FrameLimiter.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class FrameLimiter : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
[SerializeField] private int frameRate = 60;
|
||||
void Start()
|
||||
{
|
||||
// Set the target frame rate to 60
|
||||
QualitySettings.vSyncCount = 0;
|
||||
Application.targetFrameRate = frameRate;
|
||||
}
|
||||
}
|
11
Assets/Scripts/FrameLimiter.cs.meta
Normal file
11
Assets/Scripts/FrameLimiter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2538d99f2b342eb4485c7a83535b55e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1003
Assets/Scripts/GridMap.cs
Normal file
1003
Assets/Scripts/GridMap.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Scripts/GridMap.cs.meta
Normal file
11
Assets/Scripts/GridMap.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42bfb8854d690bf4fa7ccf911313d0ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
50
Assets/Scripts/GridNode.cs
Normal file
50
Assets/Scripts/GridNode.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Kelas GridNode merepresentasikan sebuah node/sel di dalam grid untuk algoritma pathfinding.
|
||||
/// Kelas ini mewarisi dari PathFinding.Node<Vector2Int> dan mengimplementasikan fungsionalitas spesifik
|
||||
/// untuk pathfinding berbasis grid.
|
||||
/// </summary>
|
||||
public class GridNode : PathFinding.Node<Vector2Int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Menentukan apakah node ini dapat dilalui oleh karakter.
|
||||
/// True jika node dapat dilalui, false jika node adalah penghalang.
|
||||
/// </summary>
|
||||
public bool IsWalkable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Referensi ke GridMap yang mengelola seluruh grid.
|
||||
/// Digunakan untuk mendapatkan tetangga dan operasi lain yang berhubungan dengan grid.
|
||||
/// </summary>
|
||||
public GridMap gridMap; // Change to internal or public
|
||||
|
||||
/// <summary>
|
||||
/// Constructor untuk membuat GridNode baru.
|
||||
/// </summary>
|
||||
/// <param name="value">Koordinat Vector2Int yang merepresentasikan posisi node di dalam grid</param>
|
||||
/// <param name="gridMap">Referensi ke GridMap yang mengelola grid ini</param>
|
||||
public GridNode(Vector2Int value, GridMap gridMap)
|
||||
: base(value)
|
||||
{
|
||||
IsWalkable = true; // Secara default node dapat dilalui
|
||||
this.gridMap = gridMap; // Simpan referensi ke GridMap
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mengimplementasikan metode abstrak dari kelas dasar untuk mendapatkan daftar node tetangga.
|
||||
/// Metode ini akan memanggil GridMap.GetNeighbours() untuk mendapatkan semua node tetangga yang dapat dilalui.
|
||||
/// </summary>
|
||||
/// <returns>Daftar node tetangga yang dapat dicapai dari node ini</returns>
|
||||
public override
|
||||
List<PathFinding.Node<Vector2Int>> GetNeighbours()
|
||||
{
|
||||
// Return an empty list for now.
|
||||
// Later we will call gridMap's GetNeighbours
|
||||
// function.
|
||||
//return new List<PathFinding.Node<Vector2Int>>();
|
||||
return gridMap.GetNeighbours(this);
|
||||
}
|
||||
}
|
11
Assets/Scripts/GridNode.cs.meta
Normal file
11
Assets/Scripts/GridNode.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b7a35795dca08b40a81b3624bdb7c8c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
43
Assets/Scripts/GridNodeView.cs
Normal file
43
Assets/Scripts/GridNodeView.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Kelas GridNodeView bertanggung jawab untuk visualisasi node grid dalam sistem pathfinding.
|
||||
/// Kelas ini menghubungkan data GridNode dengan representasi visualnya di Unity.
|
||||
/// </summary>
|
||||
public class GridNodeView : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Referensi ke SpriteRenderer untuk bagian dalam node.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
SpriteRenderer innerSprite;
|
||||
|
||||
/// <summary>
|
||||
/// Referensi ke SpriteRenderer untuk bagian luar node.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
SpriteRenderer outerSprite;
|
||||
|
||||
/// <summary>
|
||||
/// Properti yang menyimpan referensi ke objek GridNode yang terkait dengan view ini.
|
||||
/// </summary>
|
||||
public GridNode Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mengatur warna sprite bagian dalam dari node.
|
||||
/// </summary>
|
||||
/// <param name="col">Warna yang akan diaplikasikan pada sprite bagian dalam.</param>
|
||||
public void SetInnerColor(Color col)
|
||||
{
|
||||
innerSprite.color = col;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mengatur warna sprite bagian luar dari node.
|
||||
/// </summary>
|
||||
/// <param name="col">Warna yang akan diaplikasikan pada sprite bagian luar.</param>
|
||||
public void SetOuterColor(Color col)
|
||||
{
|
||||
outerSprite.color = col;
|
||||
}
|
||||
}
|
11
Assets/Scripts/GridNodeView.cs.meta
Normal file
11
Assets/Scripts/GridNodeView.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0f6674c2a748f0468bfd5694b501cf8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Scripts/Metrics.meta
Normal file
8
Assets/Scripts/Metrics.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f3bb8e91fe8a83418431baba8b407d9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
727
Assets/Scripts/NPC - Copy.backup
Normal file
727
Assets/Scripts/NPC - Copy.backup
Normal file
@ -0,0 +1,727 @@
|
||||
using PathFinding;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
public struct PathfindingMetrics
|
||||
{
|
||||
public float timeTaken; // in milliseconds
|
||||
public int pathLength; // number of nodes in path
|
||||
public long memoryUsed; // memory used by pathfinding in bytes
|
||||
public int maxOpenListSize; // maximum size of open list during pathfinding
|
||||
public int maxClosedListSize; // maximum size of closed list during pathfinding
|
||||
|
||||
// Tambahan untuk cost metrics
|
||||
public float totalGCost; // Total biaya G untuk jalur (jarak sebenarnya)
|
||||
public float totalHCost; // Total biaya H untuk jalur (heuristik)
|
||||
public float totalFCost; // Total biaya F untuk jalur (G + H)
|
||||
|
||||
// Tambahan metrik untuk analisis akademis
|
||||
public int totalExpandedNodes; // Node yang dikeluarkan dari open list dan diproses (ClosedList)
|
||||
public int totalGeneratedNodes; // Node yang ditambahkan ke open list
|
||||
public int totalTouchedNodes; // Node yang dievaluasi/diupdate (termasuk yang di-skip)
|
||||
}
|
||||
|
||||
public class NPC : MonoBehaviour
|
||||
{
|
||||
public float speed = 2.0f;
|
||||
public Queue<Vector2> wayPoints = new Queue<Vector2>();
|
||||
|
||||
// Event that fires when pathfinding is complete with performance metrics
|
||||
public event Action<PathfindingMetrics> OnPathfindingComplete;
|
||||
|
||||
public enum PathFinderType
|
||||
{
|
||||
ASTAR,
|
||||
DIJKSTRA,
|
||||
GREEDY,
|
||||
BACKTRACKING,
|
||||
BFS,
|
||||
}
|
||||
|
||||
// Mode pengukuran memori
|
||||
public enum MemoryMeasurementMode
|
||||
{
|
||||
UNITY_PROFILER // Pengukuran dari Unity Profiler
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
public PathFinderType pathFinderType = PathFinderType.ASTAR;
|
||||
|
||||
// Mode pengukuran memori
|
||||
[SerializeField]
|
||||
public MemoryMeasurementMode memoryMeasurementMode = MemoryMeasurementMode.UNITY_PROFILER;
|
||||
|
||||
PathFinder<Vector2Int> pathFinder = null;
|
||||
|
||||
public GridMap Map { get; set; }
|
||||
|
||||
// List to store all steps for visualization playback
|
||||
private List<PathfindingVisualizationStep> visualizationSteps = new List<PathfindingVisualizationStep>();
|
||||
private bool isVisualizingPath = false;
|
||||
|
||||
// Properties to control visualization
|
||||
[SerializeField]
|
||||
|
||||
// Visualization speed is time between visualization steps
|
||||
public float visualizationSpeed = 0.0f; // Default 0; set higher for slower visualization
|
||||
|
||||
// Visualization batch is the number of steps to visualize at once
|
||||
public int visualizationBatch = 1; // Default 1; set higher value for faster visualization
|
||||
|
||||
[SerializeField]
|
||||
public bool showVisualization = true; // Whether to show visualization at all
|
||||
|
||||
// Struct to store each step of the pathfinding process for visualization
|
||||
private struct PathfindingVisualizationStep
|
||||
{
|
||||
public enum StepType { CurrentNode, OpenList, ClosedList, FinalPath }
|
||||
public StepType type;
|
||||
public Vector2Int position;
|
||||
|
||||
public PathfindingVisualizationStep(StepType type, Vector2Int position)
|
||||
{
|
||||
this.type = type;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
// Tambahkan counter untuk metrik baru
|
||||
private int expandedNodesCount = 0;
|
||||
private int generatedNodesCount = 0;
|
||||
private int touchedNodesCount = 0;
|
||||
|
||||
private IEnumerator Coroutine_MoveOverSeconds(
|
||||
GameObject objectToMove,
|
||||
Vector3 end,
|
||||
float seconds)
|
||||
{
|
||||
float elaspedTime = 0.0f;
|
||||
Vector3 startingPos = objectToMove.transform.position;
|
||||
|
||||
while (elaspedTime < seconds)
|
||||
{
|
||||
objectToMove.transform.position =
|
||||
Vector3.Lerp(startingPos, end, elaspedTime / seconds);
|
||||
elaspedTime += Time.deltaTime;
|
||||
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
objectToMove.transform.position = end;
|
||||
}
|
||||
|
||||
IEnumerator Coroutine_MoveToPoint(Vector2 p, float speed)
|
||||
{
|
||||
Vector3 endP = new Vector3(p.x, p.y, transform.position.z);
|
||||
float duration = (transform.position - endP).magnitude / speed;
|
||||
|
||||
yield return StartCoroutine(
|
||||
Coroutine_MoveOverSeconds(
|
||||
transform.gameObject, endP, duration));
|
||||
}
|
||||
|
||||
public IEnumerator Coroutine_MoveTo()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (wayPoints.Count > 0)
|
||||
{
|
||||
yield return StartCoroutine(
|
||||
Coroutine_MoveToPoint(
|
||||
wayPoints.Dequeue(),
|
||||
speed));
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddWayPoint(GridNode node)
|
||||
{
|
||||
wayPoints.Enqueue(new Vector2(
|
||||
node.Value.x * Map.GridNodeWidth,
|
||||
node.Value.y * Map.GridNodeHeight));
|
||||
|
||||
// We set a color to show the path.
|
||||
GridNodeView gnv = Map.GetGridNodeView(node.Value.x, node.Value.y);
|
||||
gnv.SetInnerColor(Map.COLOR_PATH);
|
||||
}
|
||||
|
||||
public void SetStartNode(GridNode node)
|
||||
{
|
||||
wayPoints.Clear();
|
||||
transform.position = new Vector3(
|
||||
node.Value.x * Map.GridNodeWidth,
|
||||
node.Value.y * Map.GridNodeHeight,
|
||||
transform.position.z);
|
||||
}
|
||||
|
||||
private void InitializePathFinder()
|
||||
{
|
||||
// Hitung perkiraan jumlah node dalam grid
|
||||
int estimatedNodeCount = 0;
|
||||
if (Map != null)
|
||||
{
|
||||
estimatedNodeCount = Map.NumX * Map.NumY;
|
||||
}
|
||||
|
||||
// Log informasi ukuran grid dan strategi optimisasi
|
||||
bool isLargeGrid = estimatedNodeCount > 2500;
|
||||
UnityEngine.Debug.Log($"Grid Size: {Map?.NumX ?? 0}x{Map?.NumY ?? 0} ({estimatedNodeCount} nodes) - " +
|
||||
$"Using {(isLargeGrid ? "optimized" : "simplified")} pathfinding strategy");
|
||||
|
||||
// Create new pathfinder instance
|
||||
switch (pathFinderType)
|
||||
{
|
||||
case PathFinderType.ASTAR:
|
||||
pathFinder = new AStarPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.DIJKSTRA:
|
||||
pathFinder = new DijkstraPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.GREEDY:
|
||||
pathFinder = new GreedyPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BACKTRACKING:
|
||||
pathFinder = new BacktrackingPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BFS:
|
||||
pathFinder = new BFSPathFinder<Vector2Int>();
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
pathFinder.onSuccess = OnSuccessPathFinding;
|
||||
pathFinder.onFailure = OnFailurePathFinding;
|
||||
|
||||
// Gunakan setting asli
|
||||
pathFinder.HeuristicCost = GridMap.GetManhattanCost;
|
||||
pathFinder.NodeTraversalCost = GridMap.GetEuclideanCost;
|
||||
|
||||
UnityEngine.Debug.Log($"Initialized pathfinder with algorithm: {pathFinderType}");
|
||||
}
|
||||
|
||||
public void MoveTo(GridNode destination, bool silentMode = false)
|
||||
{
|
||||
if (pathFinder == null)
|
||||
{
|
||||
UnityEngine.Debug.LogError("Pathfinder is not initialized!");
|
||||
InitializePathFinder();
|
||||
}
|
||||
|
||||
if (pathFinder.Status == PathFinderStatus.RUNNING)
|
||||
{
|
||||
UnityEngine.Debug.Log("PathFinder is running. Cannot start a new pathfinding now");
|
||||
return;
|
||||
}
|
||||
|
||||
GridNode start = Map.GetGridNode(
|
||||
(int)(transform.position.x / Map.GridNodeWidth),
|
||||
(int)(transform.position.y / Map.GridNodeHeight));
|
||||
|
||||
if (start == null || destination == null)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Invalid start or destination node. Start: {start}, Destination: {destination}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!silentMode)
|
||||
{
|
||||
UnityEngine.Debug.Log($"Starting pathfinding from ({start.Value.x}, {start.Value.y}) to ({destination.Value.x}, {destination.Value.y})");
|
||||
}
|
||||
|
||||
SetStartNode(start);
|
||||
|
||||
// Reset grid colors
|
||||
if (!silentMode)
|
||||
{
|
||||
Map.ResetGridNodeColours();
|
||||
}
|
||||
|
||||
visualizationSteps.Clear();
|
||||
isVisualizingPath = false;
|
||||
|
||||
// Initialize pathfinding
|
||||
if (!pathFinder.Initialise(start, destination))
|
||||
{
|
||||
UnityEngine.Debug.LogError("Failed to initialize pathfinder!");
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(Coroutine_FindPathStep(silentMode));
|
||||
}
|
||||
|
||||
IEnumerator Coroutine_FindPathStep(bool silentMode = false)
|
||||
{
|
||||
yield return StartCoroutine(MeasurePerformance(silentMode));
|
||||
|
||||
// Start visualization after calculation is complete
|
||||
if (pathFinder.Status == PathFinderStatus.SUCCESS && showVisualization && !silentMode)
|
||||
{
|
||||
yield return StartCoroutine(VisualizePathfinding());
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator MeasurePerformance(bool silentMode = false)
|
||||
{
|
||||
// Memory tracking for pathfinding structures - tetap untuk visualisasi
|
||||
int maxOpenListSize = 0;
|
||||
int currentOpenListSize = 0;
|
||||
int maxClosedListSize = 0;
|
||||
int currentClosedListSize = 0;
|
||||
|
||||
// Pre-allocate visualizationSteps with estimated capacity to avoid reallocations
|
||||
visualizationSteps = new List<PathfindingVisualizationStep>(1000);
|
||||
|
||||
// ===== MEMORY MEASUREMENT START: Ukur memory sebelum algoritma =====
|
||||
System.GC.Collect(); // Force garbage collection untuk pengukuran yang akurat
|
||||
System.GC.WaitForPendingFinalizers();
|
||||
long memoryBefore = System.GC.GetTotalMemory(false);
|
||||
|
||||
// Setup callbacks before running algorithm
|
||||
SetupCallbacks(silentMode, ref maxOpenListSize, ref currentOpenListSize,
|
||||
ref maxClosedListSize, ref currentClosedListSize);
|
||||
|
||||
// ===== STOPWATCH START: Pengukuran waktu algoritma =====
|
||||
float startTime = Time.realtimeSinceStartup;
|
||||
|
||||
// Counter untuk jumlah step yang dilakukan algoritma
|
||||
int stepCount = 0;
|
||||
|
||||
// Execute the pathfinding algorithm synchronously in a single frame without visualization
|
||||
while (pathFinder.Status == PathFinderStatus.RUNNING)
|
||||
{
|
||||
stepCount++;
|
||||
pathFinder.Step();
|
||||
}
|
||||
|
||||
// ===== STOPWATCH STOP: Akhir pengukuran waktu algoritma =====
|
||||
float endTime = Time.realtimeSinceStartup;
|
||||
float duration = (endTime - startTime) * 1000f; // Convert to milliseconds
|
||||
|
||||
// ===== MEMORY MEASUREMENT END: Ukur memory setelah algoritma =====
|
||||
long memoryAfter = System.GC.GetTotalMemory(false);
|
||||
long memoryUsed = memoryAfter - memoryBefore;
|
||||
|
||||
UnityEngine.Debug.Log($"Memory used: {memoryUsed} bytes");
|
||||
UnityEngine.Debug.Log($"Algorithm execution time: {duration:F2} ms");
|
||||
|
||||
// Calculate path length once and reuse
|
||||
int pathLength = 0;
|
||||
float totalGCost = 0;
|
||||
float totalHCost = 0;
|
||||
float totalFCost = 0;
|
||||
|
||||
// Add memory for path reconstruction (final path)
|
||||
if (pathFinder.Status == PathFinderStatus.SUCCESS)
|
||||
{
|
||||
pathLength = CalculatePathLength();
|
||||
|
||||
// Hitung total G, H, dan F cost
|
||||
CalculatePathCosts(out totalGCost, out totalHCost, out totalFCost);
|
||||
}
|
||||
|
||||
// Koreksi estimasi touched nodes berdasarkan jumlah langkah dan expanded nodes
|
||||
// Pendekatan heuristik: touched nodes = expanded nodes * rata-rata branching factor
|
||||
float estimatedBranchingFactor = 4.0f; // Untuk grid 4-connected
|
||||
if (pathFinderType == PathFinderType.ASTAR || pathFinderType == PathFinderType.GREEDY)
|
||||
{
|
||||
// A* dan Greedy biasanya memeriksa lebih sedikit node karena heuristik
|
||||
estimatedBranchingFactor = 3.5f;
|
||||
}
|
||||
// Koreksi touchedNodesCount jika terlalu rendah, karena estimasi minimal
|
||||
int estimatedTouchedNodes = Mathf.RoundToInt(expandedNodesCount * estimatedBranchingFactor);
|
||||
touchedNodesCount = Mathf.Max(touchedNodesCount, estimatedTouchedNodes);
|
||||
|
||||
// Create and send metrics - waktu pengukuran algoritma yang tepat
|
||||
PathfindingMetrics metrics = new PathfindingMetrics
|
||||
{
|
||||
timeTaken = duration, // Waktu algoritma yang diukur dengan Time.realtimeSinceStartup
|
||||
pathLength = pathLength,
|
||||
memoryUsed = memoryUsed,
|
||||
maxOpenListSize = maxOpenListSize,
|
||||
maxClosedListSize = maxClosedListSize,
|
||||
totalGCost = totalGCost,
|
||||
totalHCost = totalHCost,
|
||||
totalFCost = totalFCost,
|
||||
// Tambahkan metrik baru
|
||||
totalExpandedNodes = expandedNodesCount,
|
||||
totalGeneratedNodes = generatedNodesCount,
|
||||
totalTouchedNodes = touchedNodesCount
|
||||
};
|
||||
|
||||
// Report metrics before visualization
|
||||
if (!silentMode)
|
||||
{
|
||||
OnPathfindingComplete?.Invoke(metrics);
|
||||
}
|
||||
|
||||
// Path visualization and handling
|
||||
HandlePathFindingResult(silentMode, pathLength);
|
||||
|
||||
// Pastikan untuk mengembalikan nilai di akhir coroutine
|
||||
yield return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup callbacks for tracking nodes in open/closed lists and visualization
|
||||
/// </summary>
|
||||
private void SetupCallbacks(bool silentMode, ref int maxOpenListSize, ref int currentOpenListSize,
|
||||
ref int maxClosedListSize, ref int currentClosedListSize)
|
||||
{
|
||||
// Reset counters setiap kali pathfinding dimulai
|
||||
expandedNodesCount = 0;
|
||||
generatedNodesCount = 0;
|
||||
touchedNodesCount = 0;
|
||||
|
||||
// Buat variabel lokal untuk menghindari masalah dengan ref parameter dalam lambda
|
||||
int localCurrentOpenListSize = currentOpenListSize;
|
||||
int localMaxOpenListSize = maxOpenListSize;
|
||||
int localCurrentClosedListSize = currentClosedListSize;
|
||||
int localMaxClosedListSize = maxClosedListSize;
|
||||
|
||||
if (silentMode)
|
||||
{
|
||||
// In silent mode, just set minimal callbacks for metrics
|
||||
pathFinder.onAddToOpenList = (node) =>
|
||||
{
|
||||
// Menghitung node yang ditambahkan ke open list (generated)
|
||||
generatedNodesCount++;
|
||||
|
||||
localCurrentOpenListSize++;
|
||||
if (localCurrentOpenListSize > localMaxOpenListSize)
|
||||
localMaxOpenListSize = localCurrentOpenListSize;
|
||||
};
|
||||
|
||||
pathFinder.onAddToClosedList = (node) =>
|
||||
{
|
||||
// Menghitung node yang dipindahkan ke closed list (expanded)
|
||||
expandedNodesCount++;
|
||||
|
||||
localCurrentClosedListSize++;
|
||||
if (localCurrentClosedListSize > localMaxClosedListSize)
|
||||
localMaxClosedListSize = localCurrentClosedListSize;
|
||||
localCurrentOpenListSize--; // When a node is moved from open to closed list
|
||||
};
|
||||
|
||||
// For touched nodes, kita menghitung semua node yang dievaluasi
|
||||
// Karena kita tidak bisa memodifikasi PathFinder class, kita estimasi ini
|
||||
// berdasarkan jumlah Step yang dilakukan algoritma
|
||||
pathFinder.onChangeCurrentNode = (node) =>
|
||||
{
|
||||
// Setiap kali node diproses, node tetangga dievaluasi (touched)
|
||||
// Kita akan mengestimasi jumlah node tetangga (umumnya 4-8 untuk grid)
|
||||
touchedNodesCount += 4; // Estimasi minimal, akan dikoreksi nanti
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// In regular mode, track and prepare for visualization
|
||||
pathFinder.onAddToOpenList = (node) =>
|
||||
{
|
||||
// Menghitung node yang ditambahkan ke open list (generated)
|
||||
generatedNodesCount++;
|
||||
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.OpenList,
|
||||
node.Location.Value));
|
||||
|
||||
localCurrentOpenListSize++;
|
||||
if (localCurrentOpenListSize > localMaxOpenListSize)
|
||||
localMaxOpenListSize = localCurrentOpenListSize;
|
||||
};
|
||||
|
||||
pathFinder.onAddToClosedList = (node) =>
|
||||
{
|
||||
// Menghitung node yang dipindahkan ke closed list (expanded)
|
||||
expandedNodesCount++;
|
||||
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.ClosedList,
|
||||
node.Location.Value));
|
||||
|
||||
localCurrentClosedListSize++;
|
||||
if (localCurrentClosedListSize > localMaxClosedListSize)
|
||||
localMaxClosedListSize = localCurrentClosedListSize;
|
||||
|
||||
localCurrentOpenListSize--; // When a node is moved from open to closed list
|
||||
};
|
||||
|
||||
pathFinder.onChangeCurrentNode = (node) =>
|
||||
{
|
||||
// Setiap kali node diproses, node tetangga dievaluasi (touched)
|
||||
touchedNodesCount += 4; // Estimasi minimal
|
||||
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.CurrentNode,
|
||||
node.Location.Value));
|
||||
};
|
||||
}
|
||||
|
||||
// Setelah lambda selesai dijalankan, perbarui variabel ref
|
||||
maxOpenListSize = localMaxOpenListSize;
|
||||
currentOpenListSize = localCurrentOpenListSize;
|
||||
maxClosedListSize = localMaxClosedListSize;
|
||||
currentClosedListSize = localCurrentClosedListSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle path finding result (success or failure)
|
||||
/// </summary>
|
||||
private void HandlePathFindingResult(bool silentMode, int pathLength)
|
||||
{
|
||||
if (pathFinder.Status == PathFinderStatus.SUCCESS)
|
||||
{
|
||||
OnSuccessPathFinding();
|
||||
|
||||
// In non-silent mode, prepare visualization data for the path
|
||||
if (!silentMode && showVisualization)
|
||||
{
|
||||
// Add the path nodes for visualization in efficient batched way
|
||||
PathFinder<Vector2Int>.PathFinderNode node = pathFinder.CurrentNode;
|
||||
List<Vector2Int> pathPositions = new List<Vector2Int>(pathLength); // Pre-allocate with known size
|
||||
|
||||
// Build path in reverse order
|
||||
while (node != null)
|
||||
{
|
||||
pathPositions.Add(node.Location.Value);
|
||||
node = node.Parent;
|
||||
}
|
||||
|
||||
// Process path in correct order
|
||||
for (int i = pathPositions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.FinalPath,
|
||||
pathPositions[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pathFinder.Status == PathFinderStatus.FAILURE)
|
||||
{
|
||||
OnFailurePathFinding();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memformat ukuran byte menjadi string yang lebih mudah dibaca
|
||||
/// </summary>
|
||||
|
||||
void OnSuccessPathFinding()
|
||||
{
|
||||
float totalGCost = 0;
|
||||
float totalHCost = 0;
|
||||
float totalFCost = 0;
|
||||
|
||||
// Hitung biaya-biaya path menggunakan metode yang sudah ada
|
||||
CalculatePathCosts(out totalGCost, out totalHCost, out totalFCost);
|
||||
|
||||
// Informasi dasar
|
||||
int pathLength = CalculatePathLength();
|
||||
|
||||
// Log informasi lengkap hanya jika dibutuhkan
|
||||
if (UnityEngine.Debug.isDebugBuild)
|
||||
{
|
||||
UnityEngine.Debug.Log($"Pathfinding Success - Algorithm: {pathFinderType}");
|
||||
UnityEngine.Debug.Log($"Final Path length: {pathLength} nodes");
|
||||
UnityEngine.Debug.Log($"Nodes explored: {expandedNodesCount}");
|
||||
|
||||
// Log node metrics untuk analisis akademis
|
||||
UnityEngine.Debug.Log($"Generated nodes: {generatedNodesCount}");
|
||||
UnityEngine.Debug.Log($"Touched nodes: {touchedNodesCount}");
|
||||
|
||||
// Log cost metrics dengan format yang lebih jelas
|
||||
bool usesHeuristic = pathFinderType == PathFinderType.ASTAR || pathFinderType == PathFinderType.GREEDY;
|
||||
|
||||
UnityEngine.Debug.Log($"Total G-Cost: {totalGCost:F2} (Jarak sebenarnya)");
|
||||
|
||||
if (usesHeuristic)
|
||||
{
|
||||
UnityEngine.Debug.Log($"Total H-Cost: {totalHCost:F2} (Estimasi heuristik)");
|
||||
UnityEngine.Debug.Log($"Total F-Cost: {totalFCost:F2} (G+H, Biaya total)");
|
||||
}
|
||||
|
||||
// Log detail tambahan jika perlu
|
||||
UnityEngine.Debug.Log($"Average cost per step: {(pathLength > 0 ? totalGCost / pathLength : 0):F2}");
|
||||
}
|
||||
}
|
||||
|
||||
void OnFailurePathFinding()
|
||||
{
|
||||
if (UnityEngine.Debug.isDebugBuild)
|
||||
{
|
||||
UnityEngine.Debug.Log("Cannot find path. No valid path exists!");
|
||||
UnityEngine.Debug.Log($"Nodes explored: {expandedNodesCount}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the pathfinding algorithm at runtime
|
||||
/// </summary>
|
||||
public void ChangeAlgorithm(PathFinderType newType)
|
||||
{
|
||||
// Don't change if pathfinding is in progress
|
||||
if (pathFinder != null && pathFinder.Status == PathFinderStatus.RUNNING)
|
||||
{
|
||||
UnityEngine.Debug.Log("Cannot change algorithm while pathfinding is running");
|
||||
return;
|
||||
}
|
||||
|
||||
pathFinderType = newType;
|
||||
|
||||
// Hitung perkiraan jumlah node dalam grid
|
||||
int estimatedNodeCount = 0;
|
||||
if (Map != null)
|
||||
{
|
||||
estimatedNodeCount = Map.NumX * Map.NumY;
|
||||
}
|
||||
|
||||
// Create new pathfinder instance
|
||||
switch (pathFinderType)
|
||||
{
|
||||
case PathFinderType.ASTAR:
|
||||
pathFinder = new AStarPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.DIJKSTRA:
|
||||
pathFinder = new DijkstraPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.GREEDY:
|
||||
pathFinder = new GreedyPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BACKTRACKING:
|
||||
pathFinder = new BacktrackingPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BFS:
|
||||
pathFinder = new BFSPathFinder<Vector2Int>();
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
pathFinder.onSuccess = OnSuccessPathFinding;
|
||||
pathFinder.onFailure = OnFailurePathFinding;
|
||||
|
||||
// Gunakan setting asli
|
||||
pathFinder.HeuristicCost = GridMap.GetManhattanCost;
|
||||
pathFinder.NodeTraversalCost = GridMap.GetEuclideanCost;
|
||||
|
||||
UnityEngine.Debug.Log($"Changed pathfinding algorithm to {pathFinderType}");
|
||||
}
|
||||
|
||||
private int CalculatePathLength()
|
||||
{
|
||||
int pathLength = 0;
|
||||
PathFinder<Vector2Int>.PathFinderNode node = pathFinder.CurrentNode;
|
||||
while (node != null)
|
||||
{
|
||||
pathLength++;
|
||||
node = node.Parent;
|
||||
}
|
||||
return pathLength;
|
||||
}
|
||||
|
||||
IEnumerator VisualizePathfinding()
|
||||
{
|
||||
if (!showVisualization)
|
||||
yield break;
|
||||
|
||||
isVisualizingPath = true;
|
||||
|
||||
// First, ensure grid is reset
|
||||
Map.ResetGridNodeColours();
|
||||
|
||||
// Visualize each step with a delay - use batch processing for efficiency
|
||||
int stepCount = visualizationSteps.Count;
|
||||
int batchSize = Mathf.Min(visualizationBatch, stepCount); // set higher value for faster visualization
|
||||
|
||||
for (int i = 0; i < stepCount; i += batchSize)
|
||||
{
|
||||
int end = Mathf.Min(i + batchSize, stepCount);
|
||||
|
||||
// Process a batch of steps
|
||||
for (int j = i; j < end; j++)
|
||||
{
|
||||
var step = visualizationSteps[j];
|
||||
GridNodeView gnv = Map.GetGridNodeView(step.position.x, step.position.y);
|
||||
if (gnv != null)
|
||||
{
|
||||
switch (step.type)
|
||||
{
|
||||
case PathfindingVisualizationStep.StepType.CurrentNode:
|
||||
gnv.SetInnerColor(Map.COLOR_CURRENT_NODE);
|
||||
break;
|
||||
case PathfindingVisualizationStep.StepType.OpenList:
|
||||
gnv.SetInnerColor(Map.COLOR_ADD_TO_OPENLIST);
|
||||
break;
|
||||
case PathfindingVisualizationStep.StepType.ClosedList:
|
||||
gnv.SetInnerColor(Map.COLOR_ADD_TO_CLOSEDLIST);
|
||||
break;
|
||||
case PathfindingVisualizationStep.StepType.FinalPath:
|
||||
gnv.SetInnerColor(Map.COLOR_PATH);
|
||||
// Also add the waypoint when we process the path
|
||||
if (step.type == PathfindingVisualizationStep.StepType.FinalPath)
|
||||
{
|
||||
GridNode pathNode = Map.GetGridNode(step.position.x, step.position.y);
|
||||
AddWayPoint(pathNode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yield after each batch to prevent frame drops
|
||||
yield return new WaitForSeconds(visualizationSpeed);
|
||||
}
|
||||
|
||||
isVisualizingPath = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Menghitung biaya G, H, dan F untuk jalur
|
||||
/// </summary>
|
||||
private void CalculatePathCosts(out float totalGCost, out float totalHCost, out float totalFCost)
|
||||
{
|
||||
// Inisialisasi nilai awal
|
||||
totalGCost = 0;
|
||||
totalHCost = 0;
|
||||
totalFCost = 0;
|
||||
|
||||
// Jika tidak ada path yang ditemukan, return nilai 0
|
||||
if (pathFinder.CurrentNode == null)
|
||||
return;
|
||||
|
||||
// Untuk algoritma yang menggunakan heuristik
|
||||
bool usesHeuristic = pathFinderType == PathFinderType.ASTAR ||
|
||||
pathFinderType == PathFinderType.GREEDY;
|
||||
|
||||
// Node final berisi total cost jalur
|
||||
PathFinder<Vector2Int>.PathFinderNode finalNode = pathFinder.CurrentNode;
|
||||
|
||||
// G cost adalah biaya sebenarnya dari start ke goal, sudah terakumulasi di node akhir
|
||||
totalGCost = finalNode.GCost;
|
||||
|
||||
// H cost di node final idealnya 0 (sudah di tujuan),
|
||||
// tapi untuk info lengkap, kita dapat path's H cost dari node awal
|
||||
if (usesHeuristic)
|
||||
{
|
||||
// H cost dari node awal ke tujuan (untuk referensi)
|
||||
totalHCost = finalNode.HCost;
|
||||
|
||||
// F cost adalah G + H di node akhir
|
||||
totalFCost = finalNode.FCost;
|
||||
|
||||
// Log additional debug info
|
||||
UnityEngine.Debug.Log($"Final node - G:{finalNode.GCost:F2}, H:{finalNode.HCost:F2}, F:{finalNode.FCost:F2}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Algoritma tanpa heuristik (seperti Dijkstra)
|
||||
totalFCost = totalGCost;
|
||||
}
|
||||
|
||||
// Hitung rata-rata biaya per langkah untuk analisis
|
||||
int pathLength = CalculatePathLength();
|
||||
float avgCostPerStep = pathLength > 0 ? totalGCost / pathLength : 0;
|
||||
|
||||
UnityEngine.Debug.Log($"[COST] Path: {pathLength} steps, Avg cost/step: {avgCostPerStep:F2}");
|
||||
}
|
||||
}
|
7
Assets/Scripts/NPC - Copy.backup.meta
Normal file
7
Assets/Scripts/NPC - Copy.backup.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26c169e6e7d6dd0418f0b28f61d26407
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
643
Assets/Scripts/NPC.cs
Normal file
643
Assets/Scripts/NPC.cs
Normal file
@ -0,0 +1,643 @@
|
||||
using PathFinding;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
|
||||
public struct PathfindingMetrics
|
||||
{
|
||||
// Untuk Pengukuran Kinerja
|
||||
public float timeTaken; // in milliseconds
|
||||
public int pathLength;
|
||||
public int nodesExplored; // number of nodes in path
|
||||
public long memoryUsed; // memory used by pathfinding in bytes
|
||||
|
||||
// Untuk Visualisasi
|
||||
public int maxOpenListSize; // maximum size of open list during pathfinding
|
||||
public int maxClosedListSize; // maximum size of closed list during pathfinding
|
||||
|
||||
// Tambahan untuk cost metrics
|
||||
public float totalGCost; // Total biaya G untuk jalur (jarak sebenarnya)
|
||||
public float totalHCost; // Total biaya H untuk jalur (heuristik)
|
||||
public float totalFCost; // Total biaya F untuk jalur (G + H)
|
||||
|
||||
}
|
||||
|
||||
public class NPC : MonoBehaviour
|
||||
{
|
||||
public float speed = 2.0f;
|
||||
public Queue<Vector2> wayPoints = new Queue<Vector2>();
|
||||
|
||||
// Event that fires when pathfinding is complete with performance metrics
|
||||
public event Action<PathfindingMetrics> OnPathfindingComplete;
|
||||
|
||||
public enum PathFinderType
|
||||
{
|
||||
ASTAR,
|
||||
DIJKSTRA,
|
||||
GREEDY,
|
||||
BACKTRACKING,
|
||||
BFS,
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
public PathFinderType pathFinderType = PathFinderType.ASTAR;
|
||||
|
||||
PathFinder<Vector2Int> pathFinder = null;
|
||||
|
||||
public GridMap Map { get; set; }
|
||||
|
||||
// List to store all steps for visualization playback
|
||||
private List<PathfindingVisualizationStep> visualizationSteps = new List<PathfindingVisualizationStep>();
|
||||
private bool isVisualizingPath = false;
|
||||
|
||||
// Properties to control visualization
|
||||
[SerializeField]
|
||||
|
||||
// Visualization speed is time between visualization steps
|
||||
public float visualizationSpeed = 0.0f; // Default 0; set higher for slower visualization
|
||||
|
||||
// Visualization batch is the number of steps to visualize at once
|
||||
public int visualizationBatch = 1; // Default 1; set higher value for faster visualization
|
||||
|
||||
[SerializeField]
|
||||
public bool showVisualization = true; // Whether to show visualization at all
|
||||
|
||||
// Struct to store each step of the pathfinding process for visualization
|
||||
private struct PathfindingVisualizationStep
|
||||
{
|
||||
public enum StepType { CurrentNode, OpenList, ClosedList, FinalPath }
|
||||
public StepType type;
|
||||
public Vector2Int position;
|
||||
|
||||
public PathfindingVisualizationStep(StepType type, Vector2Int position)
|
||||
{
|
||||
this.type = type;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator Coroutine_MoveOverSeconds(GameObject objectToMove, Vector3 end, float seconds)
|
||||
{
|
||||
float elaspedTime = 0.0f;
|
||||
Vector3 startingPos = objectToMove.transform.position;
|
||||
|
||||
while (elaspedTime < seconds)
|
||||
{
|
||||
objectToMove.transform.position =
|
||||
Vector3.Lerp(startingPos, end, elaspedTime / seconds);
|
||||
elaspedTime += Time.deltaTime;
|
||||
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
objectToMove.transform.position = end;
|
||||
}
|
||||
|
||||
IEnumerator Coroutine_MoveToPoint(Vector2 p, float speed)
|
||||
{
|
||||
Vector3 endP = new Vector3(p.x, p.y, transform.position.z);
|
||||
float duration = (transform.position - endP).magnitude / speed;
|
||||
|
||||
yield return StartCoroutine(
|
||||
Coroutine_MoveOverSeconds(
|
||||
transform.gameObject, endP, duration));
|
||||
}
|
||||
|
||||
public IEnumerator Coroutine_MoveTo()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (wayPoints.Count > 0)
|
||||
{
|
||||
yield return StartCoroutine(
|
||||
Coroutine_MoveToPoint(
|
||||
wayPoints.Dequeue(),
|
||||
speed));
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddWayPoint(GridNode node)
|
||||
{
|
||||
wayPoints.Enqueue(new Vector2(
|
||||
node.Value.x * Map.GridNodeWidth,
|
||||
node.Value.y * Map.GridNodeHeight));
|
||||
|
||||
// We set a color to show the path.
|
||||
GridNodeView gnv = Map.GetGridNodeView(node.Value.x, node.Value.y);
|
||||
gnv.SetInnerColor(Map.COLOR_PATH);
|
||||
}
|
||||
|
||||
public void SetStartNode(GridNode node)
|
||||
{
|
||||
wayPoints.Clear();
|
||||
transform.position = new Vector3(
|
||||
node.Value.x * Map.GridNodeWidth,
|
||||
node.Value.y * Map.GridNodeHeight,
|
||||
transform.position.z);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Initialize pathfinder based on type
|
||||
InitializePathFinder();
|
||||
|
||||
// Start the movement coroutine
|
||||
StartCoroutine(Coroutine_MoveTo());
|
||||
}
|
||||
|
||||
private void InitializePathFinder()
|
||||
{
|
||||
// Hitung perkiraan jumlah node dalam grid
|
||||
int estimatedNodeCount = 0;
|
||||
if (Map != null)
|
||||
{
|
||||
estimatedNodeCount = Map.NumX * Map.NumY;
|
||||
}
|
||||
|
||||
// Log informasi ukuran grid dan strategi optimisasi
|
||||
bool isLargeGrid = estimatedNodeCount > 2500;
|
||||
|
||||
// Create new pathfinder instance
|
||||
switch (pathFinderType)
|
||||
{
|
||||
case PathFinderType.ASTAR:
|
||||
pathFinder = new AStarPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.DIJKSTRA:
|
||||
pathFinder = new DijkstraPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.GREEDY:
|
||||
pathFinder = new GreedyPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BACKTRACKING:
|
||||
pathFinder = new BacktrackingPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BFS:
|
||||
pathFinder = new BFSPathFinder<Vector2Int>();
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
pathFinder.onSuccess = OnSuccessPathFinding;
|
||||
pathFinder.onFailure = OnFailurePathFinding;
|
||||
|
||||
// Gunakan setting asli
|
||||
pathFinder.HeuristicCost = GridMap.GetManhattanCost;
|
||||
pathFinder.NodeTraversalCost = GridMap.GetEuclideanCost;
|
||||
}
|
||||
|
||||
public void MoveTo(GridNode destination, bool silentMode = false)
|
||||
{
|
||||
// inialisaasi pathfinder jika belum ada
|
||||
if (pathFinder == null)
|
||||
{
|
||||
InitializePathFinder();
|
||||
}
|
||||
|
||||
|
||||
if (pathFinder.Status == PathFinderStatus.RUNNING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GridNode start = Map.GetGridNode(
|
||||
(int)(transform.position.x / Map.GridNodeWidth),
|
||||
(int)(transform.position.y / Map.GridNodeHeight));
|
||||
|
||||
if (start == null || destination == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetStartNode(start);
|
||||
|
||||
// Reset grid colors
|
||||
if (!silentMode)
|
||||
{
|
||||
Map.ResetGridNodeColours();
|
||||
}
|
||||
|
||||
visualizationSteps.Clear();
|
||||
isVisualizingPath = false;
|
||||
|
||||
// jika gagal menginisialisasi pathfinder, tidak perlu melanjutkan
|
||||
if (!pathFinder.Initialise(start, destination))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(Coroutine_FindPathStep(silentMode));
|
||||
}
|
||||
|
||||
IEnumerator Coroutine_FindPathStep(bool silentMode = false)
|
||||
{
|
||||
yield return StartCoroutine(MeasurePerformance(silentMode));
|
||||
|
||||
// Start visualization after calculation is complete
|
||||
if (pathFinder.Status == PathFinderStatus.SUCCESS && showVisualization && !silentMode)
|
||||
{
|
||||
yield return StartCoroutine(VisualizePathfinding());
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator MeasurePerformance(bool silentMode = false)
|
||||
{
|
||||
// Memory tracking for pathfinding structures - tetap untuk visualisasi
|
||||
int maxOpenListSize = 0;
|
||||
int currentOpenListSize = 0;
|
||||
int maxClosedListSize = 0;
|
||||
int currentClosedListSize = 0;
|
||||
|
||||
// Pre-allocate visualizationSteps with estimated capacity to avoid reallocations
|
||||
visualizationSteps = new List<PathfindingVisualizationStep>(4);
|
||||
|
||||
// ===== MEMORY MEASUREMENT START: Ukur memory sebelum algoritma =====
|
||||
long memoryBefore = System.GC.GetTotalMemory(false);
|
||||
|
||||
// Setup callbacks before running algorithm
|
||||
SetupCallbacks(silentMode, ref maxOpenListSize, ref currentOpenListSize,
|
||||
ref maxClosedListSize, ref currentClosedListSize);
|
||||
|
||||
// ===== STOPWATCH START: Pengukuran waktu algoritma =====
|
||||
Stopwatch algorithmTimer = Stopwatch.StartNew();
|
||||
|
||||
// Counter untuk jumlah step yang dilakukan algoritma
|
||||
int stepCount = 0;
|
||||
|
||||
// Execute the pathfinding algorithm synchronously in a single frame without visualization
|
||||
while (pathFinder.Status == PathFinderStatus.RUNNING)
|
||||
{
|
||||
stepCount++;
|
||||
pathFinder.Step();
|
||||
}
|
||||
|
||||
// ===== STOPWATCH STOP: Akhir pengukuran waktu algoritma =====
|
||||
algorithmTimer.Stop();
|
||||
|
||||
// ===== MEMORY MEASUREMENT END: Ukur memory setelah algoritma =====
|
||||
long memoryAfter = System.GC.GetTotalMemory(false);
|
||||
long memoryUsed = memoryAfter - memoryBefore;
|
||||
|
||||
float milliseconds = (algorithmTimer.ElapsedTicks * 1000.0f) / Stopwatch.Frequency;
|
||||
|
||||
// Calculate path length once and reuse
|
||||
int pathLength = 0;
|
||||
int nodesExplored = 0;
|
||||
float totalGCost = 0;
|
||||
float totalHCost = 0;
|
||||
float totalFCost = 0;
|
||||
|
||||
// Add memory for path reconstruction (final path)
|
||||
if (pathFinder.Status == PathFinderStatus.SUCCESS)
|
||||
{
|
||||
pathLength = CalculatePathLength();
|
||||
nodesExplored = pathFinder.ClosedListCount;
|
||||
|
||||
// Hitung total G, H, dan F cost
|
||||
CalculatePathCosts(out totalGCost, out totalHCost, out totalFCost);
|
||||
}
|
||||
|
||||
// Create and send metrics - waktu pengukuran algoritma yang tepat
|
||||
PathfindingMetrics metrics = new PathfindingMetrics
|
||||
{
|
||||
timeTaken = milliseconds, // Waktu algoritma yang diukur dengan stopwatch
|
||||
pathLength = pathLength,
|
||||
nodesExplored = nodesExplored,
|
||||
memoryUsed = memoryUsed,
|
||||
maxOpenListSize = maxOpenListSize,
|
||||
maxClosedListSize = maxClosedListSize,
|
||||
totalGCost = totalGCost,
|
||||
totalHCost = totalHCost,
|
||||
totalFCost = totalFCost,
|
||||
};
|
||||
|
||||
// Report metrics before visualization
|
||||
if (!silentMode)
|
||||
{
|
||||
OnPathfindingComplete?.Invoke(metrics);
|
||||
}
|
||||
|
||||
// Path visualization and handling
|
||||
HandlePathFindingResult(silentMode, pathLength);
|
||||
|
||||
// Pastikan untuk mengembalikan nilai di akhir coroutine
|
||||
yield return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Setup callbacks for tracking nodes in open/closed lists and visualization
|
||||
/// </summary>
|
||||
private void SetupCallbacks(bool silentMode, ref int maxOpenListSize, ref int currentOpenListSize,
|
||||
ref int maxClosedListSize, ref int currentClosedListSize)
|
||||
{
|
||||
// Buat variabel lokal untuk menghindari masalah dengan ref parameter dalam lambda
|
||||
int localCurrentOpenListSize = currentOpenListSize;
|
||||
int localMaxOpenListSize = maxOpenListSize;
|
||||
int localCurrentClosedListSize = currentClosedListSize;
|
||||
int localMaxClosedListSize = maxClosedListSize;
|
||||
|
||||
if (silentMode)
|
||||
{
|
||||
// In silent mode, just set minimal callbacks for metrics
|
||||
pathFinder.onAddToOpenList = (node) =>
|
||||
{
|
||||
localCurrentOpenListSize++;
|
||||
if (localCurrentOpenListSize > localMaxOpenListSize)
|
||||
localMaxOpenListSize = localCurrentOpenListSize;
|
||||
};
|
||||
|
||||
pathFinder.onAddToClosedList = (node) =>
|
||||
{
|
||||
localCurrentClosedListSize++;
|
||||
if (localCurrentClosedListSize > localMaxClosedListSize)
|
||||
localMaxClosedListSize = localCurrentClosedListSize;
|
||||
localCurrentOpenListSize--; // When a node is moved from open to closed list
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// In regular mode, track and prepare for visualization
|
||||
pathFinder.onAddToOpenList = (node) =>
|
||||
{
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.OpenList,
|
||||
node.Location.Value));
|
||||
|
||||
localCurrentOpenListSize++;
|
||||
if (localCurrentOpenListSize > localMaxOpenListSize)
|
||||
localMaxOpenListSize = localCurrentOpenListSize;
|
||||
};
|
||||
|
||||
pathFinder.onAddToClosedList = (node) =>
|
||||
{
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.ClosedList,
|
||||
node.Location.Value));
|
||||
|
||||
localCurrentClosedListSize++;
|
||||
if (localCurrentClosedListSize > localMaxClosedListSize)
|
||||
localMaxClosedListSize = localCurrentClosedListSize;
|
||||
|
||||
localCurrentOpenListSize--; // When a node is moved from open to closed list
|
||||
};
|
||||
|
||||
pathFinder.onChangeCurrentNode = (node) =>
|
||||
{
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.CurrentNode,
|
||||
node.Location.Value));
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Setelah lambda selesai dijalankan, perbarui variabel ref
|
||||
maxOpenListSize = localMaxOpenListSize;
|
||||
currentOpenListSize = localCurrentOpenListSize;
|
||||
maxClosedListSize = localMaxClosedListSize;
|
||||
currentClosedListSize = localCurrentClosedListSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle path finding result (success or failure)
|
||||
/// </summary>
|
||||
private void HandlePathFindingResult(bool silentMode, int pathLength)
|
||||
{
|
||||
|
||||
if (pathFinder.Status == PathFinderStatus.SUCCESS)
|
||||
{
|
||||
OnSuccessPathFinding();
|
||||
|
||||
// In non-silent mode, prepare visualization data for the path
|
||||
if (!silentMode && showVisualization)
|
||||
{
|
||||
// Add the path nodes for visualization in efficient batched way
|
||||
PathFinder<Vector2Int>.PathFinderNode node = pathFinder.CurrentNode;
|
||||
List<Vector2Int> pathPositions = new List<Vector2Int>(pathLength); // Pre-allocate with known size
|
||||
|
||||
// Build path in reverse order
|
||||
while (node != null)
|
||||
{
|
||||
pathPositions.Add(node.Location.Value);
|
||||
node = node.Parent;
|
||||
}
|
||||
|
||||
// Process path in correct order
|
||||
for (int i = pathPositions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
visualizationSteps.Add(new PathfindingVisualizationStep(
|
||||
PathfindingVisualizationStep.StepType.FinalPath,
|
||||
pathPositions[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pathFinder.Status == PathFinderStatus.FAILURE)
|
||||
{
|
||||
OnFailurePathFinding();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memformat ukuran byte menjadi string yang lebih mudah dibaca
|
||||
/// </summary>
|
||||
private string FormatBytes(long bytes)
|
||||
{
|
||||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||||
int order = 0;
|
||||
double size = bytes;
|
||||
while (size >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
size = size / 1024;
|
||||
}
|
||||
return $"{size:0.##} {sizes[order]}";
|
||||
}
|
||||
|
||||
void OnSuccessPathFinding()
|
||||
{
|
||||
float totalGCost = 0;
|
||||
float totalHCost = 0;
|
||||
float totalFCost = 0;
|
||||
|
||||
// Hitung biaya-biaya path menggunakan metode yang sudah ada
|
||||
CalculatePathCosts(out totalGCost, out totalHCost, out totalFCost);
|
||||
|
||||
// Informasi dasar
|
||||
int pathLength = CalculatePathLength();
|
||||
|
||||
}
|
||||
|
||||
void OnFailurePathFinding()
|
||||
{
|
||||
UnityEngine.Debug.Log("Pathfinding failed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the pathfinding algorithm at runtime
|
||||
/// </summary>
|
||||
public void ChangeAlgorithm(PathFinderType newType)
|
||||
{
|
||||
// Don't change if pathfinding is in progress
|
||||
if (pathFinder != null && pathFinder.Status == PathFinderStatus.RUNNING)
|
||||
{
|
||||
UnityEngine.Debug.Log("Cannot change algorithm while pathfinding is running");
|
||||
return;
|
||||
}
|
||||
|
||||
pathFinderType = newType;
|
||||
|
||||
// Hitung perkiraan jumlah node dalam grid
|
||||
int estimatedNodeCount = 0;
|
||||
if (Map != null)
|
||||
{
|
||||
estimatedNodeCount = Map.NumX * Map.NumY;
|
||||
}
|
||||
|
||||
// Create new pathfinder instance
|
||||
switch (pathFinderType)
|
||||
{
|
||||
case PathFinderType.ASTAR:
|
||||
pathFinder = new AStarPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.DIJKSTRA:
|
||||
pathFinder = new DijkstraPathFinder<Vector2Int>(estimatedNodeCount);
|
||||
break;
|
||||
case PathFinderType.GREEDY:
|
||||
pathFinder = new GreedyPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BACKTRACKING:
|
||||
pathFinder = new BacktrackingPathFinder<Vector2Int>();
|
||||
break;
|
||||
case PathFinderType.BFS:
|
||||
pathFinder = new BFSPathFinder<Vector2Int>();
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
pathFinder.onSuccess = OnSuccessPathFinding;
|
||||
pathFinder.onFailure = OnFailurePathFinding;
|
||||
|
||||
// Gunakan setting asli
|
||||
pathFinder.HeuristicCost = GridMap.GetManhattanCost;
|
||||
pathFinder.NodeTraversalCost = GridMap.GetEuclideanCost;
|
||||
}
|
||||
|
||||
private int CalculatePathLength()
|
||||
{
|
||||
int pathLength = 0;
|
||||
PathFinder<Vector2Int>.PathFinderNode node = pathFinder.CurrentNode;
|
||||
while (node != null)
|
||||
{
|
||||
pathLength++;
|
||||
node = node.Parent;
|
||||
}
|
||||
return pathLength;
|
||||
}
|
||||
|
||||
IEnumerator VisualizePathfinding()
|
||||
{
|
||||
if (!showVisualization)
|
||||
yield break;
|
||||
|
||||
isVisualizingPath = true;
|
||||
|
||||
// First, ensure grid is reset
|
||||
Map.ResetGridNodeColours();
|
||||
|
||||
// Visualize each step with a delay - use batch processing for efficiency
|
||||
int stepCount = visualizationSteps.Count;
|
||||
int batchSize = Mathf.Min(visualizationBatch, stepCount); // set higher value for faster visualization
|
||||
|
||||
for (int i = 0; i < stepCount; i += batchSize)
|
||||
{
|
||||
int end = Mathf.Min(i + batchSize, stepCount);
|
||||
|
||||
// Process a batch of steps
|
||||
for (int j = i; j < end; j++)
|
||||
{
|
||||
var step = visualizationSteps[j];
|
||||
GridNodeView gnv = Map.GetGridNodeView(step.position.x, step.position.y);
|
||||
if (gnv != null)
|
||||
{
|
||||
switch (step.type)
|
||||
{
|
||||
case PathfindingVisualizationStep.StepType.CurrentNode:
|
||||
gnv.SetInnerColor(Map.COLOR_CURRENT_NODE);
|
||||
break;
|
||||
case PathfindingVisualizationStep.StepType.OpenList:
|
||||
gnv.SetInnerColor(Map.COLOR_ADD_TO_OPENLIST);
|
||||
break;
|
||||
case PathfindingVisualizationStep.StepType.ClosedList:
|
||||
gnv.SetInnerColor(Map.COLOR_ADD_TO_CLOSEDLIST);
|
||||
break;
|
||||
case PathfindingVisualizationStep.StepType.FinalPath:
|
||||
gnv.SetInnerColor(Map.COLOR_PATH);
|
||||
// Also add the waypoint when we process the path
|
||||
if (step.type == PathfindingVisualizationStep.StepType.FinalPath)
|
||||
{
|
||||
GridNode pathNode = Map.GetGridNode(step.position.x, step.position.y);
|
||||
AddWayPoint(pathNode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yield after each batch to prevent frame drops
|
||||
yield return new WaitForSeconds(visualizationSpeed);
|
||||
}
|
||||
|
||||
isVisualizingPath = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Menghitung biaya G, H, dan F untuk jalur
|
||||
/// </summary>
|
||||
private void CalculatePathCosts(out float totalGCost, out float totalHCost, out float totalFCost)
|
||||
{
|
||||
// Inisialisasi nilai awal
|
||||
totalGCost = 0;
|
||||
totalHCost = 0;
|
||||
totalFCost = 0;
|
||||
|
||||
// Jika tidak ada path yang ditemukan, return nilai 0
|
||||
if (pathFinder.CurrentNode == null)
|
||||
return;
|
||||
|
||||
// Untuk algoritma yang menggunakan heuristik
|
||||
bool usesHeuristic = pathFinderType == PathFinderType.ASTAR ||
|
||||
pathFinderType == PathFinderType.GREEDY;
|
||||
|
||||
// Node final berisi total cost jalur
|
||||
PathFinder<Vector2Int>.PathFinderNode finalNode = pathFinder.CurrentNode;
|
||||
|
||||
// G cost adalah biaya sebenarnya dari start ke goal, sudah terakumulasi di node akhir
|
||||
totalGCost = finalNode.GCost;
|
||||
|
||||
// H cost di node final idealnya 0 (sudah di tujuan),
|
||||
// tapi untuk info lengkap, kita dapat path's H cost dari node awal
|
||||
if (usesHeuristic)
|
||||
{
|
||||
// H cost dari node awal ke tujuan (untuk referensi)
|
||||
totalHCost = finalNode.HCost;
|
||||
|
||||
// F cost adalah G + H di node akhir
|
||||
totalFCost = finalNode.FCost;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Algoritma tanpa heuristik (seperti Dijkstra)
|
||||
totalFCost = totalGCost;
|
||||
}
|
||||
|
||||
//// Hitung rata-rata biaya per langkah untuk analisis
|
||||
//int pathLength = CalculatePathLength();
|
||||
//float avgCostPerStep = pathLength > 0 ? totalGCost / pathLength : 0;
|
||||
}
|
||||
}
|
11
Assets/Scripts/NPC.cs.meta
Normal file
11
Assets/Scripts/NPC.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6f1f52749dee394e990900aed6b57ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1477
Assets/Scripts/PathFinder.cs
Normal file
1477
Assets/Scripts/PathFinder.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Scripts/PathFinder.cs.meta
Normal file
11
Assets/Scripts/PathFinder.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 589db701de40bca46adc805fa76421f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
491
Assets/Scripts/PathfindingUIManager.cs
Normal file
491
Assets/Scripts/PathfindingUIManager.cs
Normal file
@ -0,0 +1,491 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
|
||||
public class PathfindingUIManager : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
public GridMap gridMap;
|
||||
public NPC npc;
|
||||
|
||||
[Header("Grid Controls")]
|
||||
public TMP_InputField gridSizeXInput;
|
||||
public TMP_InputField gridSizeYInput;
|
||||
public Button applyGridSizeButton;
|
||||
public Button generateMazeButton;
|
||||
|
||||
[Header("Algorithm Controls")]
|
||||
public TMP_Dropdown algorithmDropdown;
|
||||
public Button runPathfindingButton;
|
||||
public Button resetButton;
|
||||
|
||||
[Header("Performance Metrics")]
|
||||
public TMP_Text timeEstimateText;
|
||||
public TMP_Text pathLengthText;
|
||||
public TMP_Text nodesExploredText;
|
||||
public TMP_Text memoryUsageText;
|
||||
public TMP_Text cpuUsageText; // Text untuk menampilkan penggunaan CPU
|
||||
|
||||
[Header("Map Save/Load")]
|
||||
public TMP_InputField mapNameInput;
|
||||
public Button saveButton;
|
||||
public Button loadButton;
|
||||
|
||||
[Header("Application Controls")]
|
||||
public Button exitButton; // Tombol untuk keluar aplikasi
|
||||
|
||||
[Header("Optimization")]
|
||||
[SerializeField] private bool performWarmup = true;
|
||||
[SerializeField] private bool showWarmupMessage = false;
|
||||
|
||||
[Header("Maze Generator")]
|
||||
public TMP_Dropdown mazeSizeDropdown;
|
||||
public TMP_Dropdown mazeDensityDropdown;
|
||||
|
||||
// Konstanta untuk perhitungan CPU usage
|
||||
private const float TARGET_FRAME_TIME_MS = 16.67f; // 60 FPS = 16.67ms per frame
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Initialize UI elements
|
||||
InitializeUI();
|
||||
|
||||
// Add listeners
|
||||
applyGridSizeButton.onClick.AddListener(OnApplyGridSize);
|
||||
runPathfindingButton.onClick.AddListener(OnRunPathfinding);
|
||||
resetButton.onClick.AddListener(OnResetPathfinding);
|
||||
algorithmDropdown.onValueChanged.AddListener(OnAlgorithmChanged);
|
||||
saveButton.onClick.AddListener(OnSaveMap);
|
||||
loadButton.onClick.AddListener(OnLoadMap);
|
||||
|
||||
// Add exit button listener if the button exists
|
||||
if (exitButton != null)
|
||||
exitButton.onClick.AddListener(OnExitApplication);
|
||||
|
||||
// Add listener for maze generator
|
||||
if (generateMazeButton != null)
|
||||
generateMazeButton.onClick.AddListener(OnGenerateMaze);
|
||||
|
||||
// Subscribe to NPC's pathfinding events
|
||||
npc.OnPathfindingComplete += UpdatePerformanceMetrics;
|
||||
|
||||
// Initialize performance metrics
|
||||
ClearPerformanceMetrics();
|
||||
|
||||
// Perform algorithm warmup
|
||||
if (performWarmup)
|
||||
{
|
||||
StartCoroutine(WarmupPathfindingSystem());
|
||||
}
|
||||
|
||||
// Tampilkan lokasi penyimpanan
|
||||
ShowSaveLocation();
|
||||
}
|
||||
|
||||
private IEnumerator WarmupPathfindingSystem()
|
||||
{
|
||||
// Wait one frame to ensure everything is initialized
|
||||
yield return null;
|
||||
|
||||
if (showWarmupMessage)
|
||||
{
|
||||
//Debug.Log("Performing pathfinding warmup...");
|
||||
}
|
||||
|
||||
// Get current NPC position
|
||||
Vector3 npcPos = npc.transform.position;
|
||||
int startX = (int)(npcPos.x / gridMap.GridNodeWidth);
|
||||
int startY = (int)(npcPos.y / gridMap.GridNodeHeight);
|
||||
|
||||
// Find destination node for warmup (try to use opposite corner)
|
||||
int destX = gridMap.NumX - 1;
|
||||
int destY = gridMap.NumY - 1;
|
||||
|
||||
// Ensure destination is walkable
|
||||
GridNode destNode = gridMap.GetGridNode(destX, destY);
|
||||
if (destNode == null || !destNode.IsWalkable)
|
||||
{
|
||||
// Find any walkable node for warmup
|
||||
for (int x = 0; x < gridMap.NumX; x++)
|
||||
{
|
||||
for (int y = 0; y < gridMap.NumY; y++)
|
||||
{
|
||||
GridNode testNode = gridMap.GetGridNode(x, y);
|
||||
if (testNode != null && testNode.IsWalkable && (x != startX || y != startY))
|
||||
{
|
||||
destX = x;
|
||||
destY = y;
|
||||
destNode = testNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (destNode != null && destNode.IsWalkable)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Save current destination position
|
||||
Vector3 originalDestPos = gridMap.Destination.position;
|
||||
|
||||
// Set temporary destination for warmup
|
||||
gridMap.SetDestination(destX, destY);
|
||||
|
||||
// Run pathfinding quietly (without visualization)
|
||||
GridNode startNode = gridMap.GetGridNode(startX, startY);
|
||||
|
||||
if (startNode != null && destNode != null && destNode.IsWalkable)
|
||||
{
|
||||
// Temporarily disable visualization for warmup
|
||||
float originalVisualizationSpeed = npc.visualizationSpeed;
|
||||
npc.visualizationSpeed = 0f;
|
||||
bool originalShowVisualization = npc.showVisualization;
|
||||
npc.showVisualization = false;
|
||||
|
||||
// Do warmup for each algorithm type to JIT compile all code paths
|
||||
foreach (NPC.PathFinderType algoType in System.Enum.GetValues(typeof(NPC.PathFinderType)))
|
||||
{
|
||||
// Save current algorithm
|
||||
NPC.PathFinderType originalAlgorithm = npc.pathFinderType;
|
||||
|
||||
// Change to this algorithm
|
||||
npc.ChangeAlgorithm(algoType);
|
||||
|
||||
// Run silent pathfinding
|
||||
npc.MoveTo(destNode, true);
|
||||
|
||||
// Wait a bit to ensure completion
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
|
||||
// Reset back to original algorithm
|
||||
npc.ChangeAlgorithm(originalAlgorithm);
|
||||
}
|
||||
|
||||
// Restore visualization settings
|
||||
npc.visualizationSpeed = originalVisualizationSpeed;
|
||||
npc.showVisualization = originalShowVisualization;
|
||||
}
|
||||
|
||||
// Restore original destination
|
||||
gridMap.Destination.position = originalDestPos;
|
||||
|
||||
// Clear metrics from warmup
|
||||
ClearPerformanceMetrics();
|
||||
|
||||
if (showWarmupMessage)
|
||||
{
|
||||
//Debug.Log("Pathfinding warmup complete");
|
||||
}
|
||||
|
||||
|
||||
// Reset grid colors
|
||||
gridMap.ResetGridNodeColours();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Unsubscribe from events
|
||||
if (npc != null)
|
||||
{
|
||||
npc.OnPathfindingComplete -= UpdatePerformanceMetrics;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeUI()
|
||||
{
|
||||
// Set initial values
|
||||
gridSizeXInput.text = gridMap.NumX.ToString();
|
||||
gridSizeYInput.text = gridMap.NumY.ToString();
|
||||
|
||||
// Setup algorithm dropdown
|
||||
algorithmDropdown.ClearOptions();
|
||||
algorithmDropdown.AddOptions(new System.Collections.Generic.List<string> {
|
||||
"A*",
|
||||
"Dijkstra",
|
||||
"Greedy Best-First",
|
||||
"Backtracking",
|
||||
"BFS"
|
||||
});
|
||||
|
||||
// Setup maze size dropdown
|
||||
if (mazeSizeDropdown != null)
|
||||
{
|
||||
mazeSizeDropdown.ClearOptions();
|
||||
mazeSizeDropdown.AddOptions(new System.Collections.Generic.List<string> {
|
||||
"Small",
|
||||
"Medium",
|
||||
"Big"
|
||||
});
|
||||
}
|
||||
|
||||
// Setup maze density dropdown
|
||||
if (mazeDensityDropdown != null)
|
||||
{
|
||||
mazeDensityDropdown.ClearOptions();
|
||||
mazeDensityDropdown.AddOptions(new System.Collections.Generic.List<string> {
|
||||
"Low",
|
||||
"Medium",
|
||||
"High"
|
||||
});
|
||||
}
|
||||
|
||||
ClearPerformanceMetrics();
|
||||
}
|
||||
|
||||
private void ClearPerformanceMetrics()
|
||||
{
|
||||
timeEstimateText.text = "0";
|
||||
pathLengthText.text = "0";
|
||||
memoryUsageText.text = "0";
|
||||
nodesExploredText.text = "0";
|
||||
cpuUsageText.text = "0%";
|
||||
}
|
||||
|
||||
private void UpdatePerformanceMetrics(PathfindingMetrics metrics)
|
||||
{
|
||||
timeEstimateText.text = $"{metrics.timeTaken:F2} ms";
|
||||
pathLengthText.text = $"{metrics.pathLength} nodes";
|
||||
nodesExploredText.text = $"{metrics.nodesExplored} nodes";
|
||||
memoryUsageText.text = FormatBytes(metrics.memoryUsed);
|
||||
|
||||
// Hitung dan tampilkan CPU usage
|
||||
if (cpuUsageText != null)
|
||||
{
|
||||
float cpuUsagePercentage = (metrics.timeTaken / TARGET_FRAME_TIME_MS) * 100f;
|
||||
cpuUsageText.text = $"{cpuUsagePercentage:F2}%";
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatBytes(long bytes)
|
||||
{
|
||||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||||
int order = 0;
|
||||
double size = bytes;
|
||||
while (size >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
size = size / 1024;
|
||||
}
|
||||
return $"{size:0.##} {sizes[order]}";
|
||||
}
|
||||
|
||||
private void OnApplyGridSize()
|
||||
{
|
||||
if (int.TryParse(gridSizeXInput.text, out int newSizeX) &&
|
||||
int.TryParse(gridSizeYInput.text, out int newSizeY))
|
||||
{
|
||||
gridMap.ResizeGrid(newSizeX, newSizeY);
|
||||
ClearPerformanceMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRunPathfinding()
|
||||
{
|
||||
// Get current NPC position
|
||||
Vector3 npcPos = npc.transform.position;
|
||||
int startX = (int)(npcPos.x / gridMap.GridNodeWidth);
|
||||
int startY = (int)(npcPos.y / gridMap.GridNodeHeight);
|
||||
|
||||
// Get destination position
|
||||
Vector3 destPos = gridMap.Destination.position;
|
||||
int destX = (int)(destPos.x / gridMap.GridNodeWidth);
|
||||
int destY = (int)(destPos.y / gridMap.GridNodeHeight);
|
||||
|
||||
// Run pathfinding
|
||||
GridNode startNode = gridMap.GetGridNode(startX, startY);
|
||||
GridNode endNode = gridMap.GetGridNode(destX, destY);
|
||||
|
||||
if (startNode != null && endNode != null)
|
||||
{
|
||||
ClearPerformanceMetrics();
|
||||
npc.MoveTo(endNode);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnResetPathfinding()
|
||||
{
|
||||
// Reload the current scene
|
||||
UnityEngine.SceneManagement.SceneManager.LoadScene(
|
||||
UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
|
||||
|
||||
//Debug.Log("Reloading scene...");
|
||||
}
|
||||
|
||||
private void OnAlgorithmChanged(int index)
|
||||
{
|
||||
NPC.PathFinderType newType = (NPC.PathFinderType)index;
|
||||
npc.ChangeAlgorithm(newType);
|
||||
ClearPerformanceMetrics();
|
||||
}
|
||||
|
||||
private void OnSaveMap()
|
||||
{
|
||||
if (string.IsNullOrEmpty(mapNameInput.text))
|
||||
{
|
||||
//Debug.LogWarning("Please enter a map name before saving");
|
||||
return;
|
||||
}
|
||||
|
||||
// Buat direktori jika belum ada
|
||||
string saveDirectory = Path.Combine(Application.persistentDataPath, "GridSaves");
|
||||
if (!Directory.Exists(saveDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(saveDirectory);
|
||||
}
|
||||
|
||||
string filePath = Path.Combine(saveDirectory, $"{mapNameInput.text}.json");
|
||||
gridMap.SaveGridState(filePath);
|
||||
|
||||
Debug.Log($"Map saved to: {filePath}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Membuka folder penyimpanan di File Explorer
|
||||
/// </summary>
|
||||
public void OpenSaveFolder()
|
||||
{
|
||||
string saveDirectory = Path.Combine(Application.persistentDataPath, "GridSaves");
|
||||
|
||||
// Buat direktori jika belum ada
|
||||
if (!Directory.Exists(saveDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(saveDirectory);
|
||||
}
|
||||
|
||||
// Buka folder di file explorer
|
||||
System.Diagnostics.Process.Start("explorer.exe", saveDirectory);
|
||||
}
|
||||
|
||||
private void OnLoadMap()
|
||||
{
|
||||
if (string.IsNullOrEmpty(mapNameInput.text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string saveDirectory = Path.Combine(Application.persistentDataPath, "GridSaves");
|
||||
string filePath = Path.Combine(saveDirectory, $"{mapNameInput.text}.json");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
//Debug.LogWarning($"Map file not found: {filePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
gridMap.LoadGridState(filePath);
|
||||
ClearPerformanceMetrics();
|
||||
|
||||
//Debug.Log($"Map loaded from: {filePath}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random maze with the selected size and density
|
||||
/// </summary>
|
||||
private void OnGenerateMaze()
|
||||
{
|
||||
// Get selected maze size
|
||||
int sizeX = 20;
|
||||
int sizeY = 20;
|
||||
bool isLargeGrid = false;
|
||||
|
||||
switch (mazeSizeDropdown.value)
|
||||
{
|
||||
case 0: // Kecil
|
||||
sizeX = sizeY = 20;
|
||||
break;
|
||||
case 1: // Sedang
|
||||
sizeX = sizeY = 50;
|
||||
break;
|
||||
case 2: // Besar
|
||||
sizeX = sizeY = 100;
|
||||
isLargeGrid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Resize grid if needed
|
||||
if (gridMap.NumX != sizeX || gridMap.NumY != sizeY)
|
||||
{
|
||||
gridMap.ResizeGrid(sizeX, sizeY);
|
||||
|
||||
// Update grid size inputs
|
||||
gridSizeXInput.text = sizeX.ToString();
|
||||
gridSizeYInput.text = sizeY.ToString();
|
||||
}
|
||||
|
||||
// Get selected density
|
||||
float density = 30f; // Default medium
|
||||
|
||||
switch (mazeDensityDropdown.value)
|
||||
{
|
||||
case 0: // Low
|
||||
density = 10f;
|
||||
break;
|
||||
case 1: // Medium
|
||||
density = 30f;
|
||||
break;
|
||||
case 2: // High
|
||||
density = 50f;
|
||||
break;
|
||||
}
|
||||
|
||||
// Untuk grid besar, nonaktifkan visualisasi sementara untuk performa lebih baik
|
||||
bool originalShowVisualization = false;
|
||||
float originalVisualizationSpeed = 0f;
|
||||
|
||||
if (isLargeGrid && npc != null)
|
||||
{
|
||||
// Simpan nilai asli
|
||||
originalShowVisualization = npc.showVisualization;
|
||||
originalVisualizationSpeed = npc.visualizationSpeed;
|
||||
|
||||
// Nonaktifkan visualisasi untuk grid besar
|
||||
npc.showVisualization = false;
|
||||
}
|
||||
|
||||
// Generate the maze with selected density
|
||||
gridMap.GenerateRandomMaze(density);
|
||||
|
||||
// Kembalikan nilai visualisasi jika diubah
|
||||
if (isLargeGrid && npc != null)
|
||||
{
|
||||
npc.showVisualization = originalShowVisualization;
|
||||
npc.visualizationSpeed = originalVisualizationSpeed;
|
||||
}
|
||||
|
||||
// Clear performance metrics
|
||||
ClearPerformanceMetrics();
|
||||
|
||||
// Tampilkan pesan khusus untuk grid besar
|
||||
if (isLargeGrid)
|
||||
{
|
||||
//Debug.Log("Large maze generated. For best performance, consider disabling visualization during pathfinding.");
|
||||
}
|
||||
|
||||
//Debug.Log($"Generated maze with size {sizeX}x{sizeY} and density {density}%");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Menampilkan lokasi penyimpanan file di konsol
|
||||
/// </summary>
|
||||
private void ShowSaveLocation()
|
||||
{
|
||||
string saveDirectory = Path.Combine(Application.persistentDataPath, "GridSaves");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Menutup aplikasi saat tombol exit ditekan
|
||||
/// </summary>
|
||||
public void OnExitApplication()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Jika di Unity Editor
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
#else
|
||||
// Jika di build
|
||||
Application.Quit();
|
||||
#endif
|
||||
|
||||
Debug.Log("Application exit requested");
|
||||
}
|
||||
}
|
11
Assets/Scripts/PathfindingUIManager.cs.meta
Normal file
11
Assets/Scripts/PathfindingUIManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7d82750f198246419d2d370cf23f97b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
148
Assets/Scripts/PriorityQueue.cs
Normal file
148
Assets/Scripts/PriorityQueue.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Implementasi generic Priority Queue (Antrean Prioritas) menggunakan min-heap.
|
||||
/// Memungkinkan operasi enqueue, dequeue, dan update priority dengan efisien.
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Tipe elemen yang disimpan dalam antrean</typeparam>
|
||||
/// <typeparam name="TPriority">Tipe prioritas yang digunakan, harus implementasi IComparable</typeparam>
|
||||
public class PriorityQueue<TElement, TPriority> where TPriority : IComparable<TPriority>
|
||||
{
|
||||
// Menyimpan pasangan elemen dan prioritasnya dalam struktur heap
|
||||
private List<Tuple<TElement, TPriority>> elements = new List<Tuple<TElement, TPriority>>();
|
||||
// Menyimpan indeks setiap elemen dalam heap untuk akses cepat
|
||||
private Dictionary<TElement, int> elementIndexMap = new Dictionary<TElement, int>();
|
||||
|
||||
/// <summary>
|
||||
/// Mendapatkan jumlah elemen dalam antrean prioritas
|
||||
/// </summary>
|
||||
public int Count => elements.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Menambahkan elemen baru ke dalam antrean prioritas dengan prioritas tertentu
|
||||
/// </summary>
|
||||
/// <param name="element">Elemen yang akan ditambahkan</param>
|
||||
/// <param name="priority">Prioritas dari elemen</param>
|
||||
public void Enqueue(TElement element, TPriority priority)
|
||||
{
|
||||
elements.Add(Tuple.Create(element, priority));
|
||||
elementIndexMap[element] = elements.Count - 1;
|
||||
HeapifyUp(elements.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mengambil dan menghapus elemen dengan prioritas tertinggi (nilai terkecil)
|
||||
/// dari antrean prioritas
|
||||
/// </summary>
|
||||
/// <returns>Elemen dengan prioritas tertinggi</returns>
|
||||
/// <exception cref="InvalidOperationException">Dilempar jika antrean kosong</exception>
|
||||
public TElement Dequeue()
|
||||
{
|
||||
if (elements.Count == 0)
|
||||
throw new InvalidOperationException("The priority queue is empty.");
|
||||
|
||||
var element = elements[0].Item1;
|
||||
var last = elements[elements.Count - 1];
|
||||
elements.RemoveAt(elements.Count - 1);
|
||||
|
||||
if (elements.Count > 0)
|
||||
{
|
||||
// Pindahkan elemen terakhir ke root, lalu atur ulang heap
|
||||
elements[0] = last;
|
||||
elementIndexMap[last.Item1] = 0;
|
||||
HeapifyDown(0);
|
||||
}
|
||||
|
||||
elementIndexMap.Remove(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memperbarui prioritas elemen yang sudah ada dalam antrean
|
||||
/// </summary>
|
||||
/// <param name="element">Elemen yang akan diperbarui prioritasnya</param>
|
||||
/// <param name="newPriority">Nilai prioritas baru</param>
|
||||
/// <exception cref="InvalidOperationException">Dilempar jika elemen tidak ditemukan</exception>
|
||||
public void UpdatePriority(TElement element, TPriority newPriority)
|
||||
{
|
||||
if (!elementIndexMap.ContainsKey(element))
|
||||
throw new InvalidOperationException("Element not found in priority queue.");
|
||||
|
||||
var index = elementIndexMap[element];
|
||||
var oldPriority = elements[index].Item2;
|
||||
elements[index] = Tuple.Create(element, newPriority);
|
||||
|
||||
// Jika prioritas baru lebih tinggi (nilai lebih kecil), heapify up
|
||||
if (newPriority.CompareTo(oldPriority) < 0)
|
||||
{
|
||||
HeapifyUp(index);
|
||||
}
|
||||
// Jika prioritas baru lebih rendah (nilai lebih besar), heapify down
|
||||
else
|
||||
{
|
||||
HeapifyDown(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mempertahankan properti heap dengan memindahkan elemen ke atas jika
|
||||
/// prioritasnya lebih tinggi dari parent
|
||||
/// </summary>
|
||||
/// <param name="index">Indeks elemen yang akan dipindahkan ke atas</param>
|
||||
private void HeapifyUp(int index)
|
||||
{
|
||||
var parentIndex = (index - 1) / 2;
|
||||
if (index > 0 && elements[index].Item2.CompareTo(elements[parentIndex].Item2) < 0)
|
||||
{
|
||||
Swap(index, parentIndex);
|
||||
HeapifyUp(parentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mempertahankan properti heap dengan memindahkan elemen ke bawah jika
|
||||
/// prioritasnya lebih rendah dari child
|
||||
/// </summary>
|
||||
/// <param name="index">Indeks elemen yang akan dipindahkan ke bawah</param>
|
||||
private void HeapifyDown(int index)
|
||||
{
|
||||
var leftChildIndex = 2 * index + 1;
|
||||
var rightChildIndex = 2 * index + 2;
|
||||
var smallest = index;
|
||||
|
||||
// Cari child dengan prioritas tertinggi (nilai terkecil)
|
||||
if (leftChildIndex < elements.Count && elements[leftChildIndex].Item2.CompareTo(elements[smallest].Item2) < 0)
|
||||
{
|
||||
smallest = leftChildIndex;
|
||||
}
|
||||
|
||||
if (rightChildIndex < elements.Count && elements[rightChildIndex].Item2.CompareTo(elements[smallest].Item2) < 0)
|
||||
{
|
||||
smallest = rightChildIndex;
|
||||
}
|
||||
|
||||
// Jika child memiliki prioritas lebih tinggi, tukar dan lanjutkan heapify down
|
||||
if (smallest != index)
|
||||
{
|
||||
Swap(index, smallest);
|
||||
HeapifyDown(smallest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Menukar posisi dua elemen dalam heap dan memperbarui elementIndexMap
|
||||
/// </summary>
|
||||
/// <param name="i">Indeks elemen pertama</param>
|
||||
/// <param name="j">Indeks elemen kedua</param>
|
||||
private void Swap(int i, int j)
|
||||
{
|
||||
var temp = elements[i];
|
||||
elements[i] = elements[j];
|
||||
elements[j] = temp;
|
||||
|
||||
// Perbarui elementIndexMap untuk mencerminkan posisi baru
|
||||
elementIndexMap[elements[i].Item1] = i;
|
||||
elementIndexMap[elements[j].Item1] = j;
|
||||
}
|
||||
}
|
11
Assets/Scripts/PriorityQueue.cs.meta
Normal file
11
Assets/Scripts/PriorityQueue.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc44be66e24c4c646b2de99fb912d211
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user