CrackedGrids Documentation
A comprehensive 2D isometric grid-based game framework for Unity with dynamic grid generation, object placement, pathfinding, and visual feedback systems.
Quick Start
Minimal Setup
- Create an empty GameObject and add the
GridManagercomponent - Configure grid dimensions (default: 7x7)
- Add cell prefabs to the
cellPrefabslist - Click "Initialize Grid" in the inspector
- Add
IsometricCameraControllerto your Main Camera
using CrackedGames.Grid;
public class GameSetup : MonoBehaviour
{
[SerializeField] private GridManager gridManager;
[SerializeField] private GridObject playerPrefab;
void Start()
{
// Initialize grid
gridManager.InitializeGrid(7, 7);
// Spawn player
var player = Instantiate(playerPrefab);
gridManager.RegisterObject(player, new GridPosition(3, 3));
}
}Core Concepts
Namespaces
using CrackedGames.Grid; // Core grid functionality using CrackedGames.Attributes; // Utility attributes (ReadOnly)
GridPosition
A lightweight struct representing a position on the grid using integer X, Y coordinates.
// Creating positions GridPosition pos = new GridPosition(3, 4); GridPosition origin = GridPosition.Zero; // Direction constants GridPosition.Up // (0, 1) GridPosition.Down // (0, -1) GridPosition.Left // (-1, 0) GridPosition.Right // (1, 0) GridPosition.UpLeft // (-1, 1) GridPosition.UpRight // (1, 1) GridPosition.DownLeft // (-1, -1) GridPosition.DownRight // (1, -1) // Direction arrays GridPosition.CardinalDirections // Up, Down, Left, Right GridPosition.DiagonalDirections // UpLeft, UpRight, DownLeft, DownRight GridPosition.AllDirections // All 8 directions // Arithmetic GridPosition newPos = pos + GridPosition.Up; GridPosition offset = pos - other; GridPosition scaled = pos * 2; // Distance calculations int manhattan = pos.ManhattanDistance(other); float euclidean = pos.EuclideanDistance(other); bool adjacent = pos.IsAdjacent(other); // Conversions Vector2Int v2 = pos.ToVector2Int(); Vector3 worldPos = pos.ToWorldPosition(cellSize: 1f);
GridCell
Represents a single cell in the grid. Manages occupancy, walkability, and highlighting.
GridCell cell = gridManager.GetCell(position); // Properties cell.Position // GridPosition cell.IsWalkable // Can units walk here? cell.IsOccupied // Are objects on this cell? cell.IsBlocked // Not walkable OR contains blocking object cell.IsAvailable // Walkable AND not blocked // Object management cell.AddObject(gridObject); cell.RemoveObject(gridObject); List<GridObject> objects = cell.GetObjects(); // World position Vector3 worldPos = cell.GetWorldPosition();
Grid Setup
GridManager Configuration
Initialization
// Initialize with specific dimensions gridManager.InitializeGrid(10, 10); // Clear and rebuild gridManager.ClearGrid(); // Remove cells only gridManager.ClearAll(); // Remove cells and walls // Apply procedural erosion gridManager.ApplyIrregularity();
Coordinate Conversion
// Grid to world Vector3 worldPos = gridManager.GetWorldPosition(gridPosition); // World to grid GridPosition gridPos = gridManager.WorldToGridPosition(worldPosition); // Get grid center Vector3 center = gridManager.GetCenterPosition();
Grid Objects
CrackedGrids uses a component-based architecture for grid objects. The system is split into two main components:
- GridObject: Handles grid state (position, blocking, interactions)
- GridMovement: Handles movement animations (optional)
This separation allows static objects (obstacles, items) to use only GridObject, while moving entities add GridMovement for animated transitions.
GridObject
Base component for any object that exists on the grid. Handles position tracking, blocking state, and basic placement.
public class MyUnit : GridObject
{
protected override void Awake()
{
base.Awake();
// Custom initialization
}
}Inspector Properties
Runtime Properties
GridPosition pos = myObject.GridPosition; // Current position (read-only) bool blocks = myObject.IsBlocking; bool pickable = myObject.IsPickable; bool immovable = myObject.IsImmovable; float height = myObject.GroundHeight; Vector3 offset = myObject.VisualOffset; // For centering large objects
Events
// Fired when movement starts (position is already updated)
myObject.OnMoveStart += (targetPos) => { };
// Fired when movement animation completes
myObject.OnMoveComplete += (targetPos) => { };Visual Offset
For objects that need visual centering (e.g., a large boss spanning multiple cells), override visualOffset in your subclass:
public class LargeBoss : GridObject
{
protected override void Awake()
{
base.Awake();
visualOffset = new Vector3(0.5f, 0, 0.5f); // Center on 2x2 area
}
}GridMovement
Optional component that provides animated movement for GridObjects. Add this component to any GridObject that needs smooth movement transitions.
Requires: GridObject (auto-added via [RequireComponent])
Inspector Properties
GridEntity
Extended GridObject for 2D sprite-based entities with billboarding support.
public class MySprite : GridEntity
{
// Automatically faces camera when billboard = true
}Additional Properties
Registering Objects
// Spawn and register GridObject unit = Instantiate(unitPrefab); gridManager.RegisterObject(unit, new GridPosition(3, 3)); // Remove from grid gridManager.RemoveObject(unit);
Object Interactions
// Push an object in a direction (returns false if immovable or blocked) bool success = target.Push(GridPosition.Right, gridManager); // Swap positions with another object (instant teleport) objectA.Swap(objectB, gridManager); // Teleport instantly (no animation, fires OnMoveComplete) myObject.TeleportTo(new GridPosition(5, 5));
Movement System
Movement is handled by the optional GridMovement component. Objects without this component will teleport instantly when MoveTo() is called.
Movement Types
public enum MovementType
{
Walk, // Smooth easing (smoothstep interpolation)
Teleport, // Instant position change
Jump, // Parabolic arc (4 * height * t * (1-t))
Dash, // Fast with ease-out (cosine interpolation)
Knockback // Fixed 0.2s duration with sine ease
}Component Setup
// For static objects (no animation needed): // - Add only GridObject component // For moving units (with animation): // - Add GridObject component // - Add GridMovement component
Moving Objects
// Via GridManager (recommended)
gridManager.MoveObject(unit, targetPosition, MovementType.Walk);
// With completion callback
gridManager.MoveObject(unit, targetPosition, MovementType.Jump, () => {
Debug.Log("Movement complete!");
});
// Direct object movement
unit.MoveTo(targetPosition, MovementType.Dash, OnMoveComplete);Movement Events
Events are fired on the GridObject, regardless of whether GridMovement is present:
unit.OnMoveStart += (pos) => {
// Position is already updated at this point
Debug.Log($"Started moving to {pos}");
};
unit.OnMoveComplete += (pos) => {
Debug.Log($"Arrived at {pos}");
};Pathfinding
CrackedGrids includes A* pathfinding with support for diagonal movement.
// Find path between two positions
List<GridPosition> path = gridManager.FindPath(startPos, endPos);
if (path != null && path.Count > 0)
{
// Path found - execute movement
StartCoroutine(MoveAlongPath(path));
}
else
{
Debug.Log("No valid path found");
}
IEnumerator MoveAlongPath(List<GridPosition> path)
{
foreach (var pos in path)
{
bool moveComplete = false;
gridManager.MoveObject(unit, pos, MovementType.Walk, () => moveComplete = true);
yield return new WaitUntil(() => moveComplete);
}
}Pathfinding Details
- Uses Manhattan distance heuristic
- Cardinal movement cost: 10
- Diagonal movement cost: 14
- Respects cell walkability and blocking objects
- Returns
nullif no path exists
Range Queries
Manhattan Distance (Diamond Shape)
// Get all cells within range 3 (diamond pattern) List<GridCell> cells = gridManager.GetCellsInRange(centerPos, 3); List<GridCell> walkableOnly = gridManager.GetCellsInRange(centerPos, 3, onlyWalkable: true);
Chebyshev Distance (Square Shape)
// Get all cells within range 3 (square pattern) List<GridCell> cells = gridManager.GetCellsInSquareRange(centerPos, 3);
Line Queries
// Get cells in a straight line List<GridCell> line = gridManager.GetCellsInLine(startPos, GridPosition.Right, maxDistance: 10); // Get cells until blocked List<GridCell> path = gridManager.GetStraightPath(startPos, GridPosition.Up);
Neighbor Queries
// Cardinal neighbors only List<GridCell> neighbors = gridManager.GetNeighbors(position, includeDiagonals: false); // All 8 neighbors List<GridCell> allNeighbors = gridManager.GetNeighbors(position, includeDiagonals: true);
Grid Validation
// Check if position is within grid bounds bool valid = gridManager.IsValidPosition(position); // Check if cell can be entered bool available = gridManager.IsCellAvailable(position); // Check if cell has objects bool occupied = gridManager.IsCellOccupied(position);
Highlighting System
Highlight Types
public enum HighlightType
{
None, // No highlight
ValidMove, // Blue glow for valid movement
Attack, // Red glow for attack range
Danger, // Orange-red for threat zones
Path, // Cyan for pathfinding
Hover // Light gray for mouse hover
}Cell Highlighting
// Set highlight type cell.SetHighlight(HighlightType.ValidMove); cell.SetHighlight(HighlightType.Attack); // Custom color highlight cell.SetHighlight(Color.green, intensity: 2f, pulseSpeed: 1.5f); // Mark as danger zone (persistent, highest priority) cell.SetDanger(true); // Clear highlight cell.ClearHighlight();
Batch Highlighting
// Highlight multiple cells List<GridCell> cells = gridManager.GetCellsInRange(pos, 2); gridManager.HighlightCells(cells, highlight: true); // Clear all highlights gridManager.ClearAllHighlights();
GridVisualizer
For advanced visualization with custom colors and curve drawing:
[SerializeField] private GridVisualizer visualizer; // Highlight with custom color List<GridPosition> positions = /* ... */; visualizer.HighlightCells(positions, Color.magenta, intensity: 2.5f, pulseSpeed: 1.5f); // Draw curves/paths List<Vector3> curvePoints = /* ... */; visualizer.DrawCurve(curvePoints, Color.yellow, width: 0.15f); // Clear visualizer.ClearHighlights(); visualizer.ClearCurves(); visualizer.ClearAll();
Highlight Priority
Highlights are applied in priority order (highest to lowest):
- Danger Zone
- Attack
- Valid Move / Path / Hover
Procedural Generation
Erosion System
Create organic, irregular grid edges instead of perfect rectangles.
// Configure in inspector or code gridManager.erosionChance = 0.2f; // 20% base chance gridManager.erosionIterations = 2; // Two passes gridManager.erosionDither = 0.1f; // +10% per iteration // Apply erosion gridManager.ApplyIrregularity();
Erosion Algorithm
- Edge cells (1 empty neighbor): 10% of erosion chance
- Corner cells (2+ empty neighbors): 120% of erosion chance
- Internal cells: Never eroded
- Each iteration increases chance by dither value
Safe Spawning
After erosion, use safe spawn methods to avoid placing objects on unstable edges:
// Get main connected area
List<GridCell> island = gridManager.GetMainIslandCells();
// Get safe spawn position
GridPosition spawnPos = gridManager.GetSafeSpawnPosition(
island,
minDistanceFromEdge: 2
);
// Spawn near a target position with randomness
GridPosition nearPos = gridManager.GetSafeSpawnPositionNear(
island,
minDistanceFromEdge: 2,
targetPos: new GridPosition(3, 3),
randomness: 5
);
// Check distance from void/edge
int distFromVoid = gridManager.GetDistanceFromVoid(position);Weighted Cell Prefabs
Add variety to your grid by using multiple cell prefabs:
// In inspector, add to cellPrefabs list: // - CellPrefab1 (weight: 0.7) // - CellPrefab2 (weight: 0.2) // - CellPrefab3 (weight: 0.1)
Camera System
IsometricCameraController
Automatically configures the camera for isometric or top-down views.
Camera Styles
public enum CameraStyle
{
IsometricOrthographic, // Classic isometric (45, 45) - orthographic
IsometricPerspective, // Isometric with depth perspective
TopDownOrthographic // Straight down (90, 0) - orthographic
}Configuration
Runtime Control
[SerializeField] private IsometricCameraController cameraController; // Manual setup cameraController.SetupCamera(); cameraController.PositionCamera(); // Focus on specific cell cameraController.FocusOnGridPosition(position, smoothTime: 0.5f); // Move camera cameraController.MoveCameraToPosition(worldTarget, duration: 1f); // Zoom cameraController.SetZoom(8f); float currentZoom = cameraController.GetZoom();
Wall Generation
GridWallGenerator
Generates decorative walls on grid edges to frame the playable area.
Configuration
Usage
[SerializeField] private GridWallGenerator wallGenerator; // Generate walls (usually done automatically after grid init) wallGenerator.GenerateWalls(); // Clear walls wallGenerator.ClearWalls();
Smart Wall Placement
- Only generates walls in the "back triangle" to avoid obscuring the isometric camera view
- Automatically detects edges created by erosion
- Randomizes sprite selection for visual variety
Editor Tools
GridManager Inspector
- Initialize Grid - Create/recreate the grid
- Apply Erosion - Apply irregularity to edges
- Clear All - Destroy all cells and walls
- Enable Grid Selector - Interactive cell selection in scene view
- Click cells to inspect them
- View position, blocked state, occupied state
GridWallGenerator Inspector
- Generate Walls - Create edge walls
- Clear Walls - Remove all walls
Scene View Gizmos
GridManager
- White grid lines showing cell boundaries (toggle with
showGridGizmos)
GridCell (when selected)
- Green wireframe: Walkable cell
- Red wireframe: Occupied cell
- Gray wireframe: Wall/blocked
- Yellow lines: Connection to occupying objects
IsometricCameraController
- Yellow line from camera to grid center
- Wireframe sphere at grid center
ReadOnly Attribute
Mark serialized fields as read-only in the inspector:
using CrackedGames.Attributes; [SerializeField, ReadOnly] private int calculatedValue;
API Reference
GridPosition
// Constructors
GridPosition(int x, int y)
// Properties
int X { get; }
int Y { get; }
// Static Properties
static GridPosition Zero { get; }
static GridPosition Up { get; }
static GridPosition Down { get; }
static GridPosition Left { get; }
static GridPosition Right { get; }
static GridPosition UpLeft { get; }
static GridPosition UpRight { get; }
static GridPosition DownLeft { get; }
static GridPosition DownRight { get; }
static GridPosition[] CardinalDirections { get; }
static GridPosition[] DiagonalDirections { get; }
static GridPosition[] AllDirections { get; }
// Methods
Vector2Int ToVector2Int()
Vector3 ToWorldPosition(float cellSize = 1f)
int ManhattanDistance(GridPosition other)
float EuclideanDistance(GridPosition other)
bool IsAdjacent(GridPosition other)
bool IsCardinalDirection()
bool IsDiagonal()
// Operators
GridPosition + GridPosition
GridPosition - GridPosition
GridPosition * int
bool == / !=GridCell
// Properties
GridPosition Position { get; }
bool IsWalkable { get; set; }
bool IsOccupied { get; }
bool IsBlocked { get; }
bool IsAvailable { get; }
HighlightType CurrentHighlight { get; }
// Methods
void Initialize(GridPosition position, bool walkable = true)
void AddObject(GridObject obj)
void RemoveObject(GridObject obj)
List<GridObject> GetObjects()
void Highlight(bool enable)
void SetHighlight(HighlightType type)
void SetHighlight(Color color, float intensity = 2f, float pulseSpeed = 1.5f)
void SetDanger(bool active)
void ClearHighlight()
Vector3 GetWorldPosition()GridManager
// Properties
int Width { get; }
int Height { get; }
float CellSize { get; }
Vector3 Origin { get; }
// Grid Initialization
void InitializeGrid(int width, int height)
void ClearGrid()
void ClearAll()
void ApplyIrregularity()
// Coordinate Conversion
Vector3 GetWorldPosition(GridPosition pos)
GridPosition WorldToGridPosition(Vector3 worldPos)
Vector3 GetCenterPosition()
// Grid Queries
bool IsValidPosition(GridPosition position)
GridCell GetCell(GridPosition position)
bool IsCellAvailable(GridPosition position)
bool IsCellOccupied(GridPosition position)
List<GridCell> GetMainIslandCells()
int GetDistanceFromVoid(GridPosition pos)
// Spawn Helpers
GridPosition GetSafeSpawnPosition(List<GridCell> validCells, int minDistanceFromEdge)
GridPosition GetSafeSpawnPositionNear(List<GridCell> validCells, int minDistanceFromEdge, GridPosition targetPos, int randomness = 5)
// Range Queries
List<GridCell> GetCellsInRange(GridPosition center, int range, bool onlyWalkable = true)
List<GridCell> GetCellsInSquareRange(GridPosition center, int range, bool onlyWalkable = true)
List<GridCell> GetCellsInLine(GridPosition start, GridPosition direction, int maxDistance = 100)
List<GridCell> GetNeighbors(GridPosition position, bool includeDiagonals = false)
List<GridCell> GetStraightPath(GridPosition start, GridPosition direction)
// Object Management
void RegisterObject(GridObject obj, GridPosition position)
void MoveObject(GridObject obj, GridPosition targetPos, MovementType type = MovementType.Walk, Action onComplete = null)
void RemoveObject(GridObject obj)
// Pathfinding
List<GridPosition> FindPath(GridPosition start, GridPosition end)
// Visualization
void HighlightCells(List<GridCell> cells, bool highlight = true)
void ClearAllHighlights()GridObject
// Inspector Properties
bool isBlocking // Blocks other objects
bool isPickable // Can be interacted with
bool isImmovable // Cannot be pushed
float groundHeight // Y offset from ground
// Runtime Properties
GridPosition GridPosition { get; } // Current grid position (read-only)
bool IsBlocking { get; }
bool IsPickable { get; }
bool IsImmovable { get; }
float GroundHeight { get; }
Vector3 VisualOffset { get; } // Visual centering offset
// Events
event Action<GridPosition> OnMoveStart // Fired when movement begins
event Action<GridPosition> OnMoveComplete // Fired when movement ends
// Methods
virtual void Initialize(GridPosition position)
virtual void TeleportTo(GridPosition newPosition)
virtual void MoveTo(GridPosition newPosition, MovementType type = MovementType.Walk, Action onComplete = null)
bool Push(GridPosition direction, GridManager gridManager)
void Swap(GridObject other, GridManager gridManager)
// Internal (called by GridMovement)
void OnMoveStartInternal(GridPosition targetPos)
void OnMoveCompleteInternal(GridPosition targetPos)GridMovement
// Requires: GridObject // Inspector Properties float moveSpeed // Base movement speed (default: 5) float jumpHeight // Jump arc height (default: 2) float dashSpeedMultiplier // Dash speed multiplier (default: 3) // Methods void Move(GridPosition targetPos, MovementType type, Action onComplete)
GridEntity
// Inherits all from GridObject // Additional Inspector Properties bool billboard // Face camera (default: true) float verticalOffset // Y offset for sprite pivot (default: 0.5) Vector3 baseScale // Entity scale (default: 5,5,5) // Behavior: LateUpdate handles billboarding when enabled
Examples
Complete Game Setup
using UnityEngine;
using CrackedGames.Grid;
using System.Collections.Generic;
public class GameManager : MonoBehaviour
{
[SerializeField] private GridManager gridManager;
[SerializeField] private GridObject playerPrefab;
[SerializeField] private GridObject obstaclePrefab;
[SerializeField] private int obstacleCount = 5;
private GridObject player;
private List<GridCell> mainIsland;
void Start()
{
SetupGame();
}
void SetupGame()
{
// Initialize grid with erosion
gridManager.InitializeGrid(10, 10);
gridManager.ApplyIrregularity();
// Get valid spawn area
mainIsland = gridManager.GetMainIslandCells();
// Spawn player
SpawnPlayer();
// Spawn obstacles
SpawnObstacles();
}
void SpawnPlayer()
{
GridPosition spawnPos = gridManager.GetSafeSpawnPosition(mainIsland, 2);
player = Instantiate(playerPrefab);
gridManager.RegisterObject(player, spawnPos);
}
void SpawnObstacles()
{
for (int i = 0; i < obstacleCount; i++)
{
GridPosition pos = gridManager.GetSafeSpawnPosition(mainIsland, 1);
if (pos != GridPosition.Zero)
{
var obstacle = Instantiate(obstaclePrefab);
gridManager.RegisterObject(obstacle, pos);
}
}
}
void Update()
{
HandleInput();
}
void HandleInput()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
GridPosition targetPos = gridManager.WorldToGridPosition(hit.point);
MovePlayerTo(targetPos);
}
}
}
void MovePlayerTo(GridPosition targetPos)
{
if (!gridManager.IsCellAvailable(targetPos)) return;
List<GridPosition> path = gridManager.FindPath(player.GridPosition, targetPos);
if (path != null && path.Count > 0)
{
StartCoroutine(ExecutePath(path));
}
}
System.Collections.IEnumerator ExecutePath(List<GridPosition> path)
{
foreach (var pos in path)
{
bool complete = false;
gridManager.MoveObject(player, pos, MovementType.Walk, () => complete = true);
yield return new WaitUntil(() => complete);
}
}
}Range-Based Attack System
using UnityEngine;
using CrackedGames.Grid;
using System.Collections.Generic;
public class AttackSystem : MonoBehaviour
{
[SerializeField] private GridManager gridManager;
[SerializeField] private int attackRange = 3;
private List<GridCell> highlightedCells = new List<GridCell>();
public void ShowAttackRange(GridPosition from)
{
ClearHighlights();
highlightedCells = gridManager.GetCellsInRange(from, attackRange);
foreach (var cell in highlightedCells)
{
if (cell.IsOccupied)
{
cell.SetHighlight(HighlightType.Attack);
}
else
{
cell.SetHighlight(HighlightType.ValidMove);
}
}
}
public void ClearHighlights()
{
foreach (var cell in highlightedCells)
{
cell.ClearHighlight();
}
highlightedCells.Clear();
}
public bool IsInRange(GridPosition attacker, GridPosition target)
{
return attacker.ManhattanDistance(target) <= attackRange;
}
}Danger Zone System
using UnityEngine;
using CrackedGames.Grid;
using System.Collections.Generic;
public class DangerZoneSystem : MonoBehaviour
{
[SerializeField] private GridManager gridManager;
private HashSet<GridCell> dangerCells = new HashSet<GridCell>();
public void MarkDangerZone(GridPosition center, int radius)
{
var cells = gridManager.GetCellsInRange(center, radius, onlyWalkable: false);
foreach (var cell in cells)
{
cell.SetDanger(true);
dangerCells.Add(cell);
}
}
public void ClearDangerZones()
{
foreach (var cell in dangerCells)
{
cell.SetDanger(false);
}
dangerCells.Clear();
}
public bool IsInDanger(GridPosition pos)
{
var cell = gridManager.GetCell(pos);
return cell != null && dangerCells.Contains(cell);
}
}Custom Grid Entity
using UnityEngine;
using CrackedGames.Grid;
public class PlayerUnit : GridEntity
{
[SerializeField] private int health = 100;
[SerializeField] private int attackPower = 25;
[SerializeField] private int moveRange = 3;
private SpriteRenderer spriteRenderer;
protected override void Awake()
{
base.Awake();
spriteRenderer = GetComponent<SpriteRenderer>();
}
public void TakeDamage(int damage)
{
health -= damage;
// Flash red
StartCoroutine(FlashColor(Color.red, 0.2f));
if (health <= 0)
{
Die();
}
}
public void Attack(GridObject target)
{
if (target is PlayerUnit enemy)
{
enemy.TakeDamage(attackPower);
}
}
private System.Collections.IEnumerator FlashColor(Color color, float duration)
{
Color original = spriteRenderer.color;
spriteRenderer.color = color;
yield return new WaitForSeconds(duration);
spriteRenderer.color = original;
}
private void Die()
{
// Handle death
Destroy(gameObject);
}
}Performance Considerations
- A* Pathfinding: Efficient dictionary-based implementation suitable for grids up to ~100x100
- Cell Lookup: O(1) access via position dictionary
- Flood Fill: Uses HashSet for O(1) membership checks
- MaterialPropertyBlock: Used for highlights to avoid material instancing
- Range Queries: Optimized using Manhattan/Chebyshev distance calculations
Troubleshooting
- Ensure
cellPrefabslist has at least one prefab with weight > 0 - Check that prefabs have
GridCellcomponent andMeshRenderer
- Verify start and end positions are valid and walkable
- Check that blocking objects aren't preventing passage
- Ensure grid is connected (not split by erosion)
- GridCellHighlight component is auto-created but needs proper shader
- Supported shaders: "CrackedGames/GridHighlight", URP/Unlit, Legacy/Transparent/Diffuse
- Ensure
GridManagerreference is assigned - Call
SetupCamera()after grid initialization ifautoSetupis false
- Walls are only generated in the "back triangle" by design
- Adjust
wallHeightandwallOffsetif needed
Support
For issues and feature requests, please visit the project repository or contact support.