diff --git a/Assets/Scenes/Pathfinding.unity b/Assets/Scenes/Pathfinding.unity
index a73e332..781290d 100644
--- a/Assets/Scenes/Pathfinding.unity
+++ b/Assets/Scenes/Pathfinding.unity
@@ -974,6 +974,173 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_ShowMaskGraphic: 0
+--- !u!1 &111507929
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 111507930}
+ - component: {fileID: 111507932}
+ - component: {fileID: 111507931}
+ m_Layer: 5
+ m_Name: Fill
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &111507930
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 111507929}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 1980971369}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 10, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &111507931
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 111507929}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &111507932
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 111507929}
+ m_CullTransparentMesh: 1
+--- !u!1 &115484931
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 115484932}
+ - component: {fileID: 115484933}
+ m_Layer: 5
+ m_Name: Slider
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &115484932
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 115484931}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 915989142}
+ - {fileID: 1980971369}
+ - {fileID: 351573955}
+ m_Father: {fileID: 873489265}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: 661, y: -494}
+ m_SizeDelta: {x: 160, y: 20}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &115484933
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 115484931}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 67db9e8f0e2ae9c40bc1e2b64352a6b4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Navigation:
+ m_Mode: 3
+ m_WrapAround: 0
+ m_SelectOnUp: {fileID: 0}
+ m_SelectOnDown: {fileID: 0}
+ m_SelectOnLeft: {fileID: 0}
+ m_SelectOnRight: {fileID: 0}
+ m_Transition: 1
+ m_Colors:
+ m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+ m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+ m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+ m_ColorMultiplier: 1
+ m_FadeDuration: 0.1
+ m_SpriteState:
+ m_HighlightedSprite: {fileID: 0}
+ m_PressedSprite: {fileID: 0}
+ m_SelectedSprite: {fileID: 0}
+ m_DisabledSprite: {fileID: 0}
+ m_AnimationTriggers:
+ m_NormalTrigger: Normal
+ m_HighlightedTrigger: Highlighted
+ m_PressedTrigger: Pressed
+ m_SelectedTrigger: Selected
+ m_DisabledTrigger: Disabled
+ m_Interactable: 1
+ m_TargetGraphic: {fileID: 813565619}
+ m_FillRect: {fileID: 111507930}
+ m_HandleRect: {fileID: 813565618}
+ m_Direction: 0
+ m_MinValue: 0
+ m_MaxValue: 1
+ m_WholeNumbers: 0
+ m_Value: 0
+ m_OnValueChanged:
+ m_PersistentCalls:
+ m_Calls: []
--- !u!1 &115565369
GameObject:
m_ObjectHideFlags: 0
@@ -2212,6 +2379,96 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 332997443}
m_CullTransparentMesh: 1
+--- !u!1 &351573954
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 351573955}
+ m_Layer: 5
+ m_Name: Handle Slide Area
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &351573955
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 351573954}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 813565618}
+ m_Father: {fileID: 115484932}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: -20, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!1 &371969815
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 371969817}
+ - component: {fileID: 371969816}
+ m_Layer: 0
+ m_Name: Test
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 0
+--- !u!114 &371969816
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 371969815}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: ed03b279fa9f3194f8caed356ea838dc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ gridMap: {fileID: 1193653632}
+ npc: {fileID: 497641072}
+ startTestButton: {fileID: 1287641230}
+ statusText: {fileID: 1778780372}
+ progressBar: {fileID: 115484933}
+ testsPerCombination: 3
+ delayBetweenTests: 0.5
+ saveResultsToFile: 1
+ resultsFileName: pathfinding_tests.csv
+--- !u!4 &371969817
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 371969815}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 1324.0585, y: 547.7678, z: -5.158931}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 9
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &375331086
GameObject:
m_ObjectHideFlags: 0
@@ -4605,6 +4862,82 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 809150985}
m_CullTransparentMesh: 1
+--- !u!1 &813565617
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 813565618}
+ - component: {fileID: 813565620}
+ - component: {fileID: 813565619}
+ m_Layer: 5
+ m_Name: Handle
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &813565618
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 813565617}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 351573955}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 20, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &813565619
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 813565617}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 0
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &813565620
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 813565617}
+ m_CullTransparentMesh: 1
--- !u!1 &814740740
GameObject:
m_ObjectHideFlags: 0
@@ -4829,9 +5162,11 @@ MonoBehaviour:
loadButton: {fileID: 332997445}
exitButton: {fileID: 1403363528}
performWarmup: 1
- showWarmupMessage: 0
+ showWarmupMessage: 1
mazeSizeDropdown: {fileID: 770082012}
mazeDensityDropdown: {fileID: 1448650261}
+ testerPanelPrefab: {fileID: 0}
+ testerPanelParent: {fileID: 0}
--- !u!4 &840407876
Transform:
m_ObjectHideFlags: 0
@@ -5023,6 +5358,45 @@ MonoBehaviour:
m_isRichTextEditingAllowed: 0
m_LineLimit: 0
m_InputValidator: {fileID: 0}
+--- !u!1 &873489264
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 873489265}
+ m_Layer: 5
+ m_Name: Test
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 0
+--- !u!224 &873489265
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 873489264}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 1287641229}
+ - {fileID: 1778780371}
+ - {fileID: 115484932}
+ m_Father: {fileID: 1261321007}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: -150, y: 155}
+ m_SizeDelta: {x: 100, y: 100}
+ m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &877190939
GameObject:
m_ObjectHideFlags: 0
@@ -5158,6 +5532,82 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 877190939}
m_CullTransparentMesh: 1
+--- !u!1 &915989141
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 915989142}
+ - component: {fileID: 915989144}
+ - component: {fileID: 915989143}
+ m_Layer: 5
+ m_Name: Background
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &915989142
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 915989141}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 115484932}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0.25}
+ m_AnchorMax: {x: 1, y: 0.75}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &915989143
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 915989141}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &915989144
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 915989141}
+ m_CullTransparentMesh: 1
--- !u!1 &924975606
GameObject:
m_ObjectHideFlags: 0
@@ -7627,6 +8077,7 @@ RectTransform:
- {fileID: 442352525}
- {fileID: 1963564225}
- {fileID: 1649005401}
+ - {fileID: 873489265}
m_Father: {fileID: 0}
m_RootOrder: 5
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -7711,6 +8162,128 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1269161862}
m_CullTransparentMesh: 1
+--- !u!1 &1287641228
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1287641229}
+ - component: {fileID: 1287641232}
+ - component: {fileID: 1287641231}
+ - component: {fileID: 1287641230}
+ m_Layer: 5
+ m_Name: Button
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1287641229
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1287641228}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 1956300463}
+ m_Father: {fileID: 873489265}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: 166, y: -494}
+ m_SizeDelta: {x: 160, y: 30}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1287641230
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1287641228}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Navigation:
+ m_Mode: 3
+ m_WrapAround: 0
+ m_SelectOnUp: {fileID: 0}
+ m_SelectOnDown: {fileID: 0}
+ m_SelectOnLeft: {fileID: 0}
+ m_SelectOnRight: {fileID: 0}
+ m_Transition: 1
+ m_Colors:
+ m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+ m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+ m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+ m_ColorMultiplier: 1
+ m_FadeDuration: 0.1
+ m_SpriteState:
+ m_HighlightedSprite: {fileID: 0}
+ m_PressedSprite: {fileID: 0}
+ m_SelectedSprite: {fileID: 0}
+ m_DisabledSprite: {fileID: 0}
+ m_AnimationTriggers:
+ m_NormalTrigger: Normal
+ m_HighlightedTrigger: Highlighted
+ m_PressedTrigger: Pressed
+ m_SelectedTrigger: Selected
+ m_DisabledTrigger: Disabled
+ m_Interactable: 1
+ m_TargetGraphic: {fileID: 1287641231}
+ m_OnClick:
+ m_PersistentCalls:
+ m_Calls: []
+--- !u!114 &1287641231
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1287641228}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &1287641232
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1287641228}
+ m_CullTransparentMesh: 1
--- !u!1 &1383327469
GameObject:
m_ObjectHideFlags: 0
@@ -11124,6 +11697,141 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Padding: {x: -8, y: -5, z: -8, w: -5}
m_Softness: {x: 0, y: 0}
+--- !u!1 &1778780370
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1778780371}
+ - component: {fileID: 1778780373}
+ - component: {fileID: 1778780372}
+ m_Layer: 5
+ m_Name: Status
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1778780371
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1778780370}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 873489265}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: 665.35645, y: -406.85294}
+ m_SizeDelta: {x: 196.7319, y: 125.3129}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1778780372
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1778780370}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_text: New Text
+ m_isRightToLeft: 0
+ m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
+ m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
+ m_fontSharedMaterials: []
+ m_fontMaterial: {fileID: 0}
+ m_fontMaterials: []
+ m_fontColor32:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontColor: {r: 1, g: 1, b: 1, a: 1}
+ m_enableVertexGradient: 0
+ m_colorMode: 3
+ m_fontColorGradient:
+ topLeft: {r: 1, g: 1, b: 1, a: 1}
+ topRight: {r: 1, g: 1, b: 1, a: 1}
+ bottomLeft: {r: 1, g: 1, b: 1, a: 1}
+ bottomRight: {r: 1, g: 1, b: 1, a: 1}
+ m_fontColorGradientPreset: {fileID: 0}
+ m_spriteAsset: {fileID: 0}
+ m_tintAllSprites: 0
+ m_StyleSheet: {fileID: 0}
+ m_TextStyleHashCode: -1183493901
+ m_overrideHtmlColors: 0
+ m_faceColor:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontSize: 25
+ m_fontSizeBase: 36
+ m_fontWeight: 400
+ m_enableAutoSizing: 1
+ m_fontSizeMin: 18
+ m_fontSizeMax: 25
+ m_fontStyle: 0
+ m_HorizontalAlignment: 2
+ m_VerticalAlignment: 512
+ m_textAlignment: 65535
+ m_characterSpacing: 0
+ m_wordSpacing: 0
+ m_lineSpacing: 0
+ m_lineSpacingMax: 0
+ m_paragraphSpacing: 0
+ m_charWidthMaxAdj: 0
+ m_enableWordWrapping: 1
+ m_wordWrappingRatios: 0.4
+ m_overflowMode: 0
+ m_linkedTextComponent: {fileID: 0}
+ parentLinkedComponent: {fileID: 0}
+ m_enableKerning: 1
+ m_enableExtraPadding: 0
+ checkPaddingRequired: 0
+ m_isRichText: 1
+ m_parseCtrlCharacters: 1
+ m_isOrthographic: 1
+ m_isCullingEnabled: 0
+ m_horizontalMapping: 0
+ m_verticalMapping: 0
+ m_uvLineOffset: 0
+ m_geometrySortingOrder: 0
+ m_IsTextObjectScaleStatic: 0
+ m_VertexBufferAutoSizeReduction: 0
+ m_useMaxVisibleDescender: 1
+ m_pageToDisplay: 1
+ m_margin: {x: 0, y: 0, z: 0, w: 0}
+ m_isUsingLegacyAnimationComponent: 0
+ m_isVolumetricText: 0
+ m_hasFontAssetChanged: 0
+ m_baseMaterial: {fileID: 0}
+ m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
+--- !u!222 &1778780373
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1778780370}
+ m_CullTransparentMesh: 1
--- !u!1 &1799536494
GameObject:
m_ObjectHideFlags: 0
@@ -12326,6 +13034,141 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1939810452}
m_CullTransparentMesh: 1
+--- !u!1 &1956300462
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1956300463}
+ - component: {fileID: 1956300465}
+ - component: {fileID: 1956300464}
+ m_Layer: 5
+ m_Name: Text (TMP)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1956300463
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1956300462}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 1287641229}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1956300464
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1956300462}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_text: Start
+ m_isRightToLeft: 0
+ m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
+ m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
+ m_fontSharedMaterials: []
+ m_fontMaterial: {fileID: 0}
+ m_fontMaterials: []
+ m_fontColor32:
+ serializedVersion: 2
+ rgba: 4281479730
+ m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_enableVertexGradient: 0
+ m_colorMode: 3
+ m_fontColorGradient:
+ topLeft: {r: 1, g: 1, b: 1, a: 1}
+ topRight: {r: 1, g: 1, b: 1, a: 1}
+ bottomLeft: {r: 1, g: 1, b: 1, a: 1}
+ bottomRight: {r: 1, g: 1, b: 1, a: 1}
+ m_fontColorGradientPreset: {fileID: 0}
+ m_spriteAsset: {fileID: 0}
+ m_tintAllSprites: 0
+ m_StyleSheet: {fileID: 0}
+ m_TextStyleHashCode: -1183493901
+ m_overrideHtmlColors: 0
+ m_faceColor:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontSize: 24
+ m_fontSizeBase: 24
+ m_fontWeight: 400
+ m_enableAutoSizing: 0
+ m_fontSizeMin: 18
+ m_fontSizeMax: 72
+ m_fontStyle: 0
+ m_HorizontalAlignment: 2
+ m_VerticalAlignment: 512
+ m_textAlignment: 65535
+ m_characterSpacing: 0
+ m_wordSpacing: 0
+ m_lineSpacing: 0
+ m_lineSpacingMax: 0
+ m_paragraphSpacing: 0
+ m_charWidthMaxAdj: 0
+ m_enableWordWrapping: 1
+ m_wordWrappingRatios: 0.4
+ m_overflowMode: 0
+ m_linkedTextComponent: {fileID: 0}
+ parentLinkedComponent: {fileID: 0}
+ m_enableKerning: 1
+ m_enableExtraPadding: 0
+ checkPaddingRequired: 0
+ m_isRichText: 1
+ m_parseCtrlCharacters: 1
+ m_isOrthographic: 1
+ m_isCullingEnabled: 0
+ m_horizontalMapping: 0
+ m_verticalMapping: 0
+ m_uvLineOffset: 0
+ m_geometrySortingOrder: 0
+ m_IsTextObjectScaleStatic: 0
+ m_VertexBufferAutoSizeReduction: 0
+ m_useMaxVisibleDescender: 1
+ m_pageToDisplay: 1
+ m_margin: {x: 0, y: 0, z: 0, w: 0}
+ m_isUsingLegacyAnimationComponent: 0
+ m_isVolumetricText: 0
+ m_hasFontAssetChanged: 0
+ m_baseMaterial: {fileID: 0}
+ m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
+--- !u!222 &1956300465
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1956300462}
+ m_CullTransparentMesh: 1
--- !u!1 &1960286546
GameObject:
m_ObjectHideFlags: 0
@@ -12447,6 +13290,43 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1963564224}
m_CullTransparentMesh: 1
+--- !u!1 &1980971368
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1980971369}
+ m_Layer: 5
+ m_Name: Fill Area
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1980971369
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1980971368}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 111507930}
+ m_Father: {fileID: 115484932}
+ m_RootOrder: -1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0.25}
+ m_AnchorMax: {x: 1, y: 0.75}
+ m_AnchoredPosition: {x: -5, y: 0}
+ m_SizeDelta: {x: -20, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1982768388
GameObject:
m_ObjectHideFlags: 0
diff --git a/Assets/Scripts/GridMap.cs b/Assets/Scripts/GridMap.cs
index ad1c6f2..39325d8 100644
--- a/Assets/Scripts/GridMap.cs
+++ b/Assets/Scripts/GridMap.cs
@@ -239,6 +239,15 @@ public class GridMap : MonoBehaviour
///
public void RayCastAndSetDestination()
{
+ // Don't allow changing destination during pathfinding, visualization, or movement
+ 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,
@@ -291,6 +300,15 @@ public class GridMap : MonoBehaviour
///
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 ||
+ 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,
@@ -318,6 +336,11 @@ public class GridMap : MonoBehaviour
///
void Update()
{
+ // Check if pathfinding, visualization, or movement is active
+ 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
{
@@ -336,6 +359,13 @@ 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
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
{
@@ -357,7 +387,10 @@ public class GridMap : MonoBehaviour
// Menetapkan tujuan baru saat tombol kanan mouse ditekan
if (Input.GetMouseButtonDown(1))
{
- RayCastAndSetDestination();
+ if (!isPathfindingActive)
+ {
+ RayCastAndSetDestination();
+ }
}
// Menyesuaikan ukuran kamera dengan scroll wheel
@@ -698,8 +731,24 @@ public class GridMap : MonoBehaviour
///
/// Resizes the grid to the specified dimensions
///
- public void ResizeGrid(int newSizeX, int newSizeY)
+ 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)
{
@@ -749,6 +798,8 @@ public class GridMap : MonoBehaviour
{
npc.SetStartNode(gridNodeViews[0, 0].Node);
}
+
+ return true;
}
///
@@ -771,7 +822,53 @@ public class GridMap : MonoBehaviour
/// Kepadatan dinding dalam persen (0-100), mempengaruhi rasio jalur terhadap ruang terbuka
public void GenerateRandomMaze(float density = 35f)
{
- // Use the recursive backtracking maze generation
+ // 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++)
+ {
+ GridNode node = GetGridNode(x, y);
+ if (node != null)
+ {
+ node.IsWalkable = true;
+ GridNodeView gnv = GetGridNodeView(x, y);
+ if (gnv != null)
+ {
+ gnv.SetInnerColor(COLOR_WALKABLE);
+ }
+ }
+ }
+ }
+ return;
+ }
+ 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++)
+ {
+ GridNode node = GetGridNode(x, y);
+ if (node != null)
+ {
+ node.IsWalkable = false;
+ GridNodeView gnv = GetGridNodeView(x, y);
+ if (gnv != null)
+ {
+ gnv.SetInnerColor(COLOR_NONWALKABLE);
+ }
+ }
+ }
+ }
+
+ // No path creation - truly 100% blocked
+ return;
+ }
+
+ // Use the recursive backtracking maze generation for normal density values
GenerateRecursiveBacktrackingMaze(density);
}
diff --git a/Assets/Scripts/NPC.cs b/Assets/Scripts/NPC.cs
index e607e9b..1b06128 100644
--- a/Assets/Scripts/NPC.cs
+++ b/Assets/Scripts/NPC.cs
@@ -21,17 +21,26 @@ public struct PathfindingMetrics
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)
-
}
+///
+/// NPC adalah komponen utama yang mengelola pergerakan NPC dalam sistem pathfinding.
+/// Kelas ini bertanggung jawab untuk membuat, menampilkan, dan mengelola jalur untuk NPC.
+///
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 long LastMeasuredMemoryUsage { get; private set; } = 0;
+
+ ///
+ /// Enumerasi yang merepresentasikan berbagai algoritma pathfinding yang tersedia.
+ ///
public enum PathFinderType
{
ASTAR,
@@ -44,13 +53,26 @@ public class NPC : MonoBehaviour
[SerializeField]
public PathFinderType pathFinderType = PathFinderType.ASTAR;
- PathFinder pathFinder = null;
+ public PathFinder pathFinder = null;
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]
@@ -110,11 +132,27 @@ 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;
}
}
@@ -236,8 +274,10 @@ public class NPC : MonoBehaviour
{
yield return StartCoroutine(MeasurePerformance(silentMode));
- // Start visualization after calculation is complete
- if (pathFinder.Status == PathFinderStatus.SUCCESS && showVisualization && !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 &&
+ (pathFinder.Status == PathFinderStatus.SUCCESS || pathFinder.Status == PathFinderStatus.FAILURE))
{
yield return StartCoroutine(VisualizePathfinding());
}
@@ -254,8 +294,8 @@ 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
+ //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);
@@ -284,13 +324,9 @@ 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);
-
+ // 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
@@ -324,11 +360,9 @@ public class NPC : MonoBehaviour
totalFCost = totalFCost,
};
+ // *** IMPORTANT FIX: Always invoke the event, regardless of silent mode ***
// Report metrics before visualization
- if (!silentMode)
- {
- OnPathfindingComplete?.Invoke(metrics);
- }
+ OnPathfindingComplete?.Invoke(metrics);
// Path visualization and handling
HandlePathFindingResult(silentMode, pathLength);
@@ -418,7 +452,6 @@ public class NPC : MonoBehaviour
///
private void HandlePathFindingResult(bool silentMode, int pathLength)
{
-
if (pathFinder.Status == PathFinderStatus.SUCCESS)
{
OnSuccessPathFinding();
@@ -449,6 +482,11 @@ 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");
}
}
@@ -554,8 +592,9 @@ public class NPC : MonoBehaviour
if (!showVisualization)
yield break;
+ UnityEngine.Debug.Log("Path visualization starting");
isVisualizingPath = true;
-
+
// First, ensure grid is reset
Map.ResetGridNodeColours();
@@ -563,6 +602,14 @@ public class NPC : MonoBehaviour
int stepCount = visualizationSteps.Count;
int batchSize = Mathf.Min(visualizationBatch, stepCount); // set higher value for faster visualization
+ // 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");
+ }
+
for (int i = 0; i < stepCount; i += batchSize)
{
int end = Mathf.Min(i + batchSize, stepCount);
@@ -587,8 +634,8 @@ public class NPC : MonoBehaviour
break;
case PathfindingVisualizationStep.StepType.FinalPath:
gnv.SetInnerColor(Map.COLOR_PATH);
- // Also add the waypoint when we process the path
- if (step.type == PathfindingVisualizationStep.StepType.FinalPath)
+ // Only add waypoints for successful pathfinding
+ if (!pathfindingFailed)
{
GridNode pathNode = Map.GetGridNode(step.position.x, step.position.y);
AddWayPoint(pathNode);
@@ -603,6 +650,9 @@ public class NPC : MonoBehaviour
}
isVisualizingPath = false;
+ UnityEngine.Debug.Log("Path visualization complete, invoking OnVisualizationComplete event");
+ // Notify any listeners that visualization is complete
+ OnVisualizationComplete?.Invoke();
}
///
diff --git a/Assets/Scripts/PathFinder.cs b/Assets/Scripts/PathFinder.cs
index 9dbdf22..be2bd33 100644
--- a/Assets/Scripts/PathFinder.cs
+++ b/Assets/Scripts/PathFinder.cs
@@ -1360,7 +1360,8 @@ namespace PathFinding
#region Breath-First Search Algorithm
///
/// Implementasi algoritma Breadth-First Search (BFS)
- /// Algoritma ini menjelajahi semua node pada jarak yang sama dari titik awal sebelum bergerak ke node yang lebih jauh
+ /// Algoritma ini menjelajahi semua node pada jarak yang sama dari
+ /// titik awal sebelum bergerak ke node yang lebih jauh
///
/// Tipe data nilai yang disimpan dalam node
public class BFSPathFinder : PathFinder
diff --git a/Assets/Scripts/PathfindingTestAnalyzer.cs b/Assets/Scripts/PathfindingTestAnalyzer.cs
new file mode 100644
index 0000000..b19a3fa
--- /dev/null
+++ b/Assets/Scripts/PathfindingTestAnalyzer.cs
@@ -0,0 +1,419 @@
+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
new file mode 100644
index 0000000..1709582
--- /dev/null
+++ b/Assets/Scripts/PathfindingTestAnalyzer.cs.meta
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..328c110
--- /dev/null
+++ b/Assets/Scripts/PathfindingTester.cs
@@ -0,0 +1,510 @@
+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
new file mode 100644
index 0000000..2cef5ac
--- /dev/null
+++ b/Assets/Scripts/PathfindingTester.cs.meta
@@ -0,0 +1,11 @@
+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 a98f602..60f891c 100644
--- a/Assets/Scripts/PathfindingUIManager.cs
+++ b/Assets/Scripts/PathfindingUIManager.cs
@@ -1,7 +1,9 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
-using System.Collections;
using System.IO;
public class PathfindingUIManager : MonoBehaviour
@@ -52,9 +54,23 @@ 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
@@ -91,6 +107,13 @@ public class PathfindingUIManager : MonoBehaviour
// 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();
@@ -213,6 +236,9 @@ public class PathfindingUIManager : MonoBehaviour
if (npc != null)
{
npc.OnPathfindingComplete -= UpdatePerformanceMetrics;
+ npc.OnPathfindingComplete -= OnPathfindingCompleted;
+ npc.OnVisualizationComplete -= OnVisualizationCompleted;
+ npc.OnMovementComplete -= OnMovementCompleted;
}
// Unsubscribe from UI events
@@ -228,6 +254,14 @@ public class PathfindingUIManager : MonoBehaviour
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 {
@@ -270,6 +304,20 @@ 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
+ }
+ }
+
private void ClearPerformanceMetrics()
{
timeEstimateText.text = "0";
@@ -312,8 +360,42 @@ public class PathfindingUIManager : MonoBehaviour
if (int.TryParse(gridSizeXInput.text, out int newSizeX) &&
int.TryParse(gridSizeYInput.text, out int newSizeY))
{
- gridMap.ResizeGrid(newSizeX, newSizeY);
- ClearPerformanceMetrics();
+ // 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();
+ }
}
}
@@ -336,12 +418,28 @@ public class PathfindingUIManager : MonoBehaviour
if (startNode != null && endNode != null)
{
ClearPerformanceMetrics();
+
+ // Set flags that pathfinding, visualization, and movement will happen
+ isPathfindingRunning = true;
+ isVisualizationRunning = true;
+ isNpcMoving = true; // assume movement will happen
+ SetUIInteractivity(false);
+
npc.MoveTo(endNode);
}
}
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);
@@ -352,10 +450,34 @@ public class PathfindingUIManager : MonoBehaviour
private void OnAlgorithmChanged(int index)
{
NPC.PathFinderType newType = (NPC.PathFinderType)index;
+ string algorithmName = GetAlgorithmName(newType);
+
+ Debug.Log($"Algorithm changed - Index: {index}, Type: {newType}, Name: {algorithmName}");
+
npc.ChangeAlgorithm(newType);
ClearPerformanceMetrics();
}
+ // Helper method to get the readable name of the algorithm
+ private string GetAlgorithmName(NPC.PathFinderType type)
+ {
+ switch (type)
+ {
+ case NPC.PathFinderType.ASTAR:
+ return "A*";
+ case NPC.PathFinderType.DIJKSTRA:
+ return "Dijkstra";
+ case NPC.PathFinderType.GREEDY:
+ return "Greedy BFS";
+ case NPC.PathFinderType.BACKTRACKING:
+ return "Backtracking";
+ case NPC.PathFinderType.BFS:
+ return "BFS";
+ default:
+ return "Unknown";
+ }
+ }
+
private void OnSaveMap()
{
if (string.IsNullOrEmpty(mapNameInput.text))
@@ -426,24 +548,45 @@ public class PathfindingUIManager : MonoBehaviour
int sizeY = 20;
bool isLargeGrid = false;
+ const int MAX_GRID_SIZE = 200;
+ const int MIN_GRID_SIZE = 2;
+
switch (mazeSizeDropdown.value)
{
case 0: // Kecil
sizeX = sizeY = 20;
break;
case 1: // Sedang
- sizeX = sizeY = 50;
+ sizeX = sizeY = 35;
break;
case 2: // Besar
- sizeX = sizeY = 100;
+ sizeX = sizeY = 50;
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.");
+ return;
+ }
+
+ if (sizeX < MIN_GRID_SIZE || sizeY < MIN_GRID_SIZE)
+ {
+ Debug.LogWarning($"Maze size {sizeX}x{sizeY} is below minimum of {MIN_GRID_SIZE}x{MIN_GRID_SIZE}. Operation cancelled.");
+ return;
}
// Resize grid if needed
if (gridMap.NumX != sizeX || gridMap.NumY != sizeY)
{
- gridMap.ResizeGrid(sizeX, sizeY);
+ if (!gridMap.ResizeGrid(sizeX, sizeY))
+ {
+ // Resize failed, abort maze generation
+ return;
+ }
// Update grid size inputs
gridSizeXInput.text = sizeX.ToString();
@@ -586,4 +729,196 @@ public class PathfindingUIManager : MonoBehaviour
// 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;
+
+ // Only re-enable in editor mode
+#if UNITY_EDITOR
+ SetUIInteractivity(true);
+#else
+ // In build, keep disabled
+ SetUIInteractivity(false);
+#endif
+
+ 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
+ // In build, keep disabled
+ SetUIInteractivity(false);
+#endif
+ }
+ }
+
+ // 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
+ // In build, keep disabled
+ SetUIInteractivity(false);
+#endif
+ }
+ }
+
+ // 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
+ // In build, keep disabled
+ Debug.Log("Movement complete but keeping buttons disabled in build mode");
+ SetUIInteractivity(false);
+#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))
+ {
+ // In builds, once pathfinding started, keep buttons disabled regardless
+ Debug.Log("In build - keeping buttons disabled even after completion");
+ shouldEnable = false;
+ }
+#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;
+
+ if (runPathfindingButton != null)
+ runPathfindingButton.interactable = shouldEnable;
+
+ if (algorithmDropdown != null)
+ algorithmDropdown.interactable = shouldEnable;
+
+ if (allowDiagonalToggle != null)
+ allowDiagonalToggle.interactable = shouldEnable;
+
+ if (saveButton != null)
+ saveButton.interactable = shouldEnable;
+
+ if (loadButton != null)
+ loadButton.interactable = shouldEnable;
+
+ if (generateMazeButton != null)
+ generateMazeButton.interactable = shouldEnable;
+
+ if (visualizationSpeedSlider != null)
+ visualizationSpeedSlider.interactable = shouldEnable;
+
+ if (visualizationBatchSlider != null)
+ visualizationBatchSlider.interactable = shouldEnable;
+
+ if (mazeSizeDropdown != null)
+ mazeSizeDropdown.interactable = shouldEnable;
+
+ if (mazeDensityDropdown != null)
+ mazeDensityDropdown.interactable = shouldEnable;
+
+ if (gridSizeXInput != null)
+ gridSizeXInput.interactable = shouldEnable;
+
+ if (gridSizeYInput != null)
+ gridSizeYInput.interactable = shouldEnable;
+
+ 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");
+ isVisualizationRunning = true;
+ SetUIInteractivity(false);
+ }
+
+ // Check movement
+ if (npc.IsMoving && !isNpcMoving)
+ {
+ Debug.Log("Detected active NPC movement - updating UI state");
+ isNpcMoving = true;
+ SetUIInteractivity(false);
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/Assets/Scripts/TestPlan.md b/Assets/Scripts/TestPlan.md
new file mode 100644
index 0000000..df60eb3
--- /dev/null
+++ b/Assets/Scripts/TestPlan.md
@@ -0,0 +1,167 @@
+# Rencana Pengujian Sistem Pathfinding
+
+Dokumen ini menguraikan pendekatan pengujian komprehensif untuk sistem pathfinding, termasuk pengujian komponen sistem dan pengujian kinerja algoritma.
+
+## I. Pengujian Komponen Sistem
+
+### 1. Pengujian Input Ukuran Grid
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| GS-01 | Memasukkan ukuran grid yang valid (mis., 20x20) | Grid diubah ukuran dengan benar | □ |
+| GS-02 | Memasukkan ukuran grid maksimum (200x200) | Grid diubah ukuran ke ukuran maksimum | □ |
+| GS-03 | Memasukkan nilai melebihi maksimum (mis., 201x201) | Operasi ditolak, peringatan ditampilkan | □ |
+| GS-04 | Memasukkan nilai non-numerik | Input ditolak | □ |
+| GS-05 | Memasukkan nilai negatif atau nol | Input ditolak | □ |
+| GS-06 | Mengubah ukuran selama pathfinding | Operasi diblokir selama pathfinding | □ |
+| GS-07 | Menguji grid tidak persegi (mis., 20x30) | Grid membuat bentuk persegi panjang yang benar | □ |
+
+### 2. Pengujian Dropdown Algoritma
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| ALG-01 | Pilih A* | Algoritma berubah ke A* | □ |
+| ALG-02 | Pilih Dijkstra | Algoritma berubah ke Dijkstra | □ |
+| ALG-03 | Pilih Greedy | Algoritma berubah ke Greedy | □ |
+| ALG-04 | Pilih Backtracking | Algoritma berubah ke Backtracking | □ |
+| ALG-05 | Pilih BFS | Algoritma berubah ke BFS | □ |
+| ALG-06 | Mengubah algoritma selama pathfinding | Operasi diblokir selama pathfinding | □ |
+
+### 3. Pengujian Toggle Pergerakan Diagonal
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| DIAG-01 | Aktifkan pergerakan diagonal | Pergerakan diagonal diaktifkan, jalur dapat menggunakan diagonal | □ |
+| DIAG-02 | Nonaktifkan pergerakan diagonal | Pergerakan diagonal dinonaktifkan, jalur hanya menggunakan arah kardinal | □ |
+| DIAG-03 | Toggle selama pathfinding | Operasi diblokir selama pathfinding | □ |
+
+### 4. Pengujian Kontrol Visualisasi
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| VIS-01 | Menyesuaikan kecepatan visualisasi (meningkat) | Visualisasi berjalan lebih cepat | □ |
+| VIS-02 | Menyesuaikan kecepatan visualisasi (menurun) | Visualisasi berjalan lebih lambat | □ |
+| VIS-03 | Mengatur ukuran batch ke 1 | Setiap langkah divisualisasikan secara individual | □ |
+| VIS-04 | Mengatur ukuran batch ke nilai lebih tinggi | Beberapa langkah divisualisasikan bersama | □ |
+| VIS-05 | Menyesuaikan kontrol selama pathfinding | Operasi diblokir selama pathfinding | □ |
+
+### 5. Pengujian Generator Labirin
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| MAZE-01 | Menghasilkan labirin kecil (kepadatan rendah) | Labirin dihasilkan dengan sedikit rintangan | □ |
+| MAZE-02 | Menghasilkan labirin sedang (kepadatan sedang) | Labirin dihasilkan dengan rintangan moderat | □ |
+| MAZE-03 | Menghasilkan labirin besar (kepadatan tinggi) | Labirin dihasilkan dengan banyak rintangan | □ |
+| MAZE-04 | Menghasilkan labirin dengan kepadatan 0% | Grid kosong sepenuhnya dihasilkan | □ |
+| MAZE-05 | Menghasilkan labirin dengan kepadatan 100% | Grid terisi penuh dihasilkan | □ |
+| MAZE-06 | Menghasilkan labirin selama pathfinding | Operasi diblokir selama pathfinding | □ |
+
+### 6. Pengujian Simpan dan Muat
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| SAVE-01 | Simpan dengan nama file valid | Labirin berhasil disimpan | □ |
+| SAVE-02 | Simpan dengan nama file kosong | Pesan kesalahan ditampilkan | □ |
+| SAVE-03 | Simpan selama pathfinding | Operasi diblokir selama pathfinding | □ |
+| LOAD-01 | Muat file yang ada | Labirin berhasil dimuat | □ |
+| LOAD-02 | Muat dengan nama file yang tidak ada | Pesan kesalahan ditampilkan | □ |
+| LOAD-03 | Muat selama pathfinding | Operasi diblokir selama pathfinding | □ |
+
+### 7. Pengujian Tombol Muat Ulang
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| RELOAD-01 | Klik tombol muat ulang | Scene dimuat ulang ke keadaan awal | □ |
+| RELOAD-02 | Klik muat ulang selama pathfinding | Scene dimuat ulang, operasi diizinkan selama pathfinding | □ |
+| RELOAD-03 | Klik muat ulang setelah pathfinding dalam mode build | Tombol diaktifkan kembali setelah reset | □ |
+
+### 8. Pengujian Status UI
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| UI-01 | Mulai pathfinding | Semua tombol dinonaktifkan kecuali reload dan exit | □ |
+| UI-02 | Setelah pathfinding selesai (editor) | Tombol diaktifkan kembali | □ |
+| UI-03 | Setelah pathfinding selesai (build) | Tombol tetap dinonaktifkan | □ |
+| UI-04 | Interaksi mouse selama pathfinding | Repositioning NPC/tujuan diblokir | □ |
+
+## II. Pengujian Kinerja Algoritma
+
+### 1. Pengujian Algoritma A*
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| ASTAR-01 | Grid kecil (20x20), kepadatan rendah | Jalur ditemukan secara efisien | □ |
+| ASTAR-02 | Grid sedang (50x50), kepadatan sedang | Jalur ditemukan dengan kinerja wajar | □ |
+| ASTAR-03 | Grid besar (100x100), kepadatan tinggi | Jalur ditemukan tanpa waktu/memori berlebihan | □ |
+| ASTAR-04 | Jalur mustahil (kepadatan 100%) | Dengan benar melaporkan tidak ada jalur yang ditemukan | □ |
+| ASTAR-05 | Dengan pergerakan diagonal | Jalur diagonal yang lebih pendek digunakan | □ |
+| ASTAR-06 | Tanpa pergerakan diagonal | Hanya jalur kardinal yang digunakan | □ |
+
+### 2. Pengujian Algoritma Dijkstra
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| DIJK-01 | Grid kecil (20x20), kepadatan rendah | Jalur ditemukan dengan eksplorasi lebih dari A* | □ |
+| DIJK-02 | Grid sedang (50x50), kepadatan sedang | Jalur ditemukan dengan waktu/memori lebih tinggi dari A* | □ |
+| DIJK-03 | Grid besar (100x100), kepadatan tinggi | Jalur ditemukan, mungkin dengan penggunaan sumber daya tinggi | □ |
+| DIJK-04 | Jalur mustahil (kepadatan 100%) | Dengan benar melaporkan tidak ada jalur yang ditemukan | □ |
+| DIJK-05 | Dengan pergerakan diagonal | Jalur optimal ditemukan | □ |
+| DIJK-06 | Tanpa pergerakan diagonal | Jalur kardinal optimal ditemukan | □ |
+
+### 3. Pengujian Greedy Best-First Search
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| GREEDY-01 | Grid kecil (20x20), kepadatan rendah | Kinerja cepat, jalur berpotensi tidak optimal | □ |
+| GREEDY-02 | Grid sedang (50x50), kepadatan sedang | Kinerja cepat dengan memori lebih sedikit dari A*/Dijkstra | □ |
+| GREEDY-03 | Grid besar (100x100), kepadatan tinggi | Lebih cepat dari A*/Dijkstra, tetapi mungkin tidak optimal | □ |
+| GREEDY-04 | Jalur mustahil (kepadatan 100%) | Dengan benar melaporkan tidak ada jalur yang ditemukan | □ |
+| GREEDY-05 | Labirin dengan bottleneck | Mungkin menghasilkan jalur yang tidak optimal | □ |
+
+### 4. Pengujian Algoritma Backtracking
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| BACK-01 | Grid kecil (20x20), kepadatan rendah | Jalur ditemukan, kemungkinan lebih lambat dari algoritma lain | □ |
+| BACK-02 | Grid sedang (50x50), kepadatan sedang | Degradasi kinerja dengan ukuran meningkat | □ |
+| BACK-03 | Labirin kecil dengan kepadatan tinggi | Mungkin kesulitan dengan labirin kompleks | □ |
+| BACK-04 | Jalur mustahil (kepadatan 100%) | Dengan benar melaporkan tidak ada jalur yang ditemukan | □ |
+
+### 5. Pengujian Algoritma BFS
+
+| ID Tes | Kasus Uji | Hasil yang Diharapkan | Lulus/Gagal |
+|---------|-----------|----------------|-----------|
+| BFS-01 | Grid kecil (20x20), kepadatan rendah | Jalur optimal untuk grid tanpa bobot | □ |
+| BFS-02 | Grid sedang (50x50), kepadatan sedang | Penggunaan memori lebih tinggi dari A* | □ |
+| BFS-03 | Grid besar (100x100), kepadatan tinggi | Konsumsi memori tinggi | □ |
+| BFS-04 | Jalur mustahil (kepadatan 100%) | Dengan benar melaporkan tidak ada jalur yang ditemukan | □ |
+
+## III. Matriks Kinerja Sistem Keseluruhan
+
+| Algoritma | Grid Kecil | Grid Sedang | Grid Besar | Kepadatan Rendah | Kepadatan Tinggi | Dengan Diagonal | Tanpa Diagonal |
+|-----------|------------|-------------|------------|------------------|-----------------|----------------|----------------|
+| A* | □ | □ | □ | □ | □ | □ | □ |
+| Dijkstra | □ | □ | □ | □ | □ | □ | □ |
+| Greedy | □ | □ | □ | □ | □ | □ | □ |
+| Backtracking | □ | □ | □ | □ | □ | □ | □ |
+| BFS | □ | □ | □ | □ | □ | □ | □ |
+
+## IV. Instruksi Pelaksanaan Pengujian
+
+1. **Persiapan**:
+ - Pastikan proyek dalam keadaan bersih
+ - Untuk pengujian sistem, uji satu komponen pada satu waktu
+ - Untuk pengujian algoritma, gunakan penguji otomatis dengan berbagai kombinasi
+
+2. **Dokumentasi**:
+ - Tandai setiap pengujian sebagai Lulus/Gagal dalam daftar periksa
+ - Catat anomali atau perilaku tidak terduga
+ - Dokumentasikan metrik kinerja jika berlaku
+
+3. **Pengujian Regresi**:
+ - Setelah memperbaiki bug, jalankan kembali pengujian terkait untuk memastikan lulus
+ - Secara berkala jalankan rangkaian pengujian lengkap untuk menangkap regresi
+
+4. **Pengujian Otomatis**:
+ - Gunakan PathfindingTester untuk pengujian algoritma otomatis
+ - Rekam dan analisis output CSV untuk perbandingan komprehensif
\ No newline at end of file
diff --git a/Assets/Scripts/TestPlan.md.meta b/Assets/Scripts/TestPlan.md.meta
new file mode 100644
index 0000000..9407575
--- /dev/null
+++ b/Assets/Scripts/TestPlan.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 97b0707a63200c94cb3f55a0beb75ef2
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/TestScene_README.txt b/Assets/Scripts/TestScene_README.txt
new file mode 100644
index 0000000..699b2df
--- /dev/null
+++ b/Assets/Scripts/TestScene_README.txt
@@ -0,0 +1,69 @@
+# Pathfinding Greybox Matrix Testing Setup Instructions
+
+## Overview
+This document outlines the step-by-step process for setting up a scene to perform automated greybox matrix testing of the pathfinding algorithms.
+
+## Scene Setup Steps
+
+1. Create a new scene or use an existing pathfinding scene
+
+2. Add the required components to the scene:
+ - GridMap
+ - NPC
+ - UI Canvas for the test controls
+
+3. Create the following UI elements on the Canvas:
+ - Panel (as a container)
+ - Text (TMP) for status display
+ - Button for starting tests
+ - Slider for progress visualization
+
+4. Create an empty GameObject and attach the PathfindingTester.cs script
+
+5. Configure the PathfindingTester component:
+ - Assign the GridMap reference
+ - Assign the NPC reference
+ - Assign the UI components (button, text, progress bar)
+ - Configure test parameters as needed:
+ - Tests Per Combination
+ - Delay Between Tests
+ - Results File Name
+
+## UI Layout Recommendations
+
+1. Place the status text at the top of the panel to show current test status
+2. Place the progress bar below the status text
+3. Place the "Start Tests" button at the bottom
+4. Make the panel semi-transparent so you can still see the grid behind it
+
+## Test Matrix Configuration
+
+You can customize the test matrix by modifying these arrays in the PathfindingTester.cs script:
+
+- algorithmsToTest: Which algorithms to test
+- gridSizesToTest: Which grid sizes to test
+- mazeDensitiesToTest: Which maze densities to test
+- diagonalMovementOptions: Whether to test with diagonals enabled/disabled
+
+## Running Tests
+
+1. Enter Play mode in the Unity editor
+2. Click the "Start Tests" button
+3. Wait for all tests to complete
+4. Test results will be saved in a CSV file in the "TestResults" folder in the persistent data path
+
+## Analyzing Results
+
+The CSV file contains detailed information about each test:
+- Algorithm used
+- Grid size
+- Maze density
+- Diagonal movement setting
+- Time taken (ms)
+- Path length (nodes)
+- Nodes explored
+- Memory used
+- Whether a path was found
+- Test index (for repeated tests)
+
+Import the CSV into a spreadsheet application for further analysis and visualization.
\ No newline at end of file
diff --git a/Assets/Scripts/TestScene_README.txt.meta b/Assets/Scripts/TestScene_README.txt.meta
new file mode 100644
index 0000000..07e716b
--- /dev/null
+++ b/Assets/Scripts/TestScene_README.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5fd2581c099e0644fa3c48212c93208d
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/pathfinding_tests.csv b/Assets/Scripts/pathfinding_tests.csv
new file mode 100644
index 0000000..eb68217
--- /dev/null
+++ b/Assets/Scripts/pathfinding_tests.csv
@@ -0,0 +1,271 @@
+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
new file mode 100644
index 0000000..ae7f4ef
--- /dev/null
+++ b/Assets/Scripts/pathfinding_tests.csv.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: b5f5e061be1cbf946b769794cb0ba604
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant: