Skip to content

Game.TerrainSystem

Assembly: Game
Namespace: Game.Simulation

Type: public class

Base: GameSystemBase, IDefaultSerializable, ISerializable

Summary:
TerrainSystem is the central system that manages terrain height data and in-game terrain editing for Cities: Skylines 2. It owns and updates GPU height textures (heightmap and cascaded arrays), handles world vs playable-area heightmaps, performs asynchronous GPU readbacks to produce CPU-side height data, runs jobs that cull buildings/roads/areas for terrain adjustments, and executes GPU compute shaders to apply brushes (shift, level, slope, soften). The system exposes APIs to serialize/deserialize heightmaps, replace maps at runtime, request/carry out brush applications, and query terrain/cascade metadata for rendering or mod use. It also contains internal utilities for min/max height maps used for fast bounds queries.


Fields

  • private RenderTexture m_Heightmap
    Stores the editable/playable-area GPU heightmap (R16) used when applying brush changes and when composing cascade textures.

  • private RenderTexture m_HeightmapCascade
    A Texture2DArray (4 layers) used by terrain rendering (cascade texture). Updated from m_Heightmap and world heightmap as needed.

  • private RenderTexture m_WorldMapEditable
    If the world heightmap is editable (in-editor), this holds the editable RenderTexture; otherwise worldHeightmap references a plain Texture2D asset.

  • private NativeArray<ushort> m_CPUHeights
    CPU-side height buffer (ushort) used to feed systems that need direct access to heights (e.g., GroundHeightSystem). Kept up-to-date using AsyncGPUReadback.

  • private TerrainMinMaxMap m_TerrainMinMax
    Helper object that builds a min/max map (downsampled) of terrain heights used for quick min/max queries on regions. Uses compute shaders and async GPU readback.

  • private CommandBuffer m_CommandBuffer
    CommandBuffer reused for compute-dispatch and copy operations when adjusting terrain with brushes.

  • private ComputeShader m_AdjustTerrainCS
    Compute shader used for terrain adjustment kernels (shift, blur, smooth, slope, level).

  • private NativeList<LaneSection> m_LaneCullList
    NativeList populated by culling jobs containing lane/road sections that require terrain modification (flatten/clip).

(There are many more internal fields — GPU buffers, Material instances, job handles, lists for culling and rendering — used for cascade rendering and asynchronous processing.)


Properties

  • public Vector4 VTScaleOffset { get; }
    Returns a Vector4 that packs world/cascade scale/offset values used by shaders.

  • public bool NewMap { get; }
    True for the frame a new heightmap was created/loaded.

  • public Texture heightmap { get; }
    Public getter for the currently active playable-area heightmap RenderTexture.

  • public Vector4 mapOffsetScale { get; }
    Map offset/scale used to transform world coordinates into heightmap UVs.

  • public float2 heightScaleOffset { get; set; }
    Height scale (range) and height offset used when converting stored ushort heights into world heights.

  • public TextureAsset worldMapAsset { get; set; }
    If the world heightmap is loaded as an asset, reference to it (used for unloading / reloading).

  • public Texture worldHeightmap { get; set; }
    Reference to the world-level heightmap texture (may point to m_WorldMapEditable when in-editor).

  • public float2 playableArea { get; private set; }
    Size of the playable map area in world units.

  • public float2 playableOffset { get; private set; }
    Offset (corner) of the playable map area in world units.

  • public float2 worldSize { get; private set; }
    World map size (if a world map is used that differs from playable).

  • public float2 worldOffset { get; private set; }
    World map offset (corner) in world units.

  • public bool heightMapRenderRequired { get; private set; }
    Indicates whether a cascade update / render is required this frame.

  • public float4[] heightMapSliceArea { get; }
    Array of cascade ranges (min/max for each cascade slice).

  • public static int baseLod { get; private set; }
    Base LOD used for cascades (0 or 1 depending on whether world map size differs from playable).

(Several other arrays/properties exist for per-cascade viewport and update bookkeeping.)


Constructors

  • public TerrainSystem()
    Default system constructor; actual initialization occurs in OnCreate. The system constructs many internal resources (NativeLists, CommandBuffers, compute shader kernels, Materials, buffers) during OnCreate.

