Skip to main content

Nested generators

With Frigga, it is possible to call a generator from inside another generator. That makes it possible to create complex levels where different areas are generated by different generators. For example, in the image below, we can see a level where multiple islands are generated by different generators and placed inside a single level.

Multiple islands generated inside a single level
Multiple islands generated inside a single level

Overview

The Nested generators feature provides utilities for calling a generator from inside another generator and then merging the nested levels with the root level. The same utilities can also be used for placing a handmade level (via Level editor) into another level, similar to how structures are placed in a level, but completely controlled by the calling script.

Prerequisites

Generator as a prefab

If we want to call one generator from another, we need to make sure that we have the nested generator available as a prefab. To create such a generator, create an empty prefab and add the generator component to the root of the prefab. After you generate a level, you should see that the Generated level object is created under the root/generator object.

Generator as a prefab
Generator as a prefab

Complete example

Here's a complete workflow showing how to use nested generators. This example demonstrates the typical pattern you'll follow when implementing nested generation:

// Create the nested generator runner
var nestedGeneratorRunner = new NestedGeneratorRunner(result);

// Create a copy of the nested generator prefab
var islandGenerator = nestedGeneratorRunner.InstantiateGenerator(generatorPrefab);

// Generate the nested level
var islandResult = nestedGeneratorRunner.Generate(islandGenerator);

// Place the nested level in the root level
nestedGeneratorRunner.InstantiateNestedLevel(islandResult, position);

// Clean up - disable the generator component
islandGenerator.enabled = false;
Important Requirements
  • Always use a new copy: Whenever a nested level is generated, we must use a new copy of the nested generator prefab via InstantiateGenerator(). We must not use the original generator prefab, or use the same generator object for creating 2 different nested levels.
  • Avoid overlapping: The caller needs to make sure that nested levels do not overlap each other. Each instantiated nested level will override all tiles that were previously placed in that area of the root level.

Method reference

The Nested generators feature is expected to be used from inside a post-processor or from a custom generator. For both approaches, it is expected that we have access to the GeneratorResult object which contains the current level that is being generated.

NestedGeneratorRunner

The first step is to create an instance of the NestedGeneratorRunner class which provides all methods for working with nested generators:

var nestedGeneratorRunner = new NestedGeneratorRunner(result);

InstantiateGenerator()

Create a copy of the nested generator prefab:

var islandGenerator = nestedGeneratorRunner.InstantiateGenerator(generatorPrefab);

Generate()

Generate a nested level. The method has an overload that takes a seed to be used by the nested generator. Otherwise, a random seed is generated:

var islandResult = nestedGeneratorRunner.Generate(islandGenerator);
// Or with a specific seed:
var islandResult = nestedGeneratorRunner.Generate(islandGenerator, seed);

InstantiateNestedLevel()

After generating a level, it is not yet merged with the root level. We need to call the InstantiateNestedLevel() method and provide the position where the level should be placed:

nestedGeneratorRunner.InstantiateNestedLevel(islandResult, position);

Cleanup

As a final step, we can disable the nested generator component so that it doesn't appear in the generator overlay inside the editor:

islandGenerator.enabled = false;

Handmade levels

A very similar technique can be used for placing handmade (via Level editor) levels. This is particularly useful when you want to include pre-designed areas in your generated levels.

Workflow for handmade levels

The process is nearly identical to using generators, but instead of generating new content, you're placing existing handmade content:

// Place the hub island first
// It's important to call InstantiateLevelEditor to get a copy of the hub level editor prefab!
var hubLevelEditor = nestedGeneratorRunner.InstantiateLevelEditor(Hub);
var hubResult = hubLevelEditor.GeneratorResult;
var hubBounds = hubResult.Bounds;

// Get random bounds to place the hub
var hubPlacement = GetRandomBoundsInside(result.Bounds, new Vector2Int(hubBounds.size.x, hubBounds.size.y), random);
// The next line places the hub level inside the main level
nestedGeneratorRunner.InstantiateNestedLevel(hubResult, hubPlacement.position);
// Keep track of the hub bounds
boundsList.Add(hubPlacement);
// Disable the hub level editor to prevent it from showing up in the level editor overlay
hubLevelEditor.enabled = false;

Key differences

  • Use InstantiateLevelEditor() instead of InstantiateGenerator()
  • The level content is already created (handmade), so no generation step is needed
  • Access the existing level via hubLevelEditor.GeneratorResult

Multi-island example

There is a Multi-island example scene inside the Examples/MultiIsland folder and the multi-island logic is implemented inside the FriggaMultiIslandPostProcessor post-processor. Please take a look at the post-processor if you want to see the nested generators in action.

The goal of the example is:

  • generate an ocean filled with randomly placed islands
  • use 2 different island generators - northern islands are more grassy while southern islands are more sandy
  • use 2 different sizes of islands - place large islands first and then fill the rest with small islands
  • always place a hub island which is a premade island and always looks the same

Please go and see the source code - it's heavily commented.