Skip to content
Brass Orrery

Version 1.0, coming to Fab. Orrery Terrain 1.0 is not on sale yet; this guide can still change before it is.

GuideOrrery Terrain 1.0Tutorials

5. Tutorial: sculpt at runtime

Your players raise, lower, smooth and flatten the ground, and paint it, while the game runs. You can use the ready-made brush tool, or drive the terrain from your own input.

You need a level with a terrain (see Quick start). This chapter is about sculpting while the game runs. Game code cannot edit the terrain of an editor world (it is read-only to it: ReadOnly). To shape the ground in the editor viewport, use the editor mode (Part 4).

Part 1: the ready-made brush tool (Blueprint)

Section titled “Part 1: the ready-made brush tool (Blueprint)”

The brush tool is a component. It handles input, draws a ring on the ground, shows a text overlay, and runs strokes for you.

  1. Create a Player Controller Blueprint (for example BP_SculptController).
  2. In Class Defaults of your Game Mode, set Player Controller Class to it. In Project Settings > Input > Default Classes, make sure Default Input Component Class is EnhancedInputComponent. The tool reads its keys through Enhanced Input and takes no input without it.
  3. In BP_SculptController, press Add and pick Terrain Brush Tool. The tool adds a Terrain Sculpt component to the same actor when it needs one.
  4. In the controller’s Class Defaults, tick Show Mouse Cursor. The default cursor is the mouse, so the ring follows the pointer.
  5. Select the Terrain Brush Tool component. Under Terrain > Tool > Brush, check Brush Kinds. By default it holds Raise, Lower, Smooth, Flatten and Paint. Paint Layer Names lists nine starter layers (Grass, Soil, Rock, Gravel, Meadow, Sand, Forest Floor, Snow and Cobblestone); change it to match your layer table (see Materials and layers).
  6. Press Play.

The default keys:

Key Action
Left mouse button Stroke: press and drag
[ and ] Smaller and larger radius
- and = Weaker and stronger
Shift + [ and Shift + ] Narrower and wider full-strength core
B Next brush kind
F Next falloff shape (Smooth, Linear, Constant)
N Next paint layer
Z and Y Undo and redo the last stroke

A line of key hints stays on screen. The first brush has a radius of 20 m and a strength of 20 cm per step. If the controller already has a Terrain Sculpt component, the tool uses that one and its Brush values instead.

The tool’s input assets are in the plugin content, in /OrreryTerrain/Tools: the mapping context IMC_TerrainBrush and thirteen actions named IA_TerrainBrush_*. To remap, make your own mapping context and assign your own actions in the Input section of the component. Empty slots use the defaults.

Every action is a Blueprint function on the component: Begin Stroke, End Stroke, Cycle Brush Kind, Scale Radius, Undo, Redo and more. Turn off Active On Begin Play and call Set Tool Active when your build mode starts and ends.

You can restyle the overlay (Show HUD, HUD Origin, HUD Text Scale, colours, the falloff plot), or hide it and draw your own from the tool state.

Part 2: the sculpt component without the tool

Section titled “Part 2: the sculpt component without the tool”

If you have your own input, use Terrain Sculpt alone. It turns presses into strokes and does nothing else. It binds no keys.

  1. Add Terrain Sculpt to your player controller or pawn.
  2. Choose Cursor Source: Mouse Cursor, Screen Centre (first person, gamepad), Custom Ray (override Get Custom Cursor Ray; VR pointers and touch) or Pushed Externally (call Push Cursor World Position).
  3. Set Brush on the component. Pick a Kind, a Radius and a Strength (both in centimetres).
  4. On your input event, call Begin Drag. On release, call End Drag. While you hold, the component applies steps at Step Rate Hz (default 30 per second).
  5. Call Undo and Redo to step back and forth through strokes.

The brush fields:

Field Meaning
Kind Raise, Lower, Smooth, Flatten, Paint (material layer) or PaintChannel (raw ground class; advanced).
Falloff Smooth, Linear or Constant.
Falloff Start 0 to 1. The inner part of the radius at full strength.
Radius Centimetres.
Strength Height change at the centre per step, centimetres.
Flatten To Target height in world Z for Flatten. By default the component takes it from the ground under the cursor when the drag begins (Flatten To Cursor At Start).
Flatten Direction Both, Raise Only or Lower Only.
Paint Layer, Paint Opacity Layer index 0 to 255, and the weight to paint toward.

