Skip to content

Advanced Features: Generation Pipeline

For most users, DunGen's default generation pipeline will be more than sufficient for generating dungeons. However, if you need more control over the generation process, DunGen provides a way to customize the pipeline through scripting.


Overview

DunGen's generation pipeline is made up of a series of steps that are executed in order to create the final dungeon layout. The core pipeline steps are in a fixed order, and you can derive custom types from the base pipeline steps to extend or override their functionality.

Extension steps provide a way to insert custom behaviour before or after an existing step, allowing you to augment the generation process with your own logic without having to override one of the core steps.

Additionally, a generation pipeline contains a list of services that are made available to all pipeline steps. These services provide shared functionality that can be used throughout the generation process to perform tasks such as finding potential doorway pairs and handling collisions.


Creating a Custom Generation Pipeline

A custom pipeline can be created in the project tab (Create > DunGen > Generation Pipeline). This will create a new asset that you can configure in the inspector.

There are two ways to apply your custom pipeline:

  1. Assign it to a DungeonFlow asset by setting the Custom Pipeline field in the inspector.
  2. Assign it directly to the dungeon generation settings (such as on the RuntimeDungeon component and the Generate Dungeon menu window) by setting the Pipeline Override field in the advanced section of the inspector.

Once assigned, DunGen will use your custom pipeline instead of the default one when generating dungeons.


Anatomy of a Generation Pipeline

Services

Services are shared components that provide functionality to the various pipeline steps. They are passed in directly from the pipeline asset at the start of the generation process and remain available until the process is complete.

Creating a custom service involves making a new C# class that implements a specific interface (see table below). Once created, you will be able to select your custom service from the dropdown in the pipeline asset inspector.

Generation Pipeline Services

Service Name Description Interface
Tile Placer Places TileProxy instances in the DungeonProxy. ITilePlacer
Tile Proxy Pool Manages a pool of TileProxy instances for use during generation. ITileProxyPool
Doorway Pair Finder Finds potential pairs of doorways between tiles for connections. IDoorwayPairFinder
Collision Service Handles collision detection between tiles during placement. IDungeonCollisionService
Tile Template Provider Given a reference to a tile prefab, returns a TileProxy template. ITileTemplateProvider
Yield Policy Manages yielding during generation to avoid blocking the main thread. IYieldPolicy
Dungeon Builder Constructs the final dungeon from the DungeonProxy, turning proxy instances into actual game objects. IDungeonBuilder

Pipeline Steps

Pipeline steps are the individual stages of the generation process. Each step performs a specific task, such as placing tiles along the main path, or instantiating the final dungeon game objects.

Individual steps can be customised by deriving from the base step classes (see table below). Your custom type will then appear in the dropdown menu when selecting a step in the pipeline asset inspector. Any serialized fields on your custom step will be exposed in the inspector for configuration.

Generation Steps

Core pipeline steps. Each step can have optional properties, which are exposed in the inspector.

Step Name Description Base Class
Tile Injection Stores information about injected tiles into the generation context before generation begins. TileInjectionStep
Pre-Processing Performs any necessary preprocessing on the generation context before tile placement begins. PreProcessingStep
Main Path Builds the main path of the dungeon according to the flow definition. MainPathStep
Branching Adds branching paths to the dungeon off the main path. BranchingStep
Branch Pruning Trims invalid tiles from the ends of branches to meet requirements. BranchPruningStep
Validate Required Tiles Ensures that all required tiles (e.g. injected tiles) are present in the dungeon. ValidateRequiredTilesStep
Finalise Layout Finalises the dungeon layout, making any last adjustments before building. FinaliseLayoutStep
Instantiate Tiles Instantiates the actual tile game objects in the scene based on the finalised layout. InstantiateTilesStep
Process Props Processes and places props within the instantiated tiles. ProcessPropsStep
Lock & Key Placement Places locks and keys within the dungeon according to the flow definition. LockAndKeyPlacementStep
Post-Processing Performs any necessary post-processing on the dungeon after instantiation. PostProcessingStep

Extension Steps

Extension steps allow you to insert custom logic before or after an existing pipeline step. This is useful for augmenting the generation process without having to override any of the steps.

Extension Steps

Extension pipeline step with custom properties.