Methods

  • protected override void OnCreate()
    Initializes compute kernels, native containers, materials, buffers and queries. Sets up cascade data structures and default terrain state. Loads compute shaders and creates CommandBuffers and RenderTextures when necessary.

  • protected override void OnDestroy()
    Cleans up GPU resources, disposes NativeContainers, completes outstanding jobs, and destroys materials/meshes.

  • protected override void OnUpdate()
    Runs each frame: completes CPU height reader jobs, updates cascade state, handles async GPU readback updates and triggers GPU terrain updates (RenderCascades).

  • public void Serialize<TWriter>(TWriter writer) where TWriter : IWriter
    Serializes the world heightmap and playable heightmap to a writer (used when saving); writes heightScaleOffset, playable/world offsets and sizes, and min/max world height.

  • public void Deserialize<TReader>(TReader reader) where TReader : IReader
    Deserializes saved heightmaps and initializes terrain data. Supports version checks.

  • public void ReplaceHeightmap(Texture2D inMap)
    Replace the playable heightmap with the provided Texture2D (converts to R16 if needed) and reinitializes cascade textures accordingly.

  • public void ReplaceWorldHeightmap(Texture2D inMap)
    Replaces the world heightmap (world-level map). Converts to R16 if needed. If editing, creates editable rendertexture.

  • private void InitializeTerrainData(Texture2D inMap, Texture2D worldMap, float2 heightScaleOffset, float2 inMapCorner, float2 inMapSize, float2 inWorldCorner, float2 inWorldSize, float2 inWorldHeightMinMax)
    Internal helper that sets up m_Heightmap, m_HeightmapCascade, cascade ranges, shader globals and registers CPU height readers.

  • public TerrainHeightData GetHeightData(bool waitForPending = false)
    Returns a TerrainHeightData wrapper with CPU-side height buffer and scale/offset metadata. Optional wait to ensure pending readbacks complete.

  • public void ApplyBrush(TerraformingType type, Bounds2 area, Brush brush, Texture texture)
    Apply an actual terrain brush operation (Shift/Level/Slope/Soften). This applies to both the playable heightmap and an editable world heightmap (if present) and triggers min/max updates and async GPU readback.

  • public void PreviewBrush(TerraformingType type, Bounds2 area, Brush brush, Texture texture)
    Intended for preview (no permanent application) — currently a placeholder / no-op in this listing.

  • public void TriggerAsyncChange()
    Starts or updates an async GPU readback to transfer modified heightmap region into m_CPUHeights for CPU-consumers. Handles out-of-date logic.

  • private void UpdateGPUReadback()
    Internal: polls/advances AsyncGPUReadbackHelper state and writes CPU heights when readback completes.

  • private void UpdateCascades(bool isLoaded, bool heightsReadyAfterLoading)
    Determines which cascades require update, computes per-cascade viewports, schedules culling jobs for buildings/roads/areas and prepares data for RenderCascades.

  • public void RenderCascades()
    If updates are required, issues GPU commands (via CommandBuffer) to render each cascade slice, draw procedural height adjustments for building lots, roads and areas, and finally copy results to the cascade texture array. Also triggers async readback.

  • private void CullForCascades(float4 area, bool heightMapRenderRequired, bool roadsChanged, bool terrainAreasChanged, bool clipAreasChanged, out int laneCount)
    Schedules ECS jobs to cull building lots, roads, areas and generate lists of drawing primitives to be used when applying terrain modifications in each cascade.

  • public NativeList<LaneSection> GetRoads()
    Return an up-to-date list (NativeList) of lane/road sections that were collected by cull jobs (completes lane cull job before returning).

  • public bool GetTerrainBrushUpdate(out float4 viewport)
    If a brush change occurred, provides a normalized viewport of the changed area in cascade coordinates and resets the internal flag.

  • public bool CalculateBuildingCullArea(Game.Objects.Transform transform, Entity prefab, ComponentLookup<ObjectGeometryData> geometryData, out float4 area)
    Helper that computes world-space area (xy min, zw max) affected by a building's smoothing extent and returns true if geometry was available.

  • public void OnBuildingMoved(Entity entity)
    Called when a building entity moved; calculates and enqueues the affected area for height updates.

  • public void GetLastMinMaxUpdate(out float3 min, out float3 max)
    Read last min/max update area (world-space corners and min/max heights) produced by TerrainMinMaxMap.

(There are many internal job structs and helpers for culling and procedural drawing — modders typically use the public APIs above.)


Usage Example

// Acquire the TerrainSystem from the world (example in a mod's system or utility)
var terrain = base.World.GetOrCreateSystemManaged<Game.Simulation.TerrainSystem>();

// Replace the playable heightmap (must be R16 or will be converted internally)
Texture2D myR16Heightmap = /* load or create a R16 height texture */;
terrain.ReplaceHeightmap(myR16Heightmap);

// Apply a simple shift brush (assumes you have a Brush struct and texture)
Brush myBrush = new Brush {
    m_Position = new float3(1000f, 0f, 1000f), // world position
    m_Size = 300f,
    m_Strength = 1f,
    m_Angle = 0f,
    m_Start = new float3(),
    m_Target = new float3(0f),
};
Bounds2 area = new Bounds2(new float2(850f, 850f), new float2(1150f, 1150f)); // world XY bounds
Texture brushTex = /* a texture representing brush shape */;

// Apply brush immediately
terrain.ApplyBrush(TerraformingType.Shift, area, myBrush, brushTex);

// Query CPU-side height data (wait for pending readback if you need the latest)
var heightData = terrain.GetHeightData(waitForPending: true);
// Use heightData to sample heights on CPU...

Notes for modders: - Height textures used by TerrainSystem are R16 (GraphicsFormat.R16_UNorm). If you provide an RGBA texture, the system may convert it. - Modifying the worldHeightmap vs playable heightmap: world heightmap may be non-editable at runtime unless an editable RenderTexture (m_WorldMapEditable) is created (editor mode). - Many of the lower-level APIs rely on Unity Collections / Jobs / Entities constructs and Native containers. Be careful to respect lifetime and job completion when interacting with these internals.