Skip to content

Level design tool

Each of the team members has to design a level for our game. Therefore, I (Marwan) created a tool that will allow us to easily design levels. I designed it with flexibility in mind.

Composition

Each level contains a list of waves. Waves contain data such as a maximum time, some score values and most importantly a list of wave patterns. I will get into what these patterns are later in the document.

Creating level

There is a static function in Assets/Scripts/Enemy/Level.cs named GenerateLevel. This is where the level is loaded. The place where you design your levels are within the case blocks of the switch statement in that function.

To start create a case block with the appropriate level number. Then store a new level in a variable.

switch (id)
{
    case 0:
        Level level = new();
}

Waves

To create a wave you first call its constructor and assigned the variables listed here. See the appropriate field declarations if you want to know what each of these values represent.

Wave wave = new Wave
{
    timer = TimeSpan.FromSeconds(20f),
    scoreOnSurvive = 100,
    scoreOnClear = 500
};

Now that the wave is created we can add patterns to this wave.

Wave patterns

This is where it gets a little bit more complicated if you’re not familiar with delegates, but hopefully it will make sense.

The base wave pattern class

A wave pattern contains a coroutine which is responsible for defining when where and how enemies should spawn. It also should call the End method when the coroutine is over, which is important to make sure the wave spawner knows when no more enemies are expected to spawn and thus can end the wave early if needed.

When instantiating a wave pattern, you do need to provid it with the spawner and its parent wave.

Using the delay frequency pattern (and any other pattern)

The delay frequency pattern has 5 properties:

  • Delay: How much time to wait after the previous pattern ended.
  • Frequency: How much time to wait between each enemy spawn.
  • Count: How many enemies we want to spawn
  • Position: Where the enemies should all be spawned (this position is fixed, to spawn enemies with different positions in a single pattern, you would need to create another custom pattern).
  • Spawn delegate: What function to call when spawning an enemy.

If you do not know what delegates are, in very short, a delegate is like a special variable that holds a function, so you can pass it to other parts of your code and call it when needed.

Delegates can be defined to work with certain parameters and or return values, and in our case, it needs to take in a Vector2 for position and return an Enemy.

So here is an example of how you would use such a pattern:

WavePattern pattern = new DelayFrequencyPattern(spawner, wave)
{
    delay = TimeSpan.FromSeconds(0f),
    frequency = TimeSpan.FromSeconds(1f),
    count = 5,
    spawnDelegate = SlidingEnemy.CreateDefault, // 1
    position = new Vector2(0f, 2f)
};
wave.patterns.Add(pattern); // 2

Notice that when refering to CreateDefault (1) which is actually a static method, we do not use parentheses because we are not actually calling that function, but rather storing a reference to it in the spawnDelegate variable.

You can only use this kind of syntax when the function has the exact same parameters and return type as the delegate we are using. If you are using a function with different parameters and/or return type, I suggest you look into lambdas.

We also add that pattern to the list of patterns inside our wave.

Finishing the wave

Once all the patterns are added, we call the Add method on our level. It is important to use the Add function directly on the level and not on the list of wave, since it automatically detects what the last wave is.

level.Add(wave);

You can repeat that as many times as needed before returning the level.

Making custom wave patterns


Last update: May 18, 2023