From a3359cf7e1034193e9e6d1f9f5544db15340c1ef Mon Sep 17 00:00:00 2001 From: Tran Nguyen Phuong Date: Sat, 4 Jul 2026 03:57:58 +0700 Subject: [PATCH] commit --- .idea/.idea.BABA_YAGA/.idea/workspace.xml | 24 +- .../Materials/{Transparency.mat => Red 1.mat} | 33 +- .../{Separator.mat.meta => Red 1.mat.meta} | 2 +- Assets/Materials/{Separator.mat => Red 2.mat} | 35 +- .../{Transparency.mat.meta => Red 2.mat.meta} | 2 +- Assets/Materials/Red.mat | 32 +- .../Environment/Hall/MazeVisualize 1 1.prefab | 63 ++++ .../Hall/MazeVisualize 1 1.prefab.meta | 7 + .../Environment/Hall/MazeVisualize 1.prefab | 63 ++++ .../Hall/MazeVisualize 1.prefab.meta | 7 + .../Environment/Hall/MazeVisualize.prefab | 114 +++++++ .../Hall/MazeVisualize.prefab.meta | 7 + .../Hall => Presets}/MazeRework.asset | 7 +- .../Hall => Presets}/MazeRework.asset.meta | 0 .../GameSetup/MazeRework/Algorithms.meta | 8 + .../MazeRework/Algorithms/DFSIterative.cs | 76 +++++ .../Algorithms/DFSIterative.cs.meta | 2 + .../MazeRework/Algorithms/DFSRecursive.cs | 64 ++++ .../Algorithms/DFSRecursive.cs.meta | 2 + .../Algorithms/IMazeReworkAlgorithm.cs | 36 +++ .../Algorithms/IMazeReworkAlgorithm.cs.meta | 2 + .../MazeRework/Algorithms/Kruskals.cs | 82 +++++ .../MazeRework/Algorithms/Kruskals.cs.meta | 2 + .../GameSetup/MazeRework/Algorithms/Prims.cs | 73 +++++ .../MazeRework/Algorithms/Prims.cs.meta | 2 + .../GameSetup/MazeRework/Animation.meta | 8 + .../Animation/MazeAnimationPhase.cs | 15 + .../Animation/MazeAnimationPhase.cs.meta | 2 + .../MazeRework/Animation/MazeAnimator.cs | 123 +++++++ .../MazeRework/Animation/MazeAnimator.cs.meta | 2 + .../MazeRework/Animation/MazeCellChange.cs | 24 ++ .../Animation/MazeCellChange.cs.meta | 2 + .../MazeRework/Animation/MazeCellHighlight.cs | 10 + .../Animation/MazeCellHighlight.cs.meta | 2 + .../GameSetup/MazeRework/MazeReworkConfig.cs | 18 +- .../MazeRework/MazeReworkGenerator.cs | 302 +++++++++--------- .../GameSetup/MazeRework/MazeReworkManager.cs | 60 ++-- .../GameSetup/MazeRework/MazeReworkSpawner.cs | 151 ++++++--- 38 files changed, 1201 insertions(+), 263 deletions(-) rename Assets/Materials/{Transparency.mat => Red 1.mat} (87%) rename Assets/Materials/{Separator.mat.meta => Red 1.mat.meta} (79%) rename Assets/Materials/{Separator.mat => Red 2.mat} (86%) rename Assets/Materials/{Transparency.mat.meta => Red 2.mat.meta} (79%) create mode 100644 Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab create mode 100644 Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab.meta create mode 100644 Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab create mode 100644 Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab.meta create mode 100644 Assets/Prefabs/Environment/Hall/MazeVisualize.prefab create mode 100644 Assets/Prefabs/Environment/Hall/MazeVisualize.prefab.meta rename Assets/{Prefabs/Environment/Hall => Presets}/MazeRework.asset (92%) rename Assets/{Prefabs/Environment/Hall => Presets}/MazeRework.asset.meta (100%) create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs.meta create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs create mode 100644 Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs.meta diff --git a/.idea/.idea.BABA_YAGA/.idea/workspace.xml b/.idea/.idea.BABA_YAGA/.idea/workspace.xml index 24fd8087..88bfb4db 100644 --- a/.idea/.idea.BABA_YAGA/.idea/workspace.xml +++ b/.idea/.idea.BABA_YAGA/.idea/workspace.xml @@ -12,19 +12,17 @@ - - - + + + + + + + + + - - - - - - - - + + diff --git a/Assets/Materials/Transparency.mat b/Assets/Materials/Red 1.mat similarity index 87% rename from Assets/Materials/Transparency.mat rename to Assets/Materials/Red 1.mat index 0e5f621d..5b8fdeb0 100644 --- a/Assets/Materials/Transparency.mat +++ b/Assets/Materials/Red 1.mat @@ -7,22 +7,27 @@ Material: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: Transparency + m_Name: Red 1 m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _EMISSION - _ENVIRONMENTREFLECTIONS_OFF + - _SURFACE_TYPE_TRANSPARENT m_InvalidKeywords: - _GLOSSYREFLECTIONS_OFF - m_LightmapFlags: 4 + m_LightmapFlags: 1 m_EnableInstancingVariants: 1 m_DoubleSidedGI: 0 - m_CustomRenderQueue: 2000 + m_CustomRenderQueue: 3000 stringTagMap: - RenderType: Opaque + RenderType: Transparent disabledShaderPasses: - MOTIONVECTORS + - DepthOnly + - SHADOWCASTER m_LockedProperties: m_SavedProperties: serializedVersion: 3 @@ -97,36 +102,36 @@ Material: - _Cutoff: 0.5 - _DetailAlbedoMapScale: 1 - _DetailNormalMapScale: 1 - - _DstBlend: 0 - - _DstBlendAlpha: 0 + - _DstBlend: 10 + - _DstBlendAlpha: 10 - _EnvironmentReflections: 0 - _GlossMapScale: 0 - _Glossiness: 0 - _GlossyReflections: 0 - - _Metallic: 0 + - _Metallic: 1 - _Mode: 0 - _OcclusionStrength: 1 - _Parallax: 0.005 - _QueueOffset: 0 - _ReceiveShadows: 1 - - _Smoothness: 0 + - _Smoothness: 0.485 - _SmoothnessTextureChannel: 0 - _SpecularHighlights: 1 - _SrcBlend: 1 - _SrcBlendAlpha: 1 - - _Surface: 0 + - _Surface: 1 - _UVSec: 0 - _WorkflowMode: 1 - _XRMotionVectorsPass: 1 - - _ZWrite: 1 + - _ZWrite: 0 m_Colors: - - _BaseColor: {r: 0.6462264, g: 1, b: 0.90943396, a: 1} - - _Color: {r: 0.6462264, g: 1, b: 0.90943396, a: 1} - - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _BaseColor: {r: 1, g: 0, b: 0, a: 0.54509807} + - _Color: {r: 1, g: 0, b: 0, a: 0.54509807} + - _EmissionColor: {r: 0.4433962, g: 0, b: 0.035386458, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} m_BuildTextureStacks: [] m_AllowLocking: 1 ---- !u!114 &7323331776208790253 +--- !u!114 &4579113440628220621 MonoBehaviour: m_ObjectHideFlags: 11 m_CorrespondingSourceObject: {fileID: 0} diff --git a/Assets/Materials/Separator.mat.meta b/Assets/Materials/Red 1.mat.meta similarity index 79% rename from Assets/Materials/Separator.mat.meta rename to Assets/Materials/Red 1.mat.meta index b5a9ba1b..f192ad39 100644 --- a/Assets/Materials/Separator.mat.meta +++ b/Assets/Materials/Red 1.mat.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b0a84576fc378a24cbb3bfc7be45a02e +guid: 4e2761c702df38044a3b33cb36bdabf4 NativeFormatImporter: externalObjects: {} mainObjectFileID: 2100000 diff --git a/Assets/Materials/Separator.mat b/Assets/Materials/Red 2.mat similarity index 86% rename from Assets/Materials/Separator.mat rename to Assets/Materials/Red 2.mat index 68711ec5..97ed2e35 100644 --- a/Assets/Materials/Separator.mat +++ b/Assets/Materials/Red 2.mat @@ -7,21 +7,26 @@ Material: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: Separator + m_Name: Red 2 m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 - m_ValidKeywords: [] + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _EMISSION + - _ENVIRONMENTREFLECTIONS_OFF + - _SURFACE_TYPE_TRANSPARENT m_InvalidKeywords: - _GLOSSYREFLECTIONS_OFF - m_LightmapFlags: 4 + m_LightmapFlags: 1 m_EnableInstancingVariants: 1 m_DoubleSidedGI: 0 - m_CustomRenderQueue: 2000 + m_CustomRenderQueue: 3000 stringTagMap: - RenderType: Opaque + RenderType: Transparent disabledShaderPasses: - MOTIONVECTORS + - DepthOnly - SHADOWCASTER m_LockedProperties: m_SavedProperties: @@ -97,36 +102,36 @@ Material: - _Cutoff: 0.5 - _DetailAlbedoMapScale: 1 - _DetailNormalMapScale: 1 - - _DstBlend: 0 - - _DstBlendAlpha: 0 + - _DstBlend: 10 + - _DstBlendAlpha: 10 - _EnvironmentReflections: 0 - _GlossMapScale: 0 - _Glossiness: 0 - _GlossyReflections: 0 - - _Metallic: 0 + - _Metallic: 1 - _Mode: 0 - _OcclusionStrength: 1 - _Parallax: 0.005 - _QueueOffset: 0 - _ReceiveShadows: 1 - - _Smoothness: 0 + - _Smoothness: 0.485 - _SmoothnessTextureChannel: 0 - _SpecularHighlights: 1 - _SrcBlend: 1 - _SrcBlendAlpha: 1 - - _Surface: 0 + - _Surface: 1 - _UVSec: 0 - _WorkflowMode: 1 - _XRMotionVectorsPass: 1 - - _ZWrite: 1 + - _ZWrite: 0 m_Colors: - - _BaseColor: {r: 1, g: 0, b: 0, a: 0.5686275} - - _Color: {r: 1, g: 0, b: 0, a: 0.5686275} - - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _BaseColor: {r: 1, g: 0, b: 0, a: 0.54509807} + - _Color: {r: 1, g: 0, b: 0, a: 0.54509807} + - _EmissionColor: {r: 0.4433962, g: 0, b: 0.035386458, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} m_BuildTextureStacks: [] m_AllowLocking: 1 ---- !u!114 &9152226916650191239 +--- !u!114 &4579113440628220621 MonoBehaviour: m_ObjectHideFlags: 11 m_CorrespondingSourceObject: {fileID: 0} diff --git a/Assets/Materials/Transparency.mat.meta b/Assets/Materials/Red 2.mat.meta similarity index 79% rename from Assets/Materials/Transparency.mat.meta rename to Assets/Materials/Red 2.mat.meta index 3b3b2510..90dcc5fb 100644 --- a/Assets/Materials/Transparency.mat.meta +++ b/Assets/Materials/Red 2.mat.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d7e691e02b41b9a47b417cc138d6f1bc +guid: c2de57284d214404394d08e4b5f50372 NativeFormatImporter: externalObjects: {} mainObjectFileID: 2100000 diff --git a/Assets/Materials/Red.mat b/Assets/Materials/Red.mat index 9650f183..a9d7cec1 100644 --- a/Assets/Materials/Red.mat +++ b/Assets/Materials/Red.mat @@ -11,17 +11,23 @@ Material: m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 - m_ValidKeywords: [] + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _EMISSION + - _ENVIRONMENTREFLECTIONS_OFF + - _SURFACE_TYPE_TRANSPARENT m_InvalidKeywords: - _GLOSSYREFLECTIONS_OFF - m_LightmapFlags: 4 + m_LightmapFlags: 1 m_EnableInstancingVariants: 1 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 3000 stringTagMap: - RenderType: Opaque + RenderType: Transparent disabledShaderPasses: - MOTIONVECTORS + - DepthOnly + - SHADOWCASTER m_LockedProperties: m_SavedProperties: serializedVersion: 3 @@ -96,32 +102,32 @@ Material: - _Cutoff: 0.5 - _DetailAlbedoMapScale: 1 - _DetailNormalMapScale: 1 - - _DstBlend: 0 - - _DstBlendAlpha: 0 + - _DstBlend: 10 + - _DstBlendAlpha: 10 - _EnvironmentReflections: 0 - _GlossMapScale: 0 - _Glossiness: 0 - _GlossyReflections: 0 - - _Metallic: 0 + - _Metallic: 1 - _Mode: 0 - _OcclusionStrength: 1 - _Parallax: 0.005 - _QueueOffset: 0 - _ReceiveShadows: 1 - - _Smoothness: 0 + - _Smoothness: 0.485 - _SmoothnessTextureChannel: 0 - _SpecularHighlights: 1 - _SrcBlend: 1 - _SrcBlendAlpha: 1 - - _Surface: 0 + - _Surface: 1 - _UVSec: 0 - _WorkflowMode: 1 - _XRMotionVectorsPass: 1 - - _ZWrite: 1 + - _ZWrite: 0 m_Colors: - - _BaseColor: {r: 1, g: 0, b: 0, a: 1} - - _Color: {r: 1, g: 0, b: 0, a: 1} - - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _BaseColor: {r: 1, g: 0, b: 0, a: 0.54509807} + - _Color: {r: 1, g: 0, b: 0, a: 0.54509807} + - _EmissionColor: {r: 0.4433962, g: 0, b: 0.035386458, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} m_BuildTextureStacks: [] m_AllowLocking: 1 diff --git a/Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab b/Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab new file mode 100644 index 00000000..365d68d9 --- /dev/null +++ b/Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &8524974212353532561 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3172752473709046689, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3276807805808399797, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: 'm_Materials.Array.data[0]' + value: + objectReference: {fileID: 2100000, guid: c2de57284d214404394d08e4b5f50372, type: 2} + - target: {fileID: 7410088413158269499, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} + propertyPath: m_Name + value: MazeVisualize 1 1 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 17f01b6b44475394da68b1ca1d8d7eb4, type: 3} diff --git a/Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab.meta b/Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab.meta new file mode 100644 index 00000000..695a3c49 --- /dev/null +++ b/Assets/Prefabs/Environment/Hall/MazeVisualize 1 1.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c39adeeb3a17d824392cc018d65aedc4 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab b/Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab new file mode 100644 index 00000000..c80ddca8 --- /dev/null +++ b/Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &4042319629769208918 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1449831261723959287, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1540436817844937187, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: 'm_Materials.Array.data[0]' + value: + objectReference: {fileID: 2100000, guid: 4e2761c702df38044a3b33cb36bdabf4, type: 2} + - target: {fileID: 6831072708289238637, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} + propertyPath: m_Name + value: MazeVisualize 1 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 085ffdfee17437a4f8e0d3c365edcc24, type: 3} diff --git a/Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab.meta b/Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab.meta new file mode 100644 index 00000000..7f7865ec --- /dev/null +++ b/Assets/Prefabs/Environment/Hall/MazeVisualize 1.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 17f01b6b44475394da68b1ca1d8d7eb4 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Environment/Hall/MazeVisualize.prefab b/Assets/Prefabs/Environment/Hall/MazeVisualize.prefab new file mode 100644 index 00000000..35a5965f --- /dev/null +++ b/Assets/Prefabs/Environment/Hall/MazeVisualize.prefab @@ -0,0 +1,114 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6831072708289238637 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1449831261723959287} + - component: {fileID: 7162882105187371053} + - component: {fileID: 1540436817844937187} + - component: {fileID: 1186626314013191963} + m_Layer: 0 + m_Name: MazeVisualize + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1449831261723959287 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6831072708289238637} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 111.06352, y: 0.99998, z: 75.67667} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &7162882105187371053 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6831072708289238637} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &1540436817844937187 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6831072708289238637} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9193f4635bbf98d46be9a6357461aa10, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_MaskInteraction: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &1186626314013191963 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6831072708289238637} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 0 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} diff --git a/Assets/Prefabs/Environment/Hall/MazeVisualize.prefab.meta b/Assets/Prefabs/Environment/Hall/MazeVisualize.prefab.meta new file mode 100644 index 00000000..c6ca0466 --- /dev/null +++ b/Assets/Prefabs/Environment/Hall/MazeVisualize.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 085ffdfee17437a4f8e0d3c365edcc24 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Environment/Hall/MazeRework.asset b/Assets/Presets/MazeRework.asset similarity index 92% rename from Assets/Prefabs/Environment/Hall/MazeRework.asset rename to Assets/Presets/MazeRework.asset index 16242fe9..037da5e4 100644 --- a/Assets/Prefabs/Environment/Hall/MazeRework.asset +++ b/Assets/Presets/MazeRework.asset @@ -12,13 +12,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 30892b1102feb6841a9288ddb11ef50d, type: 3} m_Name: MazeRework m_EditorClassIdentifier: Assembly-CSharp::Baba_yaga.GameSetup.MazeRework.MazeReworkConfig - width: 15 - depth: 15 + algorithm: 3 + width: 21 + depth: 21 useRandomSeed: 1 seed: 1337 startLocation: {x: 1, y: 1} generateRooms: 0 - roomCount: 207 + roomCount: 378 minRoomSize: {x: 3, y: 3} maxRoomSize: {x: 5, y: 5} extraRoomDoorChance: 0.3 diff --git a/Assets/Prefabs/Environment/Hall/MazeRework.asset.meta b/Assets/Presets/MazeRework.asset.meta similarity index 100% rename from Assets/Prefabs/Environment/Hall/MazeRework.asset.meta rename to Assets/Presets/MazeRework.asset.meta diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms.meta new file mode 100644 index 00000000..8c068543 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1207eeec52bcfcf4a9a97e054680d844 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs new file mode 100644 index 00000000..9d08961f --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Baba_yaga.GameSetup.MazeRework.Algorithms +{ + public class DFSIterative : IMazeReworkAlgorithm + { + public void Carve( + MazeReworkCellType[,] grid, + bool[,] visited, + int startX, int startZ, + int width, int depth, + System.Random rng, + System.Action onCellChanged = null) + { + Stack stack = new Stack(); + stack.Push(new Vector2Int(startX, startZ)); + onCellChanged?.Invoke(startX, startZ, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + + int[] dx = { 2, -2, 0, 0 }; + int[] dz = { 0, 0, 2, -2 }; + + while (stack.Count > 0) + { + Vector2Int current = stack.Peek(); + + List candidates = new List(4); + for (int i = 0; i < 4; i++) + { + int nx = current.x + dx[i]; + int nz = current.y + dz[i]; + if (nx > 0 && nx < width - 1 && nz > 0 && nz < depth - 1 && !visited[nx, nz]) + candidates.Add(i); + } + + if (candidates.Count > 0) + { + int pick = candidates[rng.Next(candidates.Count)]; + int nx2 = current.x + dx[pick]; + int nz2 = current.y + dz[pick]; + int wx = current.x + dx[pick] / 2; + int wz = current.y + dz[pick] / 2; + + grid[wx, wz] = MazeReworkCellType.Corridor; + grid[nx2, nz2] = MazeReworkCellType.Corridor; + visited[wx, wz] = true; + visited[nx2, nz2] = true; + + // Move head off current cell + onCellChanged?.Invoke(current.x, current.y, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + // Show breaking intermediate wall with head + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + // Move head to new cell + onCellChanged?.Invoke(nx2, nz2, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + + stack.Push(new Vector2Int(nx2, nz2)); + } + else + { + stack.Pop(); + // Clear head from popped cell + onCellChanged?.Invoke(current.x, current.y, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + // Restore head to new top of stack + if (stack.Count > 0) + { + var nextCurrent = stack.Peek(); + onCellChanged?.Invoke(nextCurrent.x, nextCurrent.y, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + } + } + } + } + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs.meta new file mode 100644 index 00000000..f76ed1d2 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSIterative.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cdda5d3deb881154481ad5f60c4bdc0a \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs new file mode 100644 index 00000000..960ff74d --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs @@ -0,0 +1,64 @@ +namespace Baba_yaga.GameSetup.MazeRework.Algorithms +{ + public class DFSRecursive : IMazeReworkAlgorithm + { + public void Carve( + MazeReworkCellType[,] grid, + bool[,] visited, + int startX, int startZ, + int width, int depth, + System.Random rng, + System.Action onCellChanged = null) + { + CarveFrom(startX, startZ, grid, visited, rng, width, depth, onCellChanged); + // Clear final head position when completely done + onCellChanged?.Invoke(startX, startZ, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + } + + private void CarveFrom(int cx, int cz, MazeReworkCellType[,] grid, bool[,] visited, + System.Random rng, int width, int depth, + System.Action onCellChanged) + { + onCellChanged?.Invoke(cx, cz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + + int[] dx = { 2, -2, 0, 0 }; + int[] dz = { 0, 0, 2, -2 }; + + int[] order = { 0, 1, 2, 3 }; + for (int i = 3; i > 0; i--) + { + int j = rng.Next(i + 1); + int tmp = order[i]; order[i] = order[j]; order[j] = tmp; + } + + for (int i = 0; i < 4; i++) + { + int nx = cx + dx[order[i]]; + int nz = cz + dz[order[i]]; + int wx = cx + dx[order[i]] / 2; + int wz = cz + dz[order[i]] / 2; + + if (nx > 0 && nx < width - 1 && nz > 0 && nz < depth - 1 && !visited[nx, nz]) + { + grid[wx, wz] = MazeReworkCellType.Corridor; + grid[nx, nz] = MazeReworkCellType.Corridor; + visited[wx, wz] = true; + visited[nx, nz] = true; + + // Move head off current cell + onCellChanged?.Invoke(cx, cz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + // Show intermediate wall breaking with head + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + CarveFrom(nx, nz, grid, visited, rng, width, depth, onCellChanged); + + // Clear head from the child cell and restore it here + onCellChanged?.Invoke(nx, nz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + onCellChanged?.Invoke(cx, cz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + } + } + } + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs.meta new file mode 100644 index 00000000..9b97cb23 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/DFSRecursive.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f20ddc35c8bf6604b84a0102bdad1821 \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs new file mode 100644 index 00000000..1bad3b82 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs @@ -0,0 +1,36 @@ +namespace Baba_yaga.GameSetup.MazeRework.Algorithms +{ + /// + /// Contract that all maze carving algorithms must fulfill. + /// The algorithm is only responsible for carving corridors. + /// Rooms, loops, Start/End placement, and connectivity repair are handled externally. + /// + public interface IMazeReworkAlgorithm + { + /// + /// Carves corridors into the grid starting from (startX, startZ). + /// All cells begin as Wall. The algorithm sets reachable cells to Corridor. + /// + /// The 2D cell array to carve into. + /// Shared visited map. Pre-marked cells (e.g. rooms) are already true. + /// X coordinate of the first cell to carve (must be odd). + /// Z coordinate of the first cell to carve (must be odd). + /// Grid width. + /// Grid depth. + /// Seeded random number generator. + /// + /// Optional callback fired every time a cell is carved or evaluated. + /// Signature: (x, z, newType, highlight). Pass null in non-animated mode. + /// + void Carve( + MazeReworkCellType[,] grid, + bool[,] visited, + int startX, + int startZ, + int width, + int depth, + System.Random rng, + System.Action onCellChanged = null + ); + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs.meta new file mode 100644 index 00000000..70f1b7fb --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/IMazeReworkAlgorithm.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f8ba54b75b8f8344aa7ccb03b8779c03 \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs new file mode 100644 index 00000000..084d7533 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Baba_yaga.GameSetup.MazeRework.Algorithms +{ + public class Kruskals : IMazeReworkAlgorithm + { + public void Carve( + MazeReworkCellType[,] grid, + bool[,] visited, + int startX, int startZ, + int width, int depth, + System.Random rng, + System.Action onCellChanged = null) + { + // Assign unique ID to every odd-coordinate (node) cell + int[,] cellId = new int[width, depth]; + int idCounter = 0; + for (int z = 1; z < depth - 1; z += 2) + for (int x = 1; x < width - 1; x += 2) + cellId[x, z] = idCounter++; + + UnionFind uf = new UnionFind(idCounter); + + // Collect all internal walls + List walls = new List(); + for (int z = 1; z < depth - 1; z++) + for (int x = 1; x < width - 1; x++) + if ((x % 2 == 0 && z % 2 == 1) || (x % 2 == 1 && z % 2 == 0)) + walls.Add(new Vector2Int(x, z)); + + // Shuffle walls + for (int i = walls.Count - 1; i > 0; i--) + { + int j = rng.Next(i + 1); + var tmp = walls[i]; walls[i] = walls[j]; walls[j] = tmp; + } + + foreach (var wall in walls) + { + int wx = wall.x, wz = wall.y; + int ax, az, bx, bz; + + if (wx % 2 == 0) { ax = wx - 1; az = wz; bx = wx + 1; bz = wz; } + else { ax = wx; az = wz - 1; bx = wx; bz = wz + 1; } + + if (ax < 1 || ax >= width - 1 || az < 1 || az >= depth - 1) continue; + if (bx < 1 || bx >= width - 1 || bz < 1 || bz >= depth - 1) continue; + + // Show evaluating highlight + onCellChanged?.Invoke(wx, wz, grid[wx, wz], Animation.MazeCellHighlight.Evaluating); + + if (uf.Find(cellId[ax, az]) != uf.Find(cellId[bx, bz])) + { + grid[wx, wz] = MazeReworkCellType.Corridor; + grid[ax, az] = MazeReworkCellType.Corridor; + grid[bx, bz] = MazeReworkCellType.Corridor; + visited[wx, wz] = visited[ax, az] = visited[bx, bz] = true; + + onCellChanged?.Invoke(ax, az, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + onCellChanged?.Invoke(bx, bz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + uf.Union(cellId[ax, az], cellId[bx, bz]); + } + else + { + // Clear evaluate highlight + onCellChanged?.Invoke(wx, wz, grid[wx, wz], Animation.MazeCellHighlight.None); + } + } + } + + private class UnionFind + { + private readonly int[] _parent, _rank; + public UnionFind(int count) { _parent = new int[count]; _rank = new int[count]; for (int i = 0; i < count; i++) _parent[i] = i; } + public int Find(int x) { if (_parent[x] != x) _parent[x] = Find(_parent[x]); return _parent[x]; } + public void Union(int a, int b) { int ra = Find(a), rb = Find(b); if (ra == rb) return; if (_rank[ra] < _rank[rb]) _parent[ra] = rb; else if (_rank[ra] > _rank[rb]) _parent[rb] = ra; else { _parent[rb] = ra; _rank[ra]++; } } + } + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs.meta new file mode 100644 index 00000000..d9927da2 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Kruskals.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7574ba4325993ac46a12a7861b70031f \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs new file mode 100644 index 00000000..e83ad20f --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Baba_yaga.GameSetup.MazeRework.Algorithms +{ + public class Prims : IMazeReworkAlgorithm + { + public void Carve( + MazeReworkCellType[,] grid, + bool[,] visited, + int startX, int startZ, + int width, int depth, + System.Random rng, + System.Action onCellChanged = null) + { + grid[startX, startZ] = MazeReworkCellType.Corridor; + visited[startX, startZ] = true; + onCellChanged?.Invoke(startX, startZ, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + List<(int nx, int nz, int wx, int wz)> frontier = new List<(int, int, int, int)>(); + AddFrontier(startX, startZ, frontier, visited, width, depth, grid, onCellChanged); + + while (frontier.Count > 0) + { + int pick = rng.Next(frontier.Count); + var (nx, nz, wx, wz) = frontier[pick]; + + // Swap-remove O(1) + frontier[pick] = frontier[frontier.Count - 1]; + frontier.RemoveAt(frontier.Count - 1); + + if (visited[nx, nz]) + { + // Clear the frontier highlight if this cell was already visited by another path + onCellChanged?.Invoke(nx, nz, grid[nx, nz], Animation.MazeCellHighlight.None); + continue; + } + + grid[wx, wz] = MazeReworkCellType.Corridor; + grid[nx, nz] = MazeReworkCellType.Corridor; + visited[wx, wz] = true; + visited[nx, nz] = true; + + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + onCellChanged?.Invoke(nx, nz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.SearchHead); + onCellChanged?.Invoke(nx, nz, MazeReworkCellType.Corridor, Animation.MazeCellHighlight.None); + + AddFrontier(nx, nz, frontier, visited, width, depth, grid, onCellChanged); + } + } + + private void AddFrontier(int cx, int cz, + List<(int nx, int nz, int wx, int wz)> frontier, + bool[,] visited, int width, int depth, MazeReworkCellType[,] grid, + System.Action onCellChanged) + { + int[] dx = { 2, -2, 0, 0 }; + int[] dz = { 0, 0, 2, -2 }; + for (int i = 0; i < 4; i++) + { + int nx = cx + dx[i], nz = cz + dz[i]; + int wx = cx + dx[i] / 2, wz = cz + dz[i] / 2; + if (nx > 0 && nx < width - 1 && nz > 0 && nz < depth - 1 && !visited[nx, nz]) + { + frontier.Add((nx, nz, wx, wz)); + onCellChanged?.Invoke(nx, nz, grid[nx, nz], Animation.MazeCellHighlight.Frontier); + } + } + } + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs.meta new file mode 100644 index 00000000..10517b4d --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Algorithms/Prims.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 321151d3d7c655047b73b6bd981800e6 \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation.meta new file mode 100644 index 00000000..6377a6f2 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 92ea88f252068ee47ad6145db600314b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs new file mode 100644 index 00000000..23fafe32 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs @@ -0,0 +1,15 @@ +namespace Baba_yaga.GameSetup.MazeRework.Animation +{ + /// + /// Labels each recorded cell change with which generation phase produced it. + /// Used to color-code or group changes during animated playback. + /// + public enum MazeAnimationPhase + { + RoomPlacement, // Rooms being stamped onto the grid + Carving, // Algorithm carving corridors + Connecting, // Hunt-and-Kill fixing isolated pockets + Loops, // Extra walls removed to create shortcuts + StartEnd // Start and End cells placed + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs.meta new file mode 100644 index 00000000..8bbb9ed4 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimationPhase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 45bda90d4b2d42245a8d9f06543053ee \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs new file mode 100644 index 00000000..d0a0a8ab --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs @@ -0,0 +1,123 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Baba_yaga.GameSetup.MazeRework.Animation +{ + public class MazeAnimator : MonoBehaviour + { + [Header("Animation Settings")] + [Tooltip("How many cell changes to process in a single frame. Higher = faster generation.")] + [Min(1)] + public int cellsPerFrame = 5; + + [Tooltip("Extra delay (in seconds) between major generation phases (like switching from Rooms to Carving).")] + public float delayBetweenPhases = 0.5f; + + private Coroutine _animationRoutine; + + public void AnimateGeneration( + List history, + MazeReworkCellType[,] grid, + MazeReworkSpawner spawner, + float yOffset, + Transform container, + System.Action onComplete = null) + { + if (_animationRoutine != null) + StopCoroutine(_animationRoutine); + + _animationRoutine = StartCoroutine(AnimateRoutine(history, grid, spawner, yOffset, container, onComplete)); + } + + public void StopAnimation() + { + if (_animationRoutine != null) + { + StopCoroutine(_animationRoutine); + _animationRoutine = null; + } + } + + private IEnumerator AnimateRoutine( + List history, + MazeReworkCellType[,] finalGrid, + MazeReworkSpawner spawner, + float yOffset, + Transform container, + System.Action onComplete) + { + int width = finalGrid.GetLength(0); + int depth = finalGrid.GetLength(1); + + // Create a working grid starting fully as walls (like the generator does) + MazeReworkCellType[,] workingGrid = new MazeReworkCellType[width, depth]; + MazeCellHighlight[,] workingHighlights = new MazeCellHighlight[width, depth]; + for (int z = 0; z < depth; z++) + { + for (int x = 0; x < width; x++) + { + workingGrid[x, z] = MazeReworkCellType.Wall; + workingHighlights[x, z] = MazeCellHighlight.None; + } + } + + spawner.Clear(); + + MazeAnimationPhase currentPhase = MazeAnimationPhase.RoomPlacement; + int processedThisFrame = 0; + + foreach (var change in history) + { + if (change.Phase != currentPhase) + { + currentPhase = change.Phase; + if (delayBetweenPhases > 0f) + { + yield return new WaitForSeconds(delayBetweenPhases); + processedThisFrame = 0; + } + } + + workingGrid[change.X, change.Z] = change.Type; + workingHighlights[change.X, change.Z] = change.Highlight; + + // Refresh the cell and its neighbors in the spawner + spawner.RefreshCell(workingGrid, workingHighlights, change.X, change.Z, yOffset, container); + + processedThisFrame++; + if (processedThisFrame >= cellsPerFrame) + { + yield return null; // Wait for next frame + processedThisFrame = 0; + } + } + + // Wait a moment at the end before finalizing + if (delayBetweenPhases > 0f) + { + yield return new WaitForSeconds(delayBetweenPhases); + } + + // One final sync with finalGrid just to be absolutely safe + for (int z = 0; z < depth; z++) + { + for (int x = 0; x < width; x++) + { + bool gridChanged = workingGrid[x, z] != finalGrid[x, z]; + bool highlightChanged = workingHighlights[x, z] != MazeCellHighlight.None; + + if (gridChanged || highlightChanged) + { + workingGrid[x, z] = finalGrid[x, z]; + workingHighlights[x, z] = MazeCellHighlight.None; + spawner.RefreshCell(workingGrid, workingHighlights, x, z, yOffset, container); + } + } + } + + _animationRoutine = null; + onComplete?.Invoke(); + } + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs.meta new file mode 100644 index 00000000..07f9433b --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeAnimator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 209db07e0fcae2e4e9e8b9e2fb822e31 \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs new file mode 100644 index 00000000..a4f5ec64 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs @@ -0,0 +1,24 @@ +namespace Baba_yaga.GameSetup.MazeRework.Animation +{ + /// + /// A single recorded cell change captured during maze generation. + /// The animator drains this list to replay generation visually. + /// + public readonly struct MazeCellChange + { + public readonly int X; + public readonly int Z; + public readonly MazeReworkCellType Type; + public readonly MazeCellHighlight Highlight; + public readonly MazeAnimationPhase Phase; + + public MazeCellChange(int x, int z, MazeReworkCellType type, MazeCellHighlight highlight, MazeAnimationPhase phase) + { + X = x; + Z = z; + Type = type; + Highlight = highlight; + Phase = phase; + } + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs.meta new file mode 100644 index 00000000..b5da66ff --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellChange.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6f6a93f9b414a3f4a83c74ee28fb441b \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs new file mode 100644 index 00000000..8af7f266 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs @@ -0,0 +1,10 @@ +namespace Baba_yaga.GameSetup.MazeRework.Animation +{ + public enum MazeCellHighlight + { + None, + SearchHead, // Current cell being evaluated (DFS, Prim's) + Frontier, // Known candidate cells (Prim's) + Evaluating // Walls currently being considered (Kruskal's) + } +} diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs.meta b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs.meta new file mode 100644 index 00000000..89215e14 --- /dev/null +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/Animation/MazeCellHighlight.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6bff934558bb70343ad947b2c9e4da45 \ No newline at end of file diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkConfig.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkConfig.cs index eb17ce5c..8ada80ac 100644 --- a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkConfig.cs +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkConfig.cs @@ -1,13 +1,29 @@ using UnityEngine; +using Baba_yaga.GameSetup.MazeRework.Algorithms; namespace Baba_yaga.GameSetup.MazeRework { + /// + /// Defines which carving algorithm the generator will use. + /// + public enum MazeAlgorithmType + { + DFSRecursive, // Long winding corridors. May stack overflow on huge grids. + DFSIterative, // Same result as recursive but safe for large grids. + Kruskals, // Short branchy corridors, open feel, no directional bias. + Prims, // Organic cave-like layout, radiates outward from start. + } + /// /// Configuration asset containing parameters for the reworked maze generation. /// - [CreateAssetMenu(fileName = "MazeReworkConfig", menuName = "BABA_YAGA/GameSetup/MazeRework")] + [CreateAssetMenu(fileName = "MazeReworkConfig", menuName = "BABA_YAGA/MazeRework/Config")] public class MazeReworkConfig : ScriptableObject { + [Header("Algorithm")] + [Tooltip("Which carving algorithm to use for generating the maze corridors.")] + public MazeAlgorithmType algorithm = MazeAlgorithmType.DFSRecursive; + [Header("Grid Dimensions")] [Tooltip("Width of the maze grid. Best if odd to align with wall-corridor-wall patterning.")] [Min(5)] diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkGenerator.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkGenerator.cs index 1004efe5..c9275725 100644 --- a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkGenerator.cs +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkGenerator.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using UnityEngine; +using Baba_yaga.GameSetup.MazeRework.Algorithms; +using Baba_yaga.GameSetup.MazeRework.Animation; namespace Baba_yaga.GameSetup.MazeRework { @@ -97,10 +99,11 @@ namespace Baba_yaga.GameSetup.MazeRework int startZ = _config.startLocation.y; EnsureValidOddCoordinates(width, depth, ref startX, ref startZ); - // 5. Carve Corridors recursively using Step-by-2 Recursive Backtracking + // 5. Carve Corridors using the selected algorithm visited[startX, startZ] = true; grid[startX, startZ] = MazeReworkCellType.Corridor; - CarveFrom(startX, startZ, grid, visited, rooms, rng, width, depth); + IMazeReworkAlgorithm algorithm = GetAlgorithm(); + algorithm.Carve(grid, visited, startX, startZ, width, depth, rng); // 6. Hunt & Kill scan to connect isolated rooms/corridors EnsureAllConnected(grid, visited, rooms, rng, width, depth); @@ -114,9 +117,86 @@ namespace Baba_yaga.GameSetup.MazeRework return grid; } + #region Animated Generation + + /// + /// Generates the maze and records every cell change with its phase label. + /// Returns the final grid AND the ordered history list for the animator to replay. + /// + public (MazeReworkCellType[,] grid, List history) GenerateAnimated() + { + if (_config == null) return (null, null); + int seed = _config.useRandomSeed + ? System.DateTime.Now.Millisecond + System.Threading.Thread.CurrentThread.ManagedThreadId + : _config.seed; + return GenerateAnimated(seed); + } + + public (MazeReworkCellType[,] grid, List history) GenerateAnimated(int seed) + { + if (_config == null) return (null, null); + + int width = _config.width; + int depth = _config.depth; + var grid = new MazeReworkCellType[width, depth]; + var history = new List(width * depth * 2); + + // Helper: record a cell change tagged with a phase + var currentPhase = MazeAnimationPhase.RoomPlacement; + Action record = + (x, z, t, h) => history.Add(new MazeCellChange(x, z, t, h, currentPhase)); + + Action recordNoHighlight = + (x, z, t) => record(x, z, t, MazeCellHighlight.None); + + // 1. Initialize all cells as Wall (no recording — start state is all-Wall) + for (int z = 0; z < depth; z++) + for (int x = 0; x < width; x++) + grid[x, z] = MazeReworkCellType.Wall; + + System.Random rng = new System.Random(seed); + + // 2. Generate Rooms — record each Room cell + currentPhase = MazeAnimationPhase.RoomPlacement; + List rooms = GenerateRooms(grid, width, depth, rng, recordNoHighlight); + + // 3. Setup visited map + bool[,] visited = new bool[width, depth]; + InitializeVisitedMap(grid, visited, rooms, width, depth); + + // 4. Starting coordinates + int startX = _config.startLocation.x; + int startZ = _config.startLocation.y; + EnsureValidOddCoordinates(width, depth, ref startX, ref startZ); + + // 5. Carve Corridors — algorithm fires back through the record callback + currentPhase = MazeAnimationPhase.Carving; + visited[startX, startZ] = true; + grid[startX, startZ] = MazeReworkCellType.Corridor; + record(startX, startZ, MazeReworkCellType.Corridor, MazeCellHighlight.None); // DFS will highlight itself + GetAlgorithm().Carve(grid, visited, startX, startZ, width, depth, rng, record); + + // 6. Hunt & Kill connecting pass + currentPhase = MazeAnimationPhase.Connecting; + EnsureAllConnected(grid, visited, rooms, rng, width, depth, recordNoHighlight); + + // 7. Loop carving + currentPhase = MazeAnimationPhase.Loops; + CarveLoops(grid, rng, width, depth, recordNoHighlight); + + // 8. Start & End placement + currentPhase = MazeAnimationPhase.StartEnd; + PlaceStartAndEnd(grid, rooms, width, depth, recordNoHighlight); + + return (grid, history); + } + + #endregion + #region Internal Generation Logic - private List GenerateRooms(MazeReworkCellType[,] grid, int width, int depth, System.Random rng) + private List GenerateRooms(MazeReworkCellType[,] grid, int width, int depth, System.Random rng, + Action onCellChanged = null) { List rooms = new List(); if (!_config.generateRooms) return rooms; @@ -144,22 +224,18 @@ namespace Baba_yaga.GameSetup.MazeRework bool overlaps = false; foreach (var r in rooms) { - if (newRoom.Overlaps(r, 1)) - { - overlaps = true; - break; - } + if (newRoom.Overlaps(r, 1)) { overlaps = true; break; } } if (!overlaps) { rooms.Add(newRoom); - // Carve room cells for (int z = rz; z < rz + rh; z++) { for (int x = rx; x < rx + rw; x++) { grid[x, z] = MazeReworkCellType.Room; + onCellChanged?.Invoke(x, z, MazeReworkCellType.Room); } } } @@ -182,7 +258,26 @@ namespace Baba_yaga.GameSetup.MazeRework } } - private void CarveFrom(int cx, int cz, MazeReworkCellType[,] grid, bool[,] visited, List rooms, System.Random rng, int width, int depth) + /// + /// Factory that returns the correct algorithm based on config selection. + /// + private IMazeReworkAlgorithm GetAlgorithm() + { + return _config.algorithm switch + { + MazeAlgorithmType.DFSRecursive => new DFSRecursive(), + MazeAlgorithmType.DFSIterative => new DFSIterative(), + MazeAlgorithmType.Kruskals => new Kruskals(), + MazeAlgorithmType.Prims => new Prims(), + _ => new DFSIterative() + }; + } + + /// + /// After corridor carving, connects room cells to the corridor network + /// by carving doorway walls between corridors and rooms. + /// + private void ConnectRoomsFrom(int cx, int cz, MazeReworkCellType[,] grid, bool[,] visited, List rooms, System.Random rng, int width, int depth) { Vector2Int[] dirs = new Vector2Int[4] { @@ -215,7 +310,7 @@ namespace Baba_yaga.GameSetup.MazeRework grid[nx, nz] = MazeReworkCellType.Corridor; visited[wx, wz] = true; visited[nx, nz] = true; - CarveFrom(nx, nz, grid, visited, rooms, rng, width, depth); + ConnectRoomsFrom(nx, nz, grid, visited, rooms, rng, width, depth); } else if (grid[nx, nz] == MazeReworkCellType.Room) { @@ -244,7 +339,9 @@ namespace Baba_yaga.GameSetup.MazeRework } } - private void EnsureAllConnected(MazeReworkCellType[,] grid, bool[,] visited, List rooms, System.Random rng, int width, int depth) + private void EnsureAllConnected(MazeReworkCellType[,] grid, bool[,] visited, List rooms, + System.Random rng, int width, int depth, + Action onCellChanged = null) { for (int z = 1; z < depth - 1; z += 2) { @@ -254,50 +351,30 @@ namespace Baba_yaga.GameSetup.MazeRework { Vector2Int[] dirs = new Vector2Int[4] { - new Vector2Int(2, 0), - new Vector2Int(-2, 0), - new Vector2Int(0, 2), - new Vector2Int(0, -2) + new Vector2Int(2, 0), new Vector2Int(-2, 0), + new Vector2Int(0, 2), new Vector2Int(0, -2) }; - - for (int i = 3; i > 0; i--) - { - int j = rng.Next(i + 1); - var temp = dirs[i]; - dirs[i] = dirs[j]; - dirs[j] = temp; - } + for (int i = 3; i > 0; i--) { int j = rng.Next(i + 1); var t = dirs[i]; dirs[i] = dirs[j]; dirs[j] = t; } bool connected = false; for (int i = 0; i < 4; i++) { - int nx = x + dirs[i].x; - int nz = z + dirs[i].y; - int wx = x + dirs[i].x / 2; - int wz = z + dirs[i].y / 2; - - if (nx > 0 && nx < width - 1 && nz > 0 && nz < depth - 1) + int nx = x + dirs[i].x, nz = z + dirs[i].y; + int wx = x + dirs[i].x / 2, wz = z + dirs[i].y / 2; + if (nx > 0 && nx < width - 1 && nz > 0 && nz < depth - 1 && visited[nx, nz]) { - if (visited[nx, nz]) - { - grid[wx, wz] = MazeReworkCellType.Corridor; - grid[x, z] = MazeReworkCellType.Corridor; - visited[wx, wz] = true; - visited[x, z] = true; - connected = true; - break; - } + grid[wx, wz] = MazeReworkCellType.Corridor; + grid[x, z] = MazeReworkCellType.Corridor; + visited[wx, wz] = visited[x, z] = true; + onCellChanged?.Invoke(wx, wz, MazeReworkCellType.Corridor); + onCellChanged?.Invoke(x, z, MazeReworkCellType.Corridor); + connected = true; + break; } } - if (connected) - { - CarveFrom(x, z, grid, visited, rooms, rng, width, depth); - } - else - { - CarveFrom(x, z, grid, visited, rooms, rng, width, depth); - } + ConnectRoomsFrom(x, z, grid, visited, rooms, rng, width, depth); + _ = connected; // suppress unused warning } } } @@ -307,74 +384,31 @@ namespace Baba_yaga.GameSetup.MazeRework var room = rooms[i]; if (!room.isConnected) { - ForceConnectRoom(grid, room, visited, rng, width, depth); + ForceConnectRoom(grid, room, visited, rng, width, depth, onCellChanged); room.isConnected = true; rooms[i] = room; } } } - private void ForceConnectRoom(MazeReworkCellType[,] grid, Room room, bool[,] visited, System.Random rng, int width, int depth) + private void ForceConnectRoom(MazeReworkCellType[,] grid, Room room, bool[,] visited, + System.Random rng, int width, int depth, + Action onCellChanged = null) { int[] sides = new int[] { 0, 1, 2, 3 }; - for (int i = 3; i > 0; i--) - { - int j = rng.Next(i + 1); - int temp = sides[i]; - sides[i] = sides[j]; - sides[j] = temp; - } + for (int i = 3; i > 0; i--) { int j = rng.Next(i + 1); int t = sides[i]; sides[i] = sides[j]; sides[j] = t; } foreach (int side in sides) { - if (side == 0) - { - int z = room.z + room.depth - 1; - if (z + 2 < depth - 1) - { - int x = room.x + 2 * rng.Next(0, room.width / 2); - grid[x, z + 1] = MazeReworkCellType.Corridor; - visited[x, z + 1] = true; - return; - } - } - else if (side == 1) - { - int z = room.z; - if (z - 2 > 0) - { - int x = room.x + 2 * rng.Next(0, room.width / 2); - grid[x, z - 1] = MazeReworkCellType.Corridor; - visited[x, z - 1] = true; - return; - } - } - else if (side == 2) - { - int x = room.x; - if (x - 2 > 0) - { - int z = room.z + 2 * rng.Next(0, room.depth / 2); - grid[x - 1, z] = MazeReworkCellType.Corridor; - visited[x - 1, z] = true; - return; - } - } - else if (side == 3) - { - int x = room.x + room.width - 1; - if (x + 2 < width - 1) - { - int z = room.z + 2 * rng.Next(0, room.depth / 2); - grid[x + 1, z] = MazeReworkCellType.Corridor; - visited[x + 1, z] = true; - return; - } - } + if (side == 0) { int z = room.z + room.depth - 1; if (z + 2 < depth - 1) { int x = room.x + 2 * rng.Next(0, room.width / 2); grid[x, z + 1] = MazeReworkCellType.Corridor; visited[x, z + 1] = true; onCellChanged?.Invoke(x, z + 1, MazeReworkCellType.Corridor); return; } } + else if (side == 1) { int z = room.z; if (z - 2 > 0) { int x = room.x + 2 * rng.Next(0, room.width / 2); grid[x, z - 1] = MazeReworkCellType.Corridor; visited[x, z - 1] = true; onCellChanged?.Invoke(x, z - 1, MazeReworkCellType.Corridor); return; } } + else if (side == 2) { int x = room.x; if (x - 2 > 0) { int z = room.z + 2 * rng.Next(0, room.depth / 2); grid[x - 1, z] = MazeReworkCellType.Corridor; visited[x - 1, z] = true; onCellChanged?.Invoke(x - 1, z, MazeReworkCellType.Corridor); return; } } + else if (side == 3) { int x = room.x + room.width - 1; if (x + 2 < width - 1) { int z = room.z + 2 * rng.Next(0, room.depth / 2); grid[x + 1, z] = MazeReworkCellType.Corridor; visited[x + 1, z] = true; onCellChanged?.Invoke(x + 1, z, MazeReworkCellType.Corridor); return; } } } } - private void CarveLoops(MazeReworkCellType[,] grid, System.Random rng, int width, int depth) + private void CarveLoops(MazeReworkCellType[,] grid, System.Random rng, int width, int depth, + Action onCellChanged = null) { if (_config.loopChance <= 0f) return; @@ -383,90 +417,66 @@ namespace Baba_yaga.GameSetup.MazeRework for (int x = 1; x < width - 1; x++) { bool isHorizontalWall = (x % 2 == 0 && z % 2 != 0); - bool isVerticalWall = (x % 2 != 0 && z % 2 == 0); - + bool isVerticalWall = (x % 2 != 0 && z % 2 == 0); if (!isHorizontalWall && !isVerticalWall) continue; if (grid[x, z] == MazeReworkCellType.Wall) { - if (isHorizontalWall) + bool carve = isHorizontalWall + ? IsPathCell(grid[x - 1, z]) && IsPathCell(grid[x + 1, z]) + : IsPathCell(grid[x, z - 1]) && IsPathCell(grid[x, z + 1]); + + if (carve && rng.NextDouble() < _config.loopChance) { - if (IsPathCell(grid[x - 1, z]) && IsPathCell(grid[x + 1, z])) - { - if (rng.NextDouble() < _config.loopChance) - { - grid[x, z] = MazeReworkCellType.Corridor; - } - } - } - else - { - if (IsPathCell(grid[x, z - 1]) && IsPathCell(grid[x, z + 1])) - { - if (rng.NextDouble() < _config.loopChance) - { - grid[x, z] = MazeReworkCellType.Corridor; - } - } + grid[x, z] = MazeReworkCellType.Corridor; + onCellChanged?.Invoke(x, z, MazeReworkCellType.Corridor); } } } } } - private void PlaceStartAndEnd(MazeReworkCellType[,] grid, List rooms, int width, int depth) + private void PlaceStartAndEnd(MazeReworkCellType[,] grid, List rooms, int width, int depth, + Action onCellChanged = null) { if (rooms != null && rooms.Count > 0) { var startRoom = rooms[0]; var startPt = startRoom.GetCenter(); grid[startPt.x, startPt.y] = MazeReworkCellType.Start; + onCellChanged?.Invoke(startPt.x, startPt.y, MazeReworkCellType.Start); var endRoom = rooms[rooms.Count - 1]; var endPt = endRoom.GetCenter(); - if (endPt == startPt && rooms.Count > 1) - { - endRoom = rooms[1]; - endPt = endRoom.GetCenter(); - } + if (endPt == startPt && rooms.Count > 1) { endRoom = rooms[1]; endPt = endRoom.GetCenter(); } grid[endPt.x, endPt.y] = MazeReworkCellType.End; + onCellChanged?.Invoke(endPt.x, endPt.y, MazeReworkCellType.End); } else { - int sx = _config.startLocation.x; - int sz = _config.startLocation.y; + int sx = _config.startLocation.x, sz = _config.startLocation.y; EnsureValidOddCoordinates(width, depth, ref sx, ref sz); grid[sx, sz] = MazeReworkCellType.Start; + onCellChanged?.Invoke(sx, sz, MazeReworkCellType.Start); - int ex = width - 2; - int ez = depth - 2; + int ex = width - 2, ez = depth - 2; EnsureValidOddCoordinates(width, depth, ref ex, ref ez); - if (grid[ex, ez] == MazeReworkCellType.Wall) { bool found = false; for (int r = 1; r < Mathf.Max(width, depth) && !found; r++) - { - for (int dx = -r; dx <= r && !found; dx++) - { - for (int dz = -r; dz <= r && !found; dz++) + for (int ddx = -r; ddx <= r && !found; ddx++) + for (int ddz = -r; ddz <= r && !found; ddz++) { - int tx = ex + dx; - int tz = ez + dz; + int tx = ex + ddx, tz = ez + ddz; if (tx >= 0 && tx < width && tz >= 0 && tz < depth && IsPathCell(grid[tx, tz])) - { - ex = tx; - ez = tz; - found = true; - } + { ex = tx; ez = tz; found = true; } } - } - } } - if (ex != sx || ez != sz) { grid[ex, ez] = MazeReworkCellType.End; + onCellChanged?.Invoke(ex, ez, MazeReworkCellType.End); } } } diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkManager.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkManager.cs index 0acf87a9..e4152e11 100644 --- a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkManager.cs +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using UnityEngine; using Sirenix.OdinInspector; +using Baba_yaga.GameSetup.MazeRework.Animation; namespace Baba_yaga.GameSetup.MazeRework { @@ -16,6 +17,11 @@ namespace Baba_yaga.GameSetup.MazeRework [Header("References")] [SerializeField] private MazeReworkSpawner spawner; [SerializeField] private Transform mazeContainer; + [SerializeField] private MazeAnimator animator; + + [Header("Animation")] + [Tooltip("If true, visually animates the generation process step-by-step. (Best used with 1 floor)")] + [SerializeField] private bool animateGeneration = false; [Header("Multi-floor Settings")] [Tooltip("Number of floors to generate.")] @@ -102,33 +108,51 @@ namespace Baba_yaga.GameSetup.MazeRework // Clear any previously spawned maze objects ClearMaze(); + if (animator != null) animator.StopAnimation(); var generator = new MazeReworkGenerator(config); - // Generate each floor using the configuration - for (int i = 0; i < floorCount; i++) + if (animateGeneration && animator != null) { - int seed = config.useRandomSeed ? Random.Range(0, 1000000) : config.seed + i * 1000; - var grid = generator.Generate(seed); + // Animated mode: generates floor 0 and replays the creation step-by-step + int seed = config.useRandomSeed ? Random.Range(0, 1000000) : config.seed; + var (grid, history) = generator.GenerateAnimated(seed); _grids.Add(grid); - } - // Connect floors with staircases if multiple floors are present - if (floorCount > 1) + BeginningWorldPosition = FindBeginningWorldPosition(grid); + + animator.AnimateGeneration(history, grid, spawner, 0f, mazeContainer, () => + { + Debug.Log($"[MazeReworkManager] Animation complete. Beginning world position: {BeginningWorldPosition}"); + }); + } + else { - GenerateConnections(); - } + // Standard instantaneous generation + for (int i = 0; i < floorCount; i++) + { + int seed = config.useRandomSeed ? Random.Range(0, 1000000) : config.seed + i * 1000; + var grid = generator.Generate(seed); + _grids.Add(grid); + } - // Spawn prefabs for all floors - for (int i = 0; i < floorCount; i++) - { - float yOffset = i * floorHeight; - spawner.Spawn(_grids[i], yOffset, mazeContainer); - } + // Connect floors with staircases if multiple floors are present + if (floorCount > 1) + { + GenerateConnections(); + } - // Compute and cache the world-space Beginning position from floor 0 - BeginningWorldPosition = FindBeginningWorldPosition(_grids[0]); - Debug.Log($"[MazeReworkManager] Beginning world position set to: {BeginningWorldPosition}"); + // Spawn prefabs for all floors + for (int i = 0; i < floorCount; i++) + { + float yOffset = i * floorHeight; + spawner.Spawn(_grids[i], yOffset, mazeContainer); + } + + // Compute and cache the world-space Beginning position from floor 0 + BeginningWorldPosition = FindBeginningWorldPosition(_grids[0]); + Debug.Log($"[MazeReworkManager] Beginning world position set to: {BeginningWorldPosition}"); + } } private void GenerateConnections() diff --git a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkSpawner.cs b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkSpawner.cs index 9b200b01..ab2976ca 100644 --- a/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkSpawner.cs +++ b/Assets/Scripts/Baba_yaga/GameSetup/MazeRework/MazeReworkSpawner.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using UnityEngine; +using Baba_yaga.GameSetup.MazeRework.Animation; namespace Baba_yaga.GameSetup.MazeRework { @@ -27,6 +28,11 @@ namespace Baba_yaga.GameSetup.MazeRework [Tooltip("Prefab for the End cell (player exit point). Auto-rotates based on neighbors.")] public GameObject endPrefab; + [Header("Highlight Prefabs (Animation Only)")] + public GameObject searchHeadPrefab; + public GameObject frontierPrefab; + public GameObject evaluatingPrefab; + [Header("Rotation Offsets (Degrees)")] [Tooltip("Rotation offset added to Hall 'I' prefab.")] public float hallRotationOffset = 0f; @@ -47,7 +53,8 @@ namespace Baba_yaga.GameSetup.MazeRework [Tooltip("Physical distance between each grid cell center.")] public float spacing = 3.0f; - private readonly List _spawnedObjects = new List(); + private readonly Dictionary _spawnedObjects = new Dictionary(); + private readonly Dictionary _spawnedHighlights = new Dictionary(); private void Start() { @@ -73,20 +80,21 @@ namespace Baba_yaga.GameSetup.MazeRework /// public void Clear() { - foreach (var obj in _spawnedObjects) + foreach (var obj in _spawnedObjects.Values) { if (obj == null) continue; - if (Application.isPlaying) - Destroy(obj); - else - DestroyImmediate(obj); + if (Application.isPlaying) Destroy(obj); else DestroyImmediate(obj); } _spawnedObjects.Clear(); + + foreach (var obj in _spawnedHighlights.Values) + { + if (obj == null) continue; + if (Application.isPlaying) Destroy(obj); else DestroyImmediate(obj); + } + _spawnedHighlights.Clear(); } - /// - /// Spawns the modular prefabs based on the generated 2D grid array. - /// public void Spawn(MazeReworkCellType[,] grid, float yOffset, Transform container) { if (grid == null) return; @@ -99,40 +107,96 @@ namespace Baba_yaga.GameSetup.MazeRework { for (int x = 0; x < width; x++) { - MazeReworkCellType type = grid[x, z]; - - // Wall cells spawn nothing - if (type == MazeReworkCellType.Wall) - { - continue; - } - - // For Start and End cells, use their dedicated prefab if assigned, - // then apply connectivity-based rotation so they face the correct open direction. - if (type == MazeReworkCellType.Start && beginningPrefab != null) - { - (_, float baseRot) = GetModularPrefabAndRotation(grid, x, z, width, depth); - SpawnPrefab(beginningPrefab, x, yOffset, z, baseRot + beginningRotationOffset, container, "Beginning"); - continue; - } - - if (type == MazeReworkCellType.End && endPrefab != null) - { - (_, float baseRot) = GetModularPrefabAndRotation(grid, x, z, width, depth); - SpawnPrefab(endPrefab, x, yOffset, z, baseRot + endRotationOffset, container, "End"); - continue; - } - - // All other path cells: evaluate connectivity to select and rotate the modular hallway prefab - (GameObject modularPrefab, float yRotation) = GetModularPrefabAndRotation(grid, x, z, width, depth); - if (modularPrefab != null) - { - SpawnPrefab(modularPrefab, x, yOffset, z, yRotation, container, $"{type}"); - } + RefreshSingleCell(grid, null, x, z, width, depth, yOffset, container); } } } + /// + /// Refreshes a specific cell and its neighbors based on the current grid state. + /// + public void RefreshCell(MazeReworkCellType[,] grid, MazeCellHighlight[,] highlights, int x, int z, float yOffset, Transform container) + { + if (grid == null) return; + int width = grid.GetLength(0); + int depth = grid.GetLength(1); + + // Refresh the target cell + RefreshSingleCell(grid, highlights, x, z, width, depth, yOffset, container); + + // Refresh neighbors because their connectivity might have changed + RefreshSingleCell(grid, highlights, x + 1, z, width, depth, yOffset, container); + RefreshSingleCell(grid, highlights, x - 1, z, width, depth, yOffset, container); + RefreshSingleCell(grid, highlights, x, z + 1, width, depth, yOffset, container); + RefreshSingleCell(grid, highlights, x, z - 1, width, depth, yOffset, container); + } + + private void RefreshSingleCell(MazeReworkCellType[,] grid, MazeCellHighlight[,] highlights, int x, int z, int width, int depth, float yOffset, Transform container) + { + if (x < 0 || x >= width || z < 0 || z >= depth) return; + + Vector2Int pos = new Vector2Int(x, z); + MazeReworkCellType type = grid[x, z]; + MazeCellHighlight hType = highlights != null ? highlights[x, z] : MazeCellHighlight.None; + + // 1. Process Highlights + if (_spawnedHighlights.TryGetValue(pos, out GameObject existingHighlight)) + { + if (Application.isPlaying) Destroy(existingHighlight); + else DestroyImmediate(existingHighlight); + _spawnedHighlights.Remove(pos); + } + + if (hType != MazeCellHighlight.None) + { + GameObject hPrefab = null; + if (hType == MazeCellHighlight.SearchHead) hPrefab = searchHeadPrefab; + else if (hType == MazeCellHighlight.Frontier) hPrefab = frontierPrefab; + else if (hType == MazeCellHighlight.Evaluating) hPrefab = evaluatingPrefab; + + if (hPrefab != null) + { + Vector3 localPos = new Vector3(x * spacing, yOffset, z * spacing); + GameObject spawnedH = Instantiate(hPrefab, container != null ? container : transform); + spawnedH.transform.localPosition = localPos; + spawnedH.name = $"Highlight_{hType}_{x}_{z}"; + _spawnedHighlights[pos] = spawnedH; + } + } + + // 2. Process Grid Cells + if (type == MazeReworkCellType.Wall) + { + if (_spawnedObjects.TryGetValue(pos, out GameObject existing)) + { + if (Application.isPlaying) Destroy(existing); + else DestroyImmediate(existing); + _spawnedObjects.Remove(pos); + } + return; + } + + if (type == MazeReworkCellType.Start && beginningPrefab != null) + { + (_, float baseRot) = GetModularPrefabAndRotation(grid, x, z, width, depth); + SpawnPrefab(beginningPrefab, x, yOffset, z, baseRot + beginningRotationOffset, container, "Beginning"); + return; + } + + if (type == MazeReworkCellType.End && endPrefab != null) + { + (_, float baseRot) = GetModularPrefabAndRotation(grid, x, z, width, depth); + SpawnPrefab(endPrefab, x, yOffset, z, baseRot + endRotationOffset, container, "End"); + return; + } + + (GameObject modularPrefab, float yRotation) = GetModularPrefabAndRotation(grid, x, z, width, depth); + if (modularPrefab != null) + { + SpawnPrefab(modularPrefab, x, yOffset, z, yRotation, container, $"{type}"); + } + } + private void SpawnPrefab(GameObject prefab, int x, float y, int z, float yRotation, Transform container, string namePrefix) { Vector3 localPosition = new Vector3(x * spacing, y, z * spacing); @@ -140,7 +204,14 @@ namespace Baba_yaga.GameSetup.MazeRework spawnedObj.transform.localPosition = localPosition; spawnedObj.transform.localRotation = Quaternion.Euler(0f, yRotation, 0f); spawnedObj.name = $"{namePrefix}_{x}_{z}"; - _spawnedObjects.Add(spawnedObj); + + var pos = new Vector2Int(x, z); + if (_spawnedObjects.TryGetValue(pos, out GameObject existing)) + { + if (Application.isPlaying) Destroy(existing); + else DestroyImmediate(existing); + } + _spawnedObjects[pos] = spawnedObj; } private (GameObject, float) GetModularPrefabAndRotation(MazeReworkCellType[,] grid, int x, int z, int width, int depth)