-
Notifications
You must be signed in to change notification settings - Fork 5
Level Design
In this document we will describe how to create a level from scratch.
Before we get into the nitty gritty lets look at the easiest way to build a test environment for your new level.
- Open the scene
Assets/_Dev/Scenes Dev/Level Dev.unity - If you want a specific player configuration for your tests set this up in
Starting Recipes RunofRogueWave_Test_Game_Mode - When you have created the level (see below) drag its definition into the
Campaign.Levelsfield of theCampaign Managerobject - Hitting play will launch you straight into your defined level
Levels are created using a combination of placed tiles and Wave Function Collapse. Placed tiles can be defined to be in a specific location or randomly within a bounded area. These are placed first; this is then followed by the generation of the rest of the level using a Wave Function Collapse algorithm. For the purposes of this document how this algorithm works is not important, therefore this document will focus on how to control the generation of the level using this algorithm.
The following Scriptable Objects are important to the generation of a level (each is discussed in more detail below):
-
WFC Definitionis the top-level description of the level. A level consists of a matrix of tiles. Each tile has zero or more items within it. -
TileDefinitionis the description of a specific tile type. It defines what prefab to instantiate for a tile of this type. It also defines which tiles can appear next to one another. -
WaveDefinitiondefines a single wave of enemies that will attack the player in this level.
The following components are important for the definition and generation of a level:
-
RogueWaveGameModeidentifies theLevelDefinition's that will be used to generate levels. -
LevelGeneratorcomponent is responsible for generating the scene. It is supplied with aLevelDefinition by theRogueWaveGameMode`
Let's take a look at these items and how to use them to define a level.
The easiest kind of level to define is an entirely procedural one. So, let's start there. In the description below we are using tiles that have already been defined. We'll look at how to define new ones later.
- Create a
WFC DefinitionwithCreate -> Rogue Wave -> Level Definition(existing levels can be found inAssets/_Dev/Resources/Levels) - Set the lot size, which is the size of each cell or tile in the level. A good lot size is 25 x 25.
- Set the size of the level in tiles, we find that something in the region of 15 x 15 is getting to be too large for most play sessions.
- Set the
Empty Tile Definitionthat will be used whenever a the algorithm fails to place a tile in any given location, that is, it is a fallback tile. A good choice here is the Empty tile, but you can use another if you like. - We won't enclose the map so uncheck the "Enclose Level" checkbox.

We can now test this level. The easiest way to do this is to load the Assets/_Dev/Scenes Dev/Level Dev scene and ensure that your newly created level is set as the first level in _Dev/Resources/Levels/Test Campaign (this is a scriptable object that defines a campaign in Rogue Wave, its function is not important at this time). Once you have done this you can run the scene:
You will see an error in the log that says "No valid spawn points found in generated level." This is because we have not told the level generator where to spawn the player, we will fix that next.
You can regenerate the level in play mode in order to test the configuration by clicking the (Re)Generate button on the Level Generator object.
How did the Wave Function Collapse algorithm generate this level? It's pretty simple really, the Tile Definition Scriptable Objects found in the project define what tiles can be placed next to one another. The algorithm iterates over all the tiles in the level looking for a tile that has the lowest "entropy", that is the one with the fewest possible tile types that can be placed in that cell.
On the first iteration the only tiles that will have content will be the boundary tiles. All other tiles will be empty. This means that many tiles can have any tile type on them, but those next to the boundary tiles might have some limitations on them.
If there are multiple tiles with the lowest entropy value then one is chosen at random. If there is more than one valid tile type allowed on these tiles then a type is chosen at random and placed (actually it's not entirely random, see weight in the discussion of the TileDefinition below). Then the algorithm iterates again. Only this time there are more tiles on the map and thus more limitations on what can be placed where.
If no tile is valid for a cell then the Default Tile defined in the WFC Definition will be placed.
This process is repeated until all tiles have a tile type assigned. At which point the content of the tiles is generated. We'll not go into how the content is generated just yet, we'll come back to that later. First lets create a spawn point for our player.
The player needs to have a starting position. To do this we need to ensure that a spawn point is placed before the level is generated. We will therefore need to create our first tile.
- In Resources/Levels/Tiles/ right click and select Create -> Rogue Wave -> Tile Definition
- Set the tile prefab, this defines the prefab that will be spawned in this tiles location. This prefab can have a fixed set of objects for the tile, or it could procedurally generate content within it. For this tile we will select the player spawn point, which is
Assets/_Rogue Wave/Resources/Prefabs/Tiles/Player Spawn Point.prefab - The ground is not defined by the tile prefab, it is generated by the tile. This is so that it can adapt to the surrounding tiles. For a spawn point we want it to be flat, so check the "Is Flat" box.
- We will also need to assign a material for this tile, we'll use
Assets/_Rogue Wave/Materials/Tiles/Ground-Tile-Park.mat
Other settings can be left at their defaults for now.
Now that we have a tile definition, we need to tell the WFC Definition that it needs to place this tile. To do this simple drop the Tile Defintiion into the Pre Placed Tiles section of the WFC Definition. Run the level and your player should now spawn in at a random spot. But what if we want it to appear in a specific area?
We can use Constraints in the Tile Definition to limit where this tile will be placed. Expand out the Constraints section of the Tile Definition. This defines the conditions that must be true for this tile to be valid in a given cell. At the time of writing there is only one condition type, which is the boundaries of the legal area for this tile.
These boundaries are defined by a bottom left and upper right corner of a rectangle. The coordinates of the rectangle are defined as a % of the total number of cells. This means that it can be a little imprecise, but the alternative is to fix the values which would make the tiles only work on specific sizes of map.
In our case we want the tile to appear in the bottom left corner (cell 0, 0). If our map is 10 x 10 then this is easy to do. Set the values to (0, 0, 0) for the bottom left and (0, 1, 0) for the top right. If we wanted it in the top left (0,10 on a 10x10 map) is would be (0, 0, 0.9) and (0, 1, 1).
Note the Y coordinates represent height, which at the time of writing is not used, but we do plan to introduce it.
So far so good. We've managed to position a single tile where we want it. But the level is very boring, all the tiles are empty, except the player spawn. We can do something about that be defining some valid adjacent tiles.
Defining what tiles can be placed next to one another is done within the TileDefinition Scriptable Object. Within this object there is a section marked Constraints, which contains constraint definitions in the X (left to right, west to east) and Z (bottom to top, south to north) directions. In order to be valid these constraints must be satisfied in both directions. That is, if we want our tile to only be next to empty tiles, we can add the Empty tile definitions to each of these directions.
Let's ensure that the players spawn always has tall buildings next to it.
- Expand the Connections section of the Spawn Tile Definition
- Expand the X Positive Constraints section
- Add a new constraint
- Add the
Assets/_Rogue Wave/Resources/Levels/Tiles/Building Procedural Tall.assetscriptable object to the constraint - Click the "Copy X Positive To Empty Constraints" button to copy this to all the other constraints

Now you can run the level and sure enough your spawn point will be surrounded by tall buildings. But wait, there entire level is now filled out. This is because the building tile placed next to the spawner has its own constraints that define what can appear next to it. This is the Wave Function Collapse algorithm in action.
But what if you don't like the way the level is created. What if you want this to be a forested area with only a couple of buildings near the player spawn? To do this we will want to define a new building tile that only allows empty tiles next to it.
- Duplicate the building tile we just used, rename it "Building Procedural Tall Only Empty Adjacent"
- Click the "Clear all constraints except X Positive direction"
- In the X Positive constraints remove all except the Empty tile definitions
- Change the empty tile definition to the "Only Empty" definition, the difference between the two is that the only empty one does not allow anything other than "only empty" tiles adjacent to it.
- Click the "Copy X Positive To Empty Constraints" button
- Use this new tile definition in the player spawn constraints
Run the level now and you will see the player spawn has tall buildings next to it and then the rest of the map is empty. But we said we wanted it to be a forest. We do have a few crystal trees present, but not many. Let's fix that.
- Duplicate the "Only Empty" Tile Definition and call it "Forest Only"
- Duplicate the "Only Flora" tile prefab used by this Tile Definition and call it "Forest"
- Increase the Furniture chance to 1
- Assign this prefab to the Forest Only Tile Definition
- In the Building tile definition change the tile definition in the X Positive Constraint to the "Forest Only" definition.
- Click the "Clear all..." button
- Click the "Copy X Positive To Empty Constraints" button
Run the level. You now have a forested level generator.
By adding more fixed tiles or by adding some alternative tiles to the Only Forest tile you can create a little more variety. For example, suppose we want a small chance of a glade appearing in this map, but for the most part we want it to be forested. No problem, we can do that.
- Duplicate the Forest Only prefab and remove the Crstal path large (leaving small and medium)
- In the Forest Only prefab remove the Crystal Patch - Small and medium, leaving only the large
- Duplicate the Only Forest Tile definition and call it "Forest Glade"
- Add a second X Positive constraint and set it to Forest Only, with a weight of 0.5 (the relative chance of this tile being chosen if multiple tiles are available when it is at its lowest entropy. The higher this number the more likely this tile will be chosen.)
- Clear all but X positive and copy X positive to all empty
- Set the Tile Prefab of the Forest Glade to the new Forest Glade prefab
- Rename to Forest Only definition to Forest, as we are going to add more possible adjacent tiles
- Add a new X Positive Constraint and set the Tile Definition to Forest Glade
- Increase the weight of the Forest constraint to 0.5
- Clear all but X positive and copy X positive to all empty
Run the level. Now you have a forest that is not quite as dense, periodically you have an open area with smaller bushes and grasses. By playing with the relative weights of these different Tile definitions you can change the way the level builds out. For example, as currently setup the glades tend to be small and relatively evenly distributed. What if we want them to be larger?
- In the glade tile definition clear out all but the x constraints.
- Decrease the Forest weight and increase the glade weight
You can, of course, make the glade spread vertically or horizontally by increasing the glade weights in the appropriate directions. You could also do ensure that only a particular part of the map had a glade in it by using the placement mechanism above and reducing the possibility of a glade occurring randomly within the map generation (hint: use the weight in the forest Tile Definition).
The tile prefab is the GameObject that will be spawned into the level at that location. It defines the content of the tile. This section describes how to construct one.
The easiest starting point is to duplicate the Assets/_Dev/Prefabs/Tiles/Completely Empty.prefab or Assets/_Dev/Prefabs/Tiles/Mostly Empty.prefab objects based on your needs.