diff --git a/Assets/Scripts/FrameLimiter.cs b/Assets/Scripts/FrameLimiter.cs index 1f35731..f4dc35b 100644 --- a/Assets/Scripts/FrameLimiter.cs +++ b/Assets/Scripts/FrameLimiter.cs @@ -2,12 +2,16 @@ using UnityEngine; public class FrameLimiter : MonoBehaviour { - // Start is called before the first frame update + /// + /// Melimit frame rate game ke 60 FPS + /// Limit dilakukan agar kalkulasi CPU yang digunakan sesuai dengan target FPS + /// + [SerializeField] private int frameRate = 60; + private int vSyncValue = 0; void Start() { - // Set the target frame rate to 60 - QualitySettings.vSyncCount = 0; + QualitySettings.vSyncCount = vSyncValue; Application.targetFrameRate = frameRate; } } diff --git a/Assets/Scripts/GridMap.cs b/Assets/Scripts/GridMap.cs index 39325d8..f8750dc 100644 --- a/Assets/Scripts/GridMap.cs +++ b/Assets/Scripts/GridMap.cs @@ -8,80 +8,64 @@ using Newtonsoft.Json; /// Kelas ini bertanggung jawab untuk membuat, menampilkan, dan mengelola node-node grid /// serta memberikan fungsionalitas untuk pathfinding. /// + public class GridMap : MonoBehaviour { - // Ukuran grid pada sumbu X [SerializeField] int numX; - // Ukuran grid pada sumbu Y + [SerializeField] int numY; - // Prefab untuk visualisasi node grid [SerializeField] GameObject gridNodeViewPrefab; - // Mengizinkan atau melarang pergerakan diagonal pada pathfinding [SerializeField] bool allowDiagonalMovement = false; - // Properti publik untuk mengakses allowDiagonalMovement dari luar public bool AllowDiagonalMovement { get { return allowDiagonalMovement; } set { allowDiagonalMovement = value; } } - // Warna-warna untuk representasi visual berbagai status node - public Color COLOR_WALKABLE = new Color(0.4f, 0.4f, 0.8f, 1.0f); // Warna untuk node yang dapat dilalui - public Color COLOR_NONWALKABLE = Color.black; // Warna untuk node yang tidak dapat dilalui - public Color COLOR_CURRENT_NODE = Color.cyan; // Warna untuk node yang sedang diproses - public Color COLOR_ADD_TO_OPENLIST = Color.green; // Warna untuk node yang ditambahkan ke open list - public Color COLOR_ADD_TO_CLOSEDLIST = Color.grey; // Warna untuk node yang ditambahkan ke closed list - public Color COLOR_PATH = Color.blue; // Warna untuk node yang menjadi bagian dari jalur final + public Color COLOR_WALKABLE = Color.white; + public Color COLOR_NONWALKABLE = Color.black; + public Color COLOR_CURRENT_NODE = Color.cyan; + public Color COLOR_ADD_TO_OPENLIST = Color.green; + public Color COLOR_ADD_TO_CLOSEDLIST = Color.grey; + public Color COLOR_PATH = Color.blue; - // Getter untuk ukuran grid public int NumX { get { return numX; } } public int NumY { get { return numY; } } - // Referensi ke NPC yang akan menggunakan path [SerializeField] NPC npc; - // Referensi ke Transform tujuan untuk visualisasi [SerializeField] Transform destination; public Transform Destination { get { return destination; } } - // Ukuran fisik setiap node dalam grid float gridNodeWidth = 1.0f; float gridNodeHeight = 1.0f; - // Getter untuk ukuran node public float GridNodeWidth { get { return gridNodeWidth; } } public float GridNodeHeight { get { return gridNodeHeight; } } - // Array 2D untuk menyimpan semua GridNodeView private GridNodeView[,] gridNodeViews = null; - - // Posisi mouse terakhir untuk tracking perubahan - private Vector2 lastMousePosition; - // Node terakhir yang statusnya diubah private GridNodeView lastToggledNode = null; - /// - /// Inisialisasi grid pada saat permainan dimulai - /// + private Vector2 lastMousePosition; + + void Start() { - // Membuat array untuk menyimpan semua node view gridNodeViews = new GridNodeView[NumX, NumY]; for (int i = 0; i < NumX; i++) { for (int j = 0; j < NumY; j++) { - // Membuat instance dari prefab node grid di posisi yang sesuai GameObject obj = Instantiate( gridNodeViewPrefab, new Vector3( @@ -90,61 +74,47 @@ public class GridMap : MonoBehaviour 0.0f), Quaternion.identity); - // Memberi nama pada objek grid node untuk identifikasi obj.name = "GridNode_" + i.ToString() + "_" + j.ToString(); GridNodeView gnv = obj.GetComponent(); gridNodeViews[i, j] = gnv; gnv.Node = new GridNode(new Vector2Int(i, j), this); - // Menjadikan node sebagai child dari GridMap obj.transform.SetParent(transform); } } - // Mengatur posisi kamera agar dapat melihat seluruh grid SetCameraPosition(); - // Menetapkan referensi grid map pada NPC + npc.Map = this; - // Menetapkan posisi awal NPC pada node (0,0) npc.SetStartNode(gridNodeViews[0, 0].Node); } - /// - /// Mengatur posisi kamera agar dapat menampilkan seluruh grid - /// void SetCameraPosition() { - // Calculate the center of the grid float gridCenterX = ((numX - 1) * GridNodeWidth) / 2; float gridCenterY = ((numY - 1) * GridNodeHeight) / 2; - // Calculate dynamic offset based on grid size float gridWidth = (numX - 1) * GridNodeWidth; float gridHeight = (numY - 1) * GridNodeHeight; - // For 20x20 grid: x=5.6, y=10.7, orthoSize=12.4 float baseX = 5.6f; float baseY = 10.7f; float baseOrthoSize = 12.4f; float baseGridSize = 20f; - // Scale position and ortho size based on current grid size relative to 20x20 float scaleFactor = Mathf.Max(numX, numY) / baseGridSize; float xPos = baseX * scaleFactor; float yPos = baseY * scaleFactor; float orthoSize = baseOrthoSize * scaleFactor; - // Position camera Camera.main.transform.position = new Vector3( xPos, yPos, -100.0f); - // Set orthographic size Camera.main.orthographicSize = orthoSize; } - // Tabel offset arah untuk pathfinding private static readonly int[,] directions = new int[,] { { 0, 1 }, // Atas { 1, 0 }, // Kanan @@ -156,33 +126,24 @@ public class GridMap : MonoBehaviour { -1, 1 } // Kiri Atas }; - /// - /// Mendapatkan daftar node tetangga yang dapat dilalui dari node yang diberikan - /// - /// Node saat ini - /// Daftar node tetangga yang dapat dilalui public List> GetNeighbours(PathFinding.Node loc) { - // Pre-alokasi dengan kapasitas maksimum untuk menghindari resize List> neighbours = new List>(8); int x = loc.Value.x; int y = loc.Value.y; - // Periksa arah kardinal terlebih dahulu (lebih cepat dan selalu ada) for (int dir = 0; dir < 4; dir++) { int nx = x + directions[dir, 0]; int ny = y + directions[dir, 1]; - // Periksa bounds dan walkability dalam satu kondisi if (nx >= 0 && nx < numX && ny >= 0 && ny < numY && gridNodeViews[nx, ny].Node.IsWalkable) { neighbours.Add(gridNodeViews[nx, ny].Node); } } - // Jika diagonal movement diizinkan, periksa 4 arah diagonal if (allowDiagonalMovement) { for (int dir = 4; dir < 8; dir++) @@ -190,40 +151,29 @@ public class GridMap : MonoBehaviour int nx = x + directions[dir, 0]; int ny = y + directions[dir, 1]; - // Periksa bounds dan walkability dalam satu kondisi if (nx >= 0 && nx < numX && ny >= 0 && ny < numY && gridNodeViews[nx, ny].Node.IsWalkable) - { neighbours.Add(gridNodeViews[nx, ny].Node); - } } } return neighbours; } - /// - /// Mendeteksi node pada posisi klik mouse dan mengubah status walkable-nya - /// public void RayCastAndToggleWalkable() { - // Konversi posisi mouse ke koordinat dunia Vector2 rayPos = new Vector2( Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y); - // Melakukan raycast untuk mendeteksi objek pada posisi mouse RaycastHit2D hit = Physics2D.Raycast(rayPos, Vector2.zero, 0f); if (hit) { - // Mendapatkan objek yang terkena raycast GameObject obj = hit.transform.gameObject; GridNodeView gnv = obj.GetComponent(); - // Memastikan node adalah valid dan belum diubah sebelumnya if (gnv != null && gnv != lastToggledNode) { - //Debug.Log($"Toggling walkable state for node at position: {gnv.Node.Value}"); ToggleWalkable(gnv); lastToggledNode = gnv; } @@ -234,34 +184,26 @@ public class GridMap : MonoBehaviour } } - /// - /// Mendeteksi node pada posisi klik mouse dan menetapkannya sebagai tujuan - /// public void RayCastAndSetDestination() { - // Don't allow changing destination during pathfinding, visualization, or movement - if (npc != null && (npc.pathFinder?.Status == PathFinding.PathFinderStatus.RUNNING || - npc.IsVisualizingPath || + if (npc != null && (npc.pathFinder?.Status == PathFinding.PathFinderStatus.RUNNING || + npc.IsVisualizingPath || npc.IsMoving)) { - // Pathfinding, visualization or movement is in progress - ignore position change return; } - - // Konversi posisi mouse ke koordinat dunia + Vector2 rayPos = new Vector2( Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y); - // Melakukan raycast untuk mendeteksi objek pada posisi mouse + RaycastHit2D hit = Physics2D.Raycast(rayPos, Vector2.zero, 0f); if (hit) { - // Mendapatkan objek yang terkena raycast GameObject obj = hit.transform.gameObject; GridNodeView gnv = obj.GetComponent(); - // Memindahkan objek destination ke posisi node yang dipilih Vector3 pos = destination.position; pos.x = gnv.Node.Value.x * gridNodeWidth; pos.y = gnv.Node.Value.y * gridNodeHeight; @@ -269,10 +211,6 @@ public class GridMap : MonoBehaviour } } - /// - /// Mengubah status walkable dari suatu node dan memperbarui warnanya - /// - /// GridNodeView yang akan diubah status walkable-nya public void ToggleWalkable(GridNodeView gnv) { if (gnv == null) @@ -281,10 +219,8 @@ public class GridMap : MonoBehaviour int x = gnv.Node.Value.x; int y = gnv.Node.Value.y; - // Membalik status walkable node gnv.Node.IsWalkable = !gnv.Node.IsWalkable; - // Memperbarui warna node berdasarkan status walkable-nya if (gnv.Node.IsWalkable) { gnv.SetInnerColor(COLOR_WALKABLE); @@ -295,54 +231,41 @@ public class GridMap : MonoBehaviour } } - /// - /// Mendeteksi node pada posisi klik mouse dan menetapkannya sebagai posisi NPC - /// public void RayCastAndSetNPCPosition() { - // Don't allow changing NPC position during pathfinding, visualization, or movement - if (npc != null && (npc.pathFinder?.Status == PathFinding.PathFinderStatus.RUNNING || - npc.IsVisualizingPath || + if (npc != null && (npc.pathFinder?.Status == PathFinding.PathFinderStatus.RUNNING || + npc.IsVisualizingPath || npc.IsMoving)) { - // Pathfinding, visualization or movement is in progress - ignore position change return; } - - // Konversi posisi mouse ke koordinat dunia + Vector2 rayPos = new Vector2( Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y); - // Melakukan raycast untuk mendeteksi objek pada posisi mouse + RaycastHit2D hit = Physics2D.Raycast(rayPos, Vector2.zero, 0f); if (hit) { - // Mendapatkan objek yang terkena raycast + GameObject obj = hit.transform.gameObject; GridNodeView gnv = obj.GetComponent(); if (gnv != null && gnv.Node.IsWalkable) { - // Set posisi NPC ke node yang dipilih npc.SetStartNode(gnv.Node); - //Debug.Log($"Setting NPC position to: {gnv.Node.Value}"); } } } - /// - /// Update dipanggil setiap frame untuk menangani input pengguna - /// void Update() { - // Check if pathfinding, visualization, or movement is active - bool isPathfindingActive = npc != null && (npc.pathFinder?.Status == PathFinding.PathFinderStatus.RUNNING || - npc.IsVisualizingPath || + bool isPathfindingActive = npc != null && (npc.pathFinder?.Status == PathFinding.PathFinderStatus.RUNNING || + npc.IsVisualizingPath || npc.IsMoving); - - // Handle camera panning with middle mouse button - if (Input.GetMouseButton(2)) // Middle mouse button + // Middle mouse button + if (Input.GetMouseButton(2)) { float mouseX = Input.GetAxis("Mouse X"); float mouseY = Input.GetAxis("Mouse Y"); @@ -350,7 +273,7 @@ public class GridMap : MonoBehaviour Camera.main.transform.position += moveDirection; } - // Mengubah status walkable node saat Shift + tombol kiri mouse ditekan + // Left mouse button if (Input.GetMouseButton(0)) { Vector2 currentMousePosition = new Vector2( @@ -359,23 +282,22 @@ public class GridMap : MonoBehaviour if (currentMousePosition != lastMousePosition) { - // If pathfinding is active, only allow camera movement, no interaction with grid if (isPathfindingActive) { lastMousePosition = currentMousePosition; return; } - - // Menggambar dinding dengan Shift+Left Click + + // Shift + Left Click if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { RayCastAndToggleWalkable(); } - // Set NPC position dengan Left Click biasa else { RayCastAndSetNPCPosition(); } + lastMousePosition = currentMousePosition; } } @@ -384,7 +306,6 @@ public class GridMap : MonoBehaviour lastToggledNode = null; } - // Menetapkan tujuan baru saat tombol kanan mouse ditekan if (Input.GetMouseButtonDown(1)) { if (!isPathfindingActive) @@ -393,63 +314,39 @@ public class GridMap : MonoBehaviour } } - // Menyesuaikan ukuran kamera dengan scroll wheel float scroll = Input.GetAxis("Mouse ScrollWheel"); if (scroll != 0.0f) { - // Increase max zoom out to 200 and keep min zoom in at 1 - Camera.main.orthographicSize = Mathf.Clamp(Camera.main.orthographicSize - scroll * Camera.main.orthographicSize, 1.0f, 200.0f); + + Camera.main.orthographicSize = Mathf.Clamp( + Camera.main.orthographicSize - scroll * Camera.main.orthographicSize, + 1.0f, + 200.0f); } } - /// - /// Mendapatkan GridNode pada koordinat (x,y) - /// - /// Koordinat X - /// Koordinat Y - /// GridNode pada koordinat tersebut atau null jika tidak valid public GridNode GetGridNode(int x, int y) { if (x >= 0 && x < numX && y >= 0 && y < numY) - { return gridNodeViews[x, y].Node; - } + return null; } - /// - /// Mendapatkan GridNodeView pada koordinat (x,y) - /// - /// Koordinat X - /// Koordinat Y - /// GridNodeView pada koordinat tersebut atau null jika tidak valid public GridNodeView GetGridNodeView(int x, int y) { if (x >= 0 && x < numX && y >= 0 && y < numY) - { return gridNodeViews[x, y]; - } + return null; } - // Berbagai fungsi penghitungan jarak untuk algoritma pathfinding - - /// - /// Menghitung jarak Manhattan (jarak grid) antara dua titik - /// - public static float GetManhattanCost( - Vector2Int a, - Vector2Int b) + public static float GetManhattanCost(Vector2Int a, Vector2Int b) { return Mathf.Abs(a.x - b.x) + Mathf.Abs(a.y - b.y); } - /// - /// Menghitung jarak Euclidean (jarak garis lurus) antara dua titik - /// - public static float GetCostBetweenTwoCells( - Vector2Int a, - Vector2Int b) + public static float GetCostBetweenTwoCells(Vector2Int a, Vector2Int b) { return Mathf.Sqrt( (a.x - b.x) * (a.x - b.x) + @@ -457,19 +354,11 @@ public class GridMap : MonoBehaviour ); } - /// - /// Alias untuk GetCostBetweenTwoCells (jarak Euclidean) - /// - public static float GetEuclideanCost( - Vector2Int a, - Vector2Int b) + public static float GetEuclideanCost(Vector2Int a, Vector2Int b) { return GetCostBetweenTwoCells(a, b); } - /// - /// Callback yang dipanggil saat algoritma pathfinding mengubah current node - /// public void OnChangeCurrentNode(PathFinding.PathFinder.PathFinderNode node) { int x = node.Location.Value.x; @@ -478,9 +367,6 @@ public class GridMap : MonoBehaviour gnv.SetInnerColor(COLOR_CURRENT_NODE); } - /// - /// Callback yang dipanggil saat node ditambahkan ke open list dalam algoritma pathfinding - /// public void OnAddToOpenList(PathFinding.PathFinder.PathFinderNode node) { int x = node.Location.Value.x; @@ -489,9 +375,6 @@ public class GridMap : MonoBehaviour gnv.SetInnerColor(COLOR_ADD_TO_OPENLIST); } - /// - /// Callback yang dipanggil saat node ditambahkan ke closed list dalam algoritma pathfinding - /// public void OnAddToClosedList(PathFinding.PathFinder.PathFinderNode node) { int x = node.Location.Value.x; @@ -500,9 +383,6 @@ public class GridMap : MonoBehaviour gnv.SetInnerColor(COLOR_ADD_TO_CLOSEDLIST); } - /// - /// Mengatur ulang warna semua node grid ke warna default berdasarkan status walkable-nya - /// public void ResetGridNodeColours() { for (int i = 0; i < numX; ++i) @@ -522,9 +402,6 @@ public class GridMap : MonoBehaviour } } - /// - /// Simple class to store grid positions for serialization - /// [System.Serializable] public class SerializablePosition { @@ -538,26 +415,18 @@ public class GridMap : MonoBehaviour } } - /// - /// Kelas untuk menyimpan status grid untuk keperluan save/load - /// [System.Serializable] public class GridState { public bool[,] walkableStates; - public SerializablePosition npcPosition; // Position of NPC - public SerializablePosition destinationPosition; // Position of destination + public SerializablePosition npcPosition; + public SerializablePosition destinationPosition; } - /// - /// Menyimpan status walkable dari semua node grid ke file - /// - /// Path file untuk menyimpan data public void SaveGridState(string filePath) { try { - // Pastikan direktori ada string directory = Path.GetDirectoryName(filePath); if (!Directory.Exists(directory) && !string.IsNullOrEmpty(directory)) { @@ -567,7 +436,6 @@ public class GridMap : MonoBehaviour GridState gridState = new GridState(); gridState.walkableStates = new bool[numX, numY]; - // Menyimpan status walkable dari setiap node for (int i = 0; i < numX; i++) { for (int j = 0; j < numY; j++) @@ -576,29 +444,25 @@ public class GridMap : MonoBehaviour } } - // Save NPC position using our serializable class gridState.npcPosition = new SerializablePosition( npc.transform.position.x / GridNodeWidth, npc.transform.position.y / GridNodeHeight ); - // Save destination position using our serializable class gridState.destinationPosition = new SerializablePosition( destination.position.x / GridNodeWidth, destination.position.y / GridNodeHeight ); - // Configure serializer settings to ignore Unity-specific circular references JsonSerializerSettings settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; - // Mengkonversi ke JSON dan menyimpan ke file string json = JsonConvert.SerializeObject(gridState, settings); File.WriteAllText(filePath, json); - Debug.Log($"Grid state saved to {filePath}"); + } catch (System.Exception e) { @@ -606,10 +470,6 @@ public class GridMap : MonoBehaviour } } - /// - /// Memuat status walkable dari semua node grid dari file - /// - /// Path file untuk memuat data public void LoadGridState(string filePath) { try @@ -620,18 +480,15 @@ public class GridMap : MonoBehaviour return; } - // Membaca dan mengkonversi data dari file JSON string json = File.ReadAllText(filePath); - - // Configure deserializer settings to handle missing properties (compatibility) + JsonSerializerSettings settings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore }; - + GridState gridState = JsonConvert.DeserializeObject(json, settings); - // Periksa apakah ukuran grid dalam file sesuai dengan grid saat ini if (gridState.walkableStates.GetLength(0) != numX || gridState.walkableStates.GetLength(1) != numY) { @@ -639,7 +496,6 @@ public class GridMap : MonoBehaviour ResizeGrid(gridState.walkableStates.GetLength(0), gridState.walkableStates.GetLength(1)); } - // Menerapkan status walkable ke setiap node for (int i = 0; i < numX; i++) { for (int j = 0; j < numY; j++) @@ -649,53 +505,42 @@ public class GridMap : MonoBehaviour } } - // Check if this is an older save file version (backward compatibility) bool hasPositionData = gridState.npcPosition != null && gridState.destinationPosition != null; - // Load NPC position if saved if (npc != null) { if (hasPositionData) { - // Find the closest valid grid node position int npcX = Mathf.Clamp(Mathf.RoundToInt(gridState.npcPosition.x), 0, numX - 1); int npcY = Mathf.Clamp(Mathf.RoundToInt(gridState.npcPosition.y), 0, numY - 1); - - // Make sure the position is walkable + GridNode npcNode = GetGridNode(npcX, npcY); if (npcNode != null && npcNode.IsWalkable) { - // Set NPC position npc.SetStartNode(npcNode); } else { - // Find a walkable node if the saved position isn't walkable FindWalkableNodeAndSetNPC(); } } else { - // For older save files, find a walkable position FindWalkableNodeAndSetNPC(); } } - // Load destination position if saved if (destination != null) { if (hasPositionData) { - // Find the closest valid grid node position int destX = Mathf.Clamp(Mathf.RoundToInt(gridState.destinationPosition.x), 0, numX - 1); int destY = Mathf.Clamp(Mathf.RoundToInt(gridState.destinationPosition.y), 0, numY - 1); - - // Set destination position + SetDestination(destX, destY); } else { - // For older save files, set destination to the opposite corner or a walkable node FindWalkableNodeAndSetDestination(); } } @@ -708,12 +553,8 @@ public class GridMap : MonoBehaviour } } - /// - /// Find a walkable node and set the NPC position to it - /// private void FindWalkableNodeAndSetNPC() { - // Find first walkable node for (int i = 0; i < numX; i++) { for (int j = 0; j < numY; j++) @@ -728,28 +569,23 @@ public class GridMap : MonoBehaviour } } - /// - /// Resizes the grid to the specified dimensions - /// public bool ResizeGrid(int newSizeX, int newSizeY) { - // Enforce grid size limits const int MAX_GRID_SIZE = 200; const int MIN_GRID_SIZE = 2; - + if (newSizeX > MAX_GRID_SIZE || newSizeY > MAX_GRID_SIZE) { Debug.LogWarning($"Attempted to resize grid beyond maximum size of {MAX_GRID_SIZE}x{MAX_GRID_SIZE}. Operation cancelled."); return false; } - + if (newSizeX < MIN_GRID_SIZE || newSizeY < MIN_GRID_SIZE) { Debug.LogWarning($"Attempted to resize grid below minimum size of {MIN_GRID_SIZE}x{MIN_GRID_SIZE}. Operation cancelled."); return false; } - - // Clean up existing grid + if (gridNodeViews != null) { for (int i = 0; i < numX; i++) @@ -764,11 +600,9 @@ public class GridMap : MonoBehaviour } } - // Update dimensions numX = newSizeX; numY = newSizeY; - // Create new grid gridNodeViews = new GridNodeView[NumX, NumY]; for (int i = 0; i < NumX; i++) { @@ -790,21 +624,16 @@ public class GridMap : MonoBehaviour } } - // Update camera position SetCameraPosition(); - // Reset NPC position if needed if (npc != null) { npc.SetStartNode(gridNodeViews[0, 0].Node); } - + return true; } - /// - /// Sets the destination position for pathfinding - /// public void SetDestination(int x, int y) { if (x >= 0 && x < numX && y >= 0 && y < numY) @@ -816,16 +645,10 @@ public class GridMap : MonoBehaviour } } - /// - /// Membuat maze dengan algoritma Recursive Backtracking - /// - /// Kepadatan dinding dalam persen (0-100), mempengaruhi rasio jalur terhadap ruang terbuka public void GenerateRandomMaze(float density = 35f) { - // Handle special cases for 0% and 100% density if (density <= 0f) { - // Make all cells walkable (no walls) for (int x = 0; x < numX; x++) { for (int y = 0; y < numY; y++) @@ -846,7 +669,6 @@ public class GridMap : MonoBehaviour } else if (density >= 100f) { - // Make all cells non-walkable (all walls) for (int x = 0; x < numX; x++) { for (int y = 0; y < numY; y++) @@ -863,22 +685,15 @@ public class GridMap : MonoBehaviour } } } - - // No path creation - truly 100% blocked + return; } - - // Use the recursive backtracking maze generation for normal density values + GenerateRecursiveBacktrackingMaze(density); } - /// - /// Membuat maze menggunakan algoritma recursive backtracking dengan kontrol densitas dinding. - /// - /// Persentase dinding dalam maze (0-100) public void GenerateRecursiveBacktrackingMaze(float density = 30f) { - // Inisialisasi semua tile sebagai dinding (bukan jalur) for (int x = 0; x < numX; x++) { for (int y = 0; y < numY; y++) @@ -896,99 +711,76 @@ public class GridMap : MonoBehaviour } } - // Hitung jumlah total tile dan target dinding int totalTiles = numX * numY; int targetWallCount = Mathf.RoundToInt((density / 100f) * totalTiles); - // Buat sistem random System.Random random = new System.Random(); // Arah: Atas (0), Kanan (1), Bawah (2), Kiri (3) int[] dx = { 0, 1, 0, -1 }; int[] dy = { 1, 0, -1, 0 }; - // Mulai dari posisi acak int startX = random.Next(0, numX); int startY = random.Next(0, numY); - // Buat cell awal menjadi jalur MakeCellWalkable(startX, startY); - // Stack untuk recursive backtracking Stack stack = new Stack(); stack.Push(new Vector2Int(startX, startY)); // Jalankan recursive backtracking while (stack.Count > 0) { - // Ambil posisi saat ini Vector2Int current = stack.Peek(); - // Daftar untuk menyimpan arah yang valid (belum dikunjungi) List directions = new List(); - // Cek semua arah for (int i = 0; i < 4; i++) { - // Periksa tetangga 2 langkah (untuk memastikan kita tidak membuat jalur yang bersebelahan) int nx = current.x + dx[i] * 2; int ny = current.y + dy[i] * 2; - // Jika tetangga dalam batas grid dan belum dikunjungi if (nx >= 0 && nx < numX && ny >= 0 && ny < numY) { GridNode neighborNode = GetGridNode(nx, ny); if (neighborNode != null && !neighborNode.IsWalkable) { - // Tambahkan arah ke daftar valid directions.Add(i); } } } - // Jika ada arah yang valid if (directions.Count > 0) { - // Pilih arah secara acak int direction = directions[random.Next(0, directions.Count)]; - // Hitung posisi tetangga dan dinding di antaranya int nx = current.x + dx[direction] * 2; int ny = current.y + dy[direction] * 2; int wallX = current.x + dx[direction]; int wallY = current.y + dy[direction]; - // Buat jalur di tetangga dan dinding di antaranya MakeCellWalkable(nx, ny); MakeCellWalkable(wallX, wallY); - // Tambahkan tetangga ke stack stack.Push(new Vector2Int(nx, ny)); } else { - // Tidak ada arah yang valid, backtrack stack.Pop(); } } - // Hitung jumlah dinding saat ini int currentWallCount = CountNonWalkableCells(); - // Jika kita memiliki terlalu banyak dinding (kepadatan terlalu tinggi) if (currentWallCount > targetWallCount) { - // Hapus dinding secara acak hingga mencapai target RemoveRandomWalls(currentWallCount - targetWallCount); } - // Jika kita memiliki terlalu sedikit dinding (kepadatan terlalu rendah) else if (currentWallCount < targetWallCount) { - // Tambahkan dinding secara acak hingga mencapai target AddRandomWalls(targetWallCount - currentWallCount); } - // Pastikan posisi NPC dan tujuan dapat dilalui EnsureNodeAndNeighborsWalkable((int)(npc.transform.position.x / gridNodeWidth), (int)(npc.transform.position.y / gridNodeHeight)); @@ -996,9 +788,6 @@ public class GridMap : MonoBehaviour (int)(destination.position.y / gridNodeHeight)); } - /// - /// Menghitung jumlah sel yang tidak dapat dilalui (dinding) di grid - /// private int CountNonWalkableCells() { int count = 0; @@ -1016,9 +805,6 @@ public class GridMap : MonoBehaviour return count; } - /// - /// Membuat sebuah sel menjadi dapat dilalui (jalur) - /// private void MakeCellWalkable(int x, int y) { GridNode node = GetGridNode(x, y); @@ -1033,9 +819,6 @@ public class GridMap : MonoBehaviour } } - /// - /// Membuat sebuah sel menjadi tidak dapat dilalui (dinding) - /// private void MakeCellNonWalkable(int x, int y) { GridNode node = GetGridNode(x, y); @@ -1050,14 +833,10 @@ public class GridMap : MonoBehaviour } } - /// - /// Menghapus dinding secara acak dari maze - /// private void RemoveRandomWalls(int count) { System.Random random = new System.Random(); - // Buat daftar semua dinding List walls = new List(); for (int x = 0; x < numX; x++) { @@ -1071,7 +850,6 @@ public class GridMap : MonoBehaviour } } - // Acak daftarnya for (int i = 0; i < walls.Count; i++) { int j = random.Next(i, walls.Count); @@ -1080,7 +858,6 @@ public class GridMap : MonoBehaviour walls[j] = temp; } - // Hapus dinding hingga mencapai target for (int i = 0; i < count && i < walls.Count; i++) { Vector2Int wall = walls[i]; @@ -1088,14 +865,10 @@ public class GridMap : MonoBehaviour } } - /// - /// Menambahkan dinding secara acak ke maze - /// private void AddRandomWalls(int count) { System.Random random = new System.Random(); - // Buat daftar semua jalur List paths = new List(); for (int x = 0; x < numX; x++) { @@ -1104,7 +877,6 @@ public class GridMap : MonoBehaviour GridNode node = GetGridNode(x, y); if (node != null && node.IsWalkable) { - // Jangan tambahkan dinding di posisi NPC atau tujuan if ((x != (int)(npc.transform.position.x / gridNodeWidth) || y != (int)(npc.transform.position.y / gridNodeHeight)) && (x != (int)(destination.position.x / gridNodeWidth) || @@ -1116,7 +888,6 @@ public class GridMap : MonoBehaviour } } - // Acak daftarnya for (int i = 0; i < paths.Count; i++) { int j = random.Next(i, paths.Count); @@ -1125,13 +896,11 @@ public class GridMap : MonoBehaviour paths[j] = temp; } - // Tambahkan dinding hingga mencapai target atau hingga daftar jalur habis int added = 0; for (int i = 0; i < paths.Count && added < count; i++) { Vector2Int path = paths[i]; - // Pastikan menambahkan dinding tidak memotong jalur penting if (!IsPathCritical(path.x, path.y)) { MakeCellNonWalkable(path.x, path.y); @@ -1140,12 +909,8 @@ public class GridMap : MonoBehaviour } } - /// - /// Memeriksa apakah sebuah sel merupakan jalur kritis yang tidak boleh diblokir - /// private bool IsPathCritical(int x, int y) { - // Hindari memblokir jalur satu-satunya int walkableNeighbors = 0; // Arah: Atas, Kanan, Bawah, Kiri @@ -1166,26 +931,18 @@ public class GridMap : MonoBehaviour } } } - - // Jika hanya memiliki satu atau dua tetangga yang dapat dilalui, ini mungkin jalur penting return walkableNeighbors <= 2; } - /// - /// Memastikan bahwa node dan tetangganya dapat dilalui - /// private void EnsureNodeAndNeighborsWalkable(int x, int y) { - // Pastikan node utama dapat dilalui MakeCellWalkable(x, y); - // Pastikan setidaknya satu tetangga dapat dilalui agar tidak terjebak int[] dx = { 0, 1, 0, -1 }; int[] dy = { 1, 0, -1, 0 }; bool hasWalkableNeighbor = false; - // Cek jika sudah ada tetangga yang dapat dilalui for (int i = 0; i < 4; i++) { int nx = x + dx[i]; @@ -1202,7 +959,6 @@ public class GridMap : MonoBehaviour } } - // Jika tidak ada tetangga yang dapat dilalui, buat salah satu tetangga dapat dilalui if (!hasWalkableNeighbor) { for (int i = 0; i < 4; i++) @@ -1219,35 +975,28 @@ public class GridMap : MonoBehaviour } } - /// - /// Find a walkable node and set the destination position to it - /// private void FindWalkableNodeAndSetDestination() { - // Try to place destination in the far corner from the NPC int npcX = (int)(npc.transform.position.x / GridNodeWidth); int npcY = (int)(npc.transform.position.y / GridNodeHeight); - - // Try opposite corner first + int destX = numX - 1; int destY = numY - 1; - + GridNode destinationNode = GetGridNode(destX, destY); if (destinationNode != null && destinationNode.IsWalkable) { SetDestination(destX, destY); return; } - - // If the opposite corner isn't walkable, find any walkable node that's different from NPC position + for (int i = numX - 1; i >= 0; i--) { for (int j = numY - 1; j >= 0; j--) { - // Skip the node where NPC is if (i == npcX && j == npcY) continue; - + GridNode node = GetGridNode(i, j); if (node != null && node.IsWalkable) { @@ -1256,8 +1005,6 @@ public class GridMap : MonoBehaviour } } } - - // If no suitable node found, use the NPC node as a fallback SetDestination(npcX, npcY); } } \ No newline at end of file diff --git a/Assets/Scripts/GridNode.cs b/Assets/Scripts/GridNode.cs index 1ff24f4..e3a7f54 100644 --- a/Assets/Scripts/GridNode.cs +++ b/Assets/Scripts/GridNode.cs @@ -9,42 +9,18 @@ using UnityEngine; /// public class GridNode : PathFinding.Node { - /// - /// Menentukan apakah node ini dapat dilalui oleh karakter. - /// True jika node dapat dilalui, false jika node adalah penghalang. - /// - public bool IsWalkable { get; set; } + public bool IsWalkable { get; set; } - /// - /// Referensi ke GridMap yang mengelola seluruh grid. - /// Digunakan untuk mendapatkan tetangga dan operasi lain yang berhubungan dengan grid. - /// - public GridMap gridMap; // Change to internal or public + internal GridMap gridMap; - /// - /// Constructor untuk membuat GridNode baru. - /// - /// Koordinat Vector2Int yang merepresentasikan posisi node di dalam grid - /// Referensi ke GridMap yang mengelola grid ini - public GridNode(Vector2Int value, GridMap gridMap) - : base(value) - { - IsWalkable = true; // Secara default node dapat dilalui - this.gridMap = gridMap; // Simpan referensi ke GridMap - } + public GridNode(Vector2Int value, GridMap gridMap) : base(value) + { + IsWalkable = true; + this.gridMap = gridMap; + } - /// - /// 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. - /// - /// Daftar node tetangga yang dapat dicapai dari node ini - public override - List> GetNeighbours() - { - // Return an empty list for now. - // Later we will call gridMap's GetNeighbours - // function. - //return new List>(); - return gridMap.GetNeighbours(this); - } + public override List> GetNeighbours() + { + return gridMap.GetNeighbours(this); + } } diff --git a/Assets/Scripts/GridNodeView.cs b/Assets/Scripts/GridNodeView.cs index 1373177..9547188 100644 --- a/Assets/Scripts/GridNodeView.cs +++ b/Assets/Scripts/GridNodeView.cs @@ -6,36 +6,19 @@ using UnityEngine; /// public class GridNodeView : MonoBehaviour { - /// - /// Referensi ke SpriteRenderer untuk bagian dalam node. - /// [SerializeField] SpriteRenderer innerSprite; - /// - /// Referensi ke SpriteRenderer untuk bagian luar node. - /// [SerializeField] SpriteRenderer outerSprite; - /// - /// Properti yang menyimpan referensi ke objek GridNode yang terkait dengan view ini. - /// public GridNode Node { get; set; } - /// - /// Mengatur warna sprite bagian dalam dari node. - /// - /// Warna yang akan diaplikasikan pada sprite bagian dalam. public void SetInnerColor(Color col) { innerSprite.color = col; } - /// - /// Mengatur warna sprite bagian luar dari node. - /// - /// Warna yang akan diaplikasikan pada sprite bagian luar. public void SetOuterColor(Color col) { outerSprite.color = col; diff --git a/Assets/Scripts/NPC.cs b/Assets/Scripts/NPC.cs index 1b06128..54fc588 100644 --- a/Assets/Scripts/NPC.cs +++ b/Assets/Scripts/NPC.cs @@ -5,19 +5,20 @@ using System.Collections.Generic; using System.Diagnostics; using UnityEngine; +/// +/// Metrik yang digunakan untuk mengukur performa pathfinding. +/// Dibuat dalam bentuk struct untuk efisiensi memori dan kemudahan penggunaan. +/// public struct PathfindingMetrics { - // Untuk Pengukuran Kinerja - public float timeTaken; // in milliseconds + public float timeTaken; // miliseconds public int pathLength; - public int nodesExplored; // number of nodes in path - public long memoryUsed; // memory used by pathfinding in bytes + public int nodesExplored; + public long memoryUsed; // bytes - // Untuk Visualisasi - public int maxOpenListSize; // maximum size of open list during pathfinding - public int maxClosedListSize; // maximum size of closed list during pathfinding + public int maxOpenListSize; + public int maxClosedListSize; - // 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) @@ -31,16 +32,10 @@ public class NPC : MonoBehaviour { public float speed = 2.0f; public Queue wayPoints = new Queue(); - - // Event that fires when pathfinding is complete with performance metrics - public event Action OnPathfindingComplete; - // Last measured memory usage (for accessing from outside) + public event Action OnPathfindingComplete; public long LastMeasuredMemoryUsage { get; private set; } = 0; - /// - /// Enumerasi yang merepresentasikan berbagai algoritma pathfinding yang tersedia. - /// public enum PathFinderType { ASTAR, @@ -57,36 +52,21 @@ public class NPC : MonoBehaviour public GridMap Map { get; set; } - // List to store all steps for visualization playback private List visualizationSteps = new List(); private bool isVisualizingPath = false; private bool isMoving = false; - - // Public accessor for visualization state + public bool IsVisualizingPath => isVisualizingPath; - - // Public accessor for movement state public bool IsMoving => isMoving; - - // Event that fires when visualization is complete + public event Action OnVisualizationComplete; - - // Event that fires when movement is complete public event Action OnMovementComplete; - // Properties to control visualization - [SerializeField] + public float visualizationSpeed = 0.0f; + public int visualizationBatch = 1; - // Visualization speed is time between visualization steps - public float visualizationSpeed = 0.0f; // Default 0; set higher for slower visualization + public bool showVisualization = true; - // 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 } @@ -132,27 +112,25 @@ public class NPC : MonoBehaviour { while (wayPoints.Count > 0) { - // Set the moving flag when starting to move if (!isMoving && wayPoints.Count > 0) { isMoving = true; UnityEngine.Debug.Log("NPC movement started"); } - + yield return StartCoroutine( Coroutine_MoveToPoint( wayPoints.Dequeue(), speed)); } - - // If we were moving but now have no more waypoints, signal movement completion + if (isMoving && wayPoints.Count == 0) { isMoving = false; UnityEngine.Debug.Log("NPC movement complete, invoking OnMovementComplete event"); OnMovementComplete?.Invoke(); } - + yield return null; } } @@ -163,7 +141,6 @@ public class NPC : MonoBehaviour 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); } @@ -179,26 +156,20 @@ public class NPC : MonoBehaviour 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: @@ -218,24 +189,20 @@ public class NPC : MonoBehaviour 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; @@ -252,7 +219,6 @@ public class NPC : MonoBehaviour SetStartNode(start); - // Reset grid colors if (!silentMode) { Map.ResetGridNodeColours(); @@ -261,7 +227,6 @@ public class NPC : MonoBehaviour visualizationSteps.Clear(); isVisualizingPath = false; - // jika gagal menginisialisasi pathfinder, tidak perlu melanjutkan if (!pathFinder.Initialise(start, destination)) { return; @@ -274,9 +239,7 @@ public class NPC : MonoBehaviour { yield return StartCoroutine(MeasurePerformance(silentMode)); - // Start visualization after calculation is complete regardless of success or failure - // This allows visualization of the explored area even when no path is found - if (showVisualization && !silentMode && + if (showVisualization && !silentMode && (pathFinder.Status == PathFinderStatus.SUCCESS || pathFinder.Status == PathFinderStatus.FAILURE)) { yield return StartCoroutine(VisualizePathfinding()); @@ -285,71 +248,54 @@ public class NPC : MonoBehaviour 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(4); - //GC.Collect(2, GCCollectionMode.Forced, true, true); - //GC.WaitForPendingFinalizers(); // Tunggu semua finalizers selesai - - // ===== 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; - // Store the memory usage for external access LastMeasuredMemoryUsage = memoryUsed > 0 ? memoryUsed : 1024; - + 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 + timeTaken = milliseconds, pathLength = pathLength, nodesExplored = nodesExplored, memoryUsed = memoryUsed, @@ -360,25 +306,20 @@ public class NPC : MonoBehaviour totalFCost = totalFCost, }; - // *** IMPORTANT FIX: Always invoke the event, regardless of silent mode *** - // Report metrics before visualization OnPathfindingComplete?.Invoke(metrics); - // Path visualization and handling HandlePathFindingResult(silentMode, pathLength); - // Pastikan untuk mengembalikan nilai di akhir coroutine yield return null; } - - /// - /// Setup callbacks for tracking nodes in open/closed lists and visualization - /// - private void SetupCallbacks(bool silentMode, ref int maxOpenListSize, ref int currentOpenListSize, - ref int maxClosedListSize, ref int currentClosedListSize) + 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; @@ -386,7 +327,6 @@ public class NPC : MonoBehaviour if (silentMode) { - // In silent mode, just set minimal callbacks for metrics pathFinder.onAddToOpenList = (node) => { localCurrentOpenListSize++; @@ -399,12 +339,11 @@ public class NPC : MonoBehaviour localCurrentClosedListSize++; if (localCurrentClosedListSize > localMaxClosedListSize) localMaxClosedListSize = localCurrentClosedListSize; - localCurrentOpenListSize--; // When a node is moved from open to closed list + localCurrentOpenListSize--; }; } else { - // In regular mode, track and prepare for visualization pathFinder.onAddToOpenList = (node) => { visualizationSteps.Add(new PathfindingVisualizationStep( @@ -426,7 +365,7 @@ public class NPC : MonoBehaviour if (localCurrentClosedListSize > localMaxClosedListSize) localMaxClosedListSize = localCurrentClosedListSize; - localCurrentOpenListSize--; // When a node is moved from open to closed list + localCurrentOpenListSize--; }; pathFinder.onChangeCurrentNode = (node) => @@ -439,38 +378,29 @@ public class NPC : MonoBehaviour } - - // Setelah lambda selesai dijalankan, perbarui variabel ref maxOpenListSize = localMaxOpenListSize; currentOpenListSize = localCurrentOpenListSize; maxClosedListSize = localMaxClosedListSize; currentClosedListSize = localCurrentClosedListSize; } - /// - /// Handle path finding result (success or failure) - /// 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.PathFinderNode node = pathFinder.CurrentNode; - List pathPositions = new List(pathLength); // Pre-allocate with known size + List pathPositions = new List(pathLength); - // 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( @@ -482,40 +412,19 @@ public class NPC : MonoBehaviour else if (pathFinder.Status == PathFinderStatus.FAILURE) { OnFailurePathFinding(); - - // For failure case, we don't add any final path visualization steps - // The exploration steps (open/closed lists) are already added during the search - // and will be visualized to show what nodes were explored before failure + UnityEngine.Debug.Log($"Pathfinding failed - visualization will show {visualizationSteps.Count} exploration steps"); } } - /// - /// Memformat ukuran byte menjadi string yang lebih mudah dibaca - /// - 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(); } @@ -525,12 +434,8 @@ public class NPC : MonoBehaviour UnityEngine.Debug.Log("Pathfinding failed"); } - /// - /// Changes the pathfinding algorithm at runtime - /// 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"); @@ -539,14 +444,12 @@ public class NPC : MonoBehaviour 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: @@ -566,11 +469,9 @@ public class NPC : MonoBehaviour break; } - // Set up callbacks pathFinder.onSuccess = OnSuccessPathFinding; pathFinder.onFailure = OnFailurePathFinding; - // Gunakan setting asli pathFinder.HeuristicCost = GridMap.GetManhattanCost; pathFinder.NodeTraversalCost = GridMap.GetEuclideanCost; } @@ -594,17 +495,14 @@ public class NPC : MonoBehaviour UnityEngine.Debug.Log("Path visualization starting"); 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 + int batchSize = Mathf.Min(visualizationBatch, stepCount); - // Detect if pathfinding failed - we'll need to know this when processing steps bool pathfindingFailed = pathFinder.Status == PathFinderStatus.FAILURE; - + if (pathfindingFailed) { UnityEngine.Debug.Log($"Visualizing failed pathfinding attempt with {stepCount} steps"); @@ -614,7 +512,6 @@ public class NPC : MonoBehaviour { int end = Mathf.Min(i + batchSize, stepCount); - // Process a batch of steps for (int j = i; j < end; j++) { var step = visualizationSteps[j]; @@ -634,7 +531,7 @@ public class NPC : MonoBehaviour break; case PathfindingVisualizationStep.StepType.FinalPath: gnv.SetInnerColor(Map.COLOR_PATH); - // Only add waypoints for successful pathfinding + if (!pathfindingFailed) { GridNode pathNode = Map.GetGridNode(step.position.x, step.position.y); @@ -644,60 +541,39 @@ public class NPC : MonoBehaviour } } } - - // Yield after each batch to prevent frame drops yield return new WaitForSeconds(visualizationSpeed); } isVisualizingPath = false; UnityEngine.Debug.Log("Path visualization complete, invoking OnVisualizationComplete event"); - // Notify any listeners that visualization is complete OnVisualizationComplete?.Invoke(); } - /// - /// Menghitung biaya G, H, dan F untuk jalur - /// 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.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; } } \ No newline at end of file diff --git a/Assets/Scripts/PathFinder.cs b/Assets/Scripts/PathFinder.cs index be2bd33..2f251f9 100644 --- a/Assets/Scripts/PathFinder.cs +++ b/Assets/Scripts/PathFinder.cs @@ -3,16 +3,18 @@ using System.Collections.Generic; namespace PathFinding { + #region PathFinderStatus Enumeration + /// /// Enumerasi yang merepresentasikan berbagai status dari PathFinder. /// Digunakan untuk melacak progress dari pencarian jalur (pathfinding). /// public enum PathFinderStatus { - NOT_INITIALISED, // PathFinder belum diinisialisasi - SUCCESS, // Pencarian jalur berhasil menemukan tujuan - FAILURE, // Pencarian jalur gagal (tidak ada jalur ditemukan) - RUNNING, // Proses pencarian jalur sedang berjalan + NOT_INITIALISED, + SUCCESS, + FAILURE, + RUNNING, } /// @@ -22,25 +24,11 @@ namespace PathFinding /// Tipe data nilai yang disimpan dalam node abstract public class Node { - /// - /// Nilai yang disimpan dalam node - /// public T Value { get; private set; } - - /// - /// Konstruktor untuk membuat node baru dengan nilai tertentu - /// - /// Nilai yang akan disimpan dalam node public Node(T value) { Value = value; } - - /// - /// Mendapatkan daftar tetangga dari node ini. - /// Metode ini harus diimplementasikan oleh kelas turunan. - /// - /// Daftar tetangga dari node ini abstract public List> GetNeighbours(); } @@ -51,31 +39,13 @@ namespace PathFinding public abstract class PathFinder { #region Delegates for Cost Calculation. - /// - /// Delegate untuk menghitung biaya perjalanan antara dua node - /// - /// Node asal - /// Node tujuan - /// Biaya perjalanan dari a ke b + public delegate float CostFunction(T a, T b); - - /// - /// Mendapatkan jumlah node dalam daftar tertutup (closedList) - /// public int ClosedListCount => closedList.Count; - public int OpenListCount => openList.Count; - - /// - /// Fungsi untuk menghitung biaya heuristik antara dua node - /// Digunakan dalam algoritma informasi seperti A* dan Greedy Best-First - /// public CostFunction HeuristicCost { get; set; } - - /// - /// Fungsi untuk menghitung biaya perjalanan antara dua node yang bertetangga - /// public CostFunction NodeTraversalCost { get; set; } + #endregion #region PathFinderNode @@ -86,43 +56,14 @@ namespace PathFinding /// public class PathFinderNode : System.IComparable { - /// - /// Node induk dalam jalur pencarian - /// public PathFinderNode Parent { get; set; } - - /// - /// Lokasi node dalam struktur graph - /// public Node Location { get; private set; } - - /// - /// Referensi ke peta grid - /// public GridMap Map { get; set; } - /// - /// Total biaya (F = G + H) - /// public float FCost { get; private set; } - - /// - /// Biaya dari node awal ke node saat ini (cost so far) - /// public float GCost { get; private set; } - - /// - /// Biaya heuristik dari node saat ini ke tujuan (estimated cost) - /// public float HCost { get; private set; } - /// - /// Konstruktor untuk PathFinderNode - /// - /// Lokasi node dalam struktur graph - /// Node induk dalam jalur pencarian - /// Biaya dari node awal ke node saat ini - /// Biaya heuristik dari node saat ini ke tujuan public PathFinderNode(Node location, PathFinderNode parent, float gCost, @@ -134,32 +75,18 @@ namespace PathFinding SetGCost(gCost); } - /// - /// Mengatur biaya G dan menghitung ulang biaya F - /// - /// Nilai baru untuk biaya G public void SetGCost(float c) { GCost = c; FCost = GCost + HCost; } - /// - /// Mengatur biaya H dan menghitung ulang biaya F - /// - /// Nilai baru untuk biaya H public void SetHCost(float h) { HCost = h; FCost = GCost + HCost; } - /// - /// Membandingkan node berdasarkan biaya F - /// Digunakan untuk menyortir prioritas node dalam pencarian - /// - /// Node lain yang dibandingkan - /// Hasil perbandingan nilai FCost public int CompareTo(PathFinderNode other) { if (other == null) return 1; @@ -169,58 +96,27 @@ namespace PathFinding #endregion #region Properties - /// - /// Status saat ini dari pathfinder - /// Nilai default-nya adalah NOT_INITIALISED - /// + public PathFinderStatus Status { get; protected set; } = PathFinderStatus.NOT_INITIALISED; - /// - /// Node awal pencarian jalur - /// public Node Start { get; protected set; } - - /// - /// Node tujuan pencarian jalur - /// public Node Goal { get; protected set; } - - /// - /// Node yang sedang diproses saat ini oleh pathfinder - /// public PathFinderNode CurrentNode { get; protected set; } - - /// - /// Referensi ke peta grid yang digunakan untuk pencarian jalur - /// public GridMap Map { get; internal set; } + #endregion #region Open and Closed Lists and Associated Functions. - /// - /// Daftar node yang belum diperiksa (open list) - /// Node dalam daftar ini akan diproses di langkah berikutnya - /// + protected List openList = new List(); - /// - /// Daftar node yang sudah diperiksa (closed list) - /// Node dalam daftar ini sudah dievaluasi - /// protected List closedList = new List(); - - /// - /// Mendapatkan node dengan biaya terendah dari suatu daftar - /// Digunakan untuk memilih node berikutnya yang akan diperiksa - /// - /// Daftar node yang akan diperiksa - /// Node dengan biaya terendah protected PathFinderNode GetLeastCostNode( List myList) { @@ -237,13 +133,6 @@ namespace PathFinding PathFinderNode n = myList[best_index]; return n; } - - /// - /// Memeriksa apakah suatu cell (nilai T) ada dalam daftar node - /// - /// Daftar node yang akan diperiksa - /// Cell yang dicari - /// Indeks cell dalam daftar jika ditemukan, -1 jika tidak protected int IsInList(List myList, T cell) { for (int i = 0; i < myList.Count; i++) @@ -256,68 +145,28 @@ namespace PathFinding #endregion #region Delegates for Action Callbacks - /// - /// Delegate untuk menangani event terkait node selama proses pathfinding - /// Dapat digunakan untuk visualisasi atau debugging - /// - /// Node yang terlibat dalam event + public delegate void DelegatePathFinderNode(PathFinderNode node); - /// - /// Event dipanggil ketika current node berubah - /// public DelegatePathFinderNode onChangeCurrentNode; - - /// - /// Event dipanggil ketika node ditambahkan ke open list - /// public DelegatePathFinderNode onAddToOpenList; - - /// - /// Event dipanggil ketika node ditambahkan ke closed list - /// public DelegatePathFinderNode onAddToClosedList; - - /// - /// Event dipanggil ketika node tujuan ditemukan - /// public DelegatePathFinderNode onDestinationFound; - /// - /// Delegate untuk menangani event tanpa parameter - /// public delegate void DelegateNoArguments(); - /// - /// Event dipanggil ketika pencarian jalur dimulai - /// public DelegateNoArguments onStarted; - - /// - /// Event dipanggil ketika pencarian jalur sedang berjalan - /// public DelegateNoArguments onRunning; - - /// - /// Event dipanggil ketika pencarian jalur gagal - /// public DelegateNoArguments onFailure; - - /// - /// Event dipanggil ketika pencarian jalur berhasil - /// public DelegateNoArguments onSuccess; + #endregion #region Pathfinding Search Related Functions - /// - /// Mereset variabel internal untuk pencarian baru - /// public virtual void Reset() { if (Status == PathFinderStatus.RUNNING) { - // Tidak bisa reset karena pathfinding sedang berlangsung return; } @@ -328,11 +177,6 @@ namespace PathFinding Status = PathFinderStatus.NOT_INITIALISED; } - /// - /// Melakukan satu langkah pencarian jalur - /// Harus dipanggil berulang kali sampai Status menjadi SUCCESS atau FAILURE - /// - /// Status pathfinder setelah langkah ini selesai public virtual PathFinderStatus Step() { closedList.Add(CurrentNode); @@ -340,20 +184,17 @@ namespace PathFinding if (openList.Count == 0) { - // Pencarian telah selesai tanpa menemukan jalur Status = PathFinderStatus.FAILURE; onFailure?.Invoke(); return Status; } - // Dapatkan node dengan biaya terendah dari openList CurrentNode = GetLeastCostNode(openList); onChangeCurrentNode?.Invoke(CurrentNode); openList.Remove(CurrentNode); - // Periksa apakah node ini mengandung cell tujuan if (EqualityComparer.Default.Equals( CurrentNode.Location.Value, Goal.Value)) { @@ -363,10 +204,8 @@ namespace PathFinding return Status; } - // Dapatkan tetangga dari node saat ini List> neighbours = CurrentNode.Location.GetNeighbours(); - // Proses setiap tetangga untuk kemungkinan ekspansi pencarian foreach (Node cell in neighbours) { AlgorithmSpecificImplementation(cell); @@ -376,25 +215,11 @@ namespace PathFinding onRunning?.Invoke(); return Status; } - - /// - /// Implementasi algoritma spesifik untuk memproses node tetangga - /// Metode ini akan diimplementasikan berbeda untuk setiap algoritma pathfinding - /// - /// Node tetangga yang akan diproses abstract protected void AlgorithmSpecificImplementation(Node cell); - - /// - /// Inisialisasi pathfinder dengan node awal dan tujuan - /// - /// Node awal pencarian - /// Node tujuan pencarian - /// True jika inisialisasi berhasil, False jika gagal public virtual bool Initialise(Node start, Node goal) { if (Status == PathFinderStatus.RUNNING) { - // Pathfinding sedang berlangsung, tidak bisa diinisialisasi ulang return false; } @@ -403,19 +228,15 @@ namespace PathFinding Start = start; Goal = goal; - // Deteksi dini jika start dan goal sama if (EqualityComparer.Default.Equals(Start.Value, Goal.Value)) { - // Pada kasus start=goal, tidak perlu menjalankan algoritma pencarian - // Cukup atur CurrentNode dan Status secara langsung + // Cost set to 0 CurrentNode = new PathFinderNode(Start, null, 0.0f, 0.0f); - // Panggil callback sesuai urutan normal onChangeCurrentNode?.Invoke(CurrentNode); onStarted?.Invoke(); onDestinationFound?.Invoke(CurrentNode); - // Atur status ke SUCCESS Status = PathFinderStatus.SUCCESS; onSuccess?.Invoke(); @@ -441,6 +262,8 @@ namespace PathFinding #endregion } + #endregion + #region Priority Queue /// /// Memprioritaskan item berdasarkan nilai komparatif mereka @@ -448,34 +271,16 @@ namespace PathFinding /// Tipe item dalam antrian prioritas public class PriorityQueue where T : IComparable { - /// - /// Data yang disimpan dalam priority queue - /// private List data; - - /// - /// Pembanding untuk menentukan prioritas item - /// private IComparer comparer; - - /// - /// Menyimpan indeks setiap elemen dalam data untuk akses cepat - /// private Dictionary elementIndexMap; // Cache untuk optimasi private T _lastDequeued; private int _count; - /// - /// Konstruktor default menggunakan pembanding default untuk tipe T - /// public PriorityQueue() : this(Comparer.Default) { } - /// - /// Konstruktor dengan custom comparer - /// - /// Pembanding untuk menentukan prioritas item public PriorityQueue(IComparer comparer) { this.data = new List(); @@ -484,10 +289,6 @@ namespace PathFinding this._count = 0; } - /// - /// Menambahkan item ke dalam antrian prioritas - /// - /// Item yang akan ditambahkan public void Enqueue(T item) { data.Add(item); @@ -497,10 +298,6 @@ namespace PathFinding _count = data.Count; } - /// - /// Mengambil dan menghapus item dengan prioritas tertinggi - /// - /// Item dengan prioritas tertinggi public T Dequeue() { if (data.Count == 0) @@ -524,11 +321,6 @@ namespace PathFinding return frontItem; } - /// - /// Menghapus item tertentu dari antrian - /// - /// Item yang akan dihapus - /// True jika berhasil dihapus, False jika tidak public bool Remove(T item) { if (!elementIndexMap.TryGetValue(item, out int index)) @@ -538,7 +330,6 @@ namespace PathFinding if (index == lastIndex) { - // Item yang dihapus adalah item terakhir data.RemoveAt(lastIndex); elementIndexMap.Remove(item); _count = data.Count; @@ -553,7 +344,6 @@ namespace PathFinding { elementIndexMap[data[index]] = index; - // Tentukan apakah perlu heapify up atau down int parentIndex = (index - 1) / 2; if (index > 0 && comparer.Compare(data[index], data[parentIndex]) < 0) HeapifyUp(index); @@ -565,21 +355,14 @@ namespace PathFinding return true; } - /// - /// Memperbarui prioritas item dalam antrian - /// - /// Item yang akan diperbarui - /// Nilai prioritas baru public void UpdatePriority(T item, float newPriority) { - // Fast check - jika item adalah yang terakhir dihapus, jangan lakukan apa-apa if (_lastDequeued != null && EqualityComparer.Default.Equals(item, _lastDequeued)) return; if (!elementIndexMap.TryGetValue(item, out int index)) return; - // Lakukan heapify - lebih efisien untuk A* pada grid kecil int parentIndex = (index - 1) / 2; if (index > 0 && comparer.Compare(data[index], data[parentIndex]) < 0) HeapifyUp(index); @@ -587,13 +370,8 @@ namespace PathFinding HeapifyDown(index); } - /// - /// Menjaga sifat heap dengan pergerakan ke atas - /// - /// Indeks node yang akan diperbaiki posisinya private void HeapifyUp(int index) { - // Iteratif lebih cepat daripada rekursif untuk grid kecil int parentIndex = (index - 1) / 2; while (index > 0 && comparer.Compare(data[index], data[parentIndex]) < 0) { @@ -603,10 +381,6 @@ namespace PathFinding } } - /// - /// Menjaga sifat heap dengan pergerakan ke bawah - /// - /// Indeks node yang akan diperbaiki posisinya private void HeapifyDown(int index) { int lastIndex = data.Count - 1; @@ -618,7 +392,6 @@ namespace PathFinding int rightChildIndex = leftChildIndex + 1; int smallestChildIndex = leftChildIndex; - // Fast check untuk rightChildIndex if (rightChildIndex <= lastIndex && comparer.Compare(data[rightChildIndex], data[leftChildIndex]) < 0) smallestChildIndex = rightChildIndex; @@ -629,11 +402,6 @@ namespace PathFinding } } - /// - /// Menukar posisi dua item dalam heap - /// - /// Indeks item pertama - /// Indeks item kedua private void Swap(int index1, int index2) { T tmp = data[index1]; @@ -643,15 +411,8 @@ namespace PathFinding elementIndexMap[data[index2]] = index2; } - /// - /// Jumlah item dalam antrian - /// public int Count => _count; - /// - /// Mendapatkan enumerator untuk data - /// - /// Enumerator untuk data public IEnumerator GetEnumerator() { return data.GetEnumerator(); @@ -667,58 +428,34 @@ namespace PathFinding /// Tipe data nilai yang disimpan dalam node public class DijkstraPathFinder : PathFinder { - /// - /// HashSet untuk pemeriksaan closed list dengan cepat (O(1) complexity) - /// private HashSet closedSet; - - /// - /// Dictionary untuk mengakses node di open list dengan cepat berdasarkan nilai mereka - /// private Dictionary openListMap; - // Flag untuk mengontrol level optimisasi berdasarkan ukuran grid private bool isGridLarge = false; private int estimatedNodesCount = 0; - /// - /// Constructor baru dengan estimasi jumlah node - /// public DijkstraPathFinder(int estimatedNodeCount = 0) { - // Estimasi ukuran grid untuk optimisasi memory this.estimatedNodesCount = estimatedNodeCount; - // Tentukan kapasitas awal berdasarkan ukuran grid int initialCapacity = estimatedNodesCount > 0 ? Math.Min(estimatedNodesCount / 4, 256) : 16; - // Grid dianggap besar jika memiliki > 2500 node (50x50) isGridLarge = estimatedNodesCount > 2500; - // Alokasi dengan kapasitas yang sesuai closedSet = new HashSet(initialCapacity); openListMap = new Dictionary(initialCapacity); } - /// - /// Implementasi spesifik algoritma Dijkstra untuk memproses node tetangga - /// - /// Node tetangga yang akan diproses protected override void AlgorithmSpecificImplementation(Node cell) { - // Pemeriksaan O(1) dengan HashSet - // Melakukan pemeriksaan apakah node sudah ada di closed list if (!closedSet.Contains(cell.Value)) { float G = CurrentNode.GCost + NodeTraversalCost( CurrentNode.Location.Value, cell.Value); - // Biaya heuristik untuk Dijkstra adalah 0 float H = 0.0f; - // Pemeriksaan O(1) dengan Dictionary - // Melakukan pemeriksaan apakah node sudah ada di open list if (!openListMap.TryGetValue(cell.Value, out PathFinderNode existingNode)) { PathFinderNode n = new PathFinderNode(cell, CurrentNode, G, H); @@ -739,10 +476,6 @@ namespace PathFinding } } - /// - /// Melakukan satu langkah pencarian jalur dengan algoritma Dijkstra - /// - /// Status pathfinder setelah langkah ini selesai public override PathFinderStatus Step() { if (CurrentNode == null) @@ -760,7 +493,6 @@ namespace PathFinding onChangeCurrentNode?.Invoke(CurrentNode); } - // Pindahkan pemeriksaan duplikasi ke sini if (!closedSet.Contains(CurrentNode.Location.Value)) { closedList.Add(CurrentNode); @@ -798,10 +530,6 @@ namespace PathFinding onRunning?.Invoke(); return Status; } - - /// - /// Reset state pathfinder - /// public override void Reset() { base.Reset(); @@ -819,50 +547,29 @@ namespace PathFinding /// Tipe data nilai yang disimpan dalam node public class AStarPathFinder : PathFinder { - /// - /// Open list diimplementasikan sebagai priority queue untuk efisiensi - /// private new PriorityQueue openList; - - /// - /// Peta untuk mengakses node dalam open list dengan cepat - /// private Dictionary openListMap; - - /// - /// HashSet untuk memeriksa closed list dengan cepat (O(1) complexity) - /// private HashSet closedSet; - // Flag untuk batch processing - hanya diaktifkan untuk grid besar private bool processingBatch = false; private List> neighborBatch; - // Flag untuk mengontrol level optimisasi private bool isGridLarge = false; private int estimatedNodesCount = 0; - /// - /// Constructor baru dengan estimasi jumlah node - /// public AStarPathFinder(int estimatedNodeCount = 0) { - // Estimasi ukuran grid untuk optimisasi memory this.estimatedNodesCount = estimatedNodeCount; - // Tentukan kapasitas awal berdasarkan ukuran grid int initialCapacity = estimatedNodesCount > 0 ? Math.Min(estimatedNodesCount / 4, 256) : 16; - // Grid dianggap besar jika memiliki > 2500 node (50x50) isGridLarge = estimatedNodesCount > 2500; - // Alokasi dengan kapasitas yang sesuai openList = new PriorityQueue(new FCostComparer()); openListMap = new Dictionary(initialCapacity); closedSet = new HashSet(initialCapacity); - // Alokasi neighborBatch hanya jika diperlukan if (isGridLarge) { neighborBatch = new List>(8); @@ -873,57 +580,39 @@ namespace PathFinding } } - /// - /// Implementasi spesifik algoritma A* untuk memproses node tetangga - /// - /// Node tetangga yang akan diproses protected override void AlgorithmSpecificImplementation(Node cell) { - // Fast-reject: node sudah di closed list if (closedSet.Contains(cell.Value)) return; - // Hitung biaya G yang sebenarnya (jarak dari start) float G = CurrentNode.GCost + NodeTraversalCost(CurrentNode.Location.Value, cell.Value); - // Periksa apakah node sudah ada di open list PathFinderNode existingNode = null; bool nodeExists = openListMap.TryGetValue(cell.Value, out existingNode); if (!nodeExists) { - // Hitung heuristik dengan normal float H = HeuristicCost(cell.Value, Goal.Value); - // Buat node baru dan tambahkan ke open list PathFinderNode n = new PathFinderNode(cell, CurrentNode, G, H); openList.Enqueue(n); openListMap[cell.Value] = n; - // Callback hanya jika tidak dalam batch processing if (!processingBatch || !isGridLarge) onAddToOpenList?.Invoke(n); } else if (G < existingNode.GCost) { - // Path lebih baik ditemukan, update nilai G existingNode.Parent = CurrentNode; existingNode.SetGCost(G); - // Jika kita menggunakan G sebagai tie-breaker, maka perlu update prioritas - // karena G yang lebih rendah sekarang bisa mempengaruhi urutan priority queue openList.UpdatePriority(existingNode, existingNode.HCost); - // Callback untuk UI if ((!processingBatch || !isGridLarge) && onAddToOpenList != null) onAddToOpenList.Invoke(existingNode); } } - /// - /// Melakukan satu langkah pencarian jalur dengan algoritma A* - /// - /// Status pathfinder setelah langkah ini selesai public override PathFinderStatus Step() { if (CurrentNode == null) @@ -949,39 +638,32 @@ namespace PathFinding } closedList.Add(CurrentNode); - closedSet.Add(CurrentNode.Location.Value); // Tambahkan ke hashset untuk pemeriksaan O(1) + closedSet.Add(CurrentNode.Location.Value); onAddToClosedList?.Invoke(CurrentNode); - // Ambil semua tetangga List> neighbors; - // Untuk grid besar, gunakan batch processing if (isGridLarge) { neighborBatch.Clear(); neighborBatch.AddRange(CurrentNode.Location.GetNeighbours()); neighbors = neighborBatch; - // Gunakan batch hanya jika cukup banyak tetangga dan grid besar processingBatch = neighbors.Count > 5; } else { - // Untuk grid kecil, tidak perlu batch processing neighbors = CurrentNode.Location.GetNeighbours(); processingBatch = false; } - // Proses semua tetangga foreach (Node cell in neighbors) { AlgorithmSpecificImplementation(cell); } - // Jika dalam batch mode, invoke callback sekali untuk semua tetangga if (processingBatch && onAddToOpenList != null && isGridLarge) { - // Notify UI untuk refresh (optional) onAddToOpenList.Invoke(CurrentNode); processingBatch = false; } @@ -1002,9 +684,6 @@ namespace PathFinding return Status; } - /// - /// Reset state pathfinder - /// public override void Reset() { base.Reset(); @@ -1015,18 +694,8 @@ namespace PathFinding processingBatch = false; } - /// - /// Pembanding F-Cost untuk priority queue dalam algoritma A* - /// private class FCostComparer : IComparer { - /// - /// Membandingkan dua node berdasarkan FCost mereka - /// Jika FCost sama, gunakan HCost sebagai tie-breaker - /// - /// Node pertama - /// Node kedua - /// Hasil perbandingan public int Compare(PathFinderNode x, PathFinderNode y) { int result = x.FCost.CompareTo(y.FCost); @@ -1048,50 +717,28 @@ namespace PathFinding /// Tipe data nilai yang disimpan dalam node public class GreedyPathFinder : PathFinder { - /// - /// Open list diimplementasikan sebagai priority queue untuk performa yang lebih baik - /// private new PriorityQueue openList = new PriorityQueue(new HeuristicComparer()); - - /// - /// Dictionary untuk akses node open list yang cepat - /// private Dictionary openSet = new Dictionary(256); - - /// - /// HashSet untuk pemeriksaan closed list O(1) - /// private HashSet closedSet = new HashSet(256); - // Flag untuk batch processing private bool processingBatch = false; private List> neighborBatch = new List>(4); - /// - /// Implementasi spesifik algoritma Greedy untuk memproses node tetangga - /// - /// Node tetangga yang akan diproses protected override void AlgorithmSpecificImplementation(Node cell) { - // Fast-reject: node sudah diproses if (closedSet.Contains(cell.Value)) return; - // Hitung biaya G yang sebenarnya (jarak dari start) float G = CurrentNode.GCost + NodeTraversalCost(CurrentNode.Location.Value, cell.Value); float H; - // Periksa apakah node sudah ada di open list PathFinderNode existingNode = null; bool nodeExists = openSet.TryGetValue(cell.Value, out existingNode); if (!nodeExists) { - // Hitung heuristik dengan normal - if (EqualityComparer.Default.Equals(cell.Value, Goal.Value)) { - // Langsung ke tujuan - prioritaskan dengan H = 0 H = 0; } else @@ -1099,38 +746,22 @@ namespace PathFinding H = HeuristicCost(cell.Value, Goal.Value); } - // Buat node dan tambahkan ke open list - gunakan G yang benar PathFinderNode n = new PathFinderNode(cell, CurrentNode, G, H); openList.Enqueue(n); onAddToOpenList?.Invoke(n); openSet[cell.Value] = n; - //if (!processingBatch && onAddToOpenList != null) - // onAddToOpenList.Invoke(n); } else if (G < existingNode.GCost) { - // Path lebih baik ditemukan, update nilai G - // Meskipun Greedy hanya menggunakan H untuk prioritas, - // G cost yang benar tetap penting untuk rekonstruksi jalur existingNode.Parent = CurrentNode; existingNode.SetGCost(G); - // Jika kita menggunakan G sebagai tie-breaker, maka perlu update prioritas - // karena G yang lebih rendah sekarang bisa mempengaruhi urutan priority queue openList.UpdatePriority(existingNode, existingNode.HCost); onAddToOpenList.Invoke(existingNode); - - // Callback untuk UI - //if (!processingBatch && onAddToOpenList != null) - } } - /// - /// Melakukan satu langkah pencarian jalur dengan algoritma Greedy - /// - /// Status pathfinder setelah langkah ini selesai public override PathFinderStatus Step() { if (CurrentNode == null) @@ -1159,22 +790,16 @@ namespace PathFinding return Status; } - // Dapatkan semua tetangga sekaligus untuk mengurangi panggilan fungsi neighborBatch.Clear(); neighborBatch.AddRange(CurrentNode.Location.GetNeighbours()); - // Proses tetangga dalam batch untuk mengurangi callback overhead - // processingBatch = neighborBatch.Count > 5; // Gunakan batch hanya jika cukup banyak tetangga - foreach (Node cell in neighborBatch) { AlgorithmSpecificImplementation(cell); } - // Jika dalam batch mode, invoke callback sekali untuk semua tetangga if (processingBatch && onAddToOpenList != null) { - // Notify UI untuk refresh (optional) onAddToOpenList.Invoke(CurrentNode); processingBatch = false; } @@ -1195,9 +820,6 @@ namespace PathFinding return Status; } - /// - /// Reset state pathfinder - /// public override void Reset() { base.Reset(); @@ -1207,28 +829,13 @@ namespace PathFinding processingBatch = false; } - /// - /// Pembanding Heuristik untuk priority queue dalam algoritma Greedy - /// private class HeuristicComparer : IComparer { - /// - /// Membandingkan dua node berdasarkan nilai heuristik mereka. - /// PENTING: Ini adalah kunci perbedaan dari algoritma Greedy - prioritas - /// hanya didasarkan pada nilai H cost (jarak ke tujuan), tidak seperti - /// A* yang menggunakan FCost (G+H). G cost tetap dilacak untuk - /// rekonstruksi jalur yang benar, tetapi tidak memengaruhi prioritas. - /// - /// Node pertama - /// Node kedua - /// Hasil perbandingan public int Compare(PathFinderNode x, PathFinderNode y) { int result = x.HCost.CompareTo(y.HCost); if (result == 0) { - // Jika H cost sama, gunakan G cost sebagai tie-breaker - // Pilih jalur dengan G cost lebih rendah (lebih dekat dari start) result = x.GCost.CompareTo(y.GCost); } return result; @@ -1245,51 +852,29 @@ namespace PathFinding /// Tipe data nilai yang disimpan dalam node public class BacktrackingPathFinder : PathFinder { - /// - /// Stack untuk menyimpan node yang akan dikunjungi (open list) - /// private Stack openStack = new Stack(); - - /// - /// HashSet untuk pemeriksaan cepat apakah node tertentu sudah dikunjungi - /// private HashSet closedSet = new HashSet(); - - /// - /// HashSet untuk pemeriksaan cepat apakah node tertentu sudah ada di open list - /// private HashSet openSet = new HashSet(); - /// - /// Implementasi spesifik algoritma Backtracking untuk memproses node tetangga - /// - /// Node tetangga yang akan diproses protected override void AlgorithmSpecificImplementation(Node cell) { if (!closedSet.Contains(cell.Value) && !openSet.Contains(cell.Value)) { - // Backtracking tidak memperhitungkan cost, tetapi tetap menghitungnya untuk visualisasi float G = CurrentNode.GCost + NodeTraversalCost(CurrentNode.Location.Value, cell.Value); float H = 0.0f; PathFinderNode n = new PathFinderNode(cell, CurrentNode, G, H); - openList.Add(n); // Tambahkan ke list untuk tujuan kompatibilitas - openStack.Push(n); // Tambahkan ke stack untuk DFS + openList.Add(n); + openStack.Push(n); openSet.Add(cell.Value); onAddToOpenList?.Invoke(n); } } - /// - /// Melakukan satu langkah pencarian jalur dengan algoritma Backtracking - /// - /// Status pathfinder setelah langkah ini selesai public override PathFinderStatus Step() { - // Jika currentNode belum diinisialisasi if (CurrentNode == null) { - // Jika tidak ada node dalam open stack, berarti pencarian gagal if (openStack.Count == 0) { Status = PathFinderStatus.FAILURE; @@ -1297,19 +882,16 @@ namespace PathFinding return Status; } - // Ambil node berikutnya dari stack (LIFO - kedalaman pertama) CurrentNode = openStack.Pop(); - openList.Remove(CurrentNode); // Hapus dari list untuk kompatibilitas + openList.Remove(CurrentNode); openSet.Remove(CurrentNode.Location.Value); onChangeCurrentNode?.Invoke(CurrentNode); } - // Tambahkan node saat ini ke closed list closedList.Add(CurrentNode); closedSet.Add(CurrentNode.Location.Value); onAddToClosedList?.Invoke(CurrentNode); - // Cek apakah kita telah mencapai tujuan if (EqualityComparer.Default.Equals(CurrentNode.Location.Value, Goal.Value)) { Status = PathFinderStatus.SUCCESS; @@ -1318,14 +900,12 @@ namespace PathFinding return Status; } - // Proses semua tetangga dari node saat ini List> neighbours = CurrentNode.Location.GetNeighbours(); foreach (Node cell in neighbours) { AlgorithmSpecificImplementation(cell); } - // Jika tidak ada lagi node yang dapat dikunjungi, berarti pencarian gagal if (openStack.Count == 0) { Status = PathFinderStatus.FAILURE; @@ -1333,9 +913,8 @@ namespace PathFinding return Status; } - // Ambil node berikutnya dari stack CurrentNode = openStack.Pop(); - openList.Remove(CurrentNode); // Hapus dari list untuk kompatibilitas + openList.Remove(CurrentNode); openSet.Remove(CurrentNode.Location.Value); onChangeCurrentNode?.Invoke(CurrentNode); @@ -1344,9 +923,6 @@ namespace PathFinding return Status; } - /// - /// Reset state pathfinder - /// public override void Reset() { base.Reset(); @@ -1366,52 +942,30 @@ namespace PathFinding /// Tipe data nilai yang disimpan dalam node public class BFSPathFinder : PathFinder { - /// - /// Queue sebagai open list untuk BFS - /// private Queue openQueue = new Queue(); - - /// - /// HashSet untuk pemeriksaan nilai node dalam closed list (O(1) complexity) - /// private HashSet closedSet = new HashSet(); - - /// - /// HashSet untuk pemeriksaan nilai node dalam open list (O(1) complexity) - /// private HashSet openSet = new HashSet(); - /// - /// Implementasi spesifik algoritma BFS untuk memproses node tetangga - /// - /// Node tetangga yang akan diproses protected override void AlgorithmSpecificImplementation(Node cell) { if (!closedSet.Contains(cell.Value) && !openSet.Contains(cell.Value)) { - // BFS tidak memperhitungkan cost, tapi kita tetap menghitungnya untuk visualisasi float G = CurrentNode.GCost + NodeTraversalCost( CurrentNode.Location.Value, cell.Value); float H = 0.0f; PathFinderNode n = new PathFinderNode(cell, CurrentNode, G, H); - openList.Add(n); // Tambahkan ke list untuk tujuan kompatibilitas - openQueue.Enqueue(n); // Tambahkan ke queue untuk BFS + openList.Add(n); + openQueue.Enqueue(n); openSet.Add(cell.Value); onAddToOpenList?.Invoke(n); } } - /// - /// Melakukan satu langkah pencarian jalur dengan algoritma BFS - /// - /// Status pathfinder setelah langkah ini selesai public override PathFinderStatus Step() { - // Jika currentNode belum diinisialisasi if (CurrentNode == null) { - // Jika tidak ada node dalam open queue, berarti pencarian gagal if (openQueue.Count == 0) { Status = PathFinderStatus.FAILURE; @@ -1419,19 +973,16 @@ namespace PathFinding return Status; } - // Ambil node berikutnya dari queue (FIFO) CurrentNode = openQueue.Dequeue(); - openList.Remove(CurrentNode); // Hapus dari list untuk kompatibilitas + openList.Remove(CurrentNode); openSet.Remove(CurrentNode.Location.Value); onChangeCurrentNode?.Invoke(CurrentNode); } - // Tambahkan node saat ini ke closed list closedList.Add(CurrentNode); closedSet.Add(CurrentNode.Location.Value); onAddToClosedList?.Invoke(CurrentNode); - // Cek apakah kita telah mencapai tujuan if (EqualityComparer.Default.Equals(CurrentNode.Location.Value, Goal.Value)) { Status = PathFinderStatus.SUCCESS; @@ -1440,14 +991,12 @@ namespace PathFinding return Status; } - // Proses semua tetangga dari node saat ini List> neighbours = CurrentNode.Location.GetNeighbours(); foreach (Node cell in neighbours) { AlgorithmSpecificImplementation(cell); } - // Jika tidak ada lagi node yang dapat dikunjungi, berarti pencarian gagal if (openQueue.Count == 0) { Status = PathFinderStatus.FAILURE; @@ -1455,9 +1004,8 @@ namespace PathFinding return Status; } - // Ambil node berikutnya dari queue CurrentNode = openQueue.Dequeue(); - openList.Remove(CurrentNode); // Hapus dari list untuk kompatibilitas + openList.Remove(CurrentNode); openSet.Remove(CurrentNode.Location.Value); onChangeCurrentNode?.Invoke(CurrentNode); @@ -1466,9 +1014,6 @@ namespace PathFinding return Status; } - /// - /// Reset state pathfinder - /// public override void Reset() { base.Reset(); diff --git a/Assets/Scripts/PathfindingTestAnalyzer.cs b/Assets/Scripts/PathfindingTestAnalyzer.cs deleted file mode 100644 index b19a3fa..0000000 --- a/Assets/Scripts/PathfindingTestAnalyzer.cs +++ /dev/null @@ -1,419 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using UnityEngine; -using UnityEngine.UI; -using TMPro; - -/// -/// Utility class to analyze pathfinding test results and display statistics -/// -public class PathfindingTestAnalyzer : MonoBehaviour -{ - [Header("UI References")] - public TMP_Text resultsText; - public TMP_Dropdown algorithmFilterDropdown; - public TMP_Dropdown statisticTypeDropdown; - public RectTransform graphContainer; - public GameObject barPrefab; - - [Header("File Settings")] - public string resultsFileName = "pathfinding_tests.csv"; - - // Data structures - private List allResults = new List(); - private Dictionary algorithmColors = new Dictionary() - { - { "ASTAR", Color.blue }, - { "DIJKSTRA", Color.green }, - { "GREEDY", Color.red }, - { "BACKTRACKING", Color.yellow }, - { "BFS", Color.magenta } - }; - - // Struct to hold a single test result - private struct TestResult - { - public string algorithm; - public int gridSizeX; - public int gridSizeY; - public float density; - public bool diagonalMovement; - public float timeTaken; - public int pathLength; - public int nodesExplored; - public long memoryUsed; - public bool pathFound; - public int testIndex; - } - - void Start() - { - SetupDropdowns(); - LoadResults(); - - // Set default visualization - if (allResults.Count > 0) - { - GenerateStatistics(); - } - } - - private void SetupDropdowns() - { - // Set up algorithm filter dropdown - algorithmFilterDropdown.ClearOptions(); - algorithmFilterDropdown.AddOptions(new List { - "All Algorithms", - "A*", - "Dijkstra", - "Greedy BFS", - "Backtracking", - "BFS" - }); - algorithmFilterDropdown.onValueChanged.AddListener(OnFilterChanged); - - // Set up statistic type dropdown - statisticTypeDropdown.ClearOptions(); - statisticTypeDropdown.AddOptions(new List { - "Execution Time", - "Path Length", - "Nodes Explored", - "Memory Used", - "Success Rate" - }); - statisticTypeDropdown.onValueChanged.AddListener(OnFilterChanged); - } - - private void LoadResults() - { - string directory = Path.Combine(Application.persistentDataPath, "TestResults"); - string filePath = Path.Combine(directory, resultsFileName); - - if (!File.Exists(filePath)) - { - Debug.LogError($"Results file not found: {filePath}"); - if (resultsText != null) - { - resultsText.text = "No test results found. Run tests first."; - } - return; - } - - // Read all lines and skip header - string[] lines = File.ReadAllLines(filePath); - if (lines.Length <= 1) - { - Debug.LogWarning("Results file is empty or only contains a header"); - return; - } - - // Clear previous results - allResults.Clear(); - - // Skip header row and parse data rows - for (int i = 1; i < lines.Length; i++) - { - string line = lines[i]; - if (string.IsNullOrWhiteSpace(line)) - continue; - - string[] values = line.Split(','); - if (values.Length < 10) - { - Debug.LogWarning($"Invalid data in line {i}: {line}"); - continue; - } - - TestResult result = new TestResult - { - algorithm = values[0], - gridSizeX = int.Parse(values[1]), - gridSizeY = int.Parse(values[2]), - density = float.Parse(values[3]), - diagonalMovement = bool.Parse(values[4]), - timeTaken = float.Parse(values[5]), - pathLength = int.Parse(values[6]), - nodesExplored = int.Parse(values[7]), - memoryUsed = long.Parse(values[8]), - pathFound = bool.Parse(values[9]), - testIndex = int.Parse(values[10]) - }; - - allResults.Add(result); - } - - Debug.Log($"Loaded {allResults.Count} test results"); - } - - public void OnFilterChanged(int value) - { - GenerateStatistics(); - } - - private void GenerateStatistics() - { - if (allResults.Count == 0) - return; - - // Clear previous graph - foreach (Transform child in graphContainer) - { - Destroy(child.gameObject); - } - - // Get selected algorithm filter - string algorithmFilter = "All"; - switch (algorithmFilterDropdown.value) - { - case 0: algorithmFilter = "All"; break; - case 1: algorithmFilter = "ASTAR"; break; - case 2: algorithmFilter = "DIJKSTRA"; break; - case 3: algorithmFilter = "GREEDY"; break; - case 4: algorithmFilter = "BACKTRACKING"; break; - case 5: algorithmFilter = "BFS"; break; - } - - // Filter results by algorithm if not "All" - List filteredResults = allResults; - if (algorithmFilter != "All") - { - filteredResults = allResults.Where(r => r.algorithm == algorithmFilter).ToList(); - } - - if (filteredResults.Count == 0) - { - resultsText.text = "No data for selected filters."; - return; - } - - // Generate statistics based on selected metric - switch (statisticTypeDropdown.value) - { - case 0: // Execution Time - GenerateTimeStatistics(filteredResults); - break; - case 1: // Path Length - GeneratePathLengthStatistics(filteredResults); - break; - case 2: // Nodes Explored - GenerateNodesExploredStatistics(filteredResults); - break; - case 3: // Memory Used - GenerateMemoryStatistics(filteredResults); - break; - case 4: // Success Rate - GenerateSuccessRateStatistics(filteredResults); - break; - } - } - - private void GenerateTimeStatistics(List results) - { - var averageTimeByAlgorithm = results - .GroupBy(r => r.algorithm) - .Select(g => new { - Algorithm = g.Key, - AverageTime = g.Average(r => r.timeTaken) - }) - .OrderByDescending(x => x.AverageTime) - .ToList(); - - // Generate graph bars - CreateBarsForData(averageTimeByAlgorithm.Select(x => x.Algorithm).ToList(), - averageTimeByAlgorithm.Select(x => (float)x.AverageTime).ToList(), - "ms"); - - // Generate text summary - resultsText.text = "Average Execution Time by Algorithm:\n\n"; - foreach (var stat in averageTimeByAlgorithm) - { - resultsText.text += $"{GetAlgorithmName(stat.Algorithm)}: {stat.AverageTime:F2} ms\n"; - } - } - - private void GeneratePathLengthStatistics(List results) - { - var averagePathByAlgorithm = results - .Where(r => r.pathFound) // Only include successful paths - .GroupBy(r => r.algorithm) - .Select(g => new { - Algorithm = g.Key, - AveragePath = g.Average(r => r.pathLength) - }) - .OrderByDescending(x => x.AveragePath) - .ToList(); - - // Generate graph bars - CreateBarsForData(averagePathByAlgorithm.Select(x => x.Algorithm).ToList(), - averagePathByAlgorithm.Select(x => (float)x.AveragePath).ToList(), - "nodes"); - - // Generate text summary - resultsText.text = "Average Path Length by Algorithm:\n\n"; - foreach (var stat in averagePathByAlgorithm) - { - resultsText.text += $"{GetAlgorithmName(stat.Algorithm)}: {stat.AveragePath:F2} nodes\n"; - } - } - - private void GenerateNodesExploredStatistics(List results) - { - var averageNodesExploredByAlgorithm = results - .GroupBy(r => r.algorithm) - .Select(g => new { - Algorithm = g.Key, - AverageNodes = g.Average(r => r.nodesExplored) - }) - .OrderByDescending(x => x.AverageNodes) - .ToList(); - - // Generate graph bars - CreateBarsForData(averageNodesExploredByAlgorithm.Select(x => x.Algorithm).ToList(), - averageNodesExploredByAlgorithm.Select(x => (float)x.AverageNodes).ToList(), - "nodes"); - - // Generate text summary - resultsText.text = "Average Nodes Explored by Algorithm:\n\n"; - foreach (var stat in averageNodesExploredByAlgorithm) - { - resultsText.text += $"{GetAlgorithmName(stat.Algorithm)}: {stat.AverageNodes:F0} nodes\n"; - } - } - - private void GenerateMemoryStatistics(List results) - { - var averageMemoryByAlgorithm = results - .GroupBy(r => r.algorithm) - .Select(g => new { - Algorithm = g.Key, - AverageMemory = g.Average(r => r.memoryUsed) / 1024.0f // Convert to KB - }) - .OrderByDescending(x => x.AverageMemory) - .ToList(); - - // Generate graph bars - CreateBarsForData(averageMemoryByAlgorithm.Select(x => x.Algorithm).ToList(), - averageMemoryByAlgorithm.Select(x => (float)x.AverageMemory).ToList(), - "KB"); - - // Generate text summary - resultsText.text = "Average Memory Usage by Algorithm:\n\n"; - foreach (var stat in averageMemoryByAlgorithm) - { - resultsText.text += $"{GetAlgorithmName(stat.Algorithm)}: {stat.AverageMemory:F2} KB\n"; - } - } - - private void GenerateSuccessRateStatistics(List results) - { - var successRateByAlgorithm = results - .GroupBy(r => r.algorithm) - .Select(g => new { - Algorithm = g.Key, - SuccessRate = g.Count(r => r.pathFound) * 100.0f / g.Count() - }) - .OrderByDescending(x => x.SuccessRate) - .ToList(); - - // Generate graph bars - CreateBarsForData(successRateByAlgorithm.Select(x => x.Algorithm).ToList(), - successRateByAlgorithm.Select(x => (float)x.SuccessRate).ToList(), - "%"); - - // Generate text summary - resultsText.text = "Success Rate by Algorithm:\n\n"; - foreach (var stat in successRateByAlgorithm) - { - resultsText.text += $"{GetAlgorithmName(stat.Algorithm)}: {stat.SuccessRate:F1}%\n"; - } - } - - private void CreateBarsForData(List labels, List values, string unit) - { - if (barPrefab == null || graphContainer == null || labels.Count == 0) - return; - - float graphWidth = graphContainer.rect.width; - float graphHeight = graphContainer.rect.height; - float maxValue = values.Max(); - float barWidth = graphWidth / (labels.Count + 1); - - for (int i = 0; i < labels.Count; i++) - { - // Create bar - GameObject barObj = Instantiate(barPrefab, graphContainer); - RectTransform barRect = barObj.GetComponent(); - Image barImage = barObj.GetComponent(); - - // Calculate height based on value - float normalizedValue = values[i] / maxValue; - float barHeight = graphHeight * normalizedValue * 0.8f; // 80% of graph height max - - // Position and size bar - barRect.anchoredPosition = new Vector2((i + 0.5f) * barWidth, barHeight / 2); - barRect.sizeDelta = new Vector2(barWidth * 0.8f, barHeight); - - // Set bar color - string algorithm = labels[i]; - if (algorithmColors.ContainsKey(algorithm)) - barImage.color = algorithmColors[algorithm]; - - // Add label - GameObject labelObj = new GameObject($"Label_{labels[i]}"); - labelObj.transform.SetParent(barObj.transform); - - TMP_Text labelText = labelObj.AddComponent(); - labelText.text = $"{values[i]:F1}{unit}"; - labelText.fontSize = 12; - labelText.alignment = TextAlignmentOptions.Center; - labelText.color = Color.black; - - RectTransform labelRect = labelObj.GetComponent(); - labelRect.anchoredPosition = new Vector2(0, barHeight + 10); - labelRect.sizeDelta = new Vector2(barWidth, 20); - - // Add algorithm name label at bottom - GameObject nameObj = new GameObject($"Name_{labels[i]}"); - nameObj.transform.SetParent(barObj.transform); - - TMP_Text nameText = nameObj.AddComponent(); - nameText.text = GetAlgorithmName(labels[i]); - nameText.fontSize = 10; - nameText.alignment = TextAlignmentOptions.Center; - nameText.color = Color.black; - - RectTransform nameRect = nameObj.GetComponent(); - nameRect.anchoredPosition = new Vector2(0, -10); - nameRect.sizeDelta = new Vector2(barWidth, 20); - } - } - - private string GetAlgorithmName(string algorithm) - { - switch (algorithm) - { - case "ASTAR": return "A*"; - case "DIJKSTRA": return "Dijkstra"; - case "GREEDY": return "Greedy"; - case "BACKTRACKING": return "Backtracking"; - case "BFS": return "BFS"; - default: return algorithm; - } - } - - // Utility function to format memory size - 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]}"; - } -} \ No newline at end of file diff --git a/Assets/Scripts/PathfindingTestAnalyzer.cs.meta b/Assets/Scripts/PathfindingTestAnalyzer.cs.meta deleted file mode 100644 index 1709582..0000000 --- a/Assets/Scripts/PathfindingTestAnalyzer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9fee3f5c29ad21b428f68d801f2377f1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/PathfindingTester.cs b/Assets/Scripts/PathfindingTester.cs deleted file mode 100644 index 328c110..0000000 --- a/Assets/Scripts/PathfindingTester.cs +++ /dev/null @@ -1,510 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Text; -using UnityEngine; -using UnityEngine.UI; -using TMPro; - -public class PathfindingTester : MonoBehaviour -{ - [Header("References")] - public GridMap gridMap; - public NPC npc; - public Button startTestButton; - public TMP_Text statusText; - public Slider progressBar; - - [Header("Test Configuration")] - [Tooltip("Number of times to run each test combination")] - public int testsPerCombination = 3; - - [Tooltip("Delay between tests in seconds")] - public float delayBetweenTests = 0.5f; - - [Tooltip("Whether to save results to a CSV file")] - public bool saveResultsToFile = true; - - [Tooltip("File name for test results (CSV)")] - public string resultsFileName = "pathfinding_tests.csv"; - - // Test matrix parameters - private NPC.PathFinderType[] algorithmsToTest = new NPC.PathFinderType[] { - NPC.PathFinderType.ASTAR, - NPC.PathFinderType.DIJKSTRA, - NPC.PathFinderType.GREEDY, - NPC.PathFinderType.BACKTRACKING, - NPC.PathFinderType.BFS - }; - - private Vector2Int[] gridSizesToTest = new Vector2Int[] { - new Vector2Int(20, 20), - new Vector2Int(35, 35), - new Vector2Int(50, 50), - new Vector2Int(40, 25) // Another non-square grid - }; - - private float[] mazeDensitiesToTest = new float[] { - 0f, // Empty (no walls) - 10f, // Very low - 30f, // Medium - 50f, // High - 100f // Fully blocked (all walls) - }; - - private bool[] diagonalMovementOptions = new bool[] { - true, - false - }; - - // Test state tracking - private bool isTestingRunning = false; - private int totalTests; - private int completedTests; - private List testResults = new List(); - - // Current test parameters - private NPC.PathFinderType currentTestAlgorithm; - private Vector2Int currentTestGridSize; - private float currentTestDensity; - private bool currentTestDiagonal; - private int currentTestIndex; - - // Structure to store test results - private struct PathfindingTestResult - { - public NPC.PathFinderType algorithm; - public Vector2Int gridSize; - public float mazeDensity; - public bool diagonalMovement; - public float timeTaken; - public int pathLength; - public int nodesExplored; - public long memoryUsed; - public bool pathFound; - public int testIndex; - } - - private void Start() - { - // Subscribe to NPC's pathfinding completion event - npc.OnPathfindingComplete += OnPathfindingComplete; - - // Set up the button listener - startTestButton.onClick.AddListener(StartTesting); - - // Initial status message - UpdateStatus("Ready to start testing. Click the Start Tests button."); - } - - private void OnDestroy() - { - // Unsubscribe from events - if (npc != null) - { - npc.OnPathfindingComplete -= OnPathfindingComplete; - } - } - - private void UpdateStatus(string message) - { - if (statusText != null) - { - statusText.text = message; - } - Debug.Log(message); - - // Update progress bar if available - if (progressBar != null && totalTests > 0) - { - progressBar.value = (float)completedTests / totalTests; - } - } - - public void StartTesting() - { - if (isTestingRunning) - { - Debug.LogWarning("Tests are already running."); - return; - } - - // Clear previous results - testResults.Clear(); - - // Calculate total number of tests - totalTests = algorithmsToTest.Length * - gridSizesToTest.Length * - mazeDensitiesToTest.Length * - diagonalMovementOptions.Length * - testsPerCombination; - - completedTests = 0; - isTestingRunning = true; - - UpdateStatus($"Starting {totalTests} tests..."); - - // Disable button during testing - startTestButton.interactable = false; - - // Start the test coroutine - StartCoroutine(RunTestMatrix()); - } - - private IEnumerator RunTestMatrix() - { - // Iterate through all test combinations - foreach (var algorithm in algorithmsToTest) - { - foreach (var gridSize in gridSizesToTest) - { - foreach (var density in mazeDensitiesToTest) - { - foreach (var useDiagonals in diagonalMovementOptions) - { - for (int testIndex = 0; testIndex < testsPerCombination; testIndex++) - { - yield return StartCoroutine(RunSingleTest(algorithm, gridSize, density, useDiagonals, testIndex)); - - // Wait between tests - yield return new WaitForSeconds(delayBetweenTests); - } - } - } - } - } - - // All tests completed - isTestingRunning = false; - startTestButton.interactable = true; - - // Save results if enabled - if (saveResultsToFile) - { - SaveResultsToCSV(); - } - - UpdateStatus($"Testing complete! {completedTests} tests run. Results saved to {resultsFileName}"); - } - - private IEnumerator RunSingleTest(NPC.PathFinderType algorithm, Vector2Int gridSize, float density, bool useDiagonals, int testIndex) - { - // Update status with current test info - UpdateStatus($"Test {completedTests+1}/{totalTests}: {algorithm} - Grid: {gridSize.x}x{gridSize.y} - Density: {density}% - Diagonals: {useDiagonals}"); - - // Configure the test environment - yield return StartCoroutine(SetupTestEnvironment(algorithm, gridSize, density, useDiagonals)); - - // Generate start and destination points - GridNode startNode = FindValidStartNode(); - GridNode destNode = FindValidDestinationNode(startNode); - - if (startNode == null || destNode == null) - { - Debug.LogWarning($"Failed to find valid start/destination nodes for test - density: {density}%"); - - // Record the test as "impossible" with a failed path - PathfindingTestResult result = new PathfindingTestResult - { - algorithm = algorithm, - gridSize = gridSize, - mazeDensity = density, - diagonalMovement = useDiagonals, - timeTaken = 0f, - pathLength = 0, - nodesExplored = 0, - memoryUsed = 0, - pathFound = false, - testIndex = testIndex - }; - - testResults.Add(result); - completedTests++; - yield break; - } - - // Position NPC at start node - npc.SetStartNode(startNode); - - // Position destination - gridMap.SetDestination(destNode.Value.x, destNode.Value.y); - - // Wait a frame to ensure everything is set up - yield return null; - - // Store the current test parameters - currentTestAlgorithm = algorithm; - currentTestGridSize = gridSize; - currentTestDensity = density; - currentTestDiagonal = useDiagonals; - currentTestIndex = testIndex; - - // Flag to track if callback was triggered - bool callbackTriggered = false; - - // Setup a temporary callback listener to detect if the event fires - System.Action tempCallback = (metrics) => { callbackTriggered = true; }; - npc.OnPathfindingComplete += tempCallback; - - // Run pathfinding in silent mode - now the event will still fire with our NPC fix - npc.MoveTo(destNode, true); - - // Wait for pathfinding to complete - float timeout = 10.0f; - float elapsed = 0f; - - while (npc.pathFinder != null && npc.pathFinder.Status == PathFinding.PathFinderStatus.RUNNING && elapsed < timeout) - { - yield return null; - elapsed += Time.deltaTime; - } - - // Wait a bit more to ensure completion - yield return new WaitForSeconds(0.1f); - - // Remove the temporary callback - npc.OnPathfindingComplete -= tempCallback; - - // If callback wasn't triggered but pathfinding is complete, manually record the result - if (!callbackTriggered && npc.pathFinder != null && npc.pathFinder.Status != PathFinding.PathFinderStatus.RUNNING) - { - Debug.LogWarning($"Callback wasn't triggered for test {completedTests+1}. Recording results manually."); - - // Create a metrics object with available data - PathfindingMetrics metrics = new PathfindingMetrics - { - timeTaken = elapsed * 1000f, // convert to ms - pathLength = CalculatePathLength(npc.pathFinder), - nodesExplored = npc.pathFinder.ClosedListCount, - memoryUsed = npc.LastMeasuredMemoryUsage // Get the last memory measurement from NPC - }; - - // Create a test result entry directly - PathfindingTestResult result = new PathfindingTestResult - { - algorithm = currentTestAlgorithm, - gridSize = currentTestGridSize, - mazeDensity = currentTestDensity, - diagonalMovement = currentTestDiagonal, - timeTaken = metrics.timeTaken, - pathLength = metrics.pathLength, - nodesExplored = metrics.nodesExplored, - memoryUsed = metrics.memoryUsed, - pathFound = npc.pathFinder.Status == PathFinding.PathFinderStatus.SUCCESS, - testIndex = currentTestIndex - }; - - // Add to results directly - testResults.Add(result); - - // Log result - Debug.Log($"Test {completedTests} (manual): {GetAlgorithmName(result.algorithm)} - {result.timeTaken:F2}ms - Path: {result.pathLength} - Nodes: {result.nodesExplored}"); - } - - // Increment test counter - completedTests++; - } - - private int CalculatePathLength(PathFinding.PathFinder pathFinder) - { - if (pathFinder == null || pathFinder.CurrentNode == null) - return 0; - - int length = 0; - var node = pathFinder.CurrentNode; - while (node != null) - { - length++; - node = node.Parent; - } - return length; - } - - private IEnumerator SetupTestEnvironment(NPC.PathFinderType algorithm, Vector2Int gridSize, float density, bool useDiagonals) - { - Debug.Log($"Setting up test environment: Algorithm={algorithm}, Grid={gridSize.x}x{gridSize.y}, Density={density}, Diagonals={useDiagonals}"); - - // Resize grid - gridMap.ResizeGrid(gridSize.x, gridSize.y); - yield return null; - - // Set algorithm - npc.ChangeAlgorithm(algorithm); - yield return null; - - // Set diagonal movement - gridMap.AllowDiagonalMovement = useDiagonals; - yield return null; - - // Generate maze with specified density - gridMap.GenerateRandomMaze(density); - yield return null; - - // Note: We no longer change visualization settings here - // This is handled in the RunSingleTest method - - yield return null; - } - - private GridNode FindValidStartNode() - { - // Find a walkable node for start position, starting from the top left - for (int x = 0; x < gridMap.NumX; x++) - { - for (int y = 0; y < gridMap.NumY; y++) - { - GridNode node = gridMap.GetGridNode(x, y); - if (node != null && node.IsWalkable) - { - return node; - } - } - } - return null; - } - - private GridNode FindValidDestinationNode(GridNode startNode) - { - if (startNode == null) - return null; - - // Find a walkable node far from the start position, starting from the bottom right - int maxDistance = 0; - GridNode bestNode = null; - - // First try to find a node with good distance - for (int x = gridMap.NumX - 1; x >= 0; x--) - { - for (int y = gridMap.NumY - 1; y >= 0; y--) - { - GridNode node = gridMap.GetGridNode(x, y); - if (node != null && node.IsWalkable && node != startNode) - { - int distance = Mathf.Abs(x - startNode.Value.x) + Mathf.Abs(y - startNode.Value.y); - if (distance > maxDistance) - { - maxDistance = distance; - bestNode = node; - } - } - } - } - - // If we found a node and the distance is decent, use it - if (bestNode != null && maxDistance > gridMap.NumX / 4) - { - return bestNode; - } - - // If no good node was found or distance is too small, try harder to find any walkable node - // different from start node (this helps with very high density mazes) - for (int x = 0; x < gridMap.NumX; x++) - { - for (int y = 0; y < gridMap.NumY; y++) - { - GridNode node = gridMap.GetGridNode(x, y); - if (node != null && node.IsWalkable && node != startNode) - { - return node; // Return the first walkable node that isn't the start - } - } - } - - // If we get here and bestNode is null, there's only one walkable node in the entire grid - // In this case, we have to return null and the test should be skipped - return bestNode; - } - - private void OnPathfindingComplete(PathfindingMetrics metrics) - { - if (!isTestingRunning) - { - Debug.Log("OnPathfindingComplete called but test is not running - ignoring"); - return; - } - - Debug.Log($"OnPathfindingComplete called for test {completedTests+1} with algorithm {currentTestAlgorithm}"); - - // Create a test result entry - PathfindingTestResult result = new PathfindingTestResult - { - algorithm = currentTestAlgorithm, - gridSize = currentTestGridSize, - mazeDensity = currentTestDensity, - diagonalMovement = currentTestDiagonal, - timeTaken = metrics.timeTaken, - pathLength = metrics.pathLength, - nodesExplored = metrics.nodesExplored, - memoryUsed = metrics.memoryUsed, - pathFound = npc.pathFinder.Status == PathFinding.PathFinderStatus.SUCCESS, - testIndex = currentTestIndex - }; - - // Add to results - testResults.Add(result); - - // Log result - Debug.Log($"Test {completedTests+1}: {GetAlgorithmName(result.algorithm)} - {result.timeTaken:F2}ms - Path: {result.pathLength} - Nodes: {result.nodesExplored} - Success: {result.pathFound} - Results count: {testResults.Count}"); - } - - private void SaveResultsToCSV() - { - try - { - Debug.Log($"Saving {testResults.Count} test results to CSV..."); - - if (testResults.Count == 0) - { - Debug.LogWarning("No test results to save!"); - return; - } - - // Create directory if it doesn't exist - string directory = Path.Combine(Application.persistentDataPath, "TestResults"); - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - string filePath = Path.Combine(directory, resultsFileName); - - StringBuilder csv = new StringBuilder(); - - // Write header - csv.AppendLine("Algorithm,GridSizeX,GridSizeY,Density,DiagonalMovement,TimeTaken,PathLength,NodesExplored,MemoryUsed,PathFound,TestIndex"); - - // Write each result - foreach (var result in testResults) - { - csv.AppendLine($"{result.algorithm},{result.gridSize.x},{result.gridSize.y},{result.mazeDensity},{result.diagonalMovement}," + - $"{result.timeTaken},{result.pathLength},{result.nodesExplored},{result.memoryUsed},{result.pathFound},{result.testIndex}"); - } - - // Write file - File.WriteAllText(filePath, csv.ToString()); - - Debug.Log($"Test results saved to {filePath}"); - - // Make it easier to find in Windows Explorer - System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\""); - } - catch (System.Exception e) - { - Debug.LogError($"Error saving test results: {e.Message}"); - } - } - - // Utility method to get algorithm name as a string - private string GetAlgorithmName(NPC.PathFinderType algorithm) - { - switch (algorithm) - { - case NPC.PathFinderType.ASTAR: return "A*"; - case NPC.PathFinderType.DIJKSTRA: return "Dijkstra"; - case NPC.PathFinderType.GREEDY: return "Greedy"; - case NPC.PathFinderType.BACKTRACKING: return "Backtracking"; - case NPC.PathFinderType.BFS: return "BFS"; - default: return "Unknown"; - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/PathfindingTester.cs.meta b/Assets/Scripts/PathfindingTester.cs.meta deleted file mode 100644 index 2cef5ac..0000000 --- a/Assets/Scripts/PathfindingTester.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ed03b279fa9f3194f8caed356ea838dc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/PathfindingUIManager.cs b/Assets/Scripts/PathfindingUIManager.cs index 6f56a07..9064d3d 100644 --- a/Assets/Scripts/PathfindingUIManager.cs +++ b/Assets/Scripts/PathfindingUIManager.cs @@ -6,6 +6,11 @@ using UnityEngine.UI; using TMPro; using System.IO; +/// +/// PathfindingUIManager merupakan kelas yang mengelola antarmuka pengguna untuk sistem pathfinding. +/// pengguna dapat mengatur ukuran grid, memilih algoritma pathfinding, menjalankan pathfinding. +/// + public class PathfindingUIManager : MonoBehaviour { [Header("References")] @@ -22,12 +27,11 @@ public class PathfindingUIManager : MonoBehaviour public TMP_Dropdown algorithmDropdown; public Button runPathfindingButton; public Button resetButton; - public Toggle allowDiagonalToggle; // Toggle untuk diagonal movement + public Toggle allowDiagonalToggle; [Header("Visualization Controls")] public Slider visualizationSpeedSlider; public Slider visualizationBatchSlider; - // public Toggle showVisualizationToggle; public TMP_Text speedValueText; public TMP_Text batchValueText; @@ -36,7 +40,7 @@ public class PathfindingUIManager : MonoBehaviour public TMP_Text pathLengthText; public TMP_Text nodesExploredText; public TMP_Text memoryUsageText; - public TMP_Text cpuUsageText; // Text untuk menampilkan penggunaan CPU + public TMP_Text cpuUsageText; [Header("Map Save/Load")] public TMP_InputField mapNameInput; @@ -44,7 +48,7 @@ public class PathfindingUIManager : MonoBehaviour public Button loadButton; [Header("Application Controls")] - public Button exitButton; // Tombol untuk keluar aplikasi + public Button exitButton; [Header("Optimization")] [SerializeField] private bool performWarmup = true; @@ -54,29 +58,16 @@ public class PathfindingUIManager : MonoBehaviour public TMP_Dropdown mazeSizeDropdown; public TMP_Dropdown mazeDensityDropdown; - [Header("Automated Testing")] - [SerializeField] private GameObject testerPanelPrefab; - [SerializeField] private Transform testerPanelParent; - private GameObject testerPanelInstance; - - // Konstanta untuk perhitungan CPU usage private const float TARGET_FRAME_TIME_MS = 16.67f; // 60 FPS = 16.67ms per frame - // Add a flag to track if pathfinding is running private bool isPathfindingRunning = false; - - // Add a flag to track if visualization is running private bool isVisualizationRunning = false; - - // Add a flag to track if NPC is moving private bool isNpcMoving = false; private void Start() { - // Initialize UI elements InitializeUI(); - // Add listeners applyGridSizeButton.onClick.AddListener(OnApplyGridSize); runPathfindingButton.onClick.AddListener(OnRunPathfinding); resetButton.onClick.AddListener(OnResetPathfinding); @@ -84,56 +75,38 @@ public class PathfindingUIManager : MonoBehaviour 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); - // Add visualization control listeners if (visualizationSpeedSlider != null) visualizationSpeedSlider.onValueChanged.AddListener(OnVisualizationSpeedChanged); if (visualizationBatchSlider != null) visualizationBatchSlider.onValueChanged.AddListener(OnVisualizationBatchChanged); - //if (showVisualizationToggle != null) - //{ - // showVisualizationToggle.isOn = npc.showVisualization; - // showVisualizationToggle.onValueChanged.AddListener(OnShowVisualizationChanged); - //} - - // Subscribe to NPC's pathfinding events npc.OnPathfindingComplete += UpdatePerformanceMetrics; npc.OnPathfindingComplete += OnPathfindingCompleted; - // Subscribe to visualization completion event npc.OnVisualizationComplete += OnVisualizationCompleted; - // Subscribe to movement completion event npc.OnMovementComplete += OnMovementCompleted; - // Initialize performance metrics ClearPerformanceMetrics(); - - // Initialize visualization controls InitializeVisualizationControls(); - // 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) @@ -141,20 +114,16 @@ public class PathfindingUIManager : MonoBehaviour //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++) @@ -173,66 +142,46 @@ public class PathfindingUIManager : MonoBehaviour } } - // 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; @@ -241,7 +190,6 @@ public class PathfindingUIManager : MonoBehaviour npc.OnMovementComplete -= OnMovementCompleted; } - // Unsubscribe from UI events if (allowDiagonalToggle != null) { allowDiagonalToggle.onValueChanged.RemoveListener(OnDiagonalMovementChanged); @@ -250,19 +198,15 @@ public class PathfindingUIManager : MonoBehaviour private void InitializeUI() { - // Set initial values gridSizeXInput.text = gridMap.NumX.ToString(); gridSizeYInput.text = gridMap.NumY.ToString(); - // Set input fields to only accept integers gridSizeXInput.contentType = TMP_InputField.ContentType.IntegerNumber; gridSizeYInput.contentType = TMP_InputField.ContentType.IntegerNumber; - // Add input validation events gridSizeXInput.onValidateInput += ValidateNumberInput; gridSizeYInput.onValidateInput += ValidateNumberInput; - // Setup algorithm dropdown algorithmDropdown.ClearOptions(); algorithmDropdown.AddOptions(new System.Collections.Generic.List { "A*", @@ -272,14 +216,12 @@ public class PathfindingUIManager : MonoBehaviour "BFS" }); - // Initialize diagonal movement toggle if (allowDiagonalToggle != null) { allowDiagonalToggle.isOn = gridMap.AllowDiagonalMovement; allowDiagonalToggle.onValueChanged.AddListener(OnDiagonalMovementChanged); } - // Setup maze size dropdown if (mazeSizeDropdown != null) { mazeSizeDropdown.ClearOptions(); @@ -292,7 +234,6 @@ public class PathfindingUIManager : MonoBehaviour }); } - // Setup maze density dropdown if (mazeDensityDropdown != null) { mazeDensityDropdown.ClearOptions(); @@ -306,17 +247,15 @@ public class PathfindingUIManager : MonoBehaviour ClearPerformanceMetrics(); } - // Validation function to only allow numeric input private char ValidateNumberInput(string text, int charIndex, char addedChar) { - // Only allow digits if (char.IsDigit(addedChar)) { return addedChar; } else { - return '\0'; // Return null character to reject the input + return '\0'; } } @@ -336,7 +275,6 @@ public class PathfindingUIManager : MonoBehaviour 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; @@ -362,38 +300,29 @@ public class PathfindingUIManager : MonoBehaviour if (int.TryParse(gridSizeXInput.text, out int newSizeX) && int.TryParse(gridSizeYInput.text, out int newSizeY)) { - // Validate grid size limits const int MAX_GRID_SIZE = 200; const int MIN_GRID_SIZE = 2; if (newSizeX > MAX_GRID_SIZE || newSizeY > MAX_GRID_SIZE) { - // Display an error message Debug.LogWarning($"Grid size cannot exceed {MAX_GRID_SIZE}x{MAX_GRID_SIZE}. Resize operation cancelled."); - // Revert input fields to current grid size gridSizeXInput.text = gridMap.NumX.ToString(); gridSizeYInput.text = gridMap.NumY.ToString(); - // Don't proceed with resize return; } - // Check for minimum size if (newSizeX < MIN_GRID_SIZE || newSizeY < MIN_GRID_SIZE) { - // Display an error message Debug.LogWarning($"Grid size cannot be less than {MIN_GRID_SIZE}x{MIN_GRID_SIZE}. Resize operation cancelled."); - // Revert input fields to current grid size gridSizeXInput.text = gridMap.NumX.ToString(); gridSizeYInput.text = gridMap.NumY.ToString(); - // Don't proceed with resize return; } - // Apply the grid size (only if within limits) if (gridMap.ResizeGrid(newSizeX, newSizeY)) { ClearPerformanceMetrics(); @@ -403,17 +332,14 @@ public class PathfindingUIManager : MonoBehaviour 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); @@ -421,10 +347,9 @@ public class PathfindingUIManager : MonoBehaviour { ClearPerformanceMetrics(); - // Set flags that pathfinding, visualization, and movement will happen isPathfindingRunning = true; isVisualizationRunning = true; - isNpcMoving = true; // assume movement will happen + isNpcMoving = true; SetUIInteractivity(false); npc.MoveTo(endNode); @@ -433,20 +358,15 @@ public class PathfindingUIManager : MonoBehaviour private void OnResetPathfinding() { - // Reset all flags to ensure UI will be enabled after reload isPathfindingRunning = false; isVisualizationRunning = false; isNpcMoving = false; - // Force enable UI - this ensures buttons will be enabled - // after reset regardless of editor/build status SetUIInteractivity(true); - // Reload the current scene UnityEngine.SceneManagement.SceneManager.LoadScene( UnityEngine.SceneManagement.SceneManager.GetActiveScene().name); - //Debug.Log("Reloading scene..."); } private void OnAlgorithmChanged(int index) @@ -460,7 +380,6 @@ public class PathfindingUIManager : MonoBehaviour ClearPerformanceMetrics(); } - // Helper method to get the readable name of the algorithm private string GetAlgorithmName(NPC.PathFinderType type) { switch (type) @@ -488,7 +407,6 @@ public class PathfindingUIManager : MonoBehaviour return; } - // Buat direktori jika belum ada string saveDirectory = Path.Combine(Application.persistentDataPath, "GridSaves"); if (!Directory.Exists(saveDirectory)) { @@ -501,20 +419,13 @@ public class PathfindingUIManager : MonoBehaviour Debug.Log($"Map saved to: {filePath} (includes grid state, NPC position, and destination position)"); } - /// - /// Membuka folder penyimpanan di File Explorer - /// 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); } @@ -530,22 +441,15 @@ public class PathfindingUIManager : MonoBehaviour if (!File.Exists(filePath)) { - //Debug.LogWarning($"Map file not found: {filePath}"); return; } gridMap.LoadGridState(filePath); ClearPerformanceMetrics(); - - //Debug.Log($"Map loaded from: {filePath}"); } - /// - /// Generates a random maze with the selected size and density - /// private void OnGenerateMaze() { - // Get selected maze size int sizeX = 20; int sizeY = 20; bool isLargeGrid = false; @@ -569,10 +473,8 @@ public class PathfindingUIManager : MonoBehaviour sizeX = sizeY = 100; isLargeGrid = true; break; - // If more options are added that exceed MAX_GRID_SIZE, they'll be rejected } - // Check if size exceeds maximum or is below minimum - reject if it does if (sizeX > MAX_GRID_SIZE || sizeY > MAX_GRID_SIZE) { Debug.LogWarning($"Maze size {sizeX}x{sizeY} exceeds maximum of {MAX_GRID_SIZE}x{MAX_GRID_SIZE}. Operation cancelled."); @@ -585,22 +487,18 @@ public class PathfindingUIManager : MonoBehaviour return; } - // Resize grid if needed if (gridMap.NumX != sizeX || gridMap.NumY != sizeY) { if (!gridMap.ResizeGrid(sizeX, sizeY)) { - // Resize failed, abort maze generation return; } - // Update grid size inputs gridSizeXInput.text = sizeX.ToString(); gridSizeYInput.text = sizeY.ToString(); } - // Get selected density - float density = 30f; // Default medium + float density = 30f; switch (mazeDensityDropdown.value) { @@ -615,61 +513,40 @@ public class PathfindingUIManager : MonoBehaviour 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}%"); } - /// - /// Menampilkan lokasi penyimpanan file di konsol - /// private void ShowSaveLocation() { string saveDirectory = Path.Combine(Application.persistentDataPath, "GridSaves"); } - /// - /// Menutup aplikasi saat tombol exit ditekan - /// public void OnExitApplication() { #if UNITY_EDITOR - // Jika di Unity Editor UnityEditor.EditorApplication.isPlaying = false; #else - // Jika di build - Application.Quit(); + // Jika di build + Application.Quit(); #endif Debug.Log("Application exit requested"); @@ -677,7 +554,6 @@ public class PathfindingUIManager : MonoBehaviour private void InitializeVisualizationControls() { - // Set initial slider values based on NPC settings if (visualizationSpeedSlider != null) { visualizationSpeedSlider.value = npc.visualizationSpeed; @@ -699,7 +575,6 @@ public class PathfindingUIManager : MonoBehaviour private void OnVisualizationBatchChanged(float newValue) { - // Pastikan nilai batch adalah integer int batchValue = Mathf.RoundToInt(newValue); npc.visualizationBatch = batchValue; UpdateBatchValueText(batchValue); @@ -725,31 +600,22 @@ public class PathfindingUIManager : MonoBehaviour batchValueText.text = value.ToString(); } } - - /// - /// Menangani perubahan toggle diagonal movement - /// private void OnDiagonalMovementChanged(bool isOn) { gridMap.AllowDiagonalMovement = isOn; - // Reset performance metrics ClearPerformanceMetrics(); } - // New method to handle pathfinding completion private void OnPathfindingCompleted(PathfindingMetrics metrics) { - // Pathfinding is completed, but visualization might still be running isPathfindingRunning = false; - // Check if pathfinding failed by looking at metrics or path length bool pathfindingFailed = (metrics.pathLength == 0) || (npc.pathFinder != null && npc.pathFinder.Status == PathFinding.PathFinderStatus.FAILURE); if (pathfindingFailed) { - // If pathfinding failed, there won't be any visualization or movement Debug.Log("Pathfinding failed - re-enabling UI controls immediately"); isVisualizationRunning = false; isNpcMoving = false; @@ -765,22 +631,15 @@ public class PathfindingUIManager : MonoBehaviour return; } - // If pathfinding succeeded, continue with normal flow - // Very important: Keep UI disabled regardless of visualization state to prevent - // the brief window of interactivity between pathfinding completion and visualization start + if (npc.showVisualization) { - // If visualization is enabled in settings, assume it will start soon - // Keep UI disabled by keeping isVisualizationRunning true isVisualizationRunning = true; - // Do NOT enable UI here - wait for visualization to complete } else { - // Only if visualization is completely disabled in settings, enable UI isVisualizationRunning = false; - // Only re-enable in editor mode #if UNITY_EDITOR SetUIInteractivity(true); #else @@ -790,25 +649,18 @@ public class PathfindingUIManager : MonoBehaviour } } - // New method to handle visualization completion private void OnVisualizationCompleted() { - // Visualization is completed, but NPC may start moving isVisualizationRunning = false; - // Check if NPC is moving or will move if (npc.IsMoving || npc.wayPoints.Count > 0) { - // Movement is starting or in progress isNpcMoving = true; - // Leave UI disabled } else { - // No movement expected isNpcMoving = false; - // Only re-enable in editor mode #if UNITY_EDITOR SetUIInteractivity(true); #else @@ -818,13 +670,10 @@ public class PathfindingUIManager : MonoBehaviour } } - // New method to handle movement completion private void OnMovementCompleted() { - // Movement is completed, re-enable UI buttons only in editor isNpcMoving = false; - // Only re-enable in editor mode #if UNITY_EDITOR SetUIInteractivity(true); #else @@ -834,13 +683,10 @@ public class PathfindingUIManager : MonoBehaviour #endif } - // Method to enable/disable UI elements based on pathfinding, visualization, and movement state private void SetUIInteractivity(bool enabled) { - // If any process is running, disable controls bool shouldEnable = enabled && !isPathfindingRunning && !isVisualizationRunning && !isNpcMoving; - // In builds (not editor), once disabled, buttons stay disabled until reset #if !UNITY_EDITOR if (shouldEnable && (isPathfindingRunning || isVisualizationRunning || isNpcMoving)) { @@ -850,13 +696,10 @@ public class PathfindingUIManager : MonoBehaviour } #endif - // Add debug logging Debug.Log($"SetUIInteractivity called with enabled={enabled}, pathfinding={isPathfindingRunning}, " + $"visualization={isVisualizationRunning}, movement={isNpcMoving}, shouldEnable={shouldEnable}, " + $"inEditor={Application.isEditor}"); - // Keep reset and exit buttons always enabled - // Disable all other buttons when processes are running if (applyGridSizeButton != null) applyGridSizeButton.interactable = shouldEnable; @@ -899,17 +742,12 @@ public class PathfindingUIManager : MonoBehaviour if (mapNameInput != null) mapNameInput.interactable = shouldEnable; - - // Reset and exit buttons remain enabled - // resetButton and exitButton stay interactable } private void Update() { - // Continuously check for various states - this ensures buttons stay disabled if (npc != null) { - // Check visualization if (npc.IsVisualizingPath && !isVisualizationRunning) { Debug.Log("Detected active visualization - updating UI state"); @@ -917,7 +755,6 @@ public class PathfindingUIManager : MonoBehaviour SetUIInteractivity(false); } - // Check movement if (npc.IsMoving && !isNpcMoving) { Debug.Log("Detected active NPC movement - updating UI state"); diff --git a/Assets/Scripts/PriorityQueue.cs b/Assets/Scripts/PriorityQueue.cs index da4a56a..f8ad147 100644 --- a/Assets/Scripts/PriorityQueue.cs +++ b/Assets/Scripts/PriorityQueue.cs @@ -9,21 +9,10 @@ using System.Collections.Generic; /// Tipe prioritas yang digunakan, harus implementasi IComparable public class PriorityQueue where TPriority : IComparable { - // Menyimpan pasangan elemen dan prioritasnya dalam struktur heap private List> elements = new List>(); - // Menyimpan indeks setiap elemen dalam heap untuk akses cepat private Dictionary elementIndexMap = new Dictionary(); - - /// - /// Mendapatkan jumlah elemen dalam antrean prioritas - /// public int Count => elements.Count; - /// - /// Menambahkan elemen baru ke dalam antrean prioritas dengan prioritas tertentu - /// - /// Elemen yang akan ditambahkan - /// Prioritas dari elemen public void Enqueue(TElement element, TPriority priority) { elements.Add(Tuple.Create(element, priority)); @@ -31,12 +20,6 @@ public class PriorityQueue where TPriority : IComparable - /// Mengambil dan menghapus elemen dengan prioritas tertinggi (nilai terkecil) - /// dari antrean prioritas - /// - /// Elemen dengan prioritas tertinggi - /// Dilempar jika antrean kosong public TElement Dequeue() { if (elements.Count == 0) @@ -48,7 +31,6 @@ public class PriorityQueue where TPriority : IComparable 0) { - // Pindahkan elemen terakhir ke root, lalu atur ulang heap elements[0] = last; elementIndexMap[last.Item1] = 0; HeapifyDown(0); @@ -58,12 +40,6 @@ public class PriorityQueue where TPriority : IComparable - /// Memperbarui prioritas elemen yang sudah ada dalam antrean - /// - /// Elemen yang akan diperbarui prioritasnya - /// Nilai prioritas baru - /// Dilempar jika elemen tidak ditemukan public void UpdatePriority(TElement element, TPriority newPriority) { if (!elementIndexMap.ContainsKey(element)) @@ -73,23 +49,16 @@ public class PriorityQueue where TPriority : IComparable - /// Mempertahankan properti heap dengan memindahkan elemen ke atas jika - /// prioritasnya lebih tinggi dari parent - /// - /// Indeks elemen yang akan dipindahkan ke atas private void HeapifyUp(int index) { var parentIndex = (index - 1) / 2; @@ -100,18 +69,12 @@ public class PriorityQueue where TPriority : IComparable - /// Mempertahankan properti heap dengan memindahkan elemen ke bawah jika - /// prioritasnya lebih rendah dari child - /// - /// Indeks elemen yang akan dipindahkan ke bawah 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; @@ -122,7 +85,6 @@ public class PriorityQueue where TPriority : IComparable where TPriority : IComparable - /// Menukar posisi dua elemen dalam heap dan memperbarui elementIndexMap - /// - /// Indeks elemen pertama - /// Indeks elemen kedua 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; } diff --git a/Assets/Scripts/pathfinding_tests.csv b/Assets/Scripts/pathfinding_tests.csv deleted file mode 100644 index eb68217..0000000 --- a/Assets/Scripts/pathfinding_tests.csv +++ /dev/null @@ -1,271 +0,0 @@ -Algorithm,GridSizeX,GridSizeY,Density,DiagonalMovement,TimeTaken,PathLength,NodesExplored,MemoryUsed,PathFound,TestIndex -ASTAR,20,20,10,True,1.0282,24,26,4096,True,0 -ASTAR,20,20,10,True,0.1261,22,22,4096,True,1 -ASTAR,20,20,10,True,0.1369,21,20,4096,True,2 -ASTAR,20,20,10,False,0.12,39,38,4096,True,0 -ASTAR,20,20,10,False,0.1708,39,53,8192,True,1 -ASTAR,20,20,10,False,0.2009,39,69,12288,True,2 -ASTAR,20,20,30,True,0.1264,26,27,8192,True,0 -ASTAR,20,20,30,True,0.1366,26,29,8192,True,1 -ASTAR,20,20,30,True,0.1186,24,25,8192,True,2 -ASTAR,20,20,30,False,0.1441,39,45,4096,True,0 -ASTAR,20,20,30,False,0.1042,39,48,12288,True,1 -ASTAR,20,20,30,False,0.2301,39,87,24576,True,2 -ASTAR,20,20,50,True,0.1618,43,89,16384,True,0 -ASTAR,20,20,50,True,0.3753,54,171,8192,True,1 -ASTAR,20,20,50,True,0.2745,48,130,8192,True,2 -ASTAR,20,20,50,False,0.1702,63,104,0,True,0 -ASTAR,20,20,50,False,0.1628,59,98,12288,True,1 -ASTAR,20,20,50,False,0.3159,135,180,40960,True,2 -ASTAR,35,35,10,True,0.2377,36,35,16384,True,0 -ASTAR,35,35,10,True,0.2482,38,39,16384,True,1 -ASTAR,35,35,10,True,0.2412,40,39,16384,True,2 -ASTAR,35,35,10,False,0.2433,69,68,8192,True,0 -ASTAR,35,35,10,False,0.281,69,95,24576,True,1 -ASTAR,35,35,10,False,0.2874,69,93,20480,True,2 -ASTAR,35,35,30,True,0.2388,47,46,20480,True,0 -ASTAR,35,35,30,True,0.3082,45,68,4096,True,1 -ASTAR,35,35,30,True,0.2438,42,44,4096,True,2 -ASTAR,35,35,30,False,0.3319,69,111,4096,True,0 -ASTAR,35,35,30,False,0.4247,69,139,20480,True,1 -ASTAR,35,35,30,False,0.2607,69,86,12288,True,2 -ASTAR,35,35,50,True,0.4856,101,261,36864,True,0 -ASTAR,35,35,50,True,1.0796,219,563,98304,True,1 -ASTAR,35,35,50,True,1.0919,97,529,114688,True,2 -ASTAR,35,35,50,False,0.4922,145,300,69632,True,0 -ASTAR,35,35,50,False,0.2173,97,134,24576,True,1 -ASTAR,35,35,50,False,0.4082,77,191,28672,True,2 -ASTAR,50,50,10,True,0.3457,54,53,40960,True,0 -ASTAR,50,50,10,True,0.3793,54,53,16384,True,1 -ASTAR,50,50,10,True,0.3698,57,56,16384,True,2 -ASTAR,50,50,10,False,0.3816,99,98,20480,True,0 -ASTAR,50,50,10,False,0.3855,99,120,20480,True,1 -ASTAR,50,50,10,False,0.4885,99,158,24576,True,2 -ASTAR,50,50,30,True,0.3388,59,61,16384,True,0 -ASTAR,50,50,30,True,0.3565,57,59,20480,True,1 -ASTAR,50,50,30,True,0.3343,64,67,20480,True,2 -ASTAR,50,50,30,False,1.1702,99,412,102400,True,0 -ASTAR,50,50,30,False,0.4831,99,153,32768,True,1 -ASTAR,50,50,30,False,0.4641,99,200,8192,True,2 -ASTAR,50,50,50,True,2.2106,147,926,73728,True,0 -ASTAR,50,50,50,True,1.8848,162,854,73728,True,1 -ASTAR,50,50,50,True,1.8428,305,1033,196608,True,2 -ASTAR,50,50,50,False,1.3853,259,813,155648,True,0 -ASTAR,50,50,50,False,1.3047,289,809,151552,True,1 -ASTAR,50,50,50,False,0.7769,223,485,77824,True,2 -DIJKSTRA,20,20,10,True,0.5732,21,360,69632,True,0 -DIJKSTRA,20,20,10,True,0.6119,21,360,69632,True,1 -DIJKSTRA,20,20,10,True,0.5995,20,360,81920,True,2 -DIJKSTRA,20,20,10,False,0.4575,39,360,126976,True,0 -DIJKSTRA,20,20,10,False,0.4468,39,359,114688,True,1 -DIJKSTRA,20,20,10,False,0.4462,39,361,32768,True,2 -DIJKSTRA,20,20,30,True,0.4703,23,281,32768,True,0 -DIJKSTRA,20,20,30,True,0.4805,25,281,32768,True,1 -DIJKSTRA,20,20,30,True,0.4821,24,281,61440,True,2 -DIJKSTRA,20,20,30,False,0.3919,39,282,65536,True,0 -DIJKSTRA,20,20,30,False,0.3812,41,280,61440,True,1 -DIJKSTRA,20,20,30,False,0.3457,39,281,61440,True,2 -DIJKSTRA,20,20,50,True,0.2829,44,197,28672,True,0 -DIJKSTRA,20,20,50,True,0.2141,58,144,20480,True,1 -DIJKSTRA,20,20,50,True,0.2861,66,192,32768,True,2 -DIJKSTRA,20,20,50,False,0.2427,59,189,28672,True,0 -DIJKSTRA,20,20,50,False,0.1485,51,115,24576,True,1 -DIJKSTRA,20,20,50,False,0.1985,91,159,45056,True,2 -DIJKSTRA,35,35,10,True,2.1114,37,1103,327680,True,0 -DIJKSTRA,35,35,10,True,2.0576,37,1103,94208,True,1 -DIJKSTRA,35,35,10,True,2.1407,37,1104,94208,True,2 -DIJKSTRA,35,35,10,False,1.5362,69,1103,253952,True,0 -DIJKSTRA,35,35,10,False,1.4893,69,1103,204800,True,1 -DIJKSTRA,35,35,10,False,1.4763,69,1103,200704,True,2 -DIJKSTRA,35,35,30,True,1.5209,42,858,163840,True,0 -DIJKSTRA,35,35,30,True,1.435,42,857,155648,True,1 -DIJKSTRA,35,35,30,True,1.4548,40,857,159744,True,2 -DIJKSTRA,35,35,30,False,1.129,69,856,253952,True,0 -DIJKSTRA,35,35,30,False,1.1949,69,855,253952,True,1 -DIJKSTRA,35,35,30,False,1.1977,69,856,294912,True,2 -DIJKSTRA,35,35,50,True,0.6041,136,400,28672,True,0 -DIJKSTRA,35,35,50,True,0.6758,145,487,28672,True,1 -DIJKSTRA,35,35,50,True,0.6081,137,424,28672,True,2 -DIJKSTRA,35,35,50,False,0.58,0,0,8192,False,0 -DIJKSTRA,35,35,50,False,0.4809,129,362,65536,True,1 -DIJKSTRA,35,35,50,False,0.7123,77,532,98304,True,2 -DIJKSTRA,50,50,10,True,4.5189,51,2250,409600,True,0 -DIJKSTRA,50,50,10,True,4.5321,53,2250,409600,True,1 -DIJKSTRA,50,50,10,True,4.3824,54,2251,573440,True,2 -DIJKSTRA,50,50,10,False,3.1707,99,2250,659456,True,0 -DIJKSTRA,50,50,10,False,3.3113,99,2250,663552,True,1 -DIJKSTRA,50,50,10,False,3.3981,99,2251,188416,True,2 -DIJKSTRA,50,50,30,True,3.2778,58,1750,258048,True,0 -DIJKSTRA,50,50,30,True,3.0818,58,1751,327680,True,1 -DIJKSTRA,50,50,30,True,3.1183,58,1750,323584,True,2 -DIJKSTRA,50,50,30,False,2.4624,99,1749,323584,True,0 -DIJKSTRA,50,50,30,False,2.5493,99,1751,323584,True,1 -DIJKSTRA,50,50,30,False,2.4891,99,1749,319488,True,2 -DIJKSTRA,50,50,50,True,1.2986,301,880,217088,True,0 -DIJKSTRA,50,50,50,True,1.6238,194,1187,344064,True,1 -DIJKSTRA,50,50,50,True,2.5347,174,565,184320,True,2 -DIJKSTRA,50,50,50,False,1.2548,271,896,73728,True,0 -DIJKSTRA,50,50,50,False,1.3304,409,934,90112,True,1 -DIJKSTRA,50,50,50,False,0.7657,357,612,122880,True,2 -GREEDY,20,20,10,True,0.1293,22,22,8192,True,0 -GREEDY,20,20,10,True,0.1508,21,21,8192,True,1 -GREEDY,20,20,10,True,0.1445,21,21,4096,True,2 -GREEDY,20,20,10,False,0.1191,39,39,4096,True,0 -GREEDY,20,20,10,False,0.1347,41,41,4096,True,1 -GREEDY,20,20,10,False,0.1692,43,46,12288,True,2 -GREEDY,20,20,30,True,0.1515,25,27,4096,True,0 -GREEDY,20,20,30,True,0.1471,25,25,8192,True,1 -GREEDY,20,20,30,True,0.1325,28,28,8192,True,2 -GREEDY,20,20,30,False,0.166,43,46,0,True,0 -GREEDY,20,20,30,False,0.1449,39,45,0,True,1 -GREEDY,20,20,30,False,0.1382,41,45,0,True,2 -GREEDY,20,20,50,True,0.2456,47,95,0,True,0 -GREEDY,20,20,50,True,0.3083,60,119,12288,True,1 -GREEDY,20,20,50,True,0.2403,60,80,8192,True,2 -GREEDY,20,20,50,False,0.2485,67,115,12288,True,0 -GREEDY,20,20,50,False,0.173,59,91,20480,True,1 -GREEDY,20,20,50,False,0.3466,79,193,20480,True,2 -GREEDY,35,35,10,True,0.2559,39,39,16384,True,0 -GREEDY,35,35,10,True,0.2487,38,38,16384,True,1 -GREEDY,35,35,10,True,0.2573,40,40,24576,True,2 -GREEDY,35,35,10,False,0.3217,83,95,20480,True,0 -GREEDY,35,35,10,False,0.6061,69,69,32768,True,1 -GREEDY,35,35,10,False,0.281,69,69,4096,True,2 -GREEDY,35,35,30,True,0.2408,43,45,8192,True,0 -GREEDY,35,35,30,True,0.2331,43,43,4096,True,1 -GREEDY,35,35,30,True,0.2392,42,42,12288,True,2 -GREEDY,35,35,30,False,0.225,71,77,8192,True,0 -GREEDY,35,35,30,False,0.2613,83,83,12288,True,1 -GREEDY,35,35,30,False,0.23,71,71,8192,True,2 -GREEDY,35,35,50,True,0.9538,91,365,65536,True,0 -GREEDY,35,35,50,True,0.9843,147,364,81920,True,1 -GREEDY,35,35,50,True,0.7238,197,300,94208,True,2 -GREEDY,35,35,50,False,0.3991,147,194,28672,True,0 -GREEDY,35,35,50,False,0.4047,117,211,49152,True,1 -GREEDY,35,35,50,False,0.3874,183,196,4096,True,2 -GREEDY,50,50,10,True,0.3848,57,58,16384,True,0 -GREEDY,50,50,10,True,0.3706,58,58,20480,True,1 -GREEDY,50,50,10,True,0.3754,54,54,24576,True,2 -GREEDY,50,50,10,False,0.3671,99,99,28672,True,0 -GREEDY,50,50,10,False,0.3749,99,99,12288,True,1 -GREEDY,50,50,10,False,0.3661,101,102,24576,True,2 -GREEDY,50,50,30,True,0.3392,63,63,24576,True,0 -GREEDY,50,50,30,True,0.3247,64,64,20480,True,1 -GREEDY,50,50,30,True,0.3234,68,69,24576,True,2 -GREEDY,50,50,30,False,0.4684,123,147,8192,True,0 -GREEDY,50,50,30,False,0.3809,107,108,4096,True,1 -GREEDY,50,50,30,False,0.3617,101,109,4096,True,2 -GREEDY,50,50,50,True,0.7341,208,268,45056,True,0 -GREEDY,50,50,50,True,0.849,172,316,65536,True,1 -GREEDY,50,50,50,True,0.6744,168,222,32768,True,2 -GREEDY,50,50,50,False,1.0245,211,568,98304,True,0 -GREEDY,50,50,50,False,1.2286,507,721,143360,True,1 -GREEDY,50,50,50,False,1.2725,419,670,172032,True,2 -BACKTRACKING,20,20,10,True,0.3594,121,131,69632,True,0 -BACKTRACKING,20,20,10,True,0.3929,111,164,61440,True,1 -BACKTRACKING,20,20,10,True,0.2945,104,116,65536,True,2 -BACKTRACKING,20,20,10,False,0.4242,183,241,28672,True,0 -BACKTRACKING,20,20,10,False,0.3568,187,205,28672,True,1 -BACKTRACKING,20,20,10,False,0.3895,177,221,28672,True,2 -BACKTRACKING,20,20,30,True,0.3235,75,129,28672,True,0 -BACKTRACKING,20,20,30,True,0.3471,77,167,36864,True,1 -BACKTRACKING,20,20,30,True,0.3286,92,140,36864,True,2 -BACKTRACKING,20,20,30,False,0.3422,147,203,45056,True,0 -BACKTRACKING,20,20,30,False,0.2824,137,191,32768,True,1 -BACKTRACKING,20,20,30,False,0.3286,157,193,36864,True,2 -BACKTRACKING,20,20,50,True,0.2677,30,137,28672,True,0 -BACKTRACKING,20,20,50,True,0.2214,37,130,24576,True,1 -BACKTRACKING,20,20,50,True,0.1721,42,104,20480,True,2 -BACKTRACKING,20,20,50,False,0.1367,71,124,36864,True,0 -BACKTRACKING,20,20,50,False,0.2432,79,179,8192,True,1 -BACKTRACKING,20,20,50,False,0.2324,43,186,8192,True,2 -BACKTRACKING,35,35,10,True,1.465,328,401,106496,True,0 -BACKTRACKING,35,35,10,True,1.3811,323,409,188416,True,1 -BACKTRACKING,35,35,10,True,1.1492,285,349,147456,True,2 -BACKTRACKING,35,35,10,False,1.4213,521,635,188416,True,0 -BACKTRACKING,35,35,10,False,1.4778,527,680,188416,True,1 -BACKTRACKING,35,35,10,False,1.4704,573,649,184320,True,2 -BACKTRACKING,35,35,30,True,1.1103,290,408,110592,True,0 -BACKTRACKING,35,35,30,True,1.0674,241,456,208896,True,1 -BACKTRACKING,35,35,30,True,1.1679,270,445,212992,True,2 -BACKTRACKING,35,35,30,False,0.931,429,556,245760,True,0 -BACKTRACKING,35,35,30,False,1.0727,403,561,69632,True,1 -BACKTRACKING,35,35,30,False,1.5718,401,618,69632,True,2 -BACKTRACKING,35,35,50,True,0.6532,77,428,24576,True,0 -BACKTRACKING,35,35,50,True,0.2979,79,179,28672,True,1 -BACKTRACKING,35,35,50,True,0.4046,125,241,40960,True,2 -BACKTRACKING,35,35,50,False,0.3571,0,0,4096,False,0 -BACKTRACKING,35,35,50,False,0.652,193,506,102400,True,1 -BACKTRACKING,35,35,50,False,0.3799,0,0,16384,False,2 -BACKTRACKING,50,50,10,True,3.1519,636,758,438272,True,0 -BACKTRACKING,50,50,10,True,2.5557,565,614,495616,True,1 -BACKTRACKING,50,50,10,True,3.3404,651,845,610304,True,2 -BACKTRACKING,50,50,10,False,3.7901,1137,1239,241664,True,0 -BACKTRACKING,50,50,10,False,3.601,1099,1299,200704,True,1 -BACKTRACKING,50,50,10,False,3.2003,967,1197,319488,True,2 -BACKTRACKING,50,50,30,True,2.6415,593,733,212992,True,0 -BACKTRACKING,50,50,30,True,2.1968,520,708,217088,True,1 -BACKTRACKING,50,50,30,True,2.4687,557,787,225280,True,2 -BACKTRACKING,50,50,30,False,2.5318,703,1337,294912,True,0 -BACKTRACKING,50,50,30,False,2.1927,687,1132,430080,True,1 -BACKTRACKING,50,50,30,False,2.2509,821,1179,438272,True,2 -BACKTRACKING,50,50,50,True,1.2183,145,849,262144,True,0 -BACKTRACKING,50,50,50,True,0.8606,234,570,204800,True,1 -BACKTRACKING,50,50,50,True,1.8035,150,1043,131072,True,2 -BACKTRACKING,50,50,50,False,1.2391,655,933,98304,True,0 -BACKTRACKING,50,50,50,False,0.9434,335,763,131072,True,1 -BACKTRACKING,50,50,50,False,0.7697,195,577,114688,True,2 -BFS,20,20,10,True,0.4677,20,360,61440,True,0 -BFS,20,20,10,True,0.621,21,361,77824,True,1 -BFS,20,20,10,True,0.5292,21,361,65536,True,2 -BFS,20,20,10,False,0.3891,39,361,65536,True,0 -BFS,20,20,10,False,0.4235,39,361,65536,True,1 -BFS,20,20,10,False,0.4674,39,360,90112,True,2 -BFS,20,20,30,True,0.3917,25,282,73728,True,0 -BFS,20,20,30,True,0.3715,26,281,86016,True,1 -BFS,20,20,30,True,0.449,23,282,24576,True,2 -BFS,20,20,30,False,0.3117,39,255,16384,True,0 -BFS,20,20,30,False,0.3499,39,281,24576,True,1 -BFS,20,20,30,False,0.3472,39,282,57344,True,2 -BFS,20,20,50,True,0.2092,42,144,20480,True,0 -BFS,20,20,50,True,0.2498,41,161,32768,True,1 -BFS,20,20,50,True,0.2399,38,163,24576,True,2 -BFS,20,20,50,False,0.2321,59,155,24576,True,0 -BFS,20,20,50,False,0.2403,71,162,24576,True,1 -BFS,20,20,50,False,0.1412,51,100,16384,True,2 -BFS,35,35,10,True,1.679,38,1104,233472,True,0 -BFS,35,35,10,True,1.6694,36,1103,294912,True,1 -BFS,35,35,10,True,1.7347,39,1104,425984,True,2 -BFS,35,35,10,False,1.4652,69,1103,118784,True,0 -BFS,35,35,10,False,1.3521,69,1103,118784,True,1 -BFS,35,35,10,False,1.3525,69,1103,221184,True,2 -BFS,35,35,30,True,1.2777,40,855,139264,True,0 -BFS,35,35,30,True,1.2761,43,857,143360,True,1 -BFS,35,35,30,True,1.3204,43,857,147456,True,2 -BFS,35,35,30,False,1.1577,69,857,176128,True,0 -BFS,35,35,30,False,1.1088,69,854,143360,True,1 -BFS,35,35,30,False,1.1091,69,857,188416,True,2 -BFS,35,35,50,True,0.6461,53,490,155648,True,0 -BFS,35,35,50,True,0.6363,88,510,155648,True,1 -BFS,35,35,50,True,0.4843,100,350,110592,True,2 -BFS,35,35,50,False,0.9047,79,544,57344,True,0 -BFS,35,35,50,False,0.3727,0,0,4096,False,1 -BFS,35,35,50,False,0.4923,0,0,8192,False,2 -BFS,50,50,10,True,3.7701,53,2250,581632,True,0 -BFS,50,50,10,True,3.4752,52,2250,462848,True,1 -BFS,50,50,10,True,3.5727,52,2250,458752,True,2 -BFS,50,50,10,False,2.7849,99,2251,471040,True,0 -BFS,50,50,10,False,2.7301,99,2251,598016,True,1 -BFS,50,50,10,False,2.6218,99,2251,716800,True,2 -BFS,50,50,30,True,2.346,62,1751,491520,True,0 -BFS,50,50,30,True,2.3452,58,1750,118784,True,1 -BFS,50,50,30,True,2.6295,58,1751,118784,True,2 -BFS,50,50,30,False,2.2686,99,1751,290816,True,0 -BFS,50,50,30,False,2.1289,99,1751,286720,True,1 -BFS,50,50,30,False,2.2602,99,1751,294912,True,2 -BFS,50,50,50,True,1.0863,240,773,139264,True,0 -BFS,50,50,50,True,0.8318,174,592,118784,True,1 -BFS,50,50,50,True,0.7613,160,535,110592,True,2 -BFS,50,50,50,False,1.3722,487,1120,282624,True,0 -BFS,50,50,50,False,0.8369,227,677,200704,True,1 -BFS,50,50,50,False,1.3386,223,996,311296,True,2 diff --git a/Assets/Scripts/pathfinding_tests.csv.meta b/Assets/Scripts/pathfinding_tests.csv.meta deleted file mode 100644 index ae7f4ef..0000000 --- a/Assets/Scripts/pathfinding_tests.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b5f5e061be1cbf946b769794cb0ba604 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: