Warman's terrain is built from the ground up for a top-down action RPG. Unity's built-in terrain system is designed for open-world first-person games: heightmaps, splatmaps, tree placement. None of that fits a game where the world is a grid of handcrafted rooms. So the terrain is entirely custom.

Points and Cells

The terrain is a grid. Each position in the grid is a TerrainPoint, a struct containing a height value, a water height, a texture layer index, a detail type, and a ramp flag. Points are the corners. Between four points sits a TerrainCell, the actual unit of terrain that the player walks on. A 30x30 room has 30x30 cells and 31x31 points.

Each point stores its data in bytes. Height is a byte (0-255 levels), water height is a byte, and the texture index is a byte that maps to one of the biome's tileset layers. The whole point struct serialises to about 6 bytes, which keeps room files small and fast to load.

Cell Cell Cell Cell Cell Cell TerrainPoint height, water, texture (~6 bytes) TerrainCell walkability, cliff state, ramp Texture transition resolved via dynamic atlas
Terrain grid: points at corners, cells between them

The Texture Atlas

This is where it gets interesting. Each cell has four corner points, each with a potentially different texture. A cell where all four corners use the same grass texture is easy. Just render a grass quad. But what about a cell where two corners are grass and two are dirt? That's a transition tile, and it needs a specific texture that blends the two materials.

Warman solves this with a dynamic texture atlas. Each unique combination of corner textures gets a tile in a runtime-generated atlas. The tile type is computed by packing the four corner indices into a single integer (using bit shifts), which creates a unique key for every possible combination. The atlas starts at 1024x1024 and grows as needed. When a new combination appears that hasn't been seen before, a tile gets reserved in the atlas, the correct transition texture is composited from the tileset layers, and the cell's UVs point to the new atlas position.

Cliffs

Cliffs appear wherever adjacent terrain points have different heights. The cliff layer scans every cell, computes which edges have height transitions, and places the appropriate cliff mesh. There are specific tilesets for cliff faces that handle internal corners, external corners, and straight edges. There are 15 variants in a standard cliff tileset, based on which of the four corners are elevated.

Ramps work similarly but are more trickier. When the editor marks points as "ramp points," the cliff system generates sloped geometry instead of vertical cliff faces. Each cell with a ramp gets per-vertex heights calculated from the ramp direction (north, east, south, or west), splitting the height transition into eighths so the slope looks smooth. The pathfinding system knows about ramps and subdivides each cell into a denser grid and assigns interpolated heights to each node so units walk smoothly up slopes.

Water

Water is a separate layer sitting on top of the terrain. Each point has a water height, and if the water height meets or exceeds the terrain height, that point is considered "visibly wet." The water layer generates its own mesh with animated UVs, positioned at the water height plus a small depth offset so it sits just above the terrain surface.

The pathfinding grid marks water cells based on the biome's surface type settings. Some biomes treat water as walkable (shallow streams), others do not (deep water). The footstep sound system also reads the surface type, so the player hears splashing when walking through water.

Doodads

Decorative objects (grass tufts, flowers, mushrooms, candles) are placed on the terrain in two ways. Most doodads are standard GameObjects, instantiated with instantiated with a position, rotation, and scale. But high-volume foliage like grass and flowers use Unity's ECS (Entity Component System). A single call allocates hundreds of entities with a shared mesh and material, which renders in a fraction of the time it would take as individual GameObjects.

The terrain system checks a hardcoded list of "ECS-eligible" doodad types. If a doodad type is on the list, it goes through the entity path. Otherwise, it's a regular GameObject. This split exists because ECS entities can't have custom configuration (like interactable components), so only purely visual doodads qualify.

Feeding into Pathfinding

After the terrain is generated, the pathfinding system reads the cell data to build its navigation grid. Each terrain cell becomes a cluster of pathfinding nodes (2x2 subdivisions per cell by default), with walkability determined by the terrain texture type, cliff occupancy, and ramp state. Cliff-occupied cells are non-walkable. Ramp cells get height offsets interpolated from the ramp direction. The result is a 3D grid where units can pathfind across flat ground, up ramps, and around cliff edges, all computed from the same underlying terrain data.