Skip to main content

tile-grid

Programmatic tile grid in FlatRedBall2. Use when building a code-driven grid at runtime (city builders, dungeon generators, puzzle games, cellular automata) — NOT loading a TMX map from Tiled. Covers the data/visual split, world↔tile coordinate conversion, per-tile visual spawning, and performance guidance for large grids (128×128+). Trigger on any question about tile coordinates, world-to-tile math, programmatic maps, or large grids without TMX files.

Ir a la instalación

Datos de origen

Repositorio
vchelaru/FlatRedBall2
Última actividad en el origen
29 de abril de 2026 a las 16:48
Idioma detectado de SKILL.md
inglés
Estrellas
14
Forks
6

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
tile-grid
description
Programmatic tile grid in FlatRedBall2. Use when building a code-driven grid at runtime (city builders, dungeon generators, puzzle games, cellular automata) — NOT loading a TMX map from Tiled. Covers the data/visual split, world↔tile coordinate conversion, per-tile visual spawning, and performance guidance for large grids (128×128+). Trigger on any question about tile coordinates, world-to-tile math, programmatic maps, or large grids without TMX files.
# Programmatic Tile Grid Use this skill when the grid is created at runtime in C#, not loaded from a `.tmx` file. Typical cases: city builders, procedural dungeons, puzzle games, cellular automata. If you have a designer-authored level in Tiled, use the **levels** and **tmx** skills instead. --- ## Data / Visual Split (Required Pattern) Keep the authoritative state in a C# array and the visuals as a separate layer. Never derive game logic from what's drawn on screen. ```csharp // Data layer — the truth: public enum TileType { Empty, Road, ZoneR, ZoneC, ZoneI, PowerPlant, WaterTower } private TileType[,] _tiles; // [col, row] indexed private int _cols, _rows; private float _tileSize; private Vector2 _gridOrigin; // world position of tile (0,0) center // Visual layer — one ColoredRectangleRuntime per visible tile: private ColoredRectangleRuntime?[,] _visuals; ``` Always update `_tiles` first, then sync the visual. Never infer tile type from the visual's color. --- ## Grid Setup ```csharp _cols = 128; _rows = 128; _tileSize = 16f; // Center the grid at world origin: _gridOrigin = new Vector2(-(_cols * _tileSize) / 2f + _tileSize / 2f, -(_rows * _tileSize) / 2f + _tileSize / 2f); _tiles = new TileType[_cols, _rows]; _visuals = new ColoredRectangleRuntime[_cols, _rows]; ``` `_gridOrigin` is the **center** of tile (0,0). Add `col * _tileSize` / `row * _tileSize` to get centers of other tiles. --- ## World ↔ Tile Coordinate Conversion This is the most-called math in any tile game. Use `Math.Floor` — not `(int)` cast — to handle negative world coordinates correctly. ```csharp // World position → tile indices (returns false if out of bounds): bool WorldToTile(Vector2 worldPos, out int col, out int row) { col = (int)Math.Floor((worldPos.X - _gridOrigin.X + _tileSize / 2f) / _tileSize); row = (int)Math.Floor((worldPos.Y - _gridOrigin.Y + _tileSize / 2f) / _tileSize); return col >= 0 && col < _cols && row >= 0 && row < _rows; } // Tile indices → world center position: Vector2 TileToWorld(int col, int row) => new Vector2(_gridOrigin.X + col * _tileSize, _gridOrigin.Y + row * _tileSize); ``` > **Gotcha:** `(int)(-0.3f)` rounds toward zero and gives `0`, but the tile at world X = -0.3 is tile `-1`. Always use `Math.Floor`. --- ## Spawning Tile Visuals For grids up to ~4,000 tiles, spawn one `ColoredRectangleRuntime` per tile in Gum (screen-space; camera does not move these automatically). This is **Option B (canvas-space overlay)** and is separate from the world-space path described in the World ↔ Tile section above. It uses a dedicated `_uiGridOrigin` in canvas-space: ```csharp private readonly Vector2 _uiGridOrigin = Vector2.Zero; void SpawnVisual(int col, int row) { var rect = new ColoredRectangleRuntime { Width = _tileSize, Height = _tileSize, }; // This snippet is canvas-space only (UI overlay grid). Add(rect); // screen-space — anchored to Gum canvas _visuals[col, row] = rect; SyncVisual(col, row); } void SyncVisual(int col, int row) { var rect = _visuals[col, row]; if (rect == null) return; // Canvas-space placement for Gum visuals. rect.X = _uiGridOrigin.X + col * _tileSize; rect.Y = _uiGridOrigin.Y + row * _tileSize; rect.Color = TileColor(_tiles[col, row]); } ``` For very large grids (16,000+ tiles), spawn only the tiles visible in the canvas viewport and re-cull when your UI scroll offset changes. At 128×128 = 16,384 tiles, spawning all at once is slow but acceptable at startup (~500ms). Profile before optimizing. If your grid lives in world-space instead, prefer world renderables (`AARect`/`Sprite`) so camera transforms are automatic. --- ## Updating a Tile Always go data-first, then sync visual: ```csharp void SetTile(int col, int row, TileType type) { if (col < 0 || col >= _cols || row < 0 || row >= _rows) return; _tiles[col, row] = type; SyncVisual(col, row); } ``` --- ## Adjacency / Neighbor Iteration ```csharp // 4-way neighbors (N/S/E/W): static readonly (int dc, int dr)[] Cardinal = [(0,1),(0,-1),(1,0),(-1,0)]; bool HasRoadAccess(int col, int row) { foreach (var (dc, dr) in Cardinal) { int nc = col + dc, nr = row + dr; if (nc >= 0 && nc < _cols && nr >= 0 && nr < _rows) if (_tiles[nc, nr] == TileType.Road) return true; } return false; } ``` --- ## Radius Queries (Power, Water Coverage) ```csharp bool IsInRadius(int srcCol, int srcRow, int targetCol, int targetRow, float radius) { float dx = (targetCol - srcCol) * _tileSize; float dy = (targetRow - srcRow) * _tileSize; return dx * dx + dy * dy <= radius * radius; } ``` Use integer math (column/row distances) when possible — it avoids the square root and is significantly faster for large grids. --- ## Performance Notes - **16,384 tiles (128×128):** Initial spawn is slow (~300–600ms for `ColoredRectangleRuntime` objects). Acceptable for city builders. If it's a problem, spawn only the visible viewport and lazy-spawn as camera pans. - **Per-tick full-grid scan:** Scanning all 16,384 tiles each sim tick is fast (< 1ms). Do not premature-optimize. Only cache derived data (e.g., "powered tiles" bitset) if profiling reveals it's a bottleneck. - **Do not use `TileShapes` for visual-only grids.** `TileShapes` is a collision structure, not a rendering structure. Use `ColoredRectangleRuntime` for programmer-art visual tiles. - **Entity-per-tile is expensive at this scale.** Avoid spawning a full `Entity` subclass for each tile — the per-entity overhead (factory registration, physics update, etc.) at 16k tiles adds up. Use lightweight Gum visuals or `ColoredRectangleRuntime` instead.
Ver en GitHub