diff --git a/.idea/.idea.HALLUCINATE/.idea/workspace.xml b/.idea/.idea.HALLUCINATE/.idea/workspace.xml index 0400f241..8814200c 100644 --- a/.idea/.idea.HALLUCINATE/.idea/workspace.xml +++ b/.idea/.idea.HALLUCINATE/.idea/workspace.xml @@ -5,14 +5,19 @@ - - + + - - - - + + + + + + + + + diff --git a/Assets/Prefabs/Maze.meta b/Assets/Prefabs/Maze.meta new file mode 100644 index 00000000..09d6c092 --- /dev/null +++ b/Assets/Prefabs/Maze.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0988e2233101908428f5f5334d85a1f7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Maze/CorridorPrefab_.prefab b/Assets/Prefabs/Maze/CorridorPrefab_.prefab new file mode 100644 index 00000000..92203547 --- /dev/null +++ b/Assets/Prefabs/Maze/CorridorPrefab_.prefab @@ -0,0 +1,114 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &7041334646084879511 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8919327890245084656} + - component: {fileID: 8819792327415332675} + - component: {fileID: 4127344303633613435} + - component: {fileID: 67198264001523534} + m_Layer: 0 + m_Name: CorridorPrefab_ + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8919327890245084656 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7041334646084879511} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 70.81325, y: 0.00003, z: 73.80717} + 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 &8819792327415332675 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7041334646084879511} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4127344303633613435 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7041334646084879511} + 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: 31321ba15b8f8eb4c954353edc038b1d, 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 &67198264001523534 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7041334646084879511} + 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: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} diff --git a/Assets/Prefabs/Maze/CorridorPrefab_.prefab.meta b/Assets/Prefabs/Maze/CorridorPrefab_.prefab.meta new file mode 100644 index 00000000..693a4212 --- /dev/null +++ b/Assets/Prefabs/Maze/CorridorPrefab_.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4872dd25f62fbfa48b25a2b636aa6865 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Maze/ProcessingPrefab.prefab b/Assets/Prefabs/Maze/ProcessingPrefab.prefab new file mode 100644 index 00000000..f06a7c7d --- /dev/null +++ b/Assets/Prefabs/Maze/ProcessingPrefab.prefab @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &8367681202885872253 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4127344303633613435, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: 'm_Materials.Array.data[0]' + value: + objectReference: {fileID: 2100000, guid: c2e40fa84d53ccd428f640eac126b1fb, type: 2} + - target: {fileID: 7041334646084879511, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_Name + value: ProcessingPrefab + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} diff --git a/Assets/Prefabs/Maze/ProcessingPrefab.prefab.meta b/Assets/Prefabs/Maze/ProcessingPrefab.prefab.meta new file mode 100644 index 00000000..cc0f2529 --- /dev/null +++ b/Assets/Prefabs/Maze/ProcessingPrefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1e8b6ed6b01405e4b9e358abc8f7a058 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Maze/WallPrefab.prefab b/Assets/Prefabs/Maze/WallPrefab.prefab new file mode 100644 index 00000000..165b8b8e --- /dev/null +++ b/Assets/Prefabs/Maze/WallPrefab.prefab @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &7905038714086068258 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4127344303633613435, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: 'm_Materials.Array.data[0]' + value: + objectReference: {fileID: 2100000, guid: b34b07a45ba63ce47896c9e674f3a7a7, type: 2} + - target: {fileID: 7041334646084879511, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_Name + value: WallPrefab + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8919327890245084656, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 4872dd25f62fbfa48b25a2b636aa6865, type: 3} diff --git a/Assets/Prefabs/Maze/WallPrefab.prefab.meta b/Assets/Prefabs/Maze/WallPrefab.prefab.meta new file mode 100644 index 00000000..4c364025 --- /dev/null +++ b/Assets/Prefabs/Maze/WallPrefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c49f25c5b1c3e4b43a2bc56717387124 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/Maze/Crawler.cs b/Assets/Scripts/GameSetup/Maze/Crawler.cs deleted file mode 100644 index 62641f8d..00000000 --- a/Assets/Scripts/GameSetup/Maze/Crawler.cs +++ /dev/null @@ -1,70 +0,0 @@ -using UnityEngine; - -namespace Hallucinate.GameSetup.Maze -{ - /// - /// A maze generation algorithm that "crawls" through the grid in a semi-random walk. - /// It creates long, winding corridors by moving vertically or horizontally. - /// - public class Crawler : Maze - { - /// - /// Orchestrates the crawling generation. - /// (Currently empty as per original procedural logic). - /// - public override void Generate() - { - // Implementation can be expanded as needed. - } - - /// - /// Performs a vertical crawl starting from a random X position at the bottom. - /// - protected void CrawlV() - { - bool done = false; - int x = Random.Range(1, width - 1); - int z = 1; - - while (!done) - { - map[x, z] = Corridor; - if (Random.Range(0, 100) < 50) - { - x += Random.Range(-1, 2); - } - else - { - z += Random.Range(0, 2); - } - - done |= (x < 1 || x >= width - 1 || z < 1 || z >= depth - 1); - } - } - - /// - /// Performs a horizontal crawl starting from a random Z position at the left. - /// - protected void CrawlH() - { - bool done = false; - int x = 1; - int z = Random.Range(1, depth - 1); - - while (!done) - { - map[x, z] = Corridor; - if (Random.Range(0, 100) < 50) - { - x += Random.Range(0, 2); - } - else - { - z += Random.Range(-1, 2); - } - - done |= (x < 1 || x >= width - 1 || z < 1 || z >= depth - 1); - } - } - } -} diff --git a/Assets/Scripts/GameSetup/Maze/Crawler.cs.meta b/Assets/Scripts/GameSetup/Maze/Crawler.cs.meta deleted file mode 100644 index 76144d4b..00000000 --- a/Assets/Scripts/GameSetup/Maze/Crawler.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7b5776943e6d841879f829c725bf4e6b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/Maze/CrawlerAlgorithm.cs b/Assets/Scripts/GameSetup/Maze/CrawlerAlgorithm.cs new file mode 100644 index 00000000..8fe9ba08 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/CrawlerAlgorithm.cs @@ -0,0 +1,75 @@ +using System.Collections; +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + public class CrawlerAlgorithm : IMazeAlgorithm + { + private const int CrawlChance = 50; + private const int MinBoundary = 1; + + public void Generate(MazeGrid grid) + { + CrawlV(grid, 0); + CrawlH(grid, 0); + } + + public IEnumerator GenerateStepByStep(MazeGrid grid, float interval) + { + yield return CrawlV(grid, interval); + yield return CrawlH(grid, interval); + } + + private IEnumerator CrawlV(MazeGrid grid, float interval) + { + bool done = false; + int x = Random.Range(MinBoundary, grid.Width - MinBoundary); + int z = MinBoundary; + + while (!done) + { + grid.SetCell(x, z, MazeCellType.Processing); + if (interval > 0) yield return new WaitForSeconds(interval); + + grid.SetCell(x, z, MazeCellType.Corridor); + + if (Random.Range(0, 100) < CrawlChance) + { + x += Random.Range(-1, 2); + } + else + { + z += Random.Range(0, 2); + } + + done |= (x < MinBoundary || x >= grid.Width - MinBoundary || z < MinBoundary || z >= grid.Depth - MinBoundary); + } + } + + private IEnumerator CrawlH(MazeGrid grid, float interval) + { + bool done = false; + int x = MinBoundary; + int z = Random.Range(MinBoundary, grid.Depth - MinBoundary); + + while (!done) + { + grid.SetCell(x, z, MazeCellType.Processing); + if (interval > 0) yield return new WaitForSeconds(interval); + + grid.SetCell(x, z, MazeCellType.Corridor); + + if (Random.Range(0, 100) < CrawlChance) + { + x += Random.Range(0, 2); + } + else + { + z += Random.Range(-1, 2); + } + + done |= (x < MinBoundary || x >= grid.Width - MinBoundary || z < MinBoundary || z >= grid.Depth - MinBoundary); + } + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/CrawlerAlgorithm.cs.meta b/Assets/Scripts/GameSetup/Maze/CrawlerAlgorithm.cs.meta new file mode 100644 index 00000000..0fd697d8 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/CrawlerAlgorithm.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bd419f9be92beac48b6f551063165e1f \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/Extensions.cs b/Assets/Scripts/GameSetup/Maze/Extensions.cs index bdb306b1..e3c09f4c 100644 --- a/Assets/Scripts/GameSetup/Maze/Extensions.cs +++ b/Assets/Scripts/GameSetup/Maze/Extensions.cs @@ -2,19 +2,13 @@ using System.Collections.Generic; namespace Hallucinate.GameSetup.Maze.Extensions { - /// - /// Provides utility extension methods for maze generation algorithms. - /// - public static class Extensions + public static class ListExtensions { private static System.Random _rng = new System.Random(); /// - /// Shuffles the elements of an using the Fisher-Yates algorithm. - /// This is used to randomize directions for maze generation. + /// Shuffles a list using the Fisher-Yates algorithm. /// - /// The type of elements in the list. - /// The list to shuffle. public static void Shuffle(this IList list) { int n = list.Count; diff --git a/Assets/Scripts/GameSetup/Maze/Extensions.cs.meta b/Assets/Scripts/GameSetup/Maze/Extensions.cs.meta index 14d44730..cbbc6316 100644 --- a/Assets/Scripts/GameSetup/Maze/Extensions.cs.meta +++ b/Assets/Scripts/GameSetup/Maze/Extensions.cs.meta @@ -1,11 +1,2 @@ fileFormatVersion: 2 -guid: 7d9791b1b03c14f16a245b2d4577c5f9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +guid: 7d9791b1b03c14f16a245b2d4577c5f9 \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/Interfaces.meta b/Assets/Scripts/GameSetup/Maze/Interfaces.meta new file mode 100644 index 00000000..6d0164e9 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6e1bb9cd9af7ffe40ad1a740c3c30dd6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/Maze/Interfaces/IMazeAlgorithm.cs b/Assets/Scripts/GameSetup/Maze/Interfaces/IMazeAlgorithm.cs new file mode 100644 index 00000000..8687135c --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/Interfaces/IMazeAlgorithm.cs @@ -0,0 +1,19 @@ +namespace Hallucinate.GameSetup.Maze +{ + /// + /// Interface for all maze generation algorithms. + /// Supports both immediate and step-by-step (animated) generation. + /// + public interface IMazeAlgorithm + { + /// + /// Generates the maze immediately in one frame. + /// + void Generate(MazeGrid grid); + + /// + /// Generates the maze step-by-step for visualization. + /// + System.Collections.IEnumerator GenerateStepByStep(MazeGrid grid, float interval); + } +} diff --git a/Assets/Scripts/GameSetup/Maze/Interfaces/IMazeAlgorithm.cs.meta b/Assets/Scripts/GameSetup/Maze/Interfaces/IMazeAlgorithm.cs.meta new file mode 100644 index 00000000..ac465940 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/Interfaces/IMazeAlgorithm.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 46b6a7796ba3c494581e4dcb884da064 \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/MapLocation.cs b/Assets/Scripts/GameSetup/Maze/MapLocation.cs new file mode 100644 index 00000000..b2eeefdb --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MapLocation.cs @@ -0,0 +1,32 @@ +namespace Hallucinate.GameSetup.Maze +{ + /// + /// Represents a 2D coordinate on the maze grid. + /// Used as a lightweight value type to avoid GC allocations. + /// + public readonly struct MapLocation + { + public readonly int x; + public readonly int z; + + public MapLocation(int _x, int _z) + { + x = _x; + z = _z; + } + + // Static predefined directions to eliminate magic numbers in algorithms + public static MapLocation Right => new MapLocation(1, 0); + public static MapLocation Left => new MapLocation(-1, 0); + public static MapLocation Up => new MapLocation(0, 1); + public static MapLocation Down => new MapLocation(0, -1); + + /// + /// Returns a list of all 4 cardinal directions. + /// + public static System.Collections.Generic.List Directions => new System.Collections.Generic.List + { + Right, Up, Left, Down + }; + } +} diff --git a/Assets/Scripts/GameSetup/Maze/MapLocation.cs.meta b/Assets/Scripts/GameSetup/Maze/MapLocation.cs.meta new file mode 100644 index 00000000..8fab4430 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MapLocation.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 987a7c46c96326a44b3a5f179fe61161 \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/Maze.cs b/Assets/Scripts/GameSetup/Maze/Maze.cs deleted file mode 100644 index f2f5f8ad..00000000 --- a/Assets/Scripts/GameSetup/Maze/Maze.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace Hallucinate.GameSetup.Maze -{ - /// - /// Represents a 2D coordinate on the maze grid. - /// Used as a lightweight value type to avoid GC allocations. - /// - public readonly struct MapLocation - { - public readonly int x; - public readonly int z; - - public MapLocation(int _x, int _z) - { - x = _x; - z = _z; - } - } - - /// - /// The base class for maze generation. - /// Handles map initialization, basic neighbor counting logic, and debug rendering. - /// - public class Maze : MonoBehaviour - { - #region Constants - public const byte Corridor = 0; - public const byte Wall = 1; - public const byte Path = 2; - #endregion - - #region Fields - [Header("Maze Settings")] - [SerializeField] protected int width = 30; // x length - [SerializeField] protected int depth = 30; // z length - [SerializeField] protected int scale = 6; - - [Header("Hierarchy Settings")] - [UnityEngine.Serialization.FormerlySerializedAs("_mapParentObjet")] - [SerializeField] protected Transform mapParentObject; - - /// - /// List of cardinal directions used for neighbor checking and navigation. - /// - protected List directions = new List() - { - new MapLocation(1, 0), - new MapLocation(0, 1), - new MapLocation(-1, 0), - new MapLocation(0, -1) - }; - - /// - /// 2D array representing the maze grid. - /// 0 = Corridor, 1 = Wall, 2 = Special Path. - /// - public byte[,] map; - #endregion - - protected virtual void Start() - { - InitialiseMap(); - Generate(); - DrawMap(); - } - - /// - /// Initializes the map array and fills it with walls. - /// - protected void InitialiseMap() - { - map = new byte[width, depth]; - for (int z = 0; z < depth; z++) - { - for (int x = 0; x < width; x++) - { - map[x, z] = Wall; - } - } - } - - /// - /// Virtual method to be overridden by specific maze generation algorithms. - /// Default implementation creates a random noise-based map. - /// - public virtual void Generate() - { - for (int z = 0; z < depth; z++) - { - for (int x = 0; x < width; x++) - { - if (Random.Range(0, 100) < 50) - { - map[x, z] = Corridor; - } - } - } - } - - /// - /// Renders the maze in the scene using Unity primitives. - /// - protected void DrawMap() - { - for (int z = 0; z < depth; z++) - { - for (int x = 0; x < width; x++) - { - if (map[x, z] == Wall) - { - Vector3 pos = new Vector3(x * scale, 0, z * scale); - GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube); - wall.transform.localScale = new Vector3(scale, scale, scale); - wall.transform.position = pos; - - if (mapParentObject != null) - { - wall.transform.SetParent(mapParentObject); - } - } - } - } - } - - #region Helpers - /// - /// Counts the number of horizontal and vertical neighbors that are corridors. - /// - /// X coordinate. - /// Z coordinate. - /// The count of square neighbors. - public int CountSquareNeighbours(int x, int z) - { - int count = 0; - if (x <= 0 || x >= width - 1 || z <= 0 || z >= depth - 1) return 5; - - if (map[x - 1, z] == Corridor) count++; - if (map[x + 1, z] == Corridor) count++; - if (map[x, z + 1] == Corridor) count++; - if (map[x, z - 1] == Corridor) count++; - - return count; - } - - /// - /// Counts the number of diagonal neighbors that are corridors. - /// - /// X coordinate. - /// Z coordinate. - /// The count of diagonal neighbors. - public int CountDiagonalNeighbours(int x, int z) - { - int count = 0; - if (x <= 0 || x >= width - 1 || z <= 0 || z >= depth - 1) return 5; - - if (map[x - 1, z - 1] == Corridor) count++; - if (map[x + 1, z + 1] == Corridor) count++; - if (map[x - 1, z + 1] == Corridor) count++; - if (map[x + 1, z - 1] == Corridor) count++; - - return count; - } - - /// - /// Counts all neighbors (square + diagonal) that are corridors. - /// - /// X coordinate. - /// Z coordinate. - /// Total neighbor count. - public int CountAllNeighbours(int x, int z) - { - return CountSquareNeighbours(x, z) + CountDiagonalNeighbours(x, z); - } - #endregion - } -} diff --git a/Assets/Scripts/GameSetup/Maze/Maze.cs.meta b/Assets/Scripts/GameSetup/Maze/Maze.cs.meta deleted file mode 100644 index 1fefe103..00000000 --- a/Assets/Scripts/GameSetup/Maze/Maze.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1039d646c358a4bd5ac697b0446a2f7e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/Maze/MazeCellType.cs b/Assets/Scripts/GameSetup/Maze/MazeCellType.cs new file mode 100644 index 00000000..f3ff132a --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeCellType.cs @@ -0,0 +1,16 @@ +namespace Hallucinate.GameSetup.Maze +{ + /// + /// Defines the state of each cell in the maze. + /// Used to replace magic numbers and drive visual changes. + /// + public enum MazeCellType + { + Wall, // Solid block + Corridor, // Finalized path + Processing, // Currently being evaluated by algorithm (Debug) + Path, // Temporary path (e.g., Wilson's crawler) + Start, // Entry point + End // Exit point + } +} diff --git a/Assets/Scripts/GameSetup/Maze/MazeCellType.cs.meta b/Assets/Scripts/GameSetup/Maze/MazeCellType.cs.meta new file mode 100644 index 00000000..87631d50 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeCellType.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f54ef08fa4922eb4a968d46c7aa71faf \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/MazeGrid.cs b/Assets/Scripts/GameSetup/Maze/MazeGrid.cs new file mode 100644 index 00000000..cb386259 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeGrid.cs @@ -0,0 +1,71 @@ +using System; +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + /// + /// Holds the logical state of the maze grid. + /// Notifies listeners whenever a cell changes to trigger visual updates. + /// + public class MazeGrid + { + public int Width { get; } + public int Depth { get; } + + private readonly MazeCellType[,] _cells; + + /// + /// Event fired when a cell's type is changed. + /// Useful for the Renderer to trigger animations/FX. + /// + public event Action OnCellChanged; + + public MazeGrid(int width, int depth) + { + Width = width; + Depth = depth; + _cells = new MazeCellType[width, depth]; + + // Initialize all as walls + for (int z = 0; z < depth; z++) + for (int x = 0; x < width; x++) + _cells[x, z] = MazeCellType.Wall; + } + + public void SetCell(int x, int z, MazeCellType type) + { + if (IsInBounds(x, z)) + { + if (_cells[x, z] != type) + { + _cells[x, z] = type; + OnCellChanged?.Invoke(x, z, type); + } + } + } + + public MazeCellType GetCell(int x, int z) + { + if (IsInBounds(x, z)) + return _cells[x, z]; + return MazeCellType.Wall; // Treat out of bounds as walls + } + + public bool IsInBounds(int x, int z) + { + return x >= 0 && x < Width && z >= 0 && z < Depth; + } + + public int CountSquareNeighbours(int x, int z, MazeCellType targetType) + { + int count = 0; + if (x <= 0 || x >= Width - 1 || z <= 0 || z >= Depth - 1) return 5; + + if (GetCell(x - 1, z) == targetType) count++; + if (GetCell(x + 1, z) == targetType) count++; + if (GetCell(x, z + 1) == targetType) count++; + if (GetCell(x, z - 1) == targetType) count++; + return count; + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/MazeGrid.cs.meta b/Assets/Scripts/GameSetup/Maze/MazeGrid.cs.meta new file mode 100644 index 00000000..f918ca79 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeGrid.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a1a7a252ff0b1014a9690f08897e2e59 \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/MazeManager.cs b/Assets/Scripts/GameSetup/Maze/MazeManager.cs new file mode 100644 index 00000000..6a8c130d --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeManager.cs @@ -0,0 +1,81 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + /// + /// Central controller for the Maze system. + /// Manages algorithm selection, debug speed, and regeneration. + /// + public class MazeManager : MonoBehaviour + { + public enum AlgorithmType { Recursive, Wilsons, Prims, Crawler } + + [Header("System Settings")] + [SerializeField] private AlgorithmType selectedAlgorithm; + [SerializeField] private int width = 30; + [SerializeField] private int depth = 30; + + [Header("Debug Settings")] + [SerializeField] private bool debugMode = true; + [Range(0.001f, 0.5f)] + [SerializeField] private float visualizationInterval = 0.05f; + + [Header("References")] + [SerializeField] private MazeRenderer mazeRenderer; + [SerializeField] private Transform mazeContainer; + + private MazeGrid _grid; + private Coroutine _generationCoroutine; + + private void Start() + { + Regenerate(); + } + + private void Update() + { + if (Input.GetKeyDown(KeyCode.R)) + { + Regenerate(); + } + } + + [ContextMenu("Regenerate")] + public void Regenerate() + { + if (_generationCoroutine != null) + { + StopCoroutine(_generationCoroutine); + } + + mazeRenderer.Clear(); + _grid = new MazeGrid(width, depth); + mazeRenderer.Initialize(_grid, mazeContainer); + + IMazeAlgorithm algorithm = GetAlgorithm(selectedAlgorithm); + + if (debugMode) + { + _generationCoroutine = StartCoroutine(algorithm.GenerateStepByStep(_grid, visualizationInterval)); + } + else + { + algorithm.Generate(_grid); + } + } + + private IMazeAlgorithm GetAlgorithm(AlgorithmType type) + { + return type switch + { + AlgorithmType.Recursive => new RecursiveAlgorithm(), + AlgorithmType.Wilsons => new WilsonsAlgorithm(), + AlgorithmType.Prims => new PrimsAlgorithm(), + AlgorithmType.Crawler => new CrawlerAlgorithm(), + _ => new RecursiveAlgorithm() + }; + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/MazeManager.cs.meta b/Assets/Scripts/GameSetup/Maze/MazeManager.cs.meta new file mode 100644 index 00000000..b11f7944 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3607adabe0c29c34591af73b414eb17a \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs b/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs new file mode 100644 index 00000000..5f77e8eb --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs @@ -0,0 +1,102 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + /// + /// Responsible for the visual representation of the maze. + /// Handles spawning, pooling, and animations. + /// + public class MazeRenderer : MonoBehaviour + { + [SerializeField] private MazeVisualProfile visualProfile; + + private readonly Dictionary _spawnedCells = new Dictionary(); + private Transform _container; + + public void Initialize(MazeGrid grid, Transform container) + { + _container = container; + grid.OnCellChanged += HandleCellChanged; + + // Initial render + for (int z = 0; z < grid.Depth; z++) + { + for (int x = 0; x < grid.Width; x++) + { + UpdateCellVisual(x, z, grid.GetCell(x, z), false); + } + } + } + + public void Clear() + { + // IMPORTANT: Stop all running animations to prevent accessing destroyed objects + StopAllCoroutines(); + + foreach (var cell in _spawnedCells.Values) + { + if (cell != null) Destroy(cell); + } + _spawnedCells.Clear(); + } + + private void HandleCellChanged(int x, int z, MazeCellType type) + { + UpdateCellVisual(x, z, type, true); + } + + private void UpdateCellVisual(int x, int z, MazeCellType type, bool animate) + { + Vector2Int pos = new Vector2Int(x, z); + + // Remove old visual if exists + if (_spawnedCells.TryGetValue(pos, out GameObject oldObj)) + { + Destroy(oldObj); + _spawnedCells.Remove(pos); + } + + GameObject prefab = visualProfile.GetPrefab(type); + if (prefab == null) return; + + Vector3 worldPos = new Vector3(x * visualProfile.scale, 0, z * visualProfile.scale); + GameObject newObj = Instantiate(prefab, worldPos, Quaternion.identity, _container); + newObj.transform.localScale = Vector3.one * visualProfile.scale; + _spawnedCells[pos] = newObj; + + if (animate) + { + StartCoroutine(AnimateCell(newObj.transform)); + } + } + + private IEnumerator AnimateCell(Transform target) + { + if (target == null) yield break; + + float duration = visualProfile.animationDuration; + float elapsed = 0; + Vector3 finalScale = target.localScale; + target.localScale = Vector3.zero; + + while (elapsed < duration) + { + // Extra safety check in case the object is destroyed mid-animation + if (target == null) yield break; + + elapsed += Time.deltaTime; + float t = elapsed / duration; + float s = Mathf.Sin(t * Mathf.PI * 0.5f); + target.localScale = finalScale * s; + yield return null; + } + + if (target != null) + { + target.localScale = finalScale; + } + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs.meta b/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs.meta new file mode 100644 index 00000000..ab28d9fa --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f30df611110713742ab984f5bead5d88 \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs b/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs new file mode 100644 index 00000000..362aa0ca --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs @@ -0,0 +1,34 @@ +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + [CreateAssetMenu(fileName = "MazeVisualProfile", menuName = "Hallucinate/Maze/Visual Profile")] + public class MazeVisualProfile : ScriptableObject + { + [Header("Prefabs")] + public GameObject wallPrefab; + public GameObject corridorPrefab; + public GameObject processingPrefab; + public GameObject pathPrefab; + public GameObject startPrefab; + public GameObject endPrefab; + + [Header("Visualization Settings")] + public float scale = 1f; + public float animationDuration = 0.25f; + + public GameObject GetPrefab(MazeCellType type) + { + return type switch + { + MazeCellType.Wall => wallPrefab, + MazeCellType.Corridor => corridorPrefab, + MazeCellType.Processing => processingPrefab, + MazeCellType.Path => pathPrefab, + MazeCellType.Start => startPrefab, + MazeCellType.End => endPrefab, + _ => null + }; + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs.meta b/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs.meta new file mode 100644 index 00000000..d0f6dbb7 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d3ff96571406a624381b7b0e596a4d1b \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/Prims.cs b/Assets/Scripts/GameSetup/Maze/Prims.cs deleted file mode 100644 index 40d7fe29..00000000 --- a/Assets/Scripts/GameSetup/Maze/Prims.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace Hallucinate.GameSetup.Maze -{ - /// - /// Implements a simplified version of Prim's algorithm for maze generation. - /// It picks walls at random and converts them into corridors if they only have one corridor neighbor. - /// - public class Prims : Maze - { - /// - /// Generates the maze using Prim's algorithm logic. - /// - public override void Generate() - { - int x = 2; - int z = 2; - - map[x, z] = Corridor; - - List walls = new List - { - new MapLocation(x + 1, z), - new MapLocation(x - 1, z), - new MapLocation(x, z + 1), - new MapLocation(x, z - 1) - }; - - int countloops = 0; - while (walls.Count > 0 && countloops < 5000) - { - int rwall = Random.Range(0, walls.Count); - x = walls[rwall].x; - z = walls[rwall].z; - walls.RemoveAt(rwall); - - if (CountSquareNeighbours(x, z) == 1) - { - map[x, z] = Corridor; - walls.Add(new MapLocation(x + 1, z)); - walls.Add(new MapLocation(x - 1, z)); - walls.Add(new MapLocation(x, z + 1)); - walls.Add(new MapLocation(x, z - 1)); - } - - countloops++; - } - } - } -} diff --git a/Assets/Scripts/GameSetup/Maze/Prims.cs.meta b/Assets/Scripts/GameSetup/Maze/Prims.cs.meta deleted file mode 100644 index 14552764..00000000 --- a/Assets/Scripts/GameSetup/Maze/Prims.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f12a5f6746a454e08a295f64a34f5dcf -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/Maze/PrimsAlgorithm.cs b/Assets/Scripts/GameSetup/Maze/PrimsAlgorithm.cs new file mode 100644 index 00000000..78ecafa3 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/PrimsAlgorithm.cs @@ -0,0 +1,95 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + public class PrimsAlgorithm : IMazeAlgorithm + { + private const int InitialX = 2; + private const int InitialZ = 2; + private const int MaxIterations = 10000; + private const int TargetCorridorNeighbours = 1; + + public void Generate(MazeGrid grid) + { + int x = InitialX; + int z = InitialZ; + grid.SetCell(x, z, MazeCellType.Corridor); + + List walls = GetNeighbouringWalls(grid, x, z); + + int iterations = 0; + while (walls.Count > 0 && iterations < MaxIterations) + { + int rIndex = Random.Range(0, walls.Count); + MapLocation w = walls[rIndex]; + walls.RemoveAt(rIndex); + + if (grid.CountSquareNeighbours(w.x, w.z, MazeCellType.Corridor) == TargetCorridorNeighbours) + { + grid.SetCell(w.x, w.z, MazeCellType.Corridor); + walls.AddRange(GetNeighbouringWalls(grid, w.x, w.z)); + } + iterations++; + } + } + + public IEnumerator GenerateStepByStep(MazeGrid grid, float interval) + { + int x = InitialX; + int z = InitialZ; + grid.SetCell(x, z, MazeCellType.Corridor); + yield return new WaitForSeconds(interval); + + List walls = GetNeighbouringWalls(grid, x, z); + + foreach(var w in walls) grid.SetCell(w.x, w.z, MazeCellType.Processing); + + int iterations = 0; + while (walls.Count > 0 && iterations < MaxIterations) + { + int rIndex = Random.Range(0, walls.Count); + MapLocation w = walls[rIndex]; + walls.RemoveAt(rIndex); + + if (grid.CountSquareNeighbours(w.x, w.z, MazeCellType.Corridor) == TargetCorridorNeighbours) + { + grid.SetCell(w.x, w.z, MazeCellType.Corridor); + if (interval > 0) yield return new WaitForSeconds(interval); + + var newWalls = GetNeighbouringWalls(grid, w.x, w.z); + foreach(var nw in newWalls) + { + if (grid.GetCell(nw.x, nw.z) == MazeCellType.Wall) + { + grid.SetCell(nw.x, nw.z, MazeCellType.Processing); + walls.Add(nw); + } + } + } + else + { + if(grid.GetCell(w.x, w.z) == MazeCellType.Processing) + grid.SetCell(w.x, w.z, MazeCellType.Wall); + } + iterations++; + } + } + + private List GetNeighbouringWalls(MazeGrid grid, int x, int z) + { + List neighbours = new List(); + foreach (var dir in MapLocation.Directions) + { + int nx = x + dir.x; + int nz = z + dir.z; + if (grid.IsInBounds(nx, nz) && grid.GetCell(nx, nz) == MazeCellType.Wall) + { + neighbours.Add(new MapLocation(nx, nz)); + } + } + return neighbours; + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/PrimsAlgorithm.cs.meta b/Assets/Scripts/GameSetup/Maze/PrimsAlgorithm.cs.meta new file mode 100644 index 00000000..842f4524 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/PrimsAlgorithm.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: edcdd3c0aa9656a4797b83cc675aa629 \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/Recursive.cs b/Assets/Scripts/GameSetup/Maze/Recursive.cs deleted file mode 100644 index 7c9d0084..00000000 --- a/Assets/Scripts/GameSetup/Maze/Recursive.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Hallucinate.GameSetup.Maze.Extensions; -using UnityEngine; - -namespace Hallucinate.GameSetup.Maze -{ - /// - /// A recursive backtracker algorithm for maze generation. - /// It explores the grid randomly and backtracks when it reaches a dead end. - /// - public class Recursive : Maze - { - /// - /// Entry point for the recursive generation. - /// Starts from a fixed position (5, 5). - /// - public override void Generate() - { - Generate(5, 5); - } - - /// - /// Internal recursive method that carves corridors by exploring neighbors in a random order. - /// - /// The current X coordinate. - /// The current Z coordinate. - protected void Generate(int x, int z) - { - if (CountSquareNeighbours(x, z) >= 2) return; - - map[x, z] = Corridor; - - directions.Shuffle(); - - Generate(x + directions[0].x, z + directions[0].z); - Generate(x + directions[1].x, z + directions[1].z); - Generate(x + directions[2].x, z + directions[2].z); - Generate(x + directions[3].x, z + directions[3].z); - } - } -} diff --git a/Assets/Scripts/GameSetup/Maze/Recursive.cs.meta b/Assets/Scripts/GameSetup/Maze/Recursive.cs.meta deleted file mode 100644 index 4bb0e7cf..00000000 --- a/Assets/Scripts/GameSetup/Maze/Recursive.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 33bbdb95ccc4b4577a62495732a02d3e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/Maze/RecursiveAlgorithm.cs b/Assets/Scripts/GameSetup/Maze/RecursiveAlgorithm.cs new file mode 100644 index 00000000..f050852a --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/RecursiveAlgorithm.cs @@ -0,0 +1,69 @@ +using System.Collections; +using System.Collections.Generic; +using Hallucinate.GameSetup.Maze.Extensions; +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + public class RecursiveAlgorithm : IMazeAlgorithm + { + private const int StartX = 5; + private const int StartZ = 5; + private const int DeadEndNeighbourThreshold = 2; + + private readonly List _directions = MapLocation.Directions; + + public void Generate(MazeGrid grid) + { + GenerateRecursive(grid, StartX, StartZ); + } + + public IEnumerator GenerateStepByStep(MazeGrid grid, float interval) + { + yield return GenerateRecursiveStepByStep(grid, StartX, StartZ, interval); + } + + private void GenerateRecursive(MazeGrid grid, int x, int z) + { + if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) return; + + grid.SetCell(x, z, MazeCellType.Corridor); + + List shuffledDirs = new List(_directions); + shuffledDirs.Shuffle(); + + foreach (var dir in shuffledDirs) + { + int nx = x + dir.x; + int nz = z + dir.z; + if (grid.IsInBounds(nx, nz)) + { + GenerateRecursive(grid, nx, nz); + } + } + } + + private IEnumerator GenerateRecursiveStepByStep(MazeGrid grid, int x, int z, float interval) + { + if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) yield break; + + grid.SetCell(x, z, MazeCellType.Processing); + if (interval > 0) yield return new WaitForSeconds(interval); + + grid.SetCell(x, z, MazeCellType.Corridor); + + List shuffledDirs = new List(_directions); + shuffledDirs.Shuffle(); + + foreach (var dir in shuffledDirs) + { + int nx = x + dir.x; + int nz = z + dir.z; + if (grid.IsInBounds(nx, nz)) + { + yield return GenerateRecursiveStepByStep(grid, nx, nz, interval); + } + } + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/RecursiveAlgorithm.cs.meta b/Assets/Scripts/GameSetup/Maze/RecursiveAlgorithm.cs.meta new file mode 100644 index 00000000..2b10f9d6 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/RecursiveAlgorithm.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2460c0e9379da9741b3d3387aa6c7a8e \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/Maze/Wilsons.cs b/Assets/Scripts/GameSetup/Maze/Wilsons.cs deleted file mode 100644 index b7f2ae8f..00000000 --- a/Assets/Scripts/GameSetup/Maze/Wilsons.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace Hallucinate.GameSetup.Maze -{ - /// - /// Implements Wilson's algorithm for generating a Uniform Spanning Tree of the grid. - /// It uses loop-erased random walks to connect unvisited cells to the existing maze. - /// - public class Wilsons : Maze - { - private List _notUsed = new List(); - - /// - /// Generates the maze using Wilson's algorithm logic. - /// - public override void Generate() - { - // Create a starting cell - int x = Random.Range(2, width - 1); - int z = Random.Range(2, depth - 1); - map[x, z] = Path; - - while (GetAvailableCells() > 1) - { - RandomWalk(); - } - } - - /// - /// Counts how many neighbors are already part of the finalized "Path". - /// - private int CountSquareMazeNeighbours(int x, int z) - { - int count = 0; - for (int d = 0; d < directions.Count; d++) - { - int nx = x + directions[d].x; - int nz = z + directions[d].z; - - if (map[nx, nz] == Path) - { - count++; - } - } - - return count; - } - - /// - /// Scans the grid for cells that are not yet connected to the maze. - /// - /// The number of available (unused) cells. - private int GetAvailableCells() - { - _notUsed.Clear(); - for (int z = 1; z < depth - 1; z++) - { - for (int x = 1; x < width - 1; x++) - { - if (CountSquareMazeNeighbours(x, z) == 0) - { - _notUsed.Add(new MapLocation(x, z)); - } - } - } - - return _notUsed.Count; - } - - /// - /// Performs a random walk from an unused cell until it hits the existing maze. - /// - private void RandomWalk() - { - List inWalk = new List(); - int rStartIndex = Random.Range(0, _notUsed.Count); - - int cx = _notUsed[rStartIndex].x; - int cz = _notUsed[rStartIndex].z; - - inWalk.Add(new MapLocation(cx, cz)); - - int loopCount = 0; - bool validPath = false; - - while (cx > 0 && cx < width - 1 && cz > 0 && cz < depth - 1 && loopCount < 5000 && !validPath) - { - map[cx, cz] = Corridor; - - if (CountSquareMazeNeighbours(cx, cz) > 1) - { - break; - } - - int rd = Random.Range(0, directions.Count); - int nx = cx + directions[rd].x; - int nz = cz + directions[rd].z; - - if (CountSquareNeighbours(nx, nz) < 2) - { - cx = nx; - cz = nz; - inWalk.Add(new MapLocation(cx, cz)); - } - - validPath = CountSquareMazeNeighbours(cx, cz) == 1; - loopCount++; - } - - if (validPath) - { - map[cx, cz] = Corridor; - Debug.Log("Path Found"); - - foreach (MapLocation m in inWalk) - { - map[m.x, m.z] = Path; - } - inWalk.Clear(); - } - else - { - foreach (MapLocation m in inWalk) - { - map[m.x, m.z] = Wall; - } - inWalk.Clear(); - } - } - } -} diff --git a/Assets/Scripts/GameSetup/Maze/Wilsons.cs.meta b/Assets/Scripts/GameSetup/Maze/Wilsons.cs.meta deleted file mode 100644 index 54e9fd95..00000000 --- a/Assets/Scripts/GameSetup/Maze/Wilsons.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 502e6aa24e7de43dfb6d20ecd0745176 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/Maze/WilsonsAlgorithm.cs b/Assets/Scripts/GameSetup/Maze/WilsonsAlgorithm.cs new file mode 100644 index 00000000..c7eeb746 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/WilsonsAlgorithm.cs @@ -0,0 +1,118 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Hallucinate.GameSetup.Maze +{ + public class WilsonsAlgorithm : IMazeAlgorithm + { + private const int MinBoundary = 2; + private const int MaxIterationSafety = 5000; + private const int TargetPathNeighbours = 1; + + private readonly List _directions = new List() + { + new MapLocation(1, 0), new MapLocation(0, 1), new MapLocation(-1, 0), new MapLocation(0, -1) + }; + + public void Generate(MazeGrid grid) + { + int x = Random.Range(MinBoundary, grid.Width - MinBoundary); + int z = Random.Range(MinBoundary, grid.Depth - MinBoundary); + grid.SetCell(x, z, MazeCellType.Corridor); + + while (GetAvailableCells(grid).Count > 1) + { + PerformRandomWalk(grid, null, 0); + } + } + + public IEnumerator GenerateStepByStep(MazeGrid grid, float interval) + { + int x = Random.Range(MinBoundary, grid.Width - MinBoundary); + int z = Random.Range(MinBoundary, grid.Depth - MinBoundary); + grid.SetCell(x, z, MazeCellType.Corridor); + yield return new WaitForSeconds(interval); + + while (true) + { + var available = GetAvailableCells(grid); + if (available.Count <= 1) break; + + yield return PerformRandomWalk(grid, available, interval); + } + } + + private List GetAvailableCells(MazeGrid grid) + { + List available = new List(); + for (int z = 1; z < grid.Depth - 1; z++) + { + for (int x = 1; x < grid.Width - 1; x++) + { + if (CountPathNeighbours(grid, x, z) == 0) + available.Add(new MapLocation(x, z)); + } + } + return available; + } + + private int CountPathNeighbours(MazeGrid grid, int x, int z) + { + int count = 0; + foreach (var d in _directions) + { + if (grid.GetCell(x + d.x, z + d.z) == MazeCellType.Corridor) count++; + } + return count; + } + + private IEnumerator PerformRandomWalk(MazeGrid grid, List available, float interval) + { + List inWalk = new List(); + int rStart = Random.Range(0, available.Count); + int cx = available[rStart].x; + int cz = available[rStart].z; + + inWalk.Add(new MapLocation(cx, cz)); + bool pathFound = false; + + int safety = 0; + while (grid.IsInBounds(cx, cz) && !pathFound && safety < MaxIterationSafety) + { + grid.SetCell(cx, cz, MazeCellType.Processing); + if (interval > 0) yield return new WaitForSeconds(interval); + + if (CountPathNeighbours(grid, cx, cz) > 1) break; + + int rd = Random.Range(0, _directions.Count); + int nx = cx + _directions[rd].x; + int nz = cz + _directions[rd].z; + + if (grid.CountSquareNeighbours(nx, nz, MazeCellType.Processing) < 2) + { + cx = nx; + cz = nz; + inWalk.Add(new MapLocation(cx, cz)); + } + + pathFound = CountPathNeighbours(grid, cx, cz) == TargetPathNeighbours; + safety++; + } + + if (pathFound) + { + foreach (var m in inWalk) + { + grid.SetCell(m.x, m.z, MazeCellType.Corridor); + if (interval > 0) yield return new WaitForSeconds(interval * 0.5f); + } + } + else + { + foreach (var m in inWalk) + grid.SetCell(m.x, m.z, MazeCellType.Wall); + } + } + } +} diff --git a/Assets/Scripts/GameSetup/Maze/WilsonsAlgorithm.cs.meta b/Assets/Scripts/GameSetup/Maze/WilsonsAlgorithm.cs.meta new file mode 100644 index 00000000..40e0edb0 --- /dev/null +++ b/Assets/Scripts/GameSetup/Maze/WilsonsAlgorithm.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 61156f8986612ca49a0672ad5542380f \ No newline at end of file diff --git a/Assets/Settings/Project Setting/DefaultMazeProfile.asset b/Assets/Settings/Project Setting/DefaultMazeProfile.asset new file mode 100644 index 00000000..f5ff2597 --- /dev/null +++ b/Assets/Settings/Project Setting/DefaultMazeProfile.asset @@ -0,0 +1,22 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d3ff96571406a624381b7b0e596a4d1b, type: 3} + m_Name: DefaultMazeProfile + m_EditorClassIdentifier: Assembly-CSharp::Hallucinate.GameSetup.Maze.MazeVisualProfile + wallPrefab: {fileID: 0} + corridorPrefab: {fileID: 0} + processingPrefab: {fileID: 0} + pathPrefab: {fileID: 0} + startPrefab: {fileID: 0} + endPrefab: {fileID: 0} + scale: 1 + animationDuration: 0.25 diff --git a/Assets/Settings/Project Setting/DefaultMazeProfile.asset.meta b/Assets/Settings/Project Setting/DefaultMazeProfile.asset.meta new file mode 100644 index 00000000..6a36cba0 --- /dev/null +++ b/Assets/Settings/Project Setting/DefaultMazeProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15b745b0bb979b84ea937c679ee0f1ed +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: