Krotox Engine › Docs › Scripting API › Systems & Utilities
Reading and writing tiles at run time, converting between world and cell coordinates, TileData, rule tiles for auto-tiling, and generating levels in code with the KrotoxEngine.PCG library (cellular automata, BSP dungeons, wave function collapse and more), then baking them into a tilemap.
Setting tilemaps up and painting them is in Tilemaps, Tile Palette & level generation. This page is about changing tiles from code: destructible terrain, doors that seal, mining games and generated levels.
var grid = GetComponent<GridComponent>(); // on the tilemap's parent
Vector3 size = grid.CellSize;
Vector3 gap = grid.CellGap;
Vector3 local = grid.CellToLocal(5, 3); // centre of cell (5, 3) in the grid's local space
grid.LocalToCell(localPosition, out int cx, out int cy);
CellToLocal and LocalToCell work in the grid entity's local space. Convert world positions first:
Entity gridEntity = Entity.Find("Level");
var grid = gridEntity.GetComponent<GridComponent>();
// The cell under the mouse
Vector2 mouseWorld = Camera.ScreenToWorldPoint(Input.MousePosition);
Vector3 local = gridEntity.InverseTransformPoint(new Vector3(mouseWorld.X, mouseWorld.Y, 0));
grid.LocalToCell(local, out int cellX, out int cellY);
// Back to world space
Vector3 cellWorld = gridEntity.TransformPoint(grid.CellToLocal(cellX, cellY));
| Member | Meaning |
|---|---|
SetTile(int x, int y, TileData tile) | Place a tile. |
SetTilesBlock(int minX, int minY, int maxX, int maxY, TileData tile) | Fill a rectangle. |
SetRuleTile(int x, int y, RuleTile ruleTile, bool updateNeighbors = true) | Place a rule tile and re-resolve its neighbours. |
TileData GetTile(int x, int y) | Read a tile. |
bool HasTile(int x, int y) | Whether a cell is filled. |
ClearTile(int x, int y) / ClearAll() | Remove one tile or all of them. |
bool GetCellBounds(out int minX, out int minY, out int maxX, out int maxY) | The rectangle around all painted tiles. Returns false when the map is empty. |
Vector4 ColorTint | Tint for every tile. |
Colliders built from the tilemap (Tilemap Collider 2D and Composite Collider 2D) update when tiles change.
public struct TileData
{
public ulong AssetHandle; // the texture the tile is cut from
public int SubTextureCoordX; // column of the cell on that texture
public int SubTextureCoordY; // row
public Vector4 ColorTint; // per-tile tint
public byte TransformFlags; // flip and rotate flags
public byte ColliderType; // collider shape for this tile
}
The easiest way to get a valid TileData is to copy one that is already painted (for example in OnCreate) and change the coordinates:
TileData m_Dirt;
void OnCreate()
{
m_Map = GetComponent<TilemapComponent>();
m_Dirt = m_Map.GetTile(0, 0); // a tile painted in the editor as a template
}
// Dig out the cell the player clicked
void Dig(int x, int y)
{
if (m_Map.HasTile(x, y))
m_Map.ClearTile(x, y);
}
// Seal a doorway three tiles high
void Seal(int x, int y)
{
m_Map.SetTilesBlock(x, y, x, y + 2, m_Dirt);
}
if (m_Map.GetCellBounds(out int minX, out int minY, out int maxX, out int maxY))
{
for (int y = minY; y <= maxY; y++)
for (int x = minX; x <= maxX; x++)
if (m_Map.HasTile(x, y)) { /* ... */ }
}
A RuleTile picks a sprite for a cell from its eight neighbours. It is a plain C# class, so you can build one in code:
var ground = new RuleTile
{
AssetHandle = m_Tileset, // the tile set texture's handle
DefaultSubTextureCoordX = 1, // used when no rule matches
DefaultSubTextureCoordY = 1,
};
// A top edge: nothing above, ground to the left and right
var topEdge = new TilingRule { SubTextureCoordX = 1, SubTextureCoordY = 2 };
topEdge.Neighbors[1] = NeighborRule.NotThis; // neighbour index order is defined by the engine
ground.Rules.Add(topEdge);
m_Map.SetRuleTile(x, y, ground); // also re-resolves the eight neighbours
TileData resolved = ground.ResolveTile(m_Map, x, y);
Each TilingRule holds eight NeighborRule values (DontCare, This, NotThis) plus the cell to use and its flags. Rules are checked in order and the first match wins.
The Level Generator panel uses this same library, so a layout you previewed in the editor is exactly what the same seed builds in game. Generators return a PCGGrid<int> where 0 is floor and 1 is wall.
using KrotoxEngine.PCG;
// Caves
var caves = new CellularAutomata();
caves.Config.RandomFillPercent = 45;
caves.Config.SmoothingIterations = 5;
PCGGrid<int> grid = caves.Generate(80, 60, seed: 1337);
ConnectivitySolver.ConnectCaverns(grid, floorValue: 0, wallValue: 1); // tunnel every pocket to the main cave
// Dungeons
var dungeon = new BSPDungeon();
dungeon.Config.MinRoomSize = 10;
PCGGrid<int> rooms = dungeon.Generate(100, 100, seed: 7); // dungeon.Rooms, dungeon.Corridors
// Check it is fully connected (one region = everything reachable)
var regions = ConnectivitySolver.FindFloorRegions(grid);
Log.Info(quot;{regions.Count} floor region(s)"); // Stamp it into a tilemap TilemapBaker.Bake(grid, GetComponent<TilemapComponent>(), wallTile, floorTile); TilemapBaker.BakeWithRuleTile(grid, GetComponent<TilemapComponent>(), wallRuleTile, floorTile);
Bake also takes clearExisting (default true) and an origin cell, to stamp a generated room into part of a larger map.
| Generator | Produces |
|---|---|
CellularAutomata | Organic caves. Config: RandomFillPercent, SmoothingIterations, BirthLimit, DeathLimit, BorderPadding, ConnectDisconnectedPockets. |
BSPDungeon | Rooms and corridors. Config: MinLeafSize, MinRoomSize, RoomPadding, CorridorWidth, ExtraCorridorLoopPercent. |
DrunkardsWalk | Meandering tunnels. |
NoiseField | Terrain from fractal noise, cut at a threshold. |
GaltonBoard | Staggered peg boards. |
ChunkStitcher | Platformer levels from hand-made chunks, connected entrance to exit. |
WaveFunctionCollapse | Layouts learned from a sample: LearnFromSample(sampleGrid). |
Solvers and helpers: ConnectivitySolver, PathValidatorAStar (is the exit reachable?), MinimumSpanningTree, PrefabPlacer (spawn prefabs on a layout), LevelMetadataSync, and campaign tools (CampaignDefinition, LevelRecipe, PrefabRoomStitcher).
There is no Random class in the engine API: use System.Random or the PCG library's seeded generator, which gives the same sequence for the same seed on every platform.
var rng = new PCGSeededRandom(1337);
int roll = rng.Range(0, 6); // 0 to 5
float jitter = rng.RangeFloat(-1f, 1f);
bool lucky = rng.Chance(25); // 25% chance
string pick = rng.Choose(names);
rng.Shuffle(deck);
Store the seed with the player's save. Rebuilding the level from the seed is far smaller than saving every tile.