To create an extension step, derive a new class from CustomGenerationStep. Your custom type will then appear in the dropdown menu when adding an extension step in the pipeline asset inspector. The checkbox can be used to disable the step without removing it.

  • Anchor: Specified how this extension step is anchored to an existing core step (e.g. Before All, After Main Path).
  • Order: Specifies the order in which this extension step is executed relative to other extension steps anchored to the same core step. Lower values are executed first.
  • Step: The actual custom step to execute. Any serialized fields on your custom step will be exposed in the inspector for configuration.

Extension step names will appear in cyan in the pipeline step list to indicate at what point in the pipeline they are executed.

Extension Step Order

Custom extension step running After Main Path.

Example: Branching paths off the start tile

Usually, branches can't come off the start tile or other nodes in the graph. With this custom pipeline step, we can add branches off the start tile by running a custom branching step immediately after the main path step.

StartTileBranchesStep.cs
using DunGen;
using DunGen.Generation;
using DunGen.Generation.Steps;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using static DunGen.Generation.PathBuilder;

[Serializable]
[SubclassDisplay(displayName: "Start Tile Branches")]
public class StartTileBranchesStep : CustomGenerationStep
{
    public override string DisplayName => "Start Tile Branches";

    public List<TileSet> TileSets = new List<TileSet>();
    public IntRange BranchCount = new IntRange(0, 1);
    public IntRange BranchDepth = new IntRange(1, 3);
    public int MaxAttemptsPerSlot = 10;
    public int MaxBacktrackSlots = 5;
    public int MaxTotalBacktracks = 10;


    public override IEnumerator Execute(GenerationContext context)
    {
        if(BranchDepth.Min < 1)
        {
            Debug.LogError($"Invalid {nameof(BranchDepth)} value. Minimum must be at least 1.");
            yield break;
        }

        var settings = context.Request.Settings;
        var startTile = context.ProxyDungeon.MainPathTiles[0];
        int branchId = -1; // Branching step uses 0 and up, so use negative numbers here to ensure uniqueness

        // We don't have enough doorways to create the minimum number of branches, so fail immediately
        if (startTile.UnusedDoorways.Count() < BranchCount.Min)
        {
            context.TilePlacementResults.Add(new NoMatchingDoorwayPlacementResult(startTile));
            yield break;
        }

        // Pick the number of branches to create
        int branchCount = BranchCount.GetRandom(context.RandomStream);

        for (int i = 0; i < branchCount; i++)
        {
            // Decide how long this branch should be
            int branchDepth = BranchDepth.GetRandom(context.RandomStream);
            TileProxy previousTile = startTile;

            // Configure the path builder
            var pathBuilderOptions = new PathBuilder.OptionsBuilder()
                .OnFailure(FailureBehaviour.KeepPartialPath)
                .MaxAttemptsPerSlot(MaxAttemptsPerSlot)
                .MaxBacktrackSlots(MaxBacktrackSlots)
                .MaxTotalBacktracks(MaxTotalBacktracks)
                .AttachTo(startTile)
                .Build();

            var builder = new PathBuilder(pathBuilderOptions);

            // Calculate placement parameters and propose a slot for each tile in this branch
            for (int j = 0; j < branchDepth; j++)
            {
                float normalizedDepth = (branchDepth <= 1) ? 1 : j / (float)(branchDepth - 1);
                var candidateTiles = TileSets.SelectMany(t => t.Tiles.Entries);

                int localBranchDepth = j;
                float localNormalizedDepth = normalizedDepth;

                builder.ProposeSlot(new PathBuilder.SlotSpec(
                    candidateEntries: candidateTiles,
                    isOnMainPath: false,
                    normalizedDepth: normalizedDepth,
                    placementParameters: previousTile.Placement.PlacementParameters,
                    onTilePlaced: (newTile) =>
                    {
                        newTile.Placement.BranchDepth = localBranchDepth;
                        newTile.Placement.NormalizedBranchDepth = localNormalizedDepth;
                        newTile.Placement.BranchId = branchId;
                        newTile.Placement.PlacementParameters = previousTile.Placement.PlacementParameters;
                        previousTile = newTile;
                    }));
            }

            yield return builder.Build(context);
            branchId--;
        }
    }
}

Custom generation pipelines are a powerful way to tailor DunGen's dungeon generation process to your specific needs. By creating custom steps and services, you can implement unique generation logic while still leveraging the core functionality provided by DunGen.