Version: 2.0.0-alpha.8

Example 1

In this tutorial, we will use this tileset by @pixel_poem. Be sure to check their work out if you like the tileset. We will not care about room decorations - we will use just basic walls, floor and door tiles.

result
Simple example
result
Real-life example

Note: All files from this example can be found at Edgar/Examples/Example1.

List of used features
Below is a list of features that are used in this example.
Custom post-processingA custom post-processing task is used to spawn enemies after a level is generated

Simple example#

The goal is to create two basic rectangular room templates of different sizes and a room template for both horizontal and vertical corridors. We will use the smaller room template for our dead-end rooms and the bigger room template for other rooms.

Basic rooms templates#

There should be nothing hard about the design of the two rectangular room templates. We use the simple doors mode configured to door length 1 and corner distance 2. We need corner distance 2 in order to easily connect corridors.

result
Bigger room
result
Smaller room

Corridors#

Corridors are very simple with this tileset. We use the specific positions doors mode to choose the two possible door positions. And because corridors are by default placed after non-corridor rooms, these room templates just work without the need of any scripting.

result
Horizontal corridor
result
Vertical corridor

We just need to make sure that we do not allow door positions of non-corridor rooms that are closer than 2 tiles from corners. Below you can see what would happen otherwise. It is possible to allow that but we would have to implement some post-processing logic that would fix such cases.

Incorrect corridor connection

Level graph#

With only two room templates for non-corridor rooms, we must think about which level graphs are possible to lay out and which are not. For example, using only the bigger room template, the algorithm is not able to lay out cycles of lengths 3 and 5 because there simply is not any way to form these cycles with such room templates. But cycles of length 4 are possible so that is what we do here.

Level graph

Results#

result
Example result
result
Example result

Real-life example#

To create something that is closer to a real-life example, we will:

  • add spawn room template that includes a player
  • add boss room that contains a ladder to the next level
  • add doors to corridors
  • add two more corridor room templates
  • add enemies
  • add more rooms to the level graph

Spawn room#

Our spawn room will look different than our basic rooms. We will also want the generator to spawn our player prefab inside the room. This can be simply achieved by placing our prefab inside the room template, next to the GameObject that holds our tilemaps.

Spawn room with player prefab

Note: A basic script for player movement is included in the example scene.

Boss room#

Our boss room will also have a special look. We also created a simple Exit prefab that looks like a ladder a generates a new level when interacted with. And similarly to placing our player prefab, we can also let the generator spawn a mighty ogre that will guard the exit.

Boss room template with exit prefab

Note: There is no enemy AI so the ogre is really not the mighty.

Additional room template#

Even for ordinary rooms, we can have non-rectangular room templates.

result
Additional room tempalte

Doors#

We can easily add doors to our corridors. We created a simple door prefab that has a collider and also a trigger that lets the player open the door.

Corridor with doors

Longer corridors#

result
Longer horizontal corridor
result
Longer vertical corridor

Enemies#

We can easily add enemies to our levels. In this tutorial, we will add enemies directly to room templates and then implement a post-processing task that spawns each enemy with a configurable chance.

We will start by creating a GameObject called "Enemies" in all the room templates that will contain enemies a make all the enemies children of this GameObject.

Enemies added to the room template

Note: We must make sure to always name the root GameObject "Enemies" as we will use that name to work with the enemies.

If we now generate the dungeon, we will see that it contains all the enemies that we added to individual room templates.

Dungeon with monsters

If we are happy with the results, we can stop here. However, to showcase how we can add some post-processing logic to the generator, we will try to spawn each monster with some predefined probability so that different monsters spawn every time. The result can be found below.

We have to create a class that inherits from DungeonGeneratorPostProcessBase and because the base class is a ScriptableObject, we need to add the CreateAssetMenu attribute so we are able to create an instance of that ScriptableObject. After a level is generated, the Run method is called and that is the place where we call our post-process logic.

[CreateAssetMenu(menuName = "Dungeon generator/Examples/Example 1/Post process", fileName = "Example1PostProcess")]
public class Example1PostProcess : DungeonGeneratorPostProcessBase
{
[Range(0, 1)]
public float EnemySpawnChance = 0.5f;
protected override void Run(GeneratedLevel level, LevelDescription levelDescription)
{
HandleEnemies(level);
}
private void HandleEnemies(GeneratedLevel level)
{
// Iterate through all the rooms
foreach (var roomInstance in level.GetRoomInstances())
{
// Get the transform that holds all the enemies
var enemiesHolder = roomInstance.RoomTemplateInstance.transform.Find("Enemies");
// Skip this room if there are no enemies
if (enemiesHolder == null)
{
continue;
}
// Iterate through all enemies (children of the enemiesHolder)
foreach (Transform enemyTransform in enemiesHolder)
{
var enemy = enemyTransform.gameObject;
// Roll a dice and check whether to spawn this enemy or not
// Use the provided Random instance so that the whole generator uses the same seed and the results can be reproduced
if (Random.NextDouble() < EnemySpawnChance)
{
enemy.SetActive(true);
}
else
{
enemy.SetActive(false);
}
}
}
}
}

With the implementation ready, we now have to create an instance of that ScriptableObject by right-clicking in the project view and Create -> Edgar -> Examples -> Example 1-> Post process. And the last step is to drag and drop this GameObject in the Custom post process tasks section of the dungeon generator.

Add the ScriptableObject to the Custom post process tasks array

Level graph#

So the goal is to have more rooms than in the simple example and also a spawn room and a boss room. You can see one such level graph below.

Level graph

You can also see that with corridors of different lengths and more room templates to choose from, we can now have cycles of various sizes. The boss and spawn rooms have assigned only a single room template.

Results#

result
Example result
result
Example result
result
Example result with enemies
result
Example result with enemies