When a stroke finishes, On Stroke Ended on the subsystem reports the volumes the stroke moved, in cubic metres: Raised_m3, Lowered_m3, and the change in plate and corridor fill and cut (FeatureFill_m3, FeatureCut_m3). Use them for costs, scores or sound.

Call the subsystem directly. This is the same path the components use.

#include "OrreryTerrainSubsystem.h"
void ADigger::Dig(const FVector& Where)
{
UOrreryTerrainSubsystem* Terrain = GetWorld()->GetSubsystem<UOrreryTerrainSubsystem>();
if (!Terrain || Terrain->GetEditAccessAt(Where, 500.0) != EOrreryTerrainEditAccess::Allowed) return;
FOrreryTerrainBrushBP Brush;
Brush.Kind = EOrreryTerrainBrushKind::Lower;
Brush.Centre = Where;
Brush.Radius = 500.0;
Brush.Strength = 30.0;
const int64 Stroke = Terrain->BeginStroke();
if (Stroke == 0) return;
Terrain->ApplyBrush(Stroke, Brush);
Terrain->EndStroke(Stroke);
}
Blueprint BP_Digger · Event Graph

The graph as text
  1. Dig (event; Custom Event; C++ line 3)
    • Out Output Delegate (delegate): not connected
    • Then: runs Is Valid
    • Out Where (Vector): to Get Edit Access At › Centre, Make Terrain Brush › Centre
  2. Orrery Terrain Subsystem (operator; C++ line 5)
    • Out Return Value (OrreryTerrainSubsystem object): to Is Valid › Input Object, Get Edit Access At › Target, Begin Stroke › Target, Apply Brush › Target, End Stroke › Target
  3. Is Valid (macro; C++ line 6)
    • In Input Object (Object object): from Orrery Terrain Subsystem › Return Value
    • Is Valid: runs Branch 1
    • Is Not Valid: not connected
  4. Get Edit Access At (pure function; Target is Orrery Terrain Subsystem; C++ line 6)
    • In Target (OrreryTerrainSubsystem object): from Orrery Terrain Subsystem › Return Value
    • In Centre (Vector): from Dig › Where
    • In Radius (Double): 500.0
    • Out Return Value (EOrreryTerrainEditAccess): to == › A
  5. == (operator; C++ line 6)
    • In A (EOrreryTerrainEditAccess): from Get Edit Access At › Return Value
    • In B (EOrreryTerrainEditAccess): Allowed
    • Out Return Value (Boolean): to Branch 1 › Condition
  6. Branch 1 (flow control; C++ line 6)
    • In Condition (Boolean): from == › Return Value
    • True: runs Begin Stroke
    • False: not connected
  7. Begin Stroke (function call; Target is Orrery Terrain Subsystem; C++ line 14)
    • In Target (OrreryTerrainSubsystem object): from Orrery Terrain Subsystem › Return Value
    • Then: runs Branch 2
    • Out Return Value (Integer64): to != › A, Apply Brush › Stroke, End Stroke › Stroke
  8. != (operator; C++ line 15)
    • In A (Integer64): from Begin Stroke › Return Value
    • In B (Integer64): 0
    • Out Return Value (Boolean): to Branch 2 › Condition
  9. Branch 2 (flow control; C++ line 15)
    • In Condition (Boolean): from != › Return Value
    • True: runs Apply Brush
    • False: not connected
  10. Make Terrain Brush (structure; C++ lines 8–12)
    • In Kind (EOrreryTerrainBrushKind): Lower
    • In Centre (Vector): from Dig › Where
    • In Radius (Double): 500.0
    • In Strength (Double): 30.0
    • Out Orrery Terrain Brush BP (OrreryTerrainBrushBP): to Apply Brush › Brush
  11. Apply Brush (function call; Target is Orrery Terrain Subsystem; C++ line 16)
    • In Target (OrreryTerrainSubsystem object): from Orrery Terrain Subsystem › Return Value
    • In Stroke (Integer64): from Begin Stroke › Return Value
    • In Brush (OrreryTerrainBrushBP): from Make Terrain Brush › Orrery Terrain Brush BP
    • Then: runs End Stroke
    • Out Return Value (EOrreryTerrainSculptResult): not connected
  12. End Stroke (function call; Target is Orrery Terrain Subsystem; C++ line 17)
    • In Target (OrreryTerrainSubsystem object): from Orrery Terrain Subsystem › Return Value
    • In Stroke (Integer64): from Begin Stroke › Return Value

