diff --git a/Assets/Docs.meta b/Assets/Docs.meta new file mode 100644 index 0000000..c13278a --- /dev/null +++ b/Assets/Docs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3c5678ad04a64df4db73256f9e23d75c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Pathfinding.unity b/Assets/Scenes/Pathfinding.unity index f3b08f9..a73e332 100644 --- a/Assets/Scenes/Pathfinding.unity +++ b/Assets/Scenes/Pathfinding.unity @@ -336,7 +336,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 52.600037, y: 49.700073} + m_AnchoredPosition: {x: 52.600098, y: 49.700073} m_SizeDelta: {x: 81.71, y: 19.77002} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &23184662 @@ -1684,7 +1684,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: -13.392517, y: 0} + m_AnchoredPosition: {x: -13.392578, y: 0} m_SizeDelta: {x: 26.785, y: 0} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &249188175 @@ -7311,8 +7311,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 42bfb8854d690bf4fa7ccf911313d0ae, type: 3} m_Name: m_EditorClassIdentifier: - numX: 20 - numY: 20 + numX: 10 + numY: 10 gridNodeViewPrefab: {fileID: 5382759684804752574, guid: bfa28317eb98f2846969e3ffce05f685, type: 3} allowDiagonalMovement: 0 COLOR_WALKABLE: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/Scripts/GridMap.cs b/Assets/Scripts/GridMap.cs index be39f7c..ad1c6f2 100644 --- a/Assets/Scripts/GridMap.cs +++ b/Assets/Scripts/GridMap.cs @@ -489,6 +489,22 @@ public class GridMap : MonoBehaviour } } + /// + /// Simple class to store grid positions for serialization + /// + [System.Serializable] + public class SerializablePosition + { + public float x; + public float y; + + public SerializablePosition(float x, float y) + { + this.x = x; + this.y = y; + } + } + /// /// Kelas untuk menyimpan status grid untuk keperluan save/load /// @@ -496,6 +512,8 @@ public class GridMap : MonoBehaviour public class GridState { public bool[,] walkableStates; + public SerializablePosition npcPosition; // Position of NPC + public SerializablePosition destinationPosition; // Position of destination } /// @@ -525,15 +543,33 @@ 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); + string json = JsonConvert.SerializeObject(gridState, settings); File.WriteAllText(filePath, json); - //Debug.Log($"Grid state saved to {filePath}"); + Debug.Log($"Grid state saved to {filePath}"); } catch (System.Exception e) { - //Debug.LogError($"Error saving grid state: {e.Message}"); + Debug.LogError($"Error saving grid state: {e.Message}"); } } @@ -547,19 +583,26 @@ public class GridMap : MonoBehaviour { if (!File.Exists(filePath)) { - //Debug.LogError($"Save file not found: {filePath}"); + Debug.LogError($"Save file not found: {filePath}"); return; } // Membaca dan mengkonversi data dari file JSON string json = File.ReadAllText(filePath); - GridState gridState = JsonConvert.DeserializeObject(json); + + // 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) { - //Debug.LogWarning($"Grid size mismatch. File: {gridState.walkableStates.GetLength(0)}x{gridState.walkableStates.GetLength(1)}, Current: {numX}x{numY}. Resizing grid..."); + Debug.LogWarning($"Grid size mismatch. File: {gridState.walkableStates.GetLength(0)}x{gridState.walkableStates.GetLength(1)}, Current: {numX}x{numY}. Resizing grid..."); ResizeGrid(gridState.walkableStates.GetLength(0), gridState.walkableStates.GetLength(1)); } @@ -573,11 +616,82 @@ public class GridMap : MonoBehaviour } } - //Debug.Log($"Grid state loaded from {filePath}"); + // 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(); + } + } + + Debug.Log($"Grid state loaded from {filePath}"); } catch (System.Exception e) { - //Debug.LogError($"Error loading grid state: {e.Message}"); + Debug.LogError($"Error loading grid state: {e.Message}"); + } + } + + /// + /// 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++) + { + GridNode node = GetGridNode(i, j); + if (node != null && node.IsWalkable) + { + npc.SetStartNode(node); + return; + } + } } } @@ -1007,4 +1121,46 @@ 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) + { + SetDestination(i, j); + return; + } + } + } + + // 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/NPC.cs b/Assets/Scripts/NPC.cs index 0265ee1..e607e9b 100644 --- a/Assets/Scripts/NPC.cs +++ b/Assets/Scripts/NPC.cs @@ -254,6 +254,9 @@ public class NPC : MonoBehaviour // Pre-allocate visualizationSteps with estimated capacity to avoid reallocations visualizationSteps = new List(4); + GC.Collect(); + GC.WaitForPendingFinalizers(); // Tunggu semua finalizers selesai + // ===== MEMORY MEASUREMENT START: Ukur memory sebelum algoritma ===== long memoryBefore = System.GC.GetTotalMemory(false); @@ -281,6 +284,13 @@ public class NPC : MonoBehaviour long memoryAfter = System.GC.GetTotalMemory(false); long memoryUsed = memoryAfter - memoryBefore; + // float miliseconds = algorithmTimer.ElapsedMilliseconds; + + //UnityEngine.Debug.Log("$algorithmTimer.ElapsedTicks: " + algorithmTimer.ElapsedTicks); + //UnityEngine.Debug.Log("$Stopwatch.Frequency: " + Stopwatch.Frequency); + //float seconds = (float)algorithmTimer.ElapsedTicks / Stopwatch.Frequency; + //UnityEngine.Debug.Log("$seconds: " + seconds); + float milliseconds = (algorithmTimer.ElapsedTicks * 1000.0f) / Stopwatch.Frequency; // Calculate path length once and reuse @@ -327,7 +337,7 @@ public class NPC : MonoBehaviour yield return null; } - + /// /// Setup callbacks for tracking nodes in open/closed lists and visualization /// diff --git a/Assets/Scripts/PathfindingUIManager.cs b/Assets/Scripts/PathfindingUIManager.cs index 4cc3c3a..a98f602 100644 --- a/Assets/Scripts/PathfindingUIManager.cs +++ b/Assets/Scripts/PathfindingUIManager.cs @@ -374,7 +374,7 @@ public class PathfindingUIManager : MonoBehaviour string filePath = Path.Combine(saveDirectory, $"{mapNameInput.text}.json"); gridMap.SaveGridState(filePath); - Debug.Log($"Map saved to: {filePath}"); + Debug.Log($"Map saved to: {filePath} (includes grid state, NPC position, and destination position)"); } ///