This commit is contained in:
2026-07-04 03:57:58 +07:00
parent ddaf07ef2f
commit a3359cf7e1
38 changed files with 1201 additions and 263 deletions

View File

@@ -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}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b0a84576fc378a24cbb3bfc7be45a02e
guid: 4e2761c702df38044a3b33cb36bdabf4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000

View File

@@ -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}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d7e691e02b41b9a47b417cc138d6f1bc
guid: c2de57284d214404394d08e4b5f50372
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000

View File

@@ -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

View File

@@ -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}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c39adeeb3a17d824392cc018d65aedc4
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 17f01b6b44475394da68b1ca1d8d7eb4
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 085ffdfee17437a4f8e0d3c365edcc24
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1207eeec52bcfcf4a9a97e054680d844
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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<int, int, MazeReworkCellType, Animation.MazeCellHighlight> onCellChanged = null)
{
Stack<Vector2Int> stack = new Stack<Vector2Int>();
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<int> candidates = new List<int>(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);
}
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cdda5d3deb881154481ad5f60c4bdc0a

View File

@@ -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<int, int, MazeReworkCellType, Animation.MazeCellHighlight> 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<int, int, MazeReworkCellType, Animation.MazeCellHighlight> 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);
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f20ddc35c8bf6604b84a0102bdad1821

View File

@@ -0,0 +1,36 @@
namespace Baba_yaga.GameSetup.MazeRework.Algorithms
{
/// <summary>
/// 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.
/// </summary>
public interface IMazeReworkAlgorithm
{
/// <summary>
/// Carves corridors into the grid starting from (startX, startZ).
/// All cells begin as Wall. The algorithm sets reachable cells to Corridor.
/// </summary>
/// <param name="grid">The 2D cell array to carve into.</param>
/// <param name="visited">Shared visited map. Pre-marked cells (e.g. rooms) are already true.</param>
/// <param name="startX">X coordinate of the first cell to carve (must be odd).</param>
/// <param name="startZ">Z coordinate of the first cell to carve (must be odd).</param>
/// <param name="width">Grid width.</param>
/// <param name="depth">Grid depth.</param>
/// <param name="rng">Seeded random number generator.</param>
/// <param name="onCellChanged">
/// Optional callback fired every time a cell is carved or evaluated.
/// Signature: (x, z, newType, highlight). Pass null in non-animated mode.
/// </param>
void Carve(
MazeReworkCellType[,] grid,
bool[,] visited,
int startX,
int startZ,
int width,
int depth,
System.Random rng,
System.Action<int, int, MazeReworkCellType, Animation.MazeCellHighlight> onCellChanged = null
);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f8ba54b75b8f8344aa7ccb03b8779c03

View File

@@ -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<int, int, MazeReworkCellType, Animation.MazeCellHighlight> 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<Vector2Int> walls = new List<Vector2Int>();
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]++; } }
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7574ba4325993ac46a12a7861b70031f

View File

@@ -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<int, int, MazeReworkCellType, Animation.MazeCellHighlight> 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<int, int, MazeReworkCellType, Animation.MazeCellHighlight> 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);
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 321151d3d7c655047b73b6bd981800e6

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 92ea88f252068ee47ad6145db600314b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
namespace Baba_yaga.GameSetup.MazeRework.Animation
{
/// <summary>
/// Labels each recorded cell change with which generation phase produced it.
/// Used to color-code or group changes during animated playback.
/// </summary>
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
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 45bda90d4b2d42245a8d9f06543053ee

View File

@@ -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<MazeCellChange> 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<MazeCellChange> 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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 209db07e0fcae2e4e9e8b9e2fb822e31

View File

@@ -0,0 +1,24 @@
namespace Baba_yaga.GameSetup.MazeRework.Animation
{
/// <summary>
/// A single recorded cell change captured during maze generation.
/// The animator drains this list to replay generation visually.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6f6a93f9b414a3f4a83c74ee28fb441b

View File

@@ -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)
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6bff934558bb70343ad947b2c9e4da45

View File

@@ -1,13 +1,29 @@
using UnityEngine;
using Baba_yaga.GameSetup.MazeRework.Algorithms;
namespace Baba_yaga.GameSetup.MazeRework
{
/// <summary>
/// Defines which carving algorithm the generator will use.
/// </summary>
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.
}
/// <summary>
/// Configuration asset containing parameters for the reworked maze generation.
/// </summary>
[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)]

View File

@@ -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
/// <summary>
/// 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.
/// </summary>
public (MazeReworkCellType[,] grid, List<MazeCellChange> 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<MazeCellChange> 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<MazeCellChange>(width * depth * 2);
// Helper: record a cell change tagged with a phase
var currentPhase = MazeAnimationPhase.RoomPlacement;
Action<int, int, MazeReworkCellType, MazeCellHighlight> record =
(x, z, t, h) => history.Add(new MazeCellChange(x, z, t, h, currentPhase));
Action<int, int, MazeReworkCellType> 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<Room> 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<Room> GenerateRooms(MazeReworkCellType[,] grid, int width, int depth, System.Random rng)
private List<Room> GenerateRooms(MazeReworkCellType[,] grid, int width, int depth, System.Random rng,
Action<int, int, MazeReworkCellType> onCellChanged = null)
{
List<Room> rooms = new List<Room>();
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<Room> rooms, System.Random rng, int width, int depth)
/// <summary>
/// Factory that returns the correct algorithm based on config selection.
/// </summary>
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()
};
}
/// <summary>
/// After corridor carving, connects room cells to the corridor network
/// by carving doorway walls between corridors and rooms.
/// </summary>
private void ConnectRoomsFrom(int cx, int cz, MazeReworkCellType[,] grid, bool[,] visited, List<Room> 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<Room> rooms, System.Random rng, int width, int depth)
private void EnsureAllConnected(MazeReworkCellType[,] grid, bool[,] visited, List<Room> rooms,
System.Random rng, int width, int depth,
Action<int, int, MazeReworkCellType> 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<int, int, MazeReworkCellType> 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<int, int, MazeReworkCellType> 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<Room> rooms, int width, int depth)
private void PlaceStartAndEnd(MazeReworkCellType[,] grid, List<Room> rooms, int width, int depth,
Action<int, int, MazeReworkCellType> 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);
}
}
}

View File

@@ -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()

View File

@@ -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<GameObject> _spawnedObjects = new List<GameObject>();
private readonly Dictionary<Vector2Int, GameObject> _spawnedObjects = new Dictionary<Vector2Int, GameObject>();
private readonly Dictionary<Vector2Int, GameObject> _spawnedHighlights = new Dictionary<Vector2Int, GameObject>();
private void Start()
{
@@ -73,20 +80,21 @@ namespace Baba_yaga.GameSetup.MazeRework
/// </summary>
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();
}
/// <summary>
/// Spawns the modular prefabs based on the generated 2D grid array.
/// </summary>
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);
}
}
}
/// <summary>
/// Refreshes a specific cell and its neighbors based on the current grid state.
/// </summary>
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)