BP_Digger in the samples project (/Game/DocExamples).

  • Begin Stroke returns 0 when edits are refused (no terrain, an editor world, a client, or a call from a ground-changed listener).
  • Apply Brush answers Applied, NoChange, Pending, Refused, OutOfOrder, NoTerrain or ReadOnly. Pending means the ground under the brush is still loading. The step is dropped and the ground under it requested, so call again later or register an invoker, a point that keeps the ground around it loaded (see Concepts). The sculpt component queues such steps for you. Refused means the brush broke a sculpt limit: it never applies, so do not retry it. After a load, see Right after a load.
  • End Stroke closes the stroke and adds it to the undo ring. Undo Stroke and Redo Stroke work on that ring. The ring keeps 32 strokes by default (SculptUndoStrokes in the actor’s Settings).

The terrain refuses a brush outside the sculpt limits, so a player cannot dig the whole map in one step. The defaults are:

Limit Default
Max Radius 100 m
Max Strength 2 m per step
Keep Out Margin 2 m around the bore, viaduct or bridge band of a corridor
Max Paint Layer 255
Paint Ground Class off
Max Stamp Radius 1000 m
Max Stamp Height 1000 m
Max Stamp Tiles 1024 edit tiles
Stamps on

Change them in the terrain actor: Settings > Sculpt Limits. Read them at run time with Get Sculpt Limits.

A step over a limit answers Refused and changes nothing, however often you send it. The first refused step of a stroke logs a warning that names the value and the limit, for example the radius 120.0 m is over this terrain's limit of 100.0 m; later ones of the same stroke for the same reason log only at Verbose.

A brush can take its shape from a picture. Make a Terrain Brush data asset (Miscellaneous > Data Asset), set Source Texture (or a single-band Tiff File) and Usage, and the editor bakes it.

  • Strength masks scale a brush’s strength per ground point (Raise, Lower, Smooth, Flatten, Paint) and bake to 256 x 256 at 8 bits. Height masks are the shape of a height stamp and bake to 1024 x 1024 at 16 bits. Side and Depth override both. A side that is not a power of two goes to the nearest one, from 8 to 2048.
  • The game keeps only the baked bytes. The source texture is editor data and is not in a cooked game.
  • A terrain holds a mask only after you register it: Register Brush (or Register Brush Set) on the subsystem, again after every load, because the registry is never saved. A brush that names a mask the terrain does not hold is refused, and the refusal says so.
  • With the brush tool, call Set Brush Mask with the asset; use Set Brush Angle, Set Rotation Mode (Fixed, Follow Stroke, Random) and Set Spacing to turn and space the dabs, and Stamp At to place one. Random angles come from the stroke and step numbers, so every machine draws the same ones.
  • Shape the mask on the brush. A brush that names a mask also takes Mask Angle (degrees), Mirror, Square (the footprint is the mask’s own rectangle, not the disc that clips it) and Mask Aspect (width over height, 0.004 to 255; 1 is square). A brush with no mask ignores all four. A Stamp brush also takes a Stamp Blend: Add, Max, Min or Blend. Its height is the brush’s strength, so it is held to Max Stamp Height.
  • Register from a game. Besides Register Brush and Register Brush Set, Register Brush Mask From Pixels takes raw single-channel pixels (1 or 2 little-endian bytes a sample) and bakes them as a strength mask (256 x 256, 8 bits) or, with Height Mask, a height mask (1024 x 1024, 16 bits). These return Registered, AlreadyHeld, NoTerrain, NotBaked, Collision (another mask under the same hash) or OverBudget (the 64 MiB limit). Unregister Brush Mask drops a mask (false while an open stroke uses it), and Is Brush Mask Known and Get Brush Mask Info read the registry.
  • In multiplayer the server sends a mask to every client the first time a step names it, and to later joiners. A client may upload a mask the server lacks only if your game allows it. See Brush masks from players.
  • Stamp Terrain (Async) waits for the ground under the brush to be editable, then stamps once as one undoable stroke. On a replica the stamp is a request to the server, and the node completes when the server’s next record is applied. The server must hold the mask too, so register the brush asset on both.

