Organize custom scripts and Shared under Assets/Scripts, and delete assembly definition files
This commit is contained in:
171
Assets/Scripts/Baba_yaga/GameSetup/CharacterAutoSetup.cs
Normal file
171
Assets/Scripts/Baba_yaga/GameSetup/CharacterAutoSetup.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using UnityEngine;
|
||||
using System.Text;
|
||||
|
||||
namespace Baba_yaga.GameSetup
|
||||
{
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "OnlyScove.Scripts.GameSetup", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class CharacterAutoSetup : MonoBehaviour
|
||||
{
|
||||
[Header("Manual Overrides (If Detection Fails)")]
|
||||
[SerializeField] private float defaultHeight = 1.8f;
|
||||
[SerializeField] private float defaultShoulderWidth = 0.4f;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private Transform modelRoot;
|
||||
[SerializeField] private bool autoDetectOnStart = true;
|
||||
[SerializeField] private float zCenterOffset = 0.05f;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (autoDetectOnStart)
|
||||
{
|
||||
ApplyAutoSetup();
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Apply Auto Setup")]
|
||||
public void ApplyAutoSetup()
|
||||
{
|
||||
CharacterController controller = GetComponent<CharacterController>();
|
||||
// PlayerStateMachine stateMachine = GetComponent<PlayerStateMachine>();
|
||||
Animator animator = GetComponentInChildren<Animator>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine($"<color=#4FC3F7><b>[AUTO-SETUP REPORT]</b></color> Character: <b>{gameObject.name}</b>");
|
||||
sb.AppendLine("<color=#757575>------------------------------------------------------------</color>");
|
||||
|
||||
// 1. HEIGHT DETECTION
|
||||
float finalHeight = defaultHeight;
|
||||
string heightMethod = "Default Fallback";
|
||||
|
||||
if (animator != null && animator.GetBoneTransform(HumanBodyBones.Head) != null)
|
||||
{
|
||||
heightMethod = "Humanoid Bones (Feet to Head)";
|
||||
Transform head = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
// We measure from the local Y=0 (feet) to the head bone and add 10% for the skull/hair
|
||||
float headHeight = transform.InverseTransformPoint(head.position).y;
|
||||
finalHeight = headHeight * 1.12f; // 12% extra for the top of the skull
|
||||
}
|
||||
else
|
||||
{
|
||||
heightMethod = "Local Mesh Bounds";
|
||||
Bounds localBounds = GetRelativeBounds();
|
||||
if (localBounds.size.y > 0) finalHeight = localBounds.size.y;
|
||||
}
|
||||
|
||||
sb.AppendLine(string.Format("<b>1. HEIGHT:</b> {0:F3}m ({1}) ➔ <color=#81C784>{2:F3}m</color>", finalHeight, heightMethod, finalHeight));
|
||||
|
||||
// 2. RADIUS DETECTION
|
||||
float shoulderWidth = defaultShoulderWidth;
|
||||
string radiusMethod = "Default Fallback";
|
||||
|
||||
if (animator != null && animator.GetBoneTransform(HumanBodyBones.Hips) != null)
|
||||
{
|
||||
radiusMethod = "Humanoid Bones (Shoulders)";
|
||||
Transform leftArm = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
|
||||
Transform rightArm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm);
|
||||
|
||||
if (leftArm != null && rightArm != null)
|
||||
{
|
||||
float distance = Vector3.Distance(leftArm.position, rightArm.position);
|
||||
shoulderWidth = distance * 1.2f; // Add 20% for arm/shoulder thickness
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
radiusMethod = "Bounds Width Fallback";
|
||||
Bounds b = GetRelativeBounds();
|
||||
shoulderWidth = b.size.x * 0.25f;
|
||||
}
|
||||
|
||||
float finalRadius = shoulderWidth / 2f;
|
||||
sb.AppendLine(string.Format("<b>2. RADIUS:</b> {0:F3}m ({1}) [/ 2] ➔ <color=#81C784>{2:F3}m</color>", shoulderWidth, radiusMethod, finalRadius));
|
||||
|
||||
// 3. COLLISION PRECISION
|
||||
float skinWidth = finalRadius * 0.10f;
|
||||
sb.AppendLine(string.Format("<b>3. SKIN WIDTH:</b> {0:F3}m (Radius) [x 0.10] ➔ <color=#81C784>{1:F3}m</color>", finalRadius, skinWidth));
|
||||
|
||||
// 4. CAPSULE CENTER
|
||||
// Center Y = (Height / 2) + SkinWidth
|
||||
float centerY = (finalHeight / 2f) + skinWidth;
|
||||
sb.AppendLine(string.Format("<b>4. CENTER Y:</b> ({0:F3}m / 2) + {1:F3}m (Skin) ➔ <color=#81C784>{2:F3}m</color>", finalHeight, skinWidth, centerY));
|
||||
sb.AppendLine(string.Format("<b>5. CENTER Z:</b> [Fixed Offset] ➔ <color=#81C784>{0:F3}m</color>", zCenterOffset));
|
||||
|
||||
// 5. MOVEMENT CONSTRAINTS
|
||||
float stepOffset = finalHeight * 0.15f;
|
||||
sb.AppendLine(string.Format("<b>6. STEP OFFSET:</b> {0:F3}m (Height) [x 0.15] ➔ <color=#81C784>{1:F3}m</color>", finalHeight, stepOffset));
|
||||
|
||||
// 6. GROUND CHECK (STATE MACHINE)
|
||||
sb.AppendLine("<color=#757575>------------------------------------------------------------</color>");
|
||||
// if (stateMachine != null)
|
||||
// {
|
||||
// float groundCheckRadius = finalRadius * 0.6f;
|
||||
// float groundCheckOffsetY = finalHeight / 24f;
|
||||
// Vector3 groundCheckOffset = new Vector3(0, groundCheckOffsetY, zCenterOffset);
|
||||
//
|
||||
// stateMachine.SetGroundCheck(groundCheckRadius, groundCheckOffset);
|
||||
//
|
||||
// sb.AppendLine("<b>7. GROUND CHECK SETUP:</b>");
|
||||
// sb.AppendLine(string.Format(" - Radius: {0:F3}m (Radius) [x 0.60] ➔ <color=#81C784>{1:F3}m</color>", finalRadius, groundCheckRadius));
|
||||
// sb.AppendLine(string.Format(" - Offset Y: {0:F3}m (Height) [/ 24] ➔ <color=#81C784>{1:F3}m</color>", finalHeight, groundCheckOffsetY));
|
||||
// sb.AppendLine(string.Format(" - Offset Z: [Sync Center Z] ➔ <color=#81C784>{0:F3}m</color>", zCenterOffset));
|
||||
// }
|
||||
|
||||
sb.AppendLine("<color=#757575>------------------------------------------------------------</color>");
|
||||
|
||||
// Apply to Controller
|
||||
controller.height = finalHeight;
|
||||
controller.radius = finalRadius;
|
||||
controller.skinWidth = skinWidth;
|
||||
controller.center = new Vector3(0, centerY, zCenterOffset);
|
||||
controller.slopeLimit = 45f;
|
||||
controller.stepOffset = stepOffset;
|
||||
controller.minMoveDistance = 0.001f;
|
||||
|
||||
Debug.Log(sb.ToString());
|
||||
}
|
||||
|
||||
private Bounds GetRelativeBounds()
|
||||
{
|
||||
Transform targetRoot = modelRoot != null ? modelRoot : transform;
|
||||
Renderer[] renderers = targetRoot.GetComponentsInChildren<Renderer>();
|
||||
|
||||
if (renderers.Length == 0) return new Bounds(Vector3.zero, Vector3.zero);
|
||||
|
||||
// Using local bounds of SkinnedMeshRenderers for better accuracy on animated characters
|
||||
Bounds combinedLocalBounds = new Bounds();
|
||||
bool first = true;
|
||||
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
if (renderer is ParticleSystemRenderer) continue;
|
||||
|
||||
Bounds localB;
|
||||
if (renderer is SkinnedMeshRenderer smr)
|
||||
{
|
||||
localB = smr.localBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For static meshes, convert world bounds back to local root space
|
||||
Vector3 min = transform.InverseTransformPoint(renderer.bounds.min);
|
||||
Vector3 max = transform.InverseTransformPoint(renderer.bounds.max);
|
||||
localB = new Bounds((min + max) / 2f, max - min);
|
||||
}
|
||||
|
||||
if (first)
|
||||
{
|
||||
combinedLocalBounds = localB;
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedLocalBounds.Encapsulate(localB);
|
||||
}
|
||||
}
|
||||
|
||||
return combinedLocalBounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e16a6690e589f0449ad89a6bf508ab62
|
||||
27
Assets/Scripts/Baba_yaga/GameSetup/CharacterSetupSettings.cs
Normal file
27
Assets/Scripts/Baba_yaga/GameSetup/CharacterSetupSettings.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup
|
||||
{
|
||||
[CreateAssetMenu(fileName = "CharacterSetupSettings", menuName = "BABA_YAGA/Setup/Character Setup Settings")]
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "OnlyScove.Scripts.GameSetup", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class CharacterSetupSettings : ScriptableObject
|
||||
{
|
||||
[Header("Movement Constraints")]
|
||||
public float slopeLimit = 45f;
|
||||
[Range(0.01f, 0.5f)] public float stepHeightRatio = 0.15f; // Step offset as % of height
|
||||
|
||||
[Header("Precision & Collision")]
|
||||
public float skinWidthRatio = 0.1f; // Skin width as % of radius
|
||||
public float minMoveDistance = 0.001f;
|
||||
|
||||
[Header("Dimension Multipliers")]
|
||||
[Tooltip("Multiplies the detected shoulder width to define Radius.")]
|
||||
public float radiusMultiplier = 0.8f;
|
||||
[Tooltip("Multiplies the detected bounding box height.")]
|
||||
public float heightMultiplier = 1.0f;
|
||||
|
||||
[Header("Center Offset")]
|
||||
[Tooltip("Y-axis offset for the center of the capsule (0.5 means exact middle).")]
|
||||
public float centerYRatio = 0.5f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d44cb4bd45c0e24bb3d8196a137db00
|
||||
42
Assets/Scripts/Baba_yaga/GameSetup/GameSettings.cs
Normal file
42
Assets/Scripts/Baba_yaga/GameSetup/GameSettings.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga
|
||||
{
|
||||
[CreateAssetMenu(fileName = "GameSettings", menuName = "BABA_YAGA/Settings/GameSettings")]
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "OnlyScove.Scripts", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class GameSettings : ScriptableObject
|
||||
{
|
||||
[BoxGroup("Camera")]
|
||||
[PropertyRange(0.1f, 10f)]
|
||||
public float sensitivity = 1.0f;
|
||||
|
||||
[BoxGroup("Camera")]
|
||||
public bool invertX = false;
|
||||
|
||||
[BoxGroup("Camera")]
|
||||
public bool invertY = false;
|
||||
|
||||
[BoxGroup("Camera")]
|
||||
public bool sideBiasRight = true; // true for Right, false for Left
|
||||
|
||||
[BoxGroup("Camera")]
|
||||
[PropertyRange(40f, 110f)]
|
||||
public float fieldOfView = 60f;
|
||||
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[BoxGroup("Camera")]
|
||||
private string SideBias => sideBiasRight ? "Right" : "Left";
|
||||
|
||||
[Button("Reset Defaults")]
|
||||
private void ResetDefaults()
|
||||
{
|
||||
sensitivity = 1.0f;
|
||||
invertX = false;
|
||||
invertY = false;
|
||||
sideBiasRight = true;
|
||||
fieldOfView = 60f;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Baba_yaga/GameSetup/GameSettings.cs.meta
Normal file
2
Assets/Scripts/Baba_yaga/GameSetup/GameSettings.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e9fd2c44d7c5bc428b9b4eb12f4a7e1
|
||||
8
Assets/Scripts/Baba_yaga/GameSetup/Maze.meta
Normal file
8
Assets/Scripts/Baba_yaga/GameSetup/Maze.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6a10948eca4f3f4eaeda0611c778875
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
88
Assets/Scripts/Baba_yaga/GameSetup/Maze/CrawlerAlgorithm.cs
Normal file
88
Assets/Scripts/Baba_yaga/GameSetup/Maze/CrawlerAlgorithm.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class CrawlerAlgorithm : IMazeAlgorithm
|
||||
{
|
||||
private const int CrawlChance = 50;
|
||||
private const int MinBoundary = 1;
|
||||
private const int VerticalCrawlerCount = 3;
|
||||
private const int HorizontalCrawlerCount = 3;
|
||||
|
||||
public void Generate(MazeGrid grid)
|
||||
{
|
||||
for (int i = 0; i < VerticalCrawlerCount; i++) CrawlV(grid, 0);
|
||||
for (int i = 0; i < HorizontalCrawlerCount; i++) CrawlH(grid, 0);
|
||||
}
|
||||
|
||||
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
|
||||
{
|
||||
for (int i = 0; i < VerticalCrawlerCount; i++) yield return CrawlV(grid, cellsPerFrame);
|
||||
for (int i = 0; i < HorizontalCrawlerCount; i++) yield return CrawlH(grid, cellsPerFrame);
|
||||
}
|
||||
|
||||
private IEnumerator CrawlV(MazeGrid grid, int cellsPerFrame)
|
||||
{
|
||||
bool done = false;
|
||||
int x = Random.Range(MinBoundary, grid.Width - MinBoundary);
|
||||
int z = MinBoundary;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
grid.SetCell(x, z, MazeCellType.Processing);
|
||||
MazeManager.cellsProcessedThisFrame++;
|
||||
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
|
||||
{
|
||||
MazeManager.cellsProcessedThisFrame = 0;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
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, int cellsPerFrame)
|
||||
{
|
||||
bool done = false;
|
||||
int x = MinBoundary;
|
||||
int z = Random.Range(MinBoundary, grid.Depth - MinBoundary);
|
||||
|
||||
while (!done)
|
||||
{
|
||||
grid.SetCell(x, z, MazeCellType.Processing);
|
||||
MazeManager.cellsProcessedThisFrame++;
|
||||
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
|
||||
{
|
||||
MazeManager.cellsProcessedThisFrame = 0;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd419f9be92beac48b6f551063165e1f
|
||||
25
Assets/Scripts/Baba_yaga/GameSetup/Maze/Extensions.cs
Normal file
25
Assets/Scripts/Baba_yaga/GameSetup/Maze/Extensions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze.Extensions
|
||||
{
|
||||
public static class ListExtensions
|
||||
{
|
||||
private static System.Random _rng = new System.Random();
|
||||
|
||||
/// <summary>
|
||||
/// Shuffles a list using the Fisher-Yates algorithm.
|
||||
/// </summary>
|
||||
public static void Shuffle<T>(this IList<T> list)
|
||||
{
|
||||
int n = list.Count;
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
int k = _rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d9791b1b03c14f16a245b2d4577c5f9
|
||||
8
Assets/Scripts/Baba_yaga/GameSetup/Maze/Interfaces.meta
Normal file
8
Assets/Scripts/Baba_yaga/GameSetup/Maze/Interfaces.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e1bb9cd9af7ffe40ad1a740c3c30dd6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for all maze generation algorithms.
|
||||
/// Supports both immediate and step-by-step (animated) generation.
|
||||
/// </summary>
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public interface IMazeAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the maze immediately in one frame.
|
||||
/// </summary>
|
||||
void Generate(MazeGrid grid);
|
||||
|
||||
/// <summary>
|
||||
/// Generates the maze step-by-step for visualization.
|
||||
/// </summary>
|
||||
System.Collections.IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46b6a7796ba3c494581e4dcb884da064
|
||||
32
Assets/Scripts/Baba_yaga/GameSetup/Maze/MapLocation.cs
Normal file
32
Assets/Scripts/Baba_yaga/GameSetup/Maze/MapLocation.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a 2D coordinate on the maze grid.
|
||||
/// Used as a lightweight value type to avoid GC allocations.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all 4 cardinal directions.
|
||||
/// </summary>
|
||||
public static System.Collections.Generic.List<MapLocation> Directions => new System.Collections.Generic.List<MapLocation>
|
||||
{
|
||||
Right, Up, Left, Down
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 987a7c46c96326a44b3a5f179fe61161
|
||||
43
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeCellType.cs
Normal file
43
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeCellType.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the state of each cell in the maze.
|
||||
/// Used to replace magic numbers and drive visual changes.
|
||||
/// </summary>
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
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
|
||||
StairsUp,
|
||||
StairsDown,
|
||||
Room
|
||||
}
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public enum PieceType
|
||||
{
|
||||
None,
|
||||
Wall,
|
||||
Vertical_Straight, // Đường thẳng dọc
|
||||
Horizontal_Straight, // Đường thẳng ngang
|
||||
Corner, // Góc cua
|
||||
T_Junction, // Ngã ba
|
||||
Crossroads, // Ngã tư
|
||||
Stairs,
|
||||
StairsUp// Cầu thang (Điểm nối)
|
||||
}
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public struct MazePieceData
|
||||
{
|
||||
public PieceType piece; // Hình dạng mảnh ghép
|
||||
// Bạn có thể thêm rotation nếu cần xoay hướng model sau này
|
||||
public int rotation;
|
||||
public GameObject model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f54ef08fa4922eb4a968d46c7aa71faf
|
||||
84
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeGrid.cs
Normal file
84
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeGrid.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the logical state of the maze grid.
|
||||
/// Notifies listeners whenever a cell changes to trigger visual updates.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class MazeGrid
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Depth { get; set; }
|
||||
public int Level { get; set; }
|
||||
public MazePieceData[,] piecePlace;
|
||||
public float scale = 1f;
|
||||
|
||||
private readonly MazeCellType[,] _cells;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a cell's type is changed.
|
||||
/// Useful for the Renderer to trigger animations/FX.
|
||||
/// </summary>
|
||||
public event Action<int, int, MazeCellType> OnCellChanged;
|
||||
|
||||
public MazeGrid(int width, int depth)
|
||||
{
|
||||
Width = width;
|
||||
Depth = depth;
|
||||
piecePlace = new MazePieceData[width, 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;
|
||||
piecePlace[x, z].piece = PieceType.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeGrid.cs.meta
Normal file
2
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeGrid.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1a7a252ff0b1014a9690f08897e2e59
|
||||
361
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeManager.cs
Normal file
361
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeManager.cs
Normal file
@@ -0,0 +1,361 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Central controller for the Maze system.
|
||||
/// Manages algorithm selection, debug speed, and regeneration.
|
||||
/// </summary>
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class MazeManager : MonoBehaviour
|
||||
{
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public enum AlgorithmType { Recursive, Wilsons, Prims, Crawler, NoiseRecursive }
|
||||
|
||||
[BoxGroup("Generation")]
|
||||
[InfoBox("Set the array size to control how many maze floors are generated. Runtime grid data is rebuilt when Regenerate runs.")]
|
||||
[ValidateInput(nameof(HasAtLeastOneFloor), "Maze Manager needs at least one floor.")]
|
||||
public MazeGrid[] mazes;
|
||||
|
||||
[BoxGroup("Generation")]
|
||||
[MinValue(0.001f)]
|
||||
public float floorHeight = 3.5f;
|
||||
|
||||
[BoxGroup("Generation")]
|
||||
[MinValue(0)]
|
||||
public int connectionsPerFloor = 2;
|
||||
|
||||
[BoxGroup("Generation")]
|
||||
[SerializeField] private AlgorithmType selectedAlgorithm;
|
||||
|
||||
[BoxGroup("Generation/Grid Size")]
|
||||
[PropertyRange(5, 200)]
|
||||
[SerializeField] private int width = 30;
|
||||
|
||||
[BoxGroup("Generation/Grid Size")]
|
||||
[PropertyRange(5, 200)]
|
||||
[SerializeField] private int depth = 30;
|
||||
|
||||
[BoxGroup("Animation")]
|
||||
[SerializeField] private bool animateGeneration = true;
|
||||
|
||||
[BoxGroup("Animation")]
|
||||
[ShowIf(nameof(animateGeneration))]
|
||||
[PropertyRange(1, 500)]
|
||||
[LabelText("Generation Speed (Cells/Frame)")]
|
||||
[SerializeField] private int generationSpeed = 50;
|
||||
|
||||
public static int cellsProcessedThisFrame;
|
||||
|
||||
[BoxGroup("Animation")]
|
||||
[ShowIf(nameof(animateGeneration))]
|
||||
public MazeRenderer.CellAnimationType cellAnimationType = MazeRenderer.CellAnimationType.ScaleUp;
|
||||
|
||||
[BoxGroup("Progress")]
|
||||
[ProgressBar(0, 100)]
|
||||
[ShowInInspector, ReadOnly]
|
||||
private float completionPercentage;
|
||||
|
||||
[BoxGroup("References")]
|
||||
[Required]
|
||||
[SerializeField] private MazeRenderer mazeRenderer;
|
||||
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
public bool generateRooms = true;
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
[ShowIf(nameof(generateRooms))]
|
||||
public int numberOfRooms = 2;
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
[ShowIf(nameof(generateRooms))]
|
||||
public Vector2Int minRoomSize = new Vector2Int(2, 2);
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
[ShowIf(nameof(generateRooms))]
|
||||
public Vector2Int maxRoomSize = new Vector2Int(4, 4);
|
||||
|
||||
[BoxGroup("References")]
|
||||
[Required]
|
||||
[SerializeField] private Transform mazeContainer;
|
||||
|
||||
[FoldoutGroup("Manhole Prefabs")]
|
||||
public GameObject straightManHoleLadder;
|
||||
[FoldoutGroup("Manhole Prefabs")]
|
||||
public GameObject straightManHoleUp;
|
||||
[FoldoutGroup("Manhole Prefabs")]
|
||||
public GameObject deadendManHoleLadder;
|
||||
[FoldoutGroup("Manhole Prefabs")]
|
||||
public GameObject deadendManHoleUp;
|
||||
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[BoxGroup("Runtime")]
|
||||
private int FloorCount => mazes?.Length ?? 0;
|
||||
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[BoxGroup("Runtime")]
|
||||
private string CurrentGrid => _grid == null ? "None" : $"{_grid.Width}x{_grid.Depth}, Level {_grid.Level}";
|
||||
|
||||
private MazeGrid _grid;
|
||||
private Coroutine _generationCoroutine;
|
||||
private HashSet<Vector3Int> _modifiedCells = new HashSet<Vector3Int>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Regenerate();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.R))
|
||||
{
|
||||
Regenerate();
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Clear Maze")]
|
||||
[Button("Clear Maze", ButtonSizes.Large)]
|
||||
public void ClearMaze()
|
||||
{
|
||||
if (_generationCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_generationCoroutine);
|
||||
_generationCoroutine = null;
|
||||
}
|
||||
|
||||
if (mazeRenderer != null)
|
||||
{
|
||||
mazeRenderer.Clear();
|
||||
}
|
||||
|
||||
completionPercentage = 0f;
|
||||
_modifiedCells?.Clear();
|
||||
}
|
||||
|
||||
[ContextMenu("Regenerate")]
|
||||
[Button("Regenerate Maze", ButtonSizes.Large)]
|
||||
public void Regenerate()
|
||||
{
|
||||
if (mazeRenderer == null)
|
||||
{
|
||||
Debug.LogError("MazeManager needs a MazeRenderer reference before regenerating.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mazeContainer == null)
|
||||
{
|
||||
Debug.LogError("MazeManager needs a maze container reference before regenerating.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mazes == null || mazes.Length == 0)
|
||||
{
|
||||
mazes = new MazeGrid[1];
|
||||
}
|
||||
|
||||
ClearMaze();
|
||||
|
||||
mazeRenderer.currentAnimationType = cellAnimationType;
|
||||
|
||||
if (animateGeneration)
|
||||
{
|
||||
_generationCoroutine = StartCoroutine(GenerateMazeRoutine());
|
||||
}
|
||||
else
|
||||
{
|
||||
GenerateMazeInstant();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateMazeInstant()
|
||||
{
|
||||
_modifiedCells.Clear();
|
||||
completionPercentage = 0f;
|
||||
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
{
|
||||
mazes[i] = new MazeGrid(width, depth);
|
||||
mazes[i].Level = i;
|
||||
|
||||
CarveRooms(mazes[i]);
|
||||
|
||||
IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm);
|
||||
algorithmForFloor.Generate(mazes[i]);
|
||||
}
|
||||
|
||||
GenerateConnections();
|
||||
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
{
|
||||
mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0);
|
||||
}
|
||||
if (mazes.Length > 0) _grid = mazes[0];
|
||||
|
||||
completionPercentage = 100f;
|
||||
}
|
||||
|
||||
private IEnumerator GenerateMazeRoutine()
|
||||
{
|
||||
_modifiedCells.Clear();
|
||||
completionPercentage = 0f;
|
||||
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
{
|
||||
mazes[i] = new MazeGrid(width, depth);
|
||||
mazes[i].Level = i;
|
||||
int floorIndex = i;
|
||||
|
||||
mazes[i].OnCellChanged += (x, z, type) =>
|
||||
{
|
||||
if (type != MazeCellType.Wall)
|
||||
{
|
||||
_modifiedCells.Add(new Vector3Int(x, floorIndex, z));
|
||||
int totalCells = width * depth * mazes.Length;
|
||||
// Approximate the progress to reach roughly 100% since algorithms don't visit all cells
|
||||
float fillRatio = 0.6f;
|
||||
completionPercentage = Mathf.Clamp((_modifiedCells.Count / ((float)totalCells * fillRatio)) * 100f, 0, 99f);
|
||||
}
|
||||
};
|
||||
|
||||
CarveRooms(mazes[i]);
|
||||
mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0);
|
||||
|
||||
IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm);
|
||||
cellsProcessedThisFrame = 0;
|
||||
yield return StartCoroutine(algorithmForFloor.GenerateStepByStep(mazes[i], generationSpeed));
|
||||
}
|
||||
|
||||
GenerateConnections();
|
||||
|
||||
if (mazes.Length > 0) _grid = mazes[0];
|
||||
|
||||
completionPercentage = 100f;
|
||||
_generationCoroutine = null;
|
||||
}
|
||||
|
||||
private void GenerateConnections()
|
||||
{
|
||||
// Step 2: Create connections between adjacent floors
|
||||
for (int i = 0; i < mazes.Length - 1; i++)
|
||||
{
|
||||
MazeGrid currentFloor = mazes[i];
|
||||
MazeGrid nextFloor = mazes[i + 1];
|
||||
|
||||
List<Vector2Int> possibleConnections = new List<Vector2Int>();
|
||||
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
// Check if both floors have a corridor at this position
|
||||
bool isCurrentFloorPath = currentFloor.GetCell(x, z) == MazeCellType.Corridor;
|
||||
bool isNextFloorPath = nextFloor.GetCell(x, z) == MazeCellType.Corridor;
|
||||
|
||||
if (isCurrentFloorPath && isNextFloorPath)
|
||||
{
|
||||
possibleConnections.Add(new Vector2Int(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShuffleList(possibleConnections);
|
||||
|
||||
int connectionsMade = 0;
|
||||
foreach (Vector2Int pos in possibleConnections)
|
||||
{
|
||||
if (connectionsMade >= connectionsPerFloor) break;
|
||||
|
||||
int x = pos.x;
|
||||
int z = pos.y;
|
||||
|
||||
// Set stair cells
|
||||
currentFloor.SetCell(x, z, MazeCellType.StairsUp);
|
||||
nextFloor.SetCell(x, z, MazeCellType.StairsDown);
|
||||
|
||||
connectionsMade++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CarveRooms(MazeGrid grid)
|
||||
{
|
||||
if (!generateRooms) return;
|
||||
|
||||
for (int i = 0; i < numberOfRooms; i++)
|
||||
{
|
||||
int w = Random.Range(minRoomSize.x, maxRoomSize.x + 1);
|
||||
int d = Random.Range(minRoomSize.y, maxRoomSize.y + 1);
|
||||
|
||||
int startX = Random.Range(1, width - w - 1);
|
||||
int startZ = Random.Range(1, depth - d - 1);
|
||||
|
||||
for (int x = startX; x < startX + w; x++)
|
||||
{
|
||||
for (int z = startZ; z < startZ + d; z++)
|
||||
{
|
||||
grid.SetCell(x, z, MazeCellType.Room);
|
||||
}
|
||||
}
|
||||
|
||||
// Carve guaranteed door to seed pathfinding
|
||||
if (Random.value > 0.5f)
|
||||
{
|
||||
int doorX = Random.Range(startX, startX + w);
|
||||
int doorZ = Random.value > 0.5f ? startZ + d : startZ - 1;
|
||||
grid.SetCell(doorX, doorZ, MazeCellType.Corridor);
|
||||
}
|
||||
else
|
||||
{
|
||||
int doorX = Random.value > 0.5f ? startX + w : startX - 1;
|
||||
int doorZ = Random.Range(startZ, startZ + d);
|
||||
grid.SetCell(doorX, doorZ, MazeCellType.Corridor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShuffleList<T>(List<T> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
T temp = list[i];
|
||||
int randomIndex = Random.Range(i, list.Count);
|
||||
list[i] = list[randomIndex];
|
||||
list[randomIndex] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
private IMazeAlgorithm GetAlgorithm(AlgorithmType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
AlgorithmType.Recursive => new RecursiveAlgorithm(),
|
||||
AlgorithmType.Wilsons => new WilsonsAlgorithm(),
|
||||
AlgorithmType.Prims => new PrimsAlgorithm(),
|
||||
AlgorithmType.Crawler => new CrawlerAlgorithm(),
|
||||
AlgorithmType.NoiseRecursive => new NoiseRecursiveGenerator(),
|
||||
_ => new RecursiveAlgorithm()
|
||||
};
|
||||
}
|
||||
|
||||
[Button("Find Scene References")]
|
||||
private void FindSceneReferences()
|
||||
{
|
||||
if (mazeRenderer == null)
|
||||
{
|
||||
mazeRenderer = GetComponentInChildren<MazeRenderer>();
|
||||
}
|
||||
|
||||
if (mazeContainer == null)
|
||||
{
|
||||
mazeContainer = transform;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasAtLeastOneFloor(MazeGrid[] floors)
|
||||
{
|
||||
return floors != null && floors.Length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3607adabe0c29c34591af73b414eb17a
|
||||
489
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeRenderer.cs
Normal file
489
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeRenderer.cs
Normal file
@@ -0,0 +1,489 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for the visual representation of the maze.
|
||||
/// Handles spawning, pooling, and animations with safety checks.
|
||||
/// </summary>
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class MazeRenderer : MonoBehaviour
|
||||
{
|
||||
[BoxGroup("Visuals")]
|
||||
[Required]
|
||||
[InlineEditor]
|
||||
[SerializeField] private MazeVisualProfile visualProfile;
|
||||
|
||||
[BoxGroup("Visuals")]
|
||||
[MinValue(0.001f)]
|
||||
public float floorHeight = 3.5f;
|
||||
|
||||
public float Scale => visualProfile != null ? visualProfile.scale : 1f;
|
||||
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public enum CellAnimationType { None, ScaleUp, DropDown, SpinIn }
|
||||
|
||||
[HideInInspector]
|
||||
public CellAnimationType currentAnimationType = CellAnimationType.ScaleUp;
|
||||
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[BoxGroup("Runtime")]
|
||||
private int SpawnedCellCount => _spawnedCells.Count;
|
||||
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[BoxGroup("Runtime")]
|
||||
private int RenderedFloorCount => _grids.Count;
|
||||
|
||||
private readonly Dictionary<Vector3Int, GameObject> _spawnedCells = new Dictionary<Vector3Int, GameObject>();
|
||||
private Transform _container;
|
||||
private List<MazeGrid> _grids = new List<MazeGrid>();
|
||||
|
||||
public void Initialize(MazeGrid grid, Transform container, bool clearExisting = true)
|
||||
{
|
||||
if (visualProfile == null)
|
||||
{
|
||||
Debug.LogError("MazeRenderer needs a MazeVisualProfile before it can render.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (grid == null)
|
||||
{
|
||||
Debug.LogError("MazeRenderer received a null MazeGrid.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clearExisting)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
_container = container;
|
||||
if (!_grids.Contains(grid))
|
||||
{
|
||||
_grids.Add(grid);
|
||||
grid.OnCellChanged += (x, z, type) => HandleCellChanged(grid, x, z, type);
|
||||
}
|
||||
|
||||
// Initial render
|
||||
for (int z = 0; z < grid.Depth; z++)
|
||||
{
|
||||
for (int x = 0; x < grid.Width; x++)
|
||||
{
|
||||
UpdateCellVisual(grid, x, z, grid.GetCell(x, z), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
|
||||
foreach (var cell in _spawnedCells.Values)
|
||||
{
|
||||
if (cell == null) continue;
|
||||
|
||||
if (Application.isPlaying)
|
||||
Destroy(cell);
|
||||
else
|
||||
DestroyImmediate(cell);
|
||||
}
|
||||
|
||||
_spawnedCells.Clear();
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
// Note: We can't easily unsubscribe because the lambda captures 'grid'.
|
||||
// In a production environment, we should use a proper event handler method.
|
||||
}
|
||||
_grids.Clear();
|
||||
}
|
||||
|
||||
[Button("Clear Spawned Maze")]
|
||||
private void ClearFromInspector()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
private void HandleCellChanged(MazeGrid grid, int x, int z, MazeCellType type)
|
||||
{
|
||||
UpdateCellVisual(grid, x, z, type, true);
|
||||
|
||||
UpdateNeighborVisual(grid, x + 1, z);
|
||||
UpdateNeighborVisual(grid, x - 1, z);
|
||||
UpdateNeighborVisual(grid, x, z + 1);
|
||||
UpdateNeighborVisual(grid, x, z - 1);
|
||||
}
|
||||
|
||||
private void UpdateNeighborVisual(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (grid != null && grid.IsInBounds(x, z))
|
||||
{
|
||||
if (IsPath(grid, x, z))
|
||||
{
|
||||
MazeCellType type = grid.GetCell(x, z);
|
||||
UpdateCellVisual(grid, x, z, type, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCellVisual(MazeGrid grid, int x, int z, MazeCellType type, bool animate)
|
||||
{
|
||||
Vector3Int posKey = new Vector3Int(x, grid.Level, z);
|
||||
|
||||
if (_spawnedCells.TryGetValue(posKey, out GameObject oldObj))
|
||||
{
|
||||
if (oldObj != null) DestroyImmediate(oldObj);
|
||||
_spawnedCells.Remove(posKey);
|
||||
}
|
||||
|
||||
if (type == MazeCellType.Wall) return;
|
||||
|
||||
float logicalSpacing = visualProfile.nodeSpacing; // Distances between Nodes
|
||||
float halfSpacing = logicalSpacing / 2f;
|
||||
float safeScale = Mathf.Max(0.001f, visualProfile.scale);
|
||||
float spacingScale = logicalSpacing * safeScale;
|
||||
|
||||
float yOffset = grid.Level * floorHeight;
|
||||
Vector3 localPos = new Vector3(x * spacingScale, yOffset, z * spacingScale);
|
||||
|
||||
GameObject cellParent = new GameObject($"Cell_{x}_{grid.Level}_{z}");
|
||||
cellParent.transform.SetParent(_container);
|
||||
cellParent.transform.localPosition = localPos;
|
||||
|
||||
bool spawnedAnything = false;
|
||||
|
||||
if (type == MazeCellType.Corridor || type == MazeCellType.Processing || type == MazeCellType.StairsUp || type == MazeCellType.StairsDown)
|
||||
{
|
||||
// 1. Spawn Node (Intersection, Corner, T, DeadEnd, or Stairs)
|
||||
GameObject nodePrefab;
|
||||
Quaternion nodeRot;
|
||||
Vector3 nodeOffset = Vector3.zero;
|
||||
|
||||
if (type == MazeCellType.StairsUp || type == MazeCellType.StairsDown)
|
||||
{
|
||||
nodePrefab = visualProfile.GetPrefab(type);
|
||||
nodeRot = Quaternion.Euler(0, visualProfile.stairsOffset, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
(nodePrefab, nodeRot, nodeOffset) = GetNodePrefabAndRotation(grid, x, z);
|
||||
}
|
||||
|
||||
if (nodePrefab != null)
|
||||
{
|
||||
GameObject node = Instantiate(nodePrefab, cellParent.transform);
|
||||
node.transform.localPosition = nodeOffset * safeScale;
|
||||
node.transform.localRotation = nodeRot;
|
||||
node.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
|
||||
// 2. Spawn Edge X (Right path)
|
||||
if (IsPath(grid, x + 1, z))
|
||||
{
|
||||
GameObject edgePrefab = visualProfile.corridorStraight;
|
||||
if (edgePrefab != null)
|
||||
{
|
||||
GameObject edgeX = Instantiate(edgePrefab, cellParent.transform);
|
||||
edgeX.transform.localPosition = new Vector3(halfSpacing * safeScale, 0, 0); // half units offset right
|
||||
edgeX.transform.localRotation = Quaternion.Euler(0, 90f, 0); // pointing along X
|
||||
edgeX.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Spawn Edge Z (Top path)
|
||||
if (IsPath(grid, x, z + 1))
|
||||
{
|
||||
GameObject edgePrefab = visualProfile.corridorStraight;
|
||||
if (edgePrefab != null)
|
||||
{
|
||||
GameObject edgeZ = Instantiate(edgePrefab, cellParent.transform);
|
||||
edgeZ.transform.localPosition = new Vector3(0, 0, halfSpacing * safeScale); // half units offset forward
|
||||
edgeZ.transform.localRotation = Quaternion.identity; // pointing along Z
|
||||
edgeZ.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == MazeCellType.Room)
|
||||
{
|
||||
// Spawn Floor
|
||||
if (visualProfile.roomFloorPrefab != null)
|
||||
{
|
||||
GameObject floor = Instantiate(visualProfile.roomFloorPrefab, cellParent.transform);
|
||||
floor.transform.localPosition = Vector3.zero;
|
||||
floor.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
|
||||
// Spawn Ceiling
|
||||
if (visualProfile.roomCeilingPrefab != null)
|
||||
{
|
||||
GameObject ceiling = Instantiate(visualProfile.roomCeilingPrefab, cellParent.transform);
|
||||
ceiling.transform.localPosition = new Vector3(0, floorHeight, 0);
|
||||
ceiling.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
|
||||
// Spawn Room Edges (Walls or Doors)
|
||||
MazeCellType top = grid.IsInBounds(x, z + 1) ? grid.GetCell(x, z + 1) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, top, new Vector3(0, 0, halfSpacing * safeScale), 0f, safeScale);
|
||||
|
||||
MazeCellType right = grid.IsInBounds(x + 1, z) ? grid.GetCell(x + 1, z) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, right, new Vector3(halfSpacing * safeScale, 0, 0), 90f, safeScale);
|
||||
|
||||
MazeCellType bottom = grid.IsInBounds(x, z - 1) ? grid.GetCell(x, z - 1) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, bottom, new Vector3(0, 0, -halfSpacing * safeScale), 180f, safeScale);
|
||||
|
||||
MazeCellType left = grid.IsInBounds(x - 1, z) ? grid.GetCell(x - 1, z) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, left, new Vector3(-halfSpacing * safeScale, 0, 0), 270f, safeScale);
|
||||
|
||||
spawnedAnything = true; // Always true if it reaches here
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-corridor logic (Start, End, etc)
|
||||
GameObject prefab = visualProfile.GetPrefab(type);
|
||||
if (prefab != null)
|
||||
{
|
||||
GameObject obj = Instantiate(prefab, cellParent.transform);
|
||||
obj.transform.localPosition = Vector3.zero;
|
||||
obj.transform.localRotation = Quaternion.identity;
|
||||
obj.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!spawnedAnything)
|
||||
{
|
||||
DestroyImmediate(cellParent);
|
||||
return;
|
||||
}
|
||||
|
||||
_spawnedCells[posKey] = cellParent;
|
||||
|
||||
if (animate && visualProfile.animationDuration > 0)
|
||||
{
|
||||
StartCoroutine(AnimateCell(cellParent.transform));
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================
|
||||
// THUẬT TOÁN BITMASK AUTO-TILING
|
||||
// =================================================================================
|
||||
private (GameObject, Quaternion, Vector3) GetNodePrefabAndRotation(MazeGrid grid, int x, int z)
|
||||
{
|
||||
bool top = IsPath(grid, x, z + 1);
|
||||
bool right = IsPath(grid, x + 1, z);
|
||||
bool bottom = IsPath(grid, x, z - 1);
|
||||
bool left = IsPath(grid, x - 1, z);
|
||||
|
||||
int mask = 0;
|
||||
if (top) mask += 1;
|
||||
if (right) mask += 2;
|
||||
if (bottom) mask += 4;
|
||||
if (left) mask += 8;
|
||||
|
||||
GameObject prefabToSpawn = null;
|
||||
float yRotation = 0f;
|
||||
Vector3 offset = Vector3.zero;
|
||||
|
||||
// Push dead ends to the boundary where edges end
|
||||
float endOffset = visualProfile.deadEndShift;
|
||||
|
||||
switch (mask)
|
||||
{
|
||||
case 1:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 0f;
|
||||
offset = new Vector3(0, 0, endOffset);
|
||||
break;
|
||||
case 2:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 90f;
|
||||
offset = new Vector3(endOffset, 0, 0);
|
||||
break;
|
||||
case 4:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 180f;
|
||||
offset = new Vector3(0, 0, -endOffset);
|
||||
break;
|
||||
case 8:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 270f;
|
||||
offset = new Vector3(-endOffset, 0, 0);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
prefabToSpawn = visualProfile.corridorStraight;
|
||||
yRotation = 0f;
|
||||
break;
|
||||
case 10:
|
||||
prefabToSpawn = visualProfile.corridorStraight;
|
||||
yRotation = 90f;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
prefabToSpawn = visualProfile.corridorCorner;
|
||||
yRotation = 0f;
|
||||
break;
|
||||
case 6:
|
||||
prefabToSpawn = visualProfile.corridorCorner;
|
||||
yRotation = 90f;
|
||||
break;
|
||||
case 12:
|
||||
prefabToSpawn = visualProfile.corridorCorner;
|
||||
yRotation = 180f;
|
||||
break;
|
||||
case 9:
|
||||
prefabToSpawn = visualProfile.corridorCorner;
|
||||
yRotation = 270f;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 0f;
|
||||
break;
|
||||
case 7:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 90f;
|
||||
break;
|
||||
case 14:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 180f;
|
||||
break;
|
||||
case 13:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 270f;
|
||||
break;
|
||||
|
||||
case 15:
|
||||
prefabToSpawn = visualProfile.corridorCross;
|
||||
yRotation = 0f;
|
||||
break;
|
||||
|
||||
default:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 0f;
|
||||
break;
|
||||
}
|
||||
|
||||
float finalRotation = yRotation;
|
||||
if (prefabToSpawn == visualProfile.corridorTJunction) finalRotation += visualProfile.tJunctionOffset;
|
||||
if (prefabToSpawn == visualProfile.corridorDeadEnd) finalRotation += visualProfile.deadEndOffset;
|
||||
if (prefabToSpawn == visualProfile.corridorCorner) finalRotation += visualProfile.cornerOffset;
|
||||
|
||||
if (prefabToSpawn == null) prefabToSpawn = visualProfile.corridorPrefab;
|
||||
|
||||
return (prefabToSpawn, Quaternion.Euler(0, finalRotation, 0), offset);
|
||||
}
|
||||
|
||||
private void SpawnRoomEdge(GameObject parent, MazeCellType neighborType, Vector3 offset, float yRot, float safeScale)
|
||||
{
|
||||
if (neighborType == MazeCellType.Room)
|
||||
return; // Open space to another room cell
|
||||
|
||||
GameObject prefabToSpawn = null;
|
||||
|
||||
if (neighborType == MazeCellType.Corridor || neighborType == MazeCellType.Processing || neighborType == MazeCellType.Start || neighborType == MazeCellType.End)
|
||||
{
|
||||
prefabToSpawn = visualProfile.roomDoorwayPrefab;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabToSpawn = visualProfile.roomWallPrefab;
|
||||
}
|
||||
|
||||
if (prefabToSpawn != null)
|
||||
{
|
||||
GameObject edge = Instantiate(prefabToSpawn, parent.transform);
|
||||
edge.transform.localPosition = offset;
|
||||
edge.transform.localRotation = Quaternion.Euler(0, yRot, 0);
|
||||
edge.transform.localScale = Vector3.one * safeScale;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPath(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (grid == null || !grid.IsInBounds(x, z)) return false;
|
||||
MazeCellType type = grid.GetCell(x, z);
|
||||
return type == MazeCellType.Corridor
|
||||
|| type == MazeCellType.Processing
|
||||
|| type == MazeCellType.Start
|
||||
|| type == MazeCellType.End
|
||||
|| type == MazeCellType.Path
|
||||
|| type == MazeCellType.StairsUp
|
||||
|| type == MazeCellType.StairsDown;
|
||||
}
|
||||
|
||||
// =================================================================================
|
||||
// ANIMATION
|
||||
// =================================================================================
|
||||
private IEnumerator AnimateCell(Transform target)
|
||||
{
|
||||
if (target == null) yield break;
|
||||
|
||||
if (currentAnimationType == CellAnimationType.None) yield break;
|
||||
|
||||
float duration = Mathf.Max(0.01f, visualProfile.animationDuration);
|
||||
float elapsed = 0;
|
||||
Vector3 finalScale = target.localScale;
|
||||
Vector3 finalPosition = target.localPosition;
|
||||
Quaternion finalRotation = target.localRotation;
|
||||
|
||||
if (currentAnimationType == CellAnimationType.ScaleUp)
|
||||
{
|
||||
target.localScale = Vector3.one * 0.001f;
|
||||
}
|
||||
else if (currentAnimationType == CellAnimationType.DropDown)
|
||||
{
|
||||
target.localPosition = finalPosition + Vector3.up * 5f;
|
||||
}
|
||||
else if (currentAnimationType == CellAnimationType.SpinIn)
|
||||
{
|
||||
target.localScale = Vector3.one * 0.001f;
|
||||
target.localRotation = finalRotation * Quaternion.Euler(0, 180, 0);
|
||||
}
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
if (target == null) yield break;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
|
||||
if (currentAnimationType == CellAnimationType.ScaleUp)
|
||||
{
|
||||
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
|
||||
target.localScale = finalScale * Mathf.Max(0.001f, s);
|
||||
}
|
||||
else if (currentAnimationType == CellAnimationType.DropDown)
|
||||
{
|
||||
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
|
||||
target.localPosition = Vector3.Lerp(finalPosition + Vector3.up * 5f, finalPosition, s);
|
||||
}
|
||||
else if (currentAnimationType == CellAnimationType.SpinIn)
|
||||
{
|
||||
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
|
||||
target.localScale = finalScale * Mathf.Max(0.001f, s);
|
||||
target.localRotation = Quaternion.Lerp(finalRotation * Quaternion.Euler(0, 180, 0), finalRotation, s);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
target.localScale = finalScale;
|
||||
target.localPosition = finalPosition;
|
||||
target.localRotation = finalRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f30df611110713742ab984f5bead5d88
|
||||
147
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeVisualProfile.cs
Normal file
147
Assets/Scripts/Baba_yaga/GameSetup/Maze/MazeVisualProfile.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
[CreateAssetMenu(fileName = "MazeVisualProfile", menuName = "BABA_YAGA/Maze/Visual Profile")]
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class MazeVisualProfile : ScriptableObject
|
||||
{
|
||||
[BoxGroup("Rotation Offsets")]
|
||||
public float tJunctionOffset = 0f;
|
||||
|
||||
[BoxGroup("Rotation Offsets")]
|
||||
public float cornerOffset = 0f;
|
||||
|
||||
[BoxGroup("Rotation Offsets")]
|
||||
public float deadEndOffset = 0f;
|
||||
|
||||
[BoxGroup("Rotation Offsets")]
|
||||
public float stairsOffset = 0f;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
[Required]
|
||||
public GameObject wallPrefab;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
[Required]
|
||||
public GameObject corridorPrefab;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
public GameObject processingPrefab;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
public GameObject pathPrefab;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
public GameObject startPrefab;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
public GameObject endPrefab;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
[Required]
|
||||
public GameObject stairUpPrefab;
|
||||
|
||||
[BoxGroup("Cell Prefabs")]
|
||||
[Required]
|
||||
public GameObject stairDownPrefab;
|
||||
|
||||
[BoxGroup("Corridor Pieces")]
|
||||
[Required]
|
||||
public GameObject corridorStraight;
|
||||
|
||||
[BoxGroup("Corridor Pieces")]
|
||||
[Required]
|
||||
public GameObject corridorCorner;
|
||||
|
||||
[BoxGroup("Corridor Pieces")]
|
||||
[Required]
|
||||
public GameObject corridorTJunction;
|
||||
|
||||
[BoxGroup("Corridor Pieces")]
|
||||
[Required]
|
||||
public GameObject corridorCross;
|
||||
|
||||
[BoxGroup("Corridor Pieces")]
|
||||
[Required]
|
||||
public GameObject corridorDeadEnd;
|
||||
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomFloorPrefab;
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomWallPrefab;
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomCeilingPrefab;
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomDoorwayPrefab;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[MinValue(0.001f)]
|
||||
public float scale = 0.167f;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[MinValue(1f)]
|
||||
[InfoBox("The physical distance between each grid cell. Default is 6 based on 3x3 intersections and 3x2 halls.")]
|
||||
public float nodeSpacing = 6f;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[InfoBox("How far to push dead-end caps from the center so they touch the hallway edge. Default is 1.0.")]
|
||||
public float deadEndShift = 1.0f;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[MinValue(0f)]
|
||||
public float animationDuration = 0.25f;
|
||||
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[BoxGroup("Validation")]
|
||||
private int MissingRequiredPrefabCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
if (wallPrefab == null) count++;
|
||||
if (corridorPrefab == null) count++;
|
||||
if (stairUpPrefab == null) count++;
|
||||
if (stairDownPrefab == null) count++;
|
||||
if (corridorStraight == null) count++;
|
||||
if (corridorCorner == null) count++;
|
||||
if (corridorTJunction == null) count++;
|
||||
if (corridorCross == null) count++;
|
||||
if (corridorDeadEnd == null) count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
MazeCellType.StairsUp => stairUpPrefab,
|
||||
MazeCellType.StairsDown => stairDownPrefab,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
[Button("Log Missing Required Prefabs")]
|
||||
private void LogMissingRequiredPrefabs()
|
||||
{
|
||||
if (MissingRequiredPrefabCount == 0)
|
||||
{
|
||||
Debug.Log($"{name}: all required maze prefabs are assigned.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"{name}: {MissingRequiredPrefabCount} required maze prefab reference(s) are missing.", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3ff96571406a624381b7b0e596a4d1b
|
||||
8
Assets/Scripts/Baba_yaga/GameSetup/Maze/Native.meta
Normal file
8
Assets/Scripts/Baba_yaga/GameSetup/Maze/Native.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05fdc25279e7ac148a44fde646c93546
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze.Native
|
||||
{
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze.Native", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class NativeNoiseProvider : IDisposable
|
||||
{
|
||||
private const string DLL_NAME = "BackroomsNoise";
|
||||
|
||||
[DllImport(DLL_NAME)]
|
||||
private static extern IntPtr CreateNoiseGenerator(int seed, float frequency, int noiseType);
|
||||
|
||||
[DllImport(DLL_NAME)]
|
||||
private static extern float GetNoiseValue(IntPtr handle, float x, float z);
|
||||
|
||||
[DllImport(DLL_NAME)]
|
||||
private static extern void GetNoiseBuffer(IntPtr handle, float startX, float startZ, int width, int depth, float[] buffer);
|
||||
|
||||
[DllImport(DLL_NAME)]
|
||||
private static extern void DestroyNoiseGenerator(IntPtr handle);
|
||||
|
||||
private IntPtr _handle;
|
||||
public bool IsInitialized => _handle != IntPtr.Zero;
|
||||
|
||||
public NativeNoiseProvider(int seed, float frequency = 0.01f, int noiseType = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_handle = CreateNoiseGenerator(seed, frequency, noiseType);
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
Debug.LogWarning($"Native library '{DLL_NAME}' not found. Ensure it is compiled and placed in Plugins folder.");
|
||||
}
|
||||
}
|
||||
|
||||
public float GetNoise(float x, float z)
|
||||
{
|
||||
if (!IsInitialized) return 0f;
|
||||
return GetNoiseValue(_handle, x, z);
|
||||
}
|
||||
|
||||
public void FillBuffer(float startX, float startZ, int width, int depth, float[] buffer)
|
||||
{
|
||||
if (!IsInitialized || buffer == null) return;
|
||||
GetNoiseBuffer(_handle, startX, startZ, width, depth, buffer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsInitialized)
|
||||
{
|
||||
DestroyNoiseGenerator(_handle);
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
~NativeNoiseProvider()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fd030227a1b87a4f8826f9b317fbf87
|
||||
@@ -0,0 +1,159 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Baba_yaga.GameSetup.Maze.Extensions;
|
||||
using Baba_yaga.GameSetup.Maze.Native;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced generator that combines C++ Native Noise with a Recursive Backtracking algorithm.
|
||||
/// Creates a hybrid layout of large rooms and chaotic corridors.
|
||||
/// </summary>
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class NoiseRecursiveGenerator : IMazeAlgorithm
|
||||
{
|
||||
private readonly List<MapLocation> _directions = MapLocation.Directions;
|
||||
private float[] _noiseMap;
|
||||
private int _seed = 1337;
|
||||
|
||||
// Thresholds
|
||||
private const float RoomThreshold = 0.5f;
|
||||
private const float CorridorThreshold = -0.3f;
|
||||
private const int DeadEndNeighbourThreshold = 2;
|
||||
|
||||
public void Generate(MazeGrid grid)
|
||||
{
|
||||
InitializeNoise(grid);
|
||||
|
||||
// Step 1: Pre-place rooms based on noise peaks
|
||||
for (int z = 0; z < grid.Depth; z++)
|
||||
{
|
||||
for (int x = 0; x < grid.Width; x++)
|
||||
{
|
||||
float noise = GetNoiseAt(x, z, grid.Width);
|
||||
if (noise > RoomThreshold)
|
||||
{
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Run recursive carving in the "connectable" zones
|
||||
// We start from a few points to ensure coverage
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
int startX = Random.Range(1, grid.Width - 1);
|
||||
int startZ = Random.Range(1, grid.Depth - 1);
|
||||
GenerateRecursive(grid, startX, startZ);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
|
||||
{
|
||||
InitializeNoise(grid);
|
||||
|
||||
// Visual feedback for noise pre-placement
|
||||
for (int z = 0; z < grid.Depth; z++)
|
||||
{
|
||||
for (int x = 0; x < grid.Width; x++)
|
||||
{
|
||||
float noise = GetNoiseAt(x, z, grid.Width);
|
||||
if (noise > RoomThreshold)
|
||||
{
|
||||
grid.SetCell(x, z, MazeCellType.Processing);
|
||||
}
|
||||
}
|
||||
if (z % 5 == 0) yield return null;
|
||||
}
|
||||
|
||||
for (int z = 0; z < grid.Depth; z++)
|
||||
{
|
||||
for (int x = 0; x < grid.Width; x++)
|
||||
{
|
||||
if (grid.GetCell(x, z) == MazeCellType.Processing)
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
}
|
||||
}
|
||||
|
||||
yield return GenerateRecursiveStepByStep(grid, 5, 5, cellsPerFrame);
|
||||
}
|
||||
|
||||
private void InitializeNoise(MazeGrid grid)
|
||||
{
|
||||
_noiseMap = new float[grid.Width * grid.Depth];
|
||||
using (var provider = new NativeNoiseProvider(_seed, 0.05f))
|
||||
{
|
||||
if (provider.IsInitialized)
|
||||
{
|
||||
provider.FillBuffer(0, 0, grid.Width, grid.Depth, _noiseMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to Unity Perlin
|
||||
for (int z = 0; z < grid.Depth; z++)
|
||||
{
|
||||
for (int x = 0; x < grid.Width; x++)
|
||||
{
|
||||
_noiseMap[z * grid.Width + x] = Mathf.PerlinNoise(x * 0.1f, z * 0.1f) * 2f - 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float GetNoiseAt(int x, int z, int width)
|
||||
{
|
||||
if (_noiseMap == null) return 0f;
|
||||
return _noiseMap[z * width + x];
|
||||
}
|
||||
|
||||
private void GenerateRecursive(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (!grid.IsInBounds(x, z)) return;
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) return;
|
||||
if (GetNoiseAt(x, z, grid.Width) < CorridorThreshold) return;
|
||||
if (grid.GetCell(x, z) == MazeCellType.Corridor) return;
|
||||
|
||||
if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) return;
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
|
||||
List<MapLocation> shuffledDirs = new List<MapLocation>(_directions);
|
||||
shuffledDirs.Shuffle();
|
||||
|
||||
foreach (var dir in shuffledDirs)
|
||||
{
|
||||
GenerateRecursive(grid, x + dir.x, z + dir.z);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator GenerateRecursiveStepByStep(MazeGrid grid, int x, int z, int cellsPerFrame)
|
||||
{
|
||||
if (!grid.IsInBounds(x, z)) yield break;
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) yield break;
|
||||
if (GetNoiseAt(x, z, grid.Width) < CorridorThreshold) yield break;
|
||||
if (grid.GetCell(x, z) == MazeCellType.Corridor) yield break;
|
||||
|
||||
if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) yield break;
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Processing);
|
||||
MazeManager.cellsProcessedThisFrame++;
|
||||
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
|
||||
{
|
||||
MazeManager.cellsProcessedThisFrame = 0;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
|
||||
List<MapLocation> shuffledDirs = new List<MapLocation>(_directions);
|
||||
shuffledDirs.Shuffle();
|
||||
|
||||
foreach (var dir in shuffledDirs)
|
||||
{
|
||||
yield return GenerateRecursiveStepByStep(grid, x + dir.x, z + dir.z, cellsPerFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22e17049420e98f43a692fd3e7d7d261
|
||||
112
Assets/Scripts/Baba_yaga/GameSetup/Maze/PrimsAlgorithm.cs
Normal file
112
Assets/Scripts/Baba_yaga/GameSetup/Maze/PrimsAlgorithm.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
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<MapLocation> 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);
|
||||
|
||||
foreach (var nw in GetNeighbouringWalls(grid, w.x, w.z))
|
||||
{
|
||||
if (!walls.Contains(nw)) walls.Add(nw);
|
||||
}
|
||||
}
|
||||
iterations++;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
|
||||
{
|
||||
int x = InitialX;
|
||||
int z = InitialZ;
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
yield return null;
|
||||
|
||||
List<MapLocation> 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);
|
||||
MazeManager.cellsProcessedThisFrame++;
|
||||
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
|
||||
{
|
||||
MazeManager.cellsProcessedThisFrame = 0;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
foreach (var nw in GetNeighbouringWalls(grid, w.x, w.z))
|
||||
{
|
||||
if (grid.GetCell(nw.x, nw.z) == MazeCellType.Wall)
|
||||
{
|
||||
grid.SetCell(nw.x, nw.z, MazeCellType.Processing);
|
||||
walls.Add(nw);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it's no longer a candidate, turn it back to Wall
|
||||
grid.SetCell(w.x, w.z, MazeCellType.Wall);
|
||||
}
|
||||
iterations++;
|
||||
}
|
||||
}
|
||||
|
||||
private List<MapLocation> GetNeighbouringWalls(MazeGrid grid, int x, int z)
|
||||
{
|
||||
List<MapLocation> neighbours = new List<MapLocation>();
|
||||
foreach (var dir in MapLocation.Directions)
|
||||
{
|
||||
int nx = x + dir.x;
|
||||
int nz = z + dir.z;
|
||||
|
||||
// Correction
|
||||
nx = x + dir.x;
|
||||
nz = z + dir.z;
|
||||
|
||||
if (grid.IsInBounds(nx, nz))
|
||||
{
|
||||
MazeCellType type = grid.GetCell(nx, nz);
|
||||
if (type == MazeCellType.Wall || type == MazeCellType.Processing)
|
||||
{
|
||||
neighbours.Add(new MapLocation(nx, nz));
|
||||
}
|
||||
}
|
||||
}
|
||||
return neighbours;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edcdd3c0aa9656a4797b83cc675aa629
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Baba_yaga.GameSetup.Maze.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class RecursiveAlgorithm : IMazeAlgorithm
|
||||
{
|
||||
private const int StartX = 5;
|
||||
private const int StartZ = 5;
|
||||
private const int DeadEndNeighbourThreshold = 2;
|
||||
|
||||
private readonly List<MapLocation> _directions = MapLocation.Directions;
|
||||
|
||||
public void Generate(MazeGrid grid)
|
||||
{
|
||||
GenerateRecursive(grid, StartX, StartZ);
|
||||
}
|
||||
|
||||
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
|
||||
{
|
||||
yield return GenerateRecursiveStepByStep(grid, StartX, StartZ, cellsPerFrame);
|
||||
}
|
||||
|
||||
private void GenerateRecursive(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) return;
|
||||
if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) return;
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
|
||||
List<MapLocation> shuffledDirs = new List<MapLocation>(_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, int cellsPerFrame)
|
||||
{
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) yield break;
|
||||
if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) yield break;
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Processing);
|
||||
MazeManager.cellsProcessedThisFrame++;
|
||||
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
|
||||
{
|
||||
MazeManager.cellsProcessedThisFrame = 0;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
|
||||
List<MapLocation> shuffledDirs = new List<MapLocation>(_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, cellsPerFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2460c0e9379da9741b3d3387aa6c7a8e
|
||||
182
Assets/Scripts/Baba_yaga/GameSetup/Maze/WilsonsAlgorithm.cs
Normal file
182
Assets/Scripts/Baba_yaga/GameSetup/Maze/WilsonsAlgorithm.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Wilson's Algorithm implementation based on the original provided logic.
|
||||
/// Ensures paths are sparse and correctly finalized using specific neighbor constraints.
|
||||
/// </summary>
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class WilsonsAlgorithm : IMazeAlgorithm
|
||||
{
|
||||
private const int MinBoundary = 2;
|
||||
private const int MaxIterationSafety = 5000;
|
||||
private const int MaxWalkSteps = 5000;
|
||||
|
||||
private readonly List<MapLocation> _directions = MapLocation.Directions;
|
||||
private List<MapLocation> _notUsed = new List<MapLocation>();
|
||||
|
||||
public void Generate(MazeGrid grid)
|
||||
{
|
||||
// 1. Create a starting finalized cell (Type.Corridor represents state 2)
|
||||
int x = Random.Range(MinBoundary, grid.Width - 1);
|
||||
int z = Random.Range(MinBoundary, grid.Depth - 1);
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
|
||||
int safety = 0;
|
||||
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
|
||||
{
|
||||
RandomWalkSync(grid);
|
||||
safety++;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
|
||||
{
|
||||
int x = Random.Range(MinBoundary, grid.Width - 1);
|
||||
int z = Random.Range(MinBoundary, grid.Depth - 1);
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
yield return null;
|
||||
|
||||
int safety = 0;
|
||||
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
|
||||
{
|
||||
yield return RandomWalk(grid, cellsPerFrame);
|
||||
safety++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts neighbors that are already part of the finalized maze (State 2 / Corridor).
|
||||
/// </summary>
|
||||
private int CountFinalizedNeighbours(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 int GetAvailableCells(MazeGrid grid)
|
||||
{
|
||||
_notUsed.Clear();
|
||||
for (int z = 1; z < grid.Depth - 1; z++)
|
||||
{
|
||||
for (int x = 1; x < grid.Width - 1; x++)
|
||||
{
|
||||
if (CountFinalizedNeighbours(grid, x, z) == 0)
|
||||
{
|
||||
_notUsed.Add(new MapLocation(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
return _notUsed.Count;
|
||||
}
|
||||
|
||||
private void RandomWalkSync(MazeGrid grid)
|
||||
{
|
||||
if (_notUsed.Count == 0) return;
|
||||
|
||||
List<MapLocation> inWalk = new List<MapLocation>();
|
||||
int rStartIndex = Random.Range(0, _notUsed.Count);
|
||||
int cx = _notUsed[rStartIndex].x;
|
||||
int cz = _notUsed[rStartIndex].z;
|
||||
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
|
||||
int loop = 0;
|
||||
bool validPath = false;
|
||||
while (cx > 0 && cx < grid.Width - 1 && cz > 0 && cz < grid.Depth - 1 && loop < MaxWalkSteps && !validPath)
|
||||
{
|
||||
// Mark as temporary walk (State 0 / Processing)
|
||||
// Note: We don't set grid cell here in sync mode to avoid triggering events unnecessarily
|
||||
// but we keep track of neighbors.
|
||||
|
||||
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;
|
||||
|
||||
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
|
||||
int nx = cx + rd.x;
|
||||
int nz = cz + rd.z;
|
||||
|
||||
// User's original constraint: CountSquareNeighbours (nx, nz) < 2
|
||||
if (CountFinalizedNeighbours(grid, nx, nz) < 2)
|
||||
{
|
||||
cx = nx;
|
||||
cz = nz;
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
}
|
||||
|
||||
validPath = CountFinalizedNeighbours(grid, cx, cz) == 1;
|
||||
loop++;
|
||||
}
|
||||
|
||||
if (validPath)
|
||||
{
|
||||
foreach (MapLocation m in inWalk)
|
||||
grid.SetCell(m.x, m.z, MazeCellType.Corridor);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator RandomWalk(MazeGrid grid, int cellsPerFrame)
|
||||
{
|
||||
if (_notUsed.Count == 0) yield break;
|
||||
|
||||
List<MapLocation> inWalk = new List<MapLocation>();
|
||||
int rStartIndex = Random.Range(0, _notUsed.Count);
|
||||
int cx = _notUsed[rStartIndex].x;
|
||||
int cz = _notUsed[rStartIndex].z;
|
||||
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
|
||||
int loop = 0;
|
||||
bool validPath = false;
|
||||
while (cx > 0 && cx < grid.Width - 1 && cz > 0 && cz < grid.Depth - 1 && loop < MaxWalkSteps && !validPath)
|
||||
{
|
||||
grid.SetCell(cx, cz, MazeCellType.Processing); // State 0
|
||||
MazeManager.cellsProcessedThisFrame++;
|
||||
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
|
||||
{
|
||||
MazeManager.cellsProcessedThisFrame = 0;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;
|
||||
|
||||
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
|
||||
int nx = cx + rd.x;
|
||||
int nz = cz + rd.z;
|
||||
|
||||
if (CountFinalizedNeighbours(grid, nx, nz) < 2)
|
||||
{
|
||||
cx = nx;
|
||||
cz = nz;
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
}
|
||||
|
||||
validPath = CountFinalizedNeighbours(grid, cx, cz) == 1;
|
||||
loop++;
|
||||
}
|
||||
|
||||
if (validPath)
|
||||
{
|
||||
foreach (MapLocation m in inWalk)
|
||||
{
|
||||
grid.SetCell(m.x, m.z, MazeCellType.Corridor); // State 2
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (MapLocation m in inWalk)
|
||||
grid.SetCell(m.x, m.z, MazeCellType.Wall); // State 1
|
||||
}
|
||||
inWalk.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61156f8986612ca49a0672ad5542380f
|
||||
15
Assets/Scripts/Baba_yaga/GameSetup/ObjectInteraction.cs
Normal file
15
Assets/Scripts/Baba_yaga/GameSetup/ObjectInteraction.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "ObjectInteraction", menuName = "BABA_YAGA/Scriptable Objects/ObjectInteraction")]
|
||||
public class ObjectInteraction : ScriptableObject
|
||||
{
|
||||
[Header("UI Settings")]
|
||||
public string promptText = "Interact";
|
||||
|
||||
[Header("Audio & Visuals")]
|
||||
public AudioClip interactionSound;
|
||||
public GameObject interactionVFX;
|
||||
|
||||
[Header("Settings")]
|
||||
public float interactionCooldown = 0.5f;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7edbad1c5ab86741af25e92524a1350
|
||||
79
Assets/Scripts/Baba_yaga/GameSetup/SettingsManager.cs
Normal file
79
Assets/Scripts/Baba_yaga/GameSetup/SettingsManager.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga
|
||||
{
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "OnlyScove.Scripts", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class SettingsManager : MonoBehaviour
|
||||
{
|
||||
public static SettingsManager Instance { get; private set; }
|
||||
|
||||
[BoxGroup("Settings")]
|
||||
[Required]
|
||||
[InlineEditor]
|
||||
[SerializeField] private GameSettings settings;
|
||||
public GameSettings Settings => settings;
|
||||
|
||||
[ShowInInspector]
|
||||
[ReadOnly]
|
||||
[BoxGroup("Runtime")]
|
||||
private bool IsActiveInstance => Instance == this;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
// Fallback or load from Resources if needed
|
||||
settings = ScriptableObject.CreateInstance<GameSettings>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSensitivity(float value)
|
||||
{
|
||||
settings.sensitivity = value;
|
||||
OnSettingsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SetInvertX(bool value)
|
||||
{
|
||||
settings.invertX = value;
|
||||
OnSettingsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SetInvertY(bool value)
|
||||
{
|
||||
settings.invertY = value;
|
||||
OnSettingsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SetSideBias(bool isRight)
|
||||
{
|
||||
settings.sideBiasRight = isRight;
|
||||
OnSettingsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void ToggleSideBias()
|
||||
{
|
||||
settings.sideBiasRight = !settings.sideBiasRight;
|
||||
OnSettingsChanged?.Invoke();
|
||||
}
|
||||
|
||||
[Button("Notify Settings Changed")]
|
||||
private void NotifySettingsChanged()
|
||||
{
|
||||
OnSettingsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public event System.Action OnSettingsChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86e70fc045fbf71469903c69f7f54e67
|
||||
Reference in New Issue
Block a user