I was looking through Final Fantasy guides to get ideas for architecture. In particular I looked at a Final Fantasy 1 handbook by Ben Siron. I was intrigued by his simple presentation of the Enemy Domain Mapping system – showing what monsters appear in random encounters at each location of a map. Put simply, there were several “recipes”, each referred to by a single letter, and those letters were arrayed in a grid according to the layout of a map. It was a simple way to store a complex amount of information in an efficient way. Here is an example recipe:
(OGRE,CREEP), (GrWOLF,WOLF), ASP, ARACHNID, GrIMP
You can interpret this recipe as a comma separated list of spawn slots. Items in parenthesis indicated that a particular slot would be implemented at random from the choices included within. In this example there would be either an Ogre or creep, a Great Wolf or normal wolf, and an Asp, Arachnid and Greater Imp.
I decided to build and improve upon this sort of recipe system to create my own Enemy Spawner, while at the same time having an opportunity to demonstrate a programming concept called Polymorphism. Polymorphism is often presented as sort of an advanced topic, but it doesn’t have to be. I would explain it as the general concept of a more specific thing. For example, I can refer to my TV, blue ray player, Xbox and iMac generically as “electronics”. Deep huh. In programming, this just means that I can know certain basic truths about each of those items, like they need electricity to operate, without having to know the specific purpose or implementation of each.
The system I am writing will also be comma separated slots, but random slots will be separated by “|” and I will add the ability to have weighted random slots, by using an equal sign. Here is an example:
Ogre,Wolf|Bat,Spider=3|Worm=1
A random encounter populated by this recipe would always produce an Ogre, would randomly add a wolf or bat, and would also randomly add a spider or worm, although the spider would be more likely to appear because it has a higher weight.
Now that the general idea of the system has been defined, it is time to start building it. First, I will create the generic blueprint for the “Spawn Slot” of our recipe, which is an object that when queried returns the name of a monster (or whatever) to load. Let’s create a new script called “Spawn.cs” and add the following code.
public abstract class Spawn { public abstract string Name { get; } }
The definition of this class, and its only property, include a word you may not have used before, “abstract”. An abstract class means that I cannot create instances of the class, I will need to sub-class it and create instances of those instead. This is because an abstract class is missing some sort of important implementation detail that must be defined somewhere in order to be complete. In this case, the property is not implemented.
The purpose of this is to be the “General Concept” that other more specific things share in common. In other scripts, I can refer to a collection of these Spawn objects and work with each of them the same way, even though they will actually be very different in implementation.
As a side note, C# has another concept called an “interface” which could also be appropriate to use here. You might want to use an interface in scenarios where you want different implementations to have different base classes. The benefit of an abstract base class is the ability to provide default implementations of methods and properties whereas an interface simply requires that they exist. For the best of both worlds, you could make a mix of the two, such as having an abstract class implement an interface.
Next, let’s create the first concrete implementation of our Spawn class. We will call it a “FixedSpawn” because it will always return the same thing. Its implementation is nothing more than providing a backer field for the Name property, which is assigned in the class constructor.
public class FixedSpawn : Spawn { public override string Name { get { return _name; } } private string _name; public FixedSpawn (string data) { _name = data; } }
The property Name now has the word “override” which means that we are replacing the implementation of the inherited class, although in our example we didn’t actually provide a default implementation.
This example wasn’t terribly exciting, so let’s create another version.
public class RandomSpawn : Spawn { public override string Name { get { return _members[UnityEngine.Random.Range(0, _members.Length)]; } } private string[] _members; public RandomSpawn (string data) { _members = data.Split('|'); } }
An instance of this class could return a different random name every time you ask. This is due to the implementation of the Name property – it picks a random string from an array of possible choices.
The constructor I show here is expecting a string which it will split into multiple name options based on the “|” character. For example I could pass “Ogre|Lion” which will be split into the individual strings “Ogre” and “Lion”.
We will create one final implementation of Spawn. It will be similar to the random version, but will allow each entry to have its own weight, or probability of being chosen. It would be like having a pie cut into pieces that are not of equal size, then spinning the pie and taking the one piece that lands closest to yourself. You would have a greater chance of getting a bigger slice than a small one.
public class WeightedSpawn : Spawn { public override string Name { get { string retValue = _members[0]; int roll = UnityEngine.Random.Range(0, _size) + 1; int sum = 0; for (int i = 0; i < _weights.Length; ++i) { sum += _weights[i]; if (sum >= roll) { retValue = _members[i]; break; } } return retValue; } } private string[] _members; private int[] _weights; private int _size; public WeightedSpawn (string data) { string[] slots = data.Split('|'); _members = new string[slots.Length]; _weights = new int[slots.Length]; for (int i = 0; i < slots.Length; ++i) { string s = slots[i]; string[] elements = s.Split('='); _members[i] = elements[0]; _weights[i] = System.Convert.ToInt32(elements[1]); _size += _weights[i]; } } }
The constructor for this class is expecting a string which will be parsed into multiple weighted entries. For example, “Ogre=10|Lion=2” would be parsed into two weighted entries, an Ogre with a weight of 10 and a Lion with a weight of 2. This means that this example would be five times as likely to return “Ogre” as it is “Lion”, but it could still return either one.
Now that the spawn slots are complete, we will build another class that holds an array of them – our SpawnOrder class. The array of Spawn slots will be based on the abstract base class version of each type of slot, so that any other class utilizing this class has to know as little as possible.
At the same time, we will finish the implementation that will parse the recipe into the instances of those spawn slot instances, using a Factory method.
public class SpawnOrder { public Spawn[] members; public static SpawnOrder Create (string recipe) { SpawnOrder retValue = new SpawnOrder(); string[] slots = recipe.Split(','); retValue.members = new Spawn[slots.Length]; for (int i = 0; i < slots.Length; ++i) { string s = slots[i]; if (s.Contains("=")) retValue.members[i] = new WeightedSpawn(s); else if (s.Contains("|")) retValue.members[i] = new RandomSpawn(s); else retValue.members[i] = new FixedSpawn(s); } return retValue; } }
Our SpawnOrder class knows about the concrete implementations of the Spawn class, but only needs to refer to them initially for creation sake. The concrete classes won’t be referred to again, making it very easy to create new versions, replace, modify, or remove them at a later date. Any class using our SpawnOrder only needs to know the most generic information of what it contains, that there is an array of Spawn objects, which is essentially just an array of strings representing the names of monsters (or whatever) to load.
In Unity I created a new scene, and created a new script called “SpawnExample.cs” which I attached to the Main Camera object in the scene. The only purpose of this scene and script are to demonstrate a use of the SpawnOrder class we will be creating. More complete implementations might use these names to instantiate a prefab, or attach a named component to a game object etc. In a real game, I would set things up differently, but this is suitable for now.
public class SpawnExample : MonoBehaviour { void Start () { string recipe = "Ogre,Wolf|Bat,Spider=3|Worm=1"; SpawnOrder example = SpawnOrder.Create(recipe); for (int i = 0; i < 10; ++i) { string encounter = string.Format("i:{0}, {1}, {2}, {3}", i, example.members[0].Name, example.members[1].Name, example.members[2].Name); Debug.Log(encounter); } } }
The example script uses the “Create” factory method to build a new instance of a SpawnOrder from a “recipe” of monsters which might appear. The SpawnOrder class splits the information up and creates an array of generic “Spawn” objects, which are internally implemented as weighted, random, or fixed spawn classes. I simulate 10 “random encounters” and log the resulting enemy formation to the console. Just press play and you can see how the simple recipe can become a diverse environment for your exploration.
There’s a nice idea for creating spawners, I’d combine it with the (thing I belive it’s called) “specification pattern” [sort of] so I could set the objects by the editor instead of relaying on strings – that are error-prone.
Thanks for the tip Marcos, I’ll check it out. I agree with the concern of string typos, but I was imagining a scenario where some sort of JSON feed, spreadsheet or database was driving the spawn orders via text so it would be really easy to create and balance large amounts of content. I would probably create some system to validate the data during edit mode and not allow it to be modified at runtime.