Splat paint paints up to four paint layers from one picture in one stroke.

  • Make a Terrain Splat data asset, set Source Texture to an RGBA image and give Layer Red, Green, Blue and Alpha the paint layer each channel paints. A channel with layer -1 is left out: it counts as zero and paints nothing, so leave Layer Alpha at -1 for an image whose alpha is opaque. Change a layer to or from -1 and rebake.
  • Read the channels as how much of each layer you want, from 0 to 1. A pixel of (0.5, 0.25, 0.25) paints the first layer at 0.5 and the others at 0.25. If the channels add up to less than 1, the rest stays what was there; above 1 they are scaled to add up to 1.
  • A splat dab is one masked Paint step per used channel, in the fixed order red, green, blue, alpha, inside one stroke, so the whole stroke is one undo. A Paint step takes from every layer already on the post, so the later channels take from the earlier ones; the bake allows for that, and the order is the same on every machine.
  • On the subsystem call Register Splat with the asset (again after every load), then Apply Splat with the open stroke, the dab (centre, radius, falloff, opacity, angle) and the splat it returned. Use a Constant falloff and Square to lay the whole picture without fading at the edge.
  • With the brush tool call Set Splat Brush, then Splat At to lay one dab as its own stroke. A splat dab on a replica goes to the server as masked Paint requests (one per used channel), so Splat At answers Pending there and the dab lands when the server’s answer arrives.
  • To lay a splat along a drag, call Set Splat Brush and then drag with the tool as for any brush: each step of the drag is one splat dab at the cursor, and the whole drag is one stroke and one undo. Set Rotation Mode, Set Spacing and Set Brush Angle turn and space the dabs as for a masked brush, and Set Brush Mask or Set Brush Kind to anything but Paint returns to the plain brush. On the sculpt component the same calls are Set Splat, Clear Splat and Splat Once. A client’s drag sends its Begin, the masked Paint steps and the End as requests.
  • In the editor’s Paint tool the Brush Mask palette also lists the Splats of your brush sets, after the brushes; pick one (or an asset in the Splat field) and drag with Target set to Layer. The whole drag is one editor transaction. A splat with no layer on any channel shows a warning and paints nothing. The pick is kept for the editor session, not saved.

Brush masks and splats are Experimental in v1.0. For ground that follows a spline instead of a brush, see Live splines (runtime).

Set Show HUD off on the tool to hide its text, plot and cursor ring. They are also hidden when the game runs with -SampleShots.

After you load a terrain in a paged (large) world, Has Terrain is true and Get Edit Access answers Allowed as soon as any ground has arrived, before the ground under your brush has. Until it does, Apply Brush answers Pending and changes nothing (about 15 frames in the 64 km sample, World64).

Ask about the place instead. Get Edit Access At takes the brush centre and radius. It answers Loading until a brush there would apply (the tiles under it are loaded, its sculpt has paged in from the save, the ground is built), then Allowed, and the other answers of Get Edit Access when edits are refused. It changes nothing and loads nothing: the ground arrives through the streaming sources.

To wait without polling, use Wait For Terrain Editable:

  1. Give it the brush centre and radius, and a Timeout in seconds (30 by default; 0 waits for ever).
  2. It loads the ground around the circle and fires On Ready once a brush there would apply. When the ground is already there, it fires in the same frame.
  3. It fires On Failed at once when waiting cannot help (the centre outside the terrain, an editor world, a client), with its Reason output Refused, and with Timeout when the time runs out.
  4. Start the stroke in On Ready. The node stops loading the ground when it fires, so keep a streaming source there (the player’s camera, Register Invoker) if the stroke starts later.

In C++, HandleEditable and HandleNotEditable being UFUNCTION()s that take an EOrreryTerrainAsyncFailure (None on On Ready):

UOrreryTerrainWaitForEditable* Wait = UOrreryTerrainWaitForEditable::WaitForEditable(this, Where, 500.f);
Wait->OnReady.AddDynamic(this, &ADigger::HandleEditable);
Wait->OnFailed.AddDynamic(this, &ADigger::HandleNotEditable);
Wait->Activate();
Blueprint BP_DiggerWait · Event Graph

