I am working on a top-down game where I need several different objects to spawn. Just following the basic tutorials from Unity one can get a working spawn manager, though my main goal is to learn Unity, C# and different approaches, so while stumbling upon the famous talk “Unite Austin 2017 - Game Architecture with Scriptable Objects” by Ryan Hipple I decided to re-write my previous code using the SO approach.
Why I created this post:
There are many great tutorials out there, and yet I was struggling to create what I wanted based on these. Searching the internet did not yield the answers I was looking for - thought that might of course be on me (wrong Google and/or AI queries, still on a learning curve, lack of experience, etc.).
That being said I got a working approach towards a spawn manager that is sufficient for my needs and yet (I think) quite versatile. Maybe my post might help some other beginner, but I am writing this mainly to get some feedback from the community - should anyone be willing/able to review my approach.
I am aware of the “right tool for the right job” approach and I am sure that the below could be done easier and without as much code, but please remember I am highly commited to learning and hope to create some modular code I could re-use in the future
Important: I am using the ScriptableObject-Architecture asset.
Spawn manager data code (ScriptableObject), :
using ScriptableObjectArchitecture;
using UnityEngine;
[CreateAssetMenu(menuName = "Systems/Spawn Manager", fileName = "SpawnManager")]
public class SpawnManagerSO : ScriptableObject
{
[Header("Spawn data")]
[SerializeField] public FloatVariable horizontalSpawnRange; // Linked to game's XY boundries.
[SerializeField] public FloatVariable verticalSpawnRange; // Linked to game's XY boundries.
[SerializeField] public GameObjectCollection whatToSpawn;
[SerializeField] public BoolVariable spawnTrigger;
[SerializeField] public FloatVariable spawnRateMin;
[SerializeField] public FloatVariable spawnRateMax;
public void Spawn()
{
float horizontalSpawnYPosition = verticalSpawnRange + 10f; // Y-coordinate - to spawn the object out of Player's sight.
Vector3 spawnPosition = new Vector3(Random.Range(-horizontalSpawnRange, horizontalSpawnRange), 0, horizontalSpawnYPosition); // Randomize X spawning position.
int objectIndex = Random.Range(0, whatToSpawn.Count);
Instantiate(whatToSpawn[objectIndex], spawnPosition, whatToSpawn[objectIndex].transform.rotation); }
}
Now a MonoBehaviour SpawnManager to attach to a scene object/prefab, with a method that gets called on spawnTrigger change and a coroutine to handle the (randomized) spawn rate:
using System.Collections;
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
[Header("Spawn Manager SO")]
[SerializeField] public SpawnManagerSO whatToSpawnData;
private void Start()
{
// Initiate the first change/trigger to start "chain reaction".
whatToSpawnData.spawnTrigger.Value = !whatToSpawnData.spawnTrigger.Value;
}
public void Spawn()
{
// Spawn is triggered every time when BoolVariable is changed.
// Important: BoolVariable actual value does not matter - Spawn is triggered "on value change", not "on true".
whatToSpawnData.Spawn();
StartCoroutine(CanSpawnTimer());
}
IEnumerator CanSpawnTimer()
{
// This handles the "chain reation" that changes the Spawn() trigger after some delay.
// 60f / rate => rate is "per minute".
yield return new WaitForSeconds(60f / Random.Range(whatToSpawnData.spawnRateMin, whatToSpawnData.spawnRateMax));
whatToSpawnData.spawnTrigger.Value = !whatToSpawnData.spawnTrigger.Value;
}
}
My Editor setup consists of the following:
- ObjectSpawnManager, created from SpawnManagerSO (holding references to ObjectCollection, variables and trigger event);
- FloatVariables for min/max spawn range (final spawn rate is randomized at runtime);
- ObjectCollection to store prefabs of objects-to-spawn (each object currently has a simple Move script attached);
- BoolVariable to use as trigger for the Spawn method;
- BoolVariable event - to run the Spawn method on trigger.
Objects (prefabs) in Scene have the following attached:
- SpawnManager (Monobehaviour) script attached with reference to the ScriptableObject “ObjectSpawnManager”;
- bool Event Listener that triggers ObjectSpawnManager’s Spawn() method.
Adding a new spawn manager with other objects requires little work (at least I think so) - copy whole folder “Objects”, adjust ObjectSpawnManager to point to correct items, fill ObjectCollection with new prefabs and add create a SpawnManager (the Monobehaviour script) in the scene with the correct event listener.
Now here are my questions:
- does this setup look like something viable and modular, or rather a hard-to-understand mess?
- is it actually following the ScriptableObject design philosophy or is it “overusing” SO too much (for example compared to putting code in Update())?
- is it a huge overkill (I am aware it is overkill to some extent) and unnecessary division into small chunks, or is it a step in the right direction on learning game programming?
- if the system is “fine”, what could be tweaked?
Since I am on the early stages of my path I would be grateful for any feedback.