Skip to main content

Post-processing - Noise Generator

Post-processing is a powerful system that allows you to extend and customize the noise generator's level generation process. By creating custom post-processing scripts, you can alter how a level is generated. You can add extra steps to the generator or modify how built-in steps work.

tip

Post-processing is only available for use with the Noise generator for now. If you would like to use it with a different generator as well, please let me know!

Overview

The post-processing system works by allowing you to register callbacks that execute at specific points during the generation pipeline. Each callback runs at a defined execution order, ensuring that operations happen in the correct sequence.

Post-processing scripts inherit from the NoiseGeneratorPostProcessor class and are automatically discovered and executed by the NoiseGenerator when attached to the generator object or as a child of the generator.

tip

The NoiseGeneratorPostProcessor base class inherits from MonoBehaviour, so it is expected that each post-processor is attached to a game object in a scene.

Execution Order

Post-processing operations follow a specific execution order. Lower order values execute earlier in the pipeline:

OrderOperationDescription
100Noise MappingConverting noise values to tiles and biomes
200BorderAdding borders around the level
300ConnectivityHandling unreachable areas
400StructuresPlacing structures in the level
500Chunk SizesEnforcing minimum tile chunk sizes
1000Layout GeneratedWhen the layout has been generated (int grid should not be modified after this point)
1100Theme EngineWhen the theme engine is applied
2000Level GeneratedWhen the level has been fully generated
tip

You should almost never use hardcoded order values when registering custom callbacks. From inside a post-processor, you can use constants from the ExecutionOrder class like this: callbacks.RegisterAfter(ExecutionOrder.NoiseMapping, PlaceIslands);.

Creating Custom Post-Processors

To create a custom post-processor:

  1. Create a new script that inherits from NoiseGeneratorPostProcessor
  2. Implement the required SetupCallbacks method
  3. Optionally override SetupNoiseEvaluators for custom noise calculations
  4. Attach the script to your NoiseGenerator or to a child object

Basic Structure

public class MyCustomPostProcessor : NoiseGeneratorPostProcessor
{
public override void SetupCallbacks(GeneratorCallbacks callbacks)
{
// Register your callbacks here
callbacks.RegisterCallbackAfter(Priorities.Structures, ProcessLevel);
}

private object ProcessLevel(GeneratorResult result)
{
// Your custom processing logic here
// The result object should contain everything you need
return null;
}
}

Callback Registration

Use the Priorities constants to register callbacks at the appropriate execution point:

// Execute after noise mapping
callbacks.RegisterCallback(Priorities.NoiseMapping, MyCallback);

// Execute after a specific priority
callbacks.RegisterAfter(Priorities.NoiseMapping, MyCallback);

// Execute before a specific priority
callbacks.RegisterBefore(Priorities.Structures, MyCallback);

Performance Considerations

For performance-heavy operations, return an IEnumerator and use yield return null to keep the game responsive:

private IEnumerator HeavyProcessing(GeneratorResult result)
{
for (int i = 0; i < largeArray.Length; i++)
{
// Process item
ProcessItem(largeArray[i]);

// Yield every few iterations to maintain responsiveness
if (i % 100 == 0)
yield return null;
}
}

Examples

Checkerboard Post-processor

Below is an example of a Checkerboard post-processor that replaces every other tile with a specific tile.

Level generated with the Checkerboard post-processor
Level generated with the Checkerboard post-processor

using Frigga;

namespace PostProcessing
{
// Inherit from the NoiseGeneratorPostProcessor base class
// Then create a child object under the generator object and attach this script to it
public class CheckerboardPostProcessor : NoiseGeneratorPostProcessor
{
// Tile that we will set on the checkerboard pattern
// Use the [IntGridTilePicker] attribute to get a tile picker in the inspector
[IntGridTilePicker] public int Tile;

// Biome that we will set on the checkerboard pattern
// Use the [BiomePicker] attribute to get a biome picker in the inspector
[BiomePicker] public int Biome;

// The SetupCallbacks needs to be overridden in each post-processor
public override void SetupCallbacks(GeneratorCallbacks callbacks)
{
// Register a callback here to alter the logic of the noise generator
// Here we let the generator run the NoiseMapping step first, then we run our checkerboard logic
callbacks.RegisterAfter(ExecutionOrder.NoiseMapping, HandleCheckerboard);
}

// Each callback receives a reference to the level being generated
private void HandleCheckerboard(GeneratorResult result)
{
foreach (var position in result.Bounds.allPositionsWithin)
{
if (position.y % 2 == 0 && position.x % 2 == 0 ||
position.y % 2 != 0 && position.x % 2 != 0)
{
// Get current tile data from the int grid
var tileData = result.IntGrid.GetTileData(position);

// Set the tile and biome if they are not zero
if (Tile != 0)
{
tileData.Tile = Tile;
}

if (Biome != 0)
{
tileData.Biomes.SetBiomeSelected(Biome, true);
}

// Save the modified tile data back to the int grid
result.IntGrid.SetTileData(position, tileData);
}
}

// Not needed here but if we change the bounds of the level, we should update them in the result
// result.Bounds = result.IntGrid.ComputeBounds();
}
}
}

Island Region Post-Processing

Look for the IslandRegionPostProcessor.cs file to see an example post-processor that generates a specified sub-region of an island level. You can find more information on the dedicated Island regions page.

Multi-Island Post-Processing

Look for the FriggaMultiIslandPostProcessor.cs file to see an example post-processor that generates a level made of multiple islands. You can find more information on the dedicated Nested generators page.

Advanced Usage

Custom Noise Evaluators

You can override the SetupNoiseEvaluators method to alter how a specific noise is computed. This feature makes it possible to create your own noise algorithms or modify the existing behavior. The main idea is that inside the method you call evaluator.AddNoiseEvaluator to replace one of the existing noises. An example of this behavior can be found in IslandRegionPostProcessor.cs.

public override void SetupNoiseEvaluators(CompositeNoiseEvaluator evaluator)
{
evaluator.AddNoiseEvaluator(noiseId, (position) => {
// Custom noise calculation logic
return Mathf.PerlinNoise(position.x * 0.1f, position.y * 0.1f);
});
}