using System; using UnityEngine; using TMPro; /// /// A generic 2D grid implementation that can be used in both 2D and 3D. /// The grid stores values of a specified type and provides methods for accessing and modifying /// these values based on grid coordinates or world coordinates. The grid can be displayed /// either vertically (X-Y plane) or horizontally (X-Z plane) using a CoordinateConverter. /// public class GridSystem2D { readonly int width; readonly int height; readonly float cellSize; readonly Vector3 origin; readonly T[,] gridArray; readonly CoordinateConverter coordinateConverter; public event Action OnValueChangeEvent; public static GridSystem2D VerticalGrid(int width, int height, float cellSize, Vector3 origin, bool debug = false) { return new GridSystem2D(width, height, cellSize, origin, new VerticalConverter(), debug); } public static GridSystem2D HorizontalGrid(int width, int height, float cellSize, Vector3 origin, bool debug = false) { return new GridSystem2D(width, height, cellSize, origin, new HorizontalConverter(), debug); } public GridSystem2D(int width, int height, float cellSize, Vector3 origin, CoordinateConverter coordinateConverter, bool debug = false) { this.width = width; this.height = height; this.cellSize = cellSize; this.origin = origin; this.coordinateConverter = coordinateConverter ?? new VerticalConverter(); gridArray = new T[width, height]; if (debug) { DrawDebugLines(); } } public void SetValue(Vector3 worldPosition, T value) { Vector2Int pos = coordinateConverter.WorldToGrid(worldPosition, cellSize, origin); SetValue(pos.x, pos.y, value); } public void SetValue(int x, int y, T value) { if (IsValid(x, y)) { gridArray[x, y] = value; OnValueChangeEvent?.Invoke(x, y, value); } } public T GetValue(Vector3 worldPosition) { Vector2Int pos = GetXY(worldPosition); return GetValue(pos.x, pos.y); } public T GetValue(int x, int y) => IsValid(x, y) ? gridArray[x, y] : default(T); public Vector2Int GetXY(Vector3 worldPosition) => coordinateConverter.WorldToGrid(worldPosition, cellSize, origin); public Vector3 GetWorldPositionCenter(int x, int y) => coordinateConverter.GridToWorldCenter(x, y, cellSize, origin); Vector3 GetWorldPosition(int x, int y) => coordinateConverter.GridToWorld(x, y, cellSize, origin); bool IsValid(int x, int y) => x >= 0 && y >= 0 && x < width && y < height; #region Debug void DrawDebugLines() { const float duration = 100f; var parent = new GameObject("Debugging"); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { CreateWorldText(parent, x + "," + y, GetWorldPositionCenter(x, y), coordinateConverter.Forward); Debug.DrawLine(GetWorldPosition(x, y), GetWorldPosition(x, y + 1), Color.white, duration); Debug.DrawLine(GetWorldPosition(x, y), GetWorldPosition(x + 1, y), Color.white, duration); } } Debug.DrawLine(GetWorldPosition(0, height), GetWorldPosition(width, height), Color.white, duration); Debug.DrawLine(GetWorldPosition(width, 0), GetWorldPosition(width, height), Color.white, duration); } TextMeshPro CreateWorldText(GameObject parent, string text, Vector3 position, Vector3 dir, int fontSize = 2, Color color = default, TextAlignmentOptions textAnchor = TextAlignmentOptions.Center, int sortingOrder = 0) { GameObject gameObject = new GameObject("DebugText_" + text, typeof(TextMeshPro)); gameObject.transform.SetParent(parent.transform); gameObject.transform.position = position; gameObject.transform.forward = dir; TextMeshPro textMeshPro = gameObject.GetComponent(); textMeshPro.text = text; textMeshPro.fontSize = fontSize; textMeshPro.color = color == default ? Color.white : color; textMeshPro.alignment = textAnchor; textMeshPro.GetComponent().sortingOrder = sortingOrder; return textMeshPro; } #endregion #region CoordinateConverter /// /// The abstract base class for coordinate converters, which provides methods to convert grid coordinates /// to world coordinates and vice versa, as well as a method to get the world position of the center of a grid cell. /// public abstract class CoordinateConverter { /// /// Returns the world position of the center of a grid cell with the given x and y indices. /// public abstract Vector3 GridToWorldCenter(int x, int y, float cellSize, Vector3 origin); /// /// Converts grid coordinates (x, y) to world coordinates. /// public abstract Vector3 GridToWorld(int x, int y, float cellSize, Vector3 origin); /// /// Converts world coordinates to grid coordinates (x, y). /// public abstract Vector2Int WorldToGrid(Vector3 worldPosition, float cellSize, Vector3 origin); /// /// Direction the grid is facing in world space. /// public abstract Vector3 Forward { get; } } /// /// A coordinate converter for vertical grids, where the grid lies on the X-Y plane. /// public class VerticalConverter : CoordinateConverter { public override Vector3 GridToWorldCenter(int x, int y, float cellSize, Vector3 origin) { return new Vector3(x * cellSize + cellSize * 0.5f, y * cellSize + cellSize * 0.5f, 0) + origin; } public override Vector3 GridToWorld(int x, int y, float cellSize, Vector3 origin) { return new Vector3(x, y, 0) * cellSize + origin; } public override Vector2Int WorldToGrid(Vector3 worldPosition, float cellSize, Vector3 origin) { Vector3 gridPosition = (worldPosition - origin) / cellSize; var x = Mathf.FloorToInt(gridPosition.x); var y = Mathf.FloorToInt(gridPosition.y); return new Vector2Int(x, y); } public override Vector3 Forward => Vector3.forward; } /// /// A coordinate converter for horizontal grids, where the grid lies on the X-Z plane. /// public class HorizontalConverter : CoordinateConverter { public override Vector3 GridToWorldCenter(int x, int y, float cellSize, Vector3 origin) { return new Vector3(x * cellSize + cellSize * 0.5f, 0, y * cellSize + cellSize * 0.5f) + origin; } public override Vector3 GridToWorld(int x, int y, float cellSize, Vector3 origin) { return new Vector3(x, 0, y) * cellSize + origin; } public override Vector2Int WorldToGrid(Vector3 worldPosition, float cellSize, Vector3 origin) { Vector3 gridPosition = (worldPosition - origin) / cellSize; var x = Mathf.FloorToInt(gridPosition.x); var y = Mathf.FloorToInt(gridPosition.z); return new Vector2Int(x, y); } public override Vector3 Forward => -Vector3.up; } #endregion }