The graph as text
  1. DigWhenReady (event; Custom Event)
    • Out Output Delegate (delegate): not connected
    • Then: runs Wait For Terrain Editable
    • Out Where (Vector): to Wait For Terrain Editable › Centre
  2. Wait For Terrain Editable (latent function; C++ lines 1–4)
    • In Centre (Vector): from DigWhenReady › Where
    • In Radius (Float): 500.0
    • In Timeout (Float): 30.0
    • Then: not connected
    • On Ready: runs Handle Editable
    • On Failed: runs Handle Not Editable
    • Out Reason (EOrreryTerrainAsyncFailure): to Handle Not Editable › Reason
  3. Handle Editable (function call; Target is BP Digger Wait; C++ line 2)
    • In Target (self object): self
  4. Handle Not Editable (function call; Target is BP Digger Wait; C++ line 3)
    • In Target (self object): self
    • In Reason (EOrreryTerrainAsyncFailure): from Wait For Terrain Editable › Reason

BP_DiggerWait in the samples project (/Game/DocExamples). To paste, the Blueprint needs a custom event HandleEditable and a custom event HandleNotEditable with Reason (enum:/Script/OrreryTerrain.EOrreryTerrainAsyncFailure).

Or bind the subsystem’s OnGroundChanged and call GetEditAccessAt there.

A step that answered Pending was not wasted: the terrain requested the ground under it, and the same step applies once the ground has arrived. A step that answered Refused never applies, however long you wait (see Sculpt limits).

The async nodes Wait For Terrain Ground and Sample Ground Exact (Async) wait for the ground for reading it. See the FAQ.

  1. Play, press the left mouse button on a hill and drag. The ground rises under the ring.
  2. Press Z. The stroke goes away.
  3. Press B until the HUD shows Flatten, then drag. The ground levels at the height under the cursor when you pressed.
  4. Call Get State Hash before and after a stroke. The number changes. After undo it returns to the earlier value.

In multiplayer, the brush tool and the sculpt component send a client’s strokes, undo and redo to the server by themselves. See Multiplayer.

The Terrain (Orrery) editor mode shapes the ground in the editor viewport, with no Play session. It has four palettes. Every tool, field and key is in The editor mode; this part is the short version.

Palette Tools
Manage New (see Quick start and Import) and the Bake action
Sculpt Sculpt (raises; Shift lowers), Smooth, Flatten (to the stroke’s start, to each dab, or to a fixed height; Ctrl+click picks the height), Stamp (places the picked height mask once per click: a mountain, a crater, a mesa)
Paint Paint (a material layer; painting another layer replaces it), Ground class (0 to 7: the geology), Holes (cut holes for caves and tunnel mouths; Shift fills them)
Features Plate, Corridor and Features (the list of the plates and corridors placed here: select one to edit or remove it)

In the sculpt and paint tools, [ and ] change the radius, Ctrl+[ ] the strength (the opacity for Paint) and Ctrl+Shift+[ ] the falloff.

Things to know:

  • Holes are on by default. Untick Allow Holes in Project Settings > Orrery Terrain Editor > Author limits to forbid them in a project; the Holes tool then refuses with that path in its message.
  • Editor edits are kept in an authoring layer beside the map, undoable as editor transactions. Bake composes the layer (sculpt, paint, ground class and features) into a new tile store beside the map, points the terrain at it and clears the undo history, so the map cooks. The old source and the layer are kept. Bake is available only while there is something unbaked.
  • The terrain actor’s details panel has a Bake button and a command-line bake (UOrreryTerrainBakeCommandlet) for build machines.
  • Foliage, tagged instanced meshes and ground-follow actors follow editor edits inside the same undo step.
  • Editor tools can also be driven from Python through UOrreryTerrainEditorLibrary. A scripted stroke that leaves Falloff Start at -1 (its default) uses the tool’s own default, as the tool’s details panel shows it.
  • Plate, Corridor and a feature removal over ground that is still loading are held until the tiles arrive, not refused.
Plugin 1.0 · Unreal Engine 5.8Found a mistake or something unclear? E-mail us