Skip to content

Level Editor Tools and Devkit System

Unturned ships two editor layers: the legacy Editor component hierarchy (EditorObjects, EditorRoads, EditorSpawns) and the newer Framework.Devkit system (TerrainEditor, FoliageEditor, DevkitTransactionManager, DevkitSelectionManager). The devkit provides an undo/redo transaction system, a selection manager with begin/end selection callbacks, and a consistent IDevkitTool interface for terrain manipulation, foliage painting, volume placement, and hierarchy management. The legacy editor handles object placement, road paving, and spawnpoint editing with its own undo stack.

Source code location: Unturned/Edit/*.cs, Framework/Devkit/

IDevkitTool Interface

csharp
public interface IDevkitTool
{
    void update();
    void equip();
    void dequip();
}

Every active tool in the devkit implements this interface. equip() is called when the tool becomes active (it subscribes to GLRenderer.render for preview rendering and may set global state like Landscape.DisableHoleColliders). dequip() unsubscribes and resets state. update() is called every frame while the tool is active and contains the core interaction logic including raycasting, brush parameter hotkeys, and terrain modification.

Terrain Editor

TerrainEditor implements IDevkitTool and is the most complex devkit tool. It supports three top-level modes (EDevkitLandscapeToolMode):

HEIGHTMAP Mode

Sub-modes accessible via Q/W/E/R keys:

ADJUST (Q): Raises or lowers terrain. Brush strength is multiplied by heightmapAdjustSensitivity. Holding Shift inverts the direction (lower instead of raise). The delta applied per vertex is:

csharp
float delta = Time.deltaTime * heightmapBrushStrength * alpha;
delta *= heightmapAdjustSensitivity;
if (InputEx.GetKey(KeyCode.LeftShift)) delta = -delta;
currentHeight += delta;

FLATTEN (W): Pulls terrain toward a target height. Supports MIN and MAX flatten methods in DevkitLandscapeToolHeightmapOptions.flattenMethod:

  • MIN — only lowers terrain to the target.
  • MAX — only raises terrain to the target.
  • Default — pulls toward target from both directions.
csharp
float normalizedTarget = (heightmapFlattenTarget + (Landscape.TILE_HEIGHT / 2)) / Landscape.TILE_HEIGHT;
switch (flattenMethod)
{
    case EDevkitLandscapeToolHeightmapFlattenMethod.MIN:
        normalizedTarget = Mathf.Min(normalizedTarget, currentHeight);
        break;
    case EDevkitLandscapeToolHeightmapFlattenMethod.MAX:
        normalizedTarget = Mathf.Max(normalizedTarget, currentHeight);
        break;
}
float delta = normalizedTarget - currentHeight;
float speed = Time.deltaTime * heightmapBrushStrength * alpha;
delta = Mathf.Clamp(delta, -speed, speed);
delta *= heightmapFlattenSensitivity;
currentHeight += delta;

Alt+click samples the target height from the world position.

SMOOTH (E): Averages height values within the brush. Two smooth methods via DevkitLandscapeToolHeightmapOptions.smoothMethod:

  • BRUSH_AVERAGE — samples all vertices within the brush radius and computes a single average, then lerps each vertex toward the average.
  • PIXEL_AVERAGE — samples the four cardinal neighbors of each pixel individually and averages them, preserving local detail better.
csharp
// BRUSH_AVERAGE: collect all heights
void HandleHeightmapReadBrushAverage(..., float currentHeight)
{
    float distance = (worldPosition - brushWorldPosition).magnitude / heightmapBrushRadius;
    if (distance > 1) return;
    heightmapSmoothSampleCount++;
    heightmapSmoothSampleAverage += currentHeight;
}
// Then write: currentHeight = Mathf.Lerp(currentHeight, heightmapSmoothTarget,
//     Time.deltaTime * heightmapBrushStrength * alpha);

RAMP (R): Click to set ramp start, click again to set ramp end. The brush interpolates height linearly between the two positions, creating a sloped transition. The ramp calculation:

csharp
Vector2 rampDirection = rampOffset / rampMagnitude;
Vector2 rampCross = rampDirection.Cross();
// For each vertex: check alignment with ramp direction, reject if behind start or past end
float worldRampDirectionDistance = worldMagnitude * worldRampDirectionAlignment / rampMagnitude;
// Interpolate between begin and end heights
currentHeight = Mathf.Lerp(currentHeight,
    Mathf.Lerp(beginHeight01, endHeight01, worldRampDirectionDistance), alpha);

Brush Parameter Hotkeys

Brush parameters (radius, falloff, strength) are stored in DevkitLandscapeToolHeightmapOptions and modified with hotkeys:

  • B — change brush radius (drag cursor to set distance from pivot).
  • F — change brush falloff (distance / radius, clamped 0-1).
  • V — change brush strength (distance / radius, normalized).
  • G — change weight target (splatmap mode, distance / radius, clamped 0-1).

Each parameter change is wrapped in a DevkitTransactionUtility generic transaction so it can be undone:

csharp
protected virtual void beginChangeHotkeyTransaction()
{
    DevkitTransactionUtility.beginGenericTransaction();
    DevkitTransactionUtility.recordObjectDelta(DevkitLandscapeToolHeightmapOptions.instance);
    DevkitTransactionUtility.recordObjectDelta(DevkitLandscapeToolSplatmapOptions.instance);
}

Brush parameters provide both public instance properties (which delegate to the options objects) and keyboard/mouse interaction for real-time adjustment with visual feedback.

SPLATMAP Mode

Sub-modes accessed via Q/W/E/R keys:

PAINT (Q): Paints the selected LandscapeMaterialAsset onto the terrain:

csharp
protected void handleSplatmapWritePaint(..., float[] currentWeights)
{
    int targetMaterialLayer = getSplatmapTargetMaterialLayerIndex(tile, splatmapMaterialTarget);
    if (targetMaterialLayer == -1) return; // No space for this material

    float target = 0.5f;
    if (useAutoFoundation || useAutoSlope)
    {
        // Auto-detection logic for structure foundations and terrain angles
        if (useAutoFoundation)
        {
            // SphereCast down to detect objects below
            // If object is not a snowshoe-type, auto-paint beneath it
        }
        if (!handled && useAutoSlope)
        {
            float angle = Vector3.Angle(Vector3.up, normal);
            // Material target varies based on angle
        }
    }
    blendSplatmapWeights(currentWeights, targetMaterialLayer, target, speed);
}

Supports:

  • useAutoFoundation — detects structure objects below the brush and applies material only on valid foundation areas.
  • useAutoSlope — material fades based on terrain angle (configured via autoMinAngleBegin, autoMinAngleEnd, autoMaxAngleBegin, autoMaxAngleEnd on the material asset).
  • Weight target — Ctrl+drag sets a target weight (0.0-1.0) for the painted material.

AUTO (W): Automatic painting based on the LandscapeMaterialAsset's own useAutoFoundation and useAutoSlope settings. The material asset defines its own angle ranges and foundation detection parameters.

SMOOTH (E): Blends material weights across adjacent pixels to soften transitions:

csharp
protected void handleSplatmapWriteSmooth(..., float[] currentWeights)
{
    // Average neighboring pixel material weights
    // Apply lerp toward the averaged weights
    for (int layer = 0; layer < Landscape.SPLATMAP_LAYERS; layer++)
    {
        float delta = averagedWeight - currentWeights[layer];
        delta *= speed;
        currentWeights[layer] += delta;
    }
}

CUT (R): Creates holes in the terrain. Shift+click removes holes (restores terrain). Uses Landscape.writeHoles() with a boolean callback:

csharp
protected bool handleSplatmapWriteCut(Vector3 worldPosition, bool currentlyVisible)
{
    float distance = (worldPosition - brushWorldPosition).magnitude / splatmapBrushRadius;
    if (distance > 1) return currentlyVisible;
    return InputEx.GetKey(KeyCode.LeftShift); // Shift = restore terrain
}

TILE Mode

Adds or removes landscape tiles. Clicking on an empty tile slot creates a new LandscapeTile at that LandscapeCoord. The tile creation process:

csharp
if (tile == null && tilePlaneDistance < 4096)
{
    LandscapeTile newTile = Landscape.addTile(pointerTileCoord);
    newTile.readHeightmaps();
    newTile.readSplatmaps();
    newTile.updatePrototypes();
    Landscape.linkNeighbors();
    Landscape.reconcileNeighbors(newTile);
    Landscape.applyLOD();
    LevelHierarchy.MarkDirty();
    selectedTile = newTile;
}

Selecting an existing tile and pressing Delete removes it. Tiles are 1024x1024 unit landscape subdivisions.

Preview Rendering

All heightmap and splatmap operations render a preview circle using GLRenderer hooks. The brush preview shows the falloff gradient:

csharp
protected float getBrushAlpha(float normalizedDistance)
{
    if (normalizedDistance <= brushFalloff || brushFalloff >= 1.0f)
        return 1.0f;
    else
        return (1.0f - normalizedDistance) / (1.0f - brushFalloff);
}

This maps falloff as a linear ramp past the falloff threshold, clamped to [0, 1]. The handleGLRender() method draws the brush circle and tile grid using GL.LINES with GLUtility.LINE_FLAT_COLOR.

Foliage Editor

FoliageEditor provides brush-based foliage placement and removal. It operates on the FoliageSystem and supports:

  • Painting individual foliage assets within a brush radius.
  • Erasing existing foliage with a secondary brush mode.
  • Density control for how many foliage instances are placed per brush stroke.
  • Alignment to terrain surface normal.

Parameters are configured via DevkitFoliageToolOptions.

Devkit Transaction System

The devkit undo/redo system is DevkitTransactionManager in Framework/Devkit/Transactions/. It maintains two collections: a LinkedList<DevkitTransactionGroup> for undoable groups (capped at historyLength, default 25), and a Stack<DevkitTransactionGroup> for redoable groups.

IDevkitTransaction Interface

csharp
public interface IDevkitTransaction
{
    bool delta { get; }
    void undo();
    void redo();
    void begin();
    void end();
    void forget();
}

delta signals whether the transaction actually changed anything. If no transaction in a group recorded a delta, the entire group is discarded. forget() is called when the group is evicted from the history buffer.

Transaction Types

  • DevkitGameObjectDestructionTransaction — records the full serialized state of a GameObject before destruction. On undo, reinstantiates the object from the serialized state. On redo, destroys it again.
  • DevkitGameObjectInstantiationTransaction — records instantiation of a new GameObject. On undo, destroys the object. On redo, reinstantiates it.
  • DevkitObjectDeltaTransaction — captures the serialized state of a Unity Object using EditorJsonUtility.ToJson() before and after a change. On undo/redo, applies the full saved state via EditorJsonUtility.FromJsonOverwrite().
  • DevkitTransformChangeParentTransaction — records a transform's parent before reparenting and restores it on undo.
  • TransactionFieldDelta / TransactionPropertyDelta — record individual field or property changes using reflection. These are lightweight transactions for atomic property changes.

DevkitTransactionGroup

csharp
public class DevkitTransactionGroup
{
    public string name { get; protected set; }
    public List<IDevkitTransaction> transactions { get; protected set; }

    public void record(IDevkitTransaction transaction)
    {
        transaction.begin();
        transactions.Add(transaction);
    }

    public bool delta
    {
        get
        {
            for (int index = transactions.Count - 1; index >= 0; index--)
            {
                if (!transactions[index].delta)
                    transactions.RemoveAt(index);
            }
            return transactions.Count > 0;
        }
    }

    public void undo() { for (int i = 0; i < transactions.Count; i++) transactions[i].undo(); }
    public void redo() { for (int i = 0; i < transactions.Count; i++) transactions[i].redo(); }
    public void end() { for (int i = 0; i < transactions.Count; i++) transactions[i].end(); }
    public void forget() { for (int i = 0; i < transactions.Count; i++) transactions[i].forget(); }
}

Transaction Flow

  1. DevkitTransactionManager.beginTransaction("Name") opens a new group. This clears the redo stack and increments a nesting counter.
  2. Individual operations call recordTransaction(transaction) to add actions to the current group.
  3. endTransaction() decrements the nesting counter. At depth zero, it calls group.end(), checks group.delta, and either pushes the group to the undo list or discards it.
  4. undo() pops the last group from undoable, calls group.undo(), and pushes to redoable.
  5. redo() pops from redoable, calls group.redo(), and pushes to undoable.

The manager maintains transactionDepth for nested transactions — e.g., a compound operation that opens sub-transactions for each sub-action. Only the outermost endTransaction() commits the group.

DevkitTransactionUtility

A static helper class:

  • beginGenericTransaction() — wraps a transaction for generic object property changes.
  • endGenericTransaction() — commits the generic transaction.
  • recordObjectDelta(object) — captures the current state of an object for delta-based undo.

This is used by the brush parameter hotkey system to make radius/falloff/strength changes undoable.

DevkitSelectionManager

DevkitSelectionManager manages a HashSet<DevkitSelection> of currently selected devkit objects:

csharp
public static void select(DevkitSelection select)
{
    if (InputEx.GetKey(KeyCode.LeftShift) || InputEx.GetKey(KeyCode.LeftControl))
    {
        if (selection.Contains(select)) remove(select);
        else add(select);
    }
    else
    {
        clear();
        add(select);
    }
}
  • Click selects (clears previous selection unless Shift/Ctrl is held for additive toggle).
  • beginSelection() calls beginSelection() on all IDevkitInteractableBeginSelectionHandler components of the selected GameObject.
  • endSelection() calls endSelection() on all IDevkitInteractableEndSelectionHandler components.
  • mostRecentGameObject tracks the last selected object for context-sensitive UI.

DevkitSelection wraps a GameObject and its Collider, providing the link between the raycast hit and the selection handler.

LevelHierarchy

LevelHierarchy maintains the editor's scene hierarchy. Key operations:

  • MarkDirty() — signals that the hierarchy has changed and needs a UI refresh in the hierarchy panel.
  • Called when tiles are added/removed, objects are placed/deleted, or any structural change affecting the editor outline view.

Legacy Editor — Editor Objects

EditorObjects handles object placement, selection, and transformation in the legacy editor. It manages a List<EditorSelection> and a TransformHandles instance for 3D manipulation.

Selection

Objects are selected by clicking (raycast against object colliders) or by drag-selection (viewport rectangle). The drag system scans objects in the nine-region area around the camera (current region plus eight neighbors), projects their positions to viewport space, and tests containment against the drag rectangle:

csharp
for (int x = region_x - 1; x <= region_x + 1; x++)
{
    for (int y = region_y - 1; y <= region_y + 1; y++)
    {
        foreach (LevelObject levelObject in LevelObjects.objects[x, y])
        {
            Vector3 screen = MainCamera.instance.WorldToViewportPoint(levelObject.transform.position);
            if (screen.z < 0) continue;
            dragable.Add(new EditorDrag(levelObject.transform, screen));
        }
    }
}

Transform Handles

TransformHandles provides position, rotation, and scale gizmos. Modes are cycled via tool keys:

  • Tool 0 — translate (press again for bounds editor).
  • Tool 1 — rotate.
  • Tool 3 — scale (press again for scale bounds editor).

Handle modes:

  • Translate: Applies worldPositionDelta and worldRotationDelta to multi-object selections. Objects offset from pivot maintain relative position.
  • Rotate: Quaternion multiplication for relative rotation.
  • Scale: Matrix-based scale with pivot center using pivotToWorld * relativeToPivot.

Coordinate system toggles between EDragCoordinate.GLOBAL (world-aligned handles) and EDragCoordinate.LOCAL (object-aligned handles). Scale mode forces local coordinates.

Copy/Paste/Duplicate

csharp
// Ctrl+B: Copy handle pivot position
copyPosition = handles.GetPivotPosition();
copyRotation = handles.GetPivotRotation();

// Ctrl+N: Snap selection to stored pivot
selection[0].transform.position = copyPosition;
if (hasCopiedRotation) selection[0].transform.rotation = copyRotation;

// Ctrl+C: Duplicate selected objects as EditorCopy instances
copies.Add(new EditorCopy(selection[index].transform.position,
    selection[index].transform.rotation,
    selection[index].transform.localScale,
    objectAsset, itemAsset));

// Ctrl+V: Paste copies
Transform model = LevelObjects.registerAddObject(copies[index].position,
    copies[index].rotation, copies[index].scale,
    copies[index].objectAsset, copies[index].itemAsset);
addSelection(model);

Undo/Redo

The legacy object editor uses LevelObjects.step for undo tracking:

csharp
if (InputEx.GetKeyDown(KeyCode.Z) && InputEx.GetKey(KeyCode.LeftControl))
{
    clearSelection();
    LevelObjects.undo();
}
if (InputEx.GetKeyDown(KeyCode.X) && InputEx.GetKey(KeyCode.LeftControl))
{
    clearSelection();
    LevelObjects.redo();
}

Transformations are registered via LevelObjects.registerTransformObject() before and after handle drags, storing the old and new position/rotation/scale.

Snapping

csharp
// Loaded from {path}/Editor/Objects.dat
handles.snapPositionInterval = snapTransform;  // Default 1
handles.snapRotationIntervalDegrees = snapRotation;  // Default 15

Spawning

Clicking tool 2 (middle mouse / teleport key) in empty space spawns the currently selected ObjectAsset or ItemAsset at the world hit point, rotated -90 on X (vertical alignment). The new object is automatically added to the selection.

Legacy Editor — Editor Roads

EditorRoads manages road placement and editing. Road mode is toggled via isPaving. The road system operates on Road objects containing RoadPath[] and RoadJoint[].

Road Joint System

Each RoadJoint has:

  • vertex — Vector3 position.
  • getTangent(0) / getTangent(1) — tangent control points for spline interpolation.
  • The road path is defined by the sequence of vertices and their tangents, forming a Catmull-Rom spline.

Vertex Operations

csharp
// Add vertex at terrain click point
if (road != null)
{
    if (tangentIndex > -1)
    {
        // Adding vertex connecting to tangent
        select(road.addVertex(vertexIndex + tangentIndex, point));
    }
    else
    {
        // Determine insertion side by tangent dot product comparison
        float dot_0 = Vector3.Dot(point - joint.vertex, joint.getTangent(0));
        float dot_1 = Vector3.Dot(point - joint.vertex, joint.getTangent(1));
        if (dot_0 > dot_1)
            select(road.addVertex(vertexIndex, point));
        else
            select(road.addVertex(vertexIndex + 1, point));
    }
}
else
{
    select(LevelRoads.addRoad(point)); // Create new road
}

Tangent Editing

Middle-click moves the selected vertex or tangent to the world hit point. Tangent movement modifies the control point relative to the vertex:

csharp
road.moveTangent(vertexIndex, tangentIndex, point - joint.vertex);

Deletion

Delete/Backspace removes the selected vertex. Shift+Delete removes the entire road.

Rendering

The highlighter (a red sphere at Edit/Highlighter prefab) follows the mouse cursor on the terrain while in paving mode. Path and tangent handles are separate GameObjects named "Path" and "Tangent" for logicHit detection via EditorInteract.logicHit.

Legacy Editor — EditorSpawns

EditorSpawns manages spawnpoint placement for players, zombies, animals, vehicles, and items. Each spawn type has its own list in the Level* manager classes. The editor UI lets the user select spawn type, add/remove spawnpoints, and adjust properties.

Legacy Editor — Editor UI

EditorUI provides the editor's heads-up display:

  • hint(EEditorMessage, string) — displays context-sensitive tooltips (asset name and origin on hover).
  • window — the main editor window toggled via hotkey.
  • Save via Editor.save()EditorInteract.save(), EditorObjects.save(), EditorSpawns.save().

The Editor base class fires onEditorCreated when initialized, allowing devkit systems to hook into editor availability.

Devkit Type Factory

DevkitTypeFactory is a registration system for devkit types, handling instantiation of devkit hierarchy items and binding them to the editor UI. Types registered here include all volume types, node systems, and utility components.

DirtyManager and IDirtyable

DirtyManager and IDirtyable provide a change-tracking system for devkit objects. When an object's state is modified, MarkDirty() is called, and the manager batches dirty objects for saving. This ensures that editor changes are persisted without per-frame file writes.

Reun System (Legacy Undo Bridge)

The IReun interface and its implementations (ReunObjectAdd, ReunObjectRemove, ReunObjectTransform) provide the undo/redo system for the legacy LevelObjects editor. Each class records before/after state for object addition, removal, or transformation. The LevelObjects.step counter tracks the current undo position, and LevelObjects.undo() / LevelObjects.redo() walk through the Reun records.

  • ReunObjectAdd: Records the object asset and world transform before adding. Undo removes it; redo re-adds.
  • ReunObjectRemove: Records the object before removal. Undo reinstantiates; redo removes again.
  • ReunObjectTransform: Records old and new transform (position, rotation, scale). Undo/redo swaps between them.

The ScopedObjectUndo class wraps a group of Reun changes into a scoped operation: when disposed, it commits or rolls back the recorded changes.

Workzone Selection

WorkzoneSelection extends the selection system for terrain-level editing operations. It tracks transformable objects within a work zone and provides batch operations for moving, rotating, and scaling multiple objects together.

TransformHandler and TransformHandles

TransformHandler is the base class for the handle interaction system. TransformHandles provides the 3D gizmo rendering and interaction:

csharp
public class TransformHandles
{
    public enum EMode { Position, Rotation, Scale, PositionBounds, ScaleBounds }

    public void SetPreferredMode(EMode mode);
    public void SetPreferredPivot(Vector3 position, Quaternion rotation);
    public bool Raycast(Ray ray);
    public void Render(Ray ray);
    public void MouseDown(Ray ray);
    public void MouseMove(Ray ray);
    public void MouseUp();

    public Vector3 GetPivotPosition();
    public Quaternion GetPivotRotation();
    public void ExternallyTransformPivot(Vector3 position, Quaternion rotation, bool modifyRotation);

    public event Action<Matrix4x4> OnPreTransform;
    public event Action<Vector3, Quaternion, Vector3, bool> OnTranslatedAndRotated;
    public event Action<Matrix4x4> OnTransformed;
}

The handles support:

  • Position (translate): Arrow handles for each axis, plane handles for two-axis translation.
  • Rotation: Arc-ball rotation with axis rings.
  • Scale: Box handles at corners and edges, uniform scale center handle.
  • Bounds Editor: Special mode for editing collider extents directly in the scene view.

The coordinate system (global/local) affects handle orientation. In local mode, handles align to the first selected object's rotation. In global mode, handles align to world axes.

Editor Interact

EditorInteract provides the core raycasting infrastructure for the legacy editor:

  • ray — the current frame's camera ray.
  • objectHit — raycast result against editor-selectable objects.
  • worldHit — raycast result against terrain and world geometry.
  • logicHit — raycast result against editor logic objects (road handles, spawnpoint markers).
  • isFlying — whether the editor camera is in fly mode (disables object interaction).

SelectionTool

SelectionTool provides the UI for the selection tool mode in the devkit. It integrates with DevkitSelectionManager to provide click, marquee, and modifier-key selection behaviors.

Editor Area and Regions

The editor divides the level into EditorArea regions for efficient culling and object management. The active region tracks the camera's current position, and nearby regions are loaded for object drag-selection. Regions are LevelObject.regions[x, y] arrays indexed by REGION_SIZE (typically dividing the level into a grid).

Editor Camera and Movement

EditorLook and EditorMovement handle camera control in the editor. EditorLook manages mouse-look rotation (orbital camera). EditorMovement handles WASD movement, speed adjustment, and flying mode toggle. The EditorNavigation system provides "focus on object" functionality via the focus key.

Editor Copy System

EditorCopy stores a snapshot of an object's state for duplication:

csharp
public class EditorCopy
{
    public Vector3 position;
    public Quaternion rotation;
    public Vector3 scale;
    public ObjectAsset objectAsset;
    public ItemAsset itemAsset;
}

When pasting, the system calls LevelObjects.registerAddObject() which handles both ObjectAsset (static world objects) and ItemAsset (placeable items like barricades and structures). The copy buffer persists across selections, allowing multi-object copy from one area and paste into another.

Nodes Editor

NodesEditor manages game logic nodes (spawnpoints, safezones, deadzones, etc.) in the editor. Each node type has a visual representation (colored sphere or icon in the scene view) and editor handles for position adjustment. Node types include:

  • LocationNode — named location for navigation.
  • SafezoneNode — safe zone area.
  • DeadzoneNode — radiation zone.
  • AirdropNode — airdrop landing zone.
  • EffectNode — ambient effect area.
  • PurchaseNode — purchase zone for horde mode.
  • ArenaNode — arena-related node.

The editor provides UI to add, remove, and configure each node type, with properties specific to the node's purpose (radius, effect ID, purchase cost, etc.).

Volumes Editor

VolumesEditor provides the unified UI for all devkit volume types. Each volume is represented as a colored wireframe box or sphere in the scene view. The editor supports:

  • Add volume: Select volume type from dropdown, click to place.
  • Configure: Property panel shows volume-specific settings (trigger radius, effect type, teleporter target, etc.).
  • Remove: Delete key removes selected volume.
  • Resize: Handles on the wireframe allow drag-resizing.

Volume visibility can be toggled per-type, and volumes are saved in the level's volume registry.

Editor Import/Export

The editor supports importing and exporting objects between levels:

  • EditorAssetRedirector — manages asset ID remapping when objects reference assets that don't exist in the current level.
  • Copy/paste between editor instances is supported via clipboard serialization (Ctrl+C / Ctrl+V across editor sessions).

Reverb Gizmo

ReverbGizmoComponent provides a visual gizmo for audio reverb zones in the editor. It displays the zone bounds and connection lines for spatial audio debugging.

Editor Screen Capture

EditorScreenCaptureComponent captures high-resolution screenshots of the editor viewport for creating map previews. It renders the current camera view at a configurable resolution and saves to Level.png or Screenshots/ directory. This is used by map creators to generate the loading screen and preview images without external tools.

Best Practices for Editor Tool Development

When creating custom editor tools (via modding):

  1. Implement IDevkitTool: Use the devkit tool interface for terrain manipulation tools.
  2. Use transaction system: Wrap state-changing operations in DevkitTransactionManager.beginTransaction() / endTransaction().
  3. Register transactions: Call DevkitTransactionManager.recordTransaction() for each undoable action within the group.
  4. Use DevkitSelectionManager: Integrate with the existing selection system rather than creating a custom one.
  5. Check dirty state: Use DirtyManager to track unsaved changes.
  6. Preview via GLRenderer: Use GLRenderer.render events for in-scene preview rendering to maintain consistency with built-in tools.