How do I make a pre-made set of waves each spawn randomly at a certain time?

My game is a basic 3D obstacle dodge. The camera looks down on a character that can only move along the x-axis to dodge obstacles moving towards it on the z-axis.

At the moment I am using a code (which I’ll refer to as A) which allows me to: 1. Set the time that the obstacles begin to spawn after the game starts, 2. Spawn a certain number of objects, 3. Set at what rate it spawns the objects and 4. Set where on the x-axis the obstacle spawns, which is random and set within the boundary of the game.

My game layout is as follows.

  • Start game

  • Wait 2 seconds for the player to get ready

  • Begin spawning a ‘tutorial’ scenario with basic wall obstacles (using A) for 15 secs.

  • Wait 2 seconds, then choose from a set of pre-made obstacle layouts to instantiate, each of which last 15 secs. (if Random int = 1, use this obstacle layout. if Random int = 2, use this etc.)

  • Then, use the selected pre-made wave and instantiate it at x=0.

  • Wait 2 seconds, etc.

  • Player loses game if they touch two obstacles in quick succession.

What kind of code would I use to do this? I have thought of using ‘Random int’ and ‘if int = 1 then… if int = 2 then… etc.’ but I don’t know how to write it, or spawn the appropriate ‘wave’.

Please note that I am a complete beginner at Unity. Thanks in advance!

Code A is as follows:

public class GameController : MonoBehaviour {

    public GameObject obstacle;
    public Vector3 spawnValues;
    public int ObCount;
    public float spawnWait;
    public float startWait;

    void Start ()
        {
            StartCoroutine (SpawnObstacles ());
        }


    IEnumerator SpawnObstacles ()
        {
            yield return new WaitForSeconds (startWait);
            for (int i = 0; i < ObCount; i++)
            {
            Vector3 spawnPosition = new Vector3 (Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
            Quaternion spawnRotation = Quaternion.identity;
            Instantiate (obstacle, spawnPosition, spawnRotation);
            yield return new WaitForSeconds (spawnWait);
        }
    }
}

If I understand you correctly, I think the simplest solution is this:

  1. Define each wave as a Prefab that contains all the obstacles for that wave, all in their correct starting positions. This is pretty easy: just drag the obstacles out in the scene editor, all contained within a parent (an empty GameObject), and arrange them how you like. Then drag the parent into the Project view somewhere. This makes the original parent a prefab instance (that includes all its children, the actual obstacles). Delete that instance from the scene, and repeat fro the other waves.

  2. Now, give your spawner script a “public GameObject[ ] wavePrefabs;” array. Randomly pick from this array, and instantiate it. That will cause all of the obstacles to appear in their correct positions.

I know you’re new to Unity so if any of this is unclear, just say so and we’ll try to help more!

1 Like

Using a coroutine, as you have done in your code already, is probably the best way to go, as far as I know.
So, using a coroutine would be good for everything you said where you have to wait a specific amount of time for.
However, upon starting the game, I advise not using a coroutine for getting the player ready.
The reason for this is if there is lag when you load your tutorial scene (loading objects in the elvel,
enemies, etc.), what the game thinks as two seconds may not be what the player would think would be two seconds.
What I suggest for you to do in this case is to have something in the update such as
a check when the player hits the ground. This way, you know everything has loaded and
you would not deal with the player getting ready with what seems like longer than 2 seconds due to something like lag.

However, you said that you want the “tutorial” scenario to spawn, presumably after the player is ready.
So, what I said may not be necessary for this case, but if you do it the other way where you spawn the
objects in the scene first and then get the player ready afterwards, than I would advise doing what I said above.

Then for other things that you requested, coroutines would be great to use in the update function as
everything is loaded and you don’t really have to worry about lag at that point, unless your scene
has an abundant quantity of objects.

In addition, one thing I would suggest for choosing your random obstacle is to make an array of
all your obstacle scene layouts that you would want randomly chosen and then you would be easily able to
just choose a random obstacle scene layout from 0 to the size of array.

Sorry for any possible typos as its late currently.

Hi JoeStrout,

That sounds good. Can you give me some advice on how to time the instantiations (15 secs between each other), the code which selects the wave prefab from the array, and making both prefab waves and random waves choices for the randomizing code to instantiate? If I’m not wrong it should go something like:

  • Wait 2-3 secs
  • Spawn randomized ‘tutorial’ wave for 15 secs
  • (20 secs from start) ARRAY: Select a number randomly from array
  • Take number and instantiate the selected prefab (if … = (1), if … = 2, etc, then instantiate ?)
  • Wait 20 secs (when the wave ends) then repeat ARRAY

Congrats in getting your logic straight. Good code flows naturally out of good logic.

// An inspector adjustable array if waves
[SerialiseField] GameObject[] waves;

// Start is called as soon as the level is ready
void Start () {
    StartCoroutine (SpawnWaves());
}

// Coroutines are code that can be executed over time
IEnumerator SpawnWaves(){
    // Wait three seconds to start
    yield return new WaitForSeconds(3);
   
    // Anything inside this loop will repeat until something stopes the coroutine
    while (true) {
        // Get a random value
        int Index = Random.Range(0, waves.length)
        // Create a new wave
        Instantiate (waves[index]);
        // Wait 20 seconds
        yield return new WaitForSeconds(20);
    }
}

Thanks for the help, Mormon. I hope the community doesn’t mind holding my hand and guiding me blindly through the script you’ve written so I can understand it better.

// An inspector adjustable array if waves
[SerialiseField] GameObject[] waves;
  • What in the heck is SerialiseField? The documentation hurts my brain. Also I’m pretty sure you mean ‘of waves’. Otherwise I’m stumped.
  • [ ]? Is this an indication of something that will appear in the components area of a GameObject where I can input my various prefab’d waves?
// Start is called as soon as the level is ready
void Start () {
    StartCoroutine (SpawnWaves());
}

// Coroutines are code that can be executed over time
IEnumerator SpawnWaves(){
    // Wait three seconds to start
    yield return new WaitForSeconds(3);
  • What is this IEnumerator thing? It’s certainly central to the idea of a Coroutine. Do I need to know much about it at all?
// Anything inside this loop will repeat until something stopes the coroutine while (true) {
        // Get a random value
        int Index = Random.Range(0, waves.length)
        // Create a new wave
        Instantiate (waves[index]);
        // Wait 20 seconds
        yield return new WaitForSeconds(20);
  • While… what’s (true)? Is the coroutine (true) by default? If a lose condition were achieved, will turning the coroutine (false) switch it off?
  • What determines waves.length? Does that go somehow hand in hand with the [SerialiseField] GameObject… etc code at the beginning? Obviously there is something that determines what wave prefab is (0), (1), (2), etc. and caps at waves.length.
  • Doesn’t instantiate require a rotation and position along with a stated GameObject?

On a different note, to make my ‘tutorial’ wave be the one out of all of the prefabs to be instantiated, can I plug in [Instantiate (waves[0])] (assuming the tutorial wave is registered as (0)) after yield return new WaitForSeconds(3), then putting a WaitForSeconds(20) after it?

Thanks for your help so far guys.

Damn Apple auto correct. You are right.

SerialiseField simply makes a private variable show in the inspector. You can use public instead, but that’s a bad habit to get into.

[ ] is used to declare an array. An array is a group of objects of the same type.

IEnumerators are awesome. But you can completely ignore them for now if you like. There is no need to understand an IEnumerator in order to use a coroutine.

While true is true. In other words always and forever. You can replace it with a bool variable in order to be able to turn it off. Or you can use StopCoroutine.

You can set waves.length in the inspector.

If Instantiate doesn’t have a position or rotation it will use the ones from the prefab.

I would suggest using a seperate prefab for the tutorial. But the idea is the same.

I plugged the code into an empty gameobject’s script in C# to see what would happen at first, and a whole lot of things went haywire. I checked the array documentation, and it says it only works in Javascript?

2244865--149916--Capture.PNG

Serialize is spelled with a “z”. Length has a capital L.

1 Like

After a few spelling checks, a semicolon and discovering that Index =/= index, it works like a charm. Thanks!

On an unrelated note, how does one allow a script to access the values of another script? For example I want a pickup to reduce the speed of everything by a half of their original speed for 3 seconds. How do I make the trigger on collision to alter the speed values of the other moving gameobjects?

The object you collide is given to you as a parameter of the OnCollisionEnter/Stay/Exit functions- you just have to add “Collision col” between the parenthesis, if it isn’t there already. That’ll give you the collision details, one of which is col.gameObject, which you can then use a GetComponent() on to get the script in question, and check/change its values from there.

Edit: Misread. If you want global effects on enemies, you should really use a manager that keeps track of all of the enemies in the area. You can then just reference and call a function on that manager like “SlowEffect(slowTimer);” and have that manager iterate through its internal list of enemies (or children, if you make all enemies children of the manager) and call their personal SlowEffect(slowTimer) functions if they’re in range or w/e.

An alternative is using FindGameObjectsWithTag and such and iterating over the results, and that’s a really bad idea for performance reasons.

Another alternative SPECIFICALLY to Slow and SpeedUp effects is to alter the Time.timeScale value to less than 1 to slow things down and greater than 1 to speed them up. Be careful setting it to zero, as this will stop time entirely for anything relying on the timeScale to function, which includes the FixedUpdate calls.

Serves me right for trying to code on an iPhone.