Enemy waves not spawning when loading scene from another scene

So I’ve been stuck here for a few hours now. If I load my “game” scene first, everything runs fine.
If I load my “main menu” scene first then click the start button to load the “game” scene, the “game” scene loads but enemy waves don’t spawn and I get this null exception error:

NullReferenceException: Object reference not set to an instance of an object
WaveConfig.GetWaypoints () (at Assets/Scripts/WaveConfig.cs:26)
EnemySpawner+<SpawnAllEnemiesInWave>d__6.MoveNext () (at Assets/Scripts/EnemySpawner.cs:36)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <a1ac446df41c4a67becf2f8317dc1792>:0)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
<SpawnAllWaves>d__5:MoveNext() (at Assets/Scripts/EnemySpawner.cs:28)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

(WaveConfig.cs:26 is the foreach loop that calls my pathprefab variable under the “getwaypoints” method)

The enemyspawner object still has all the waveconfig files referenced at runtime and the waveconfig scriptable objects still have the paths referenced. I’ve checked all references at runtime and they all seem to be fine, so I can’t figure out why this null exception happens.

The biggest issue is that this seems to be random. I implemented 3 waves and SOMETIMES wave 3 does actually run instead of giving me a null reference. Some other times, I’d have all 3 waves run, then when I go to the quit scene, then start menu scene, then game scene again, only wave 3 would start or no wave would start. This “randomness” seems to change everytime Unity recompiles after I change anything in the scripts on visual studio. I can’t reproduce the few times it decides to work. This makes me think that the issue could be in the script execution order but I tried playing around there and still no luck.
None of the solutions that other users here or on stackoverflow that I could find fixed the issue for me.

The only consistency here is that it runs fine if I start by loading my “game” scene and that I get a null reference when I load the “game” scene from the another scene.

Hi,

NullReferenceException means that a reference (“link”) to an instance is missing. Double click on the error message to see to which line in your code it is referring. If you exposed a field in the Inspector, make sure that it’s not empty.

That assumed “randomness” sounds indeed weird, especially since we didn’t implement any randomness. Have you already tried to add Debug.Logs to your code to see what is going on during runtime? GetInstanceID() and Time.frameCount could be interesting in combination with the relevant objects. The execution order in Unity is arbitrary, so maybe something executed in the “wrong” order could be causing the problem for you. This is just a guess, though.

Your waypoints don’t get destroyed during runtime, do they? And the WaveConfig objects in your Assets folder reference a path, don’t they?

During run-time, the enemyspawner and waveconfig files all reference the path prefabs correctly every time according to the inspector.
I used Debug.Log everywhere to find where the issue begins and it’s clear there’s an issue with how it tries to find the path prefabs when loading the scene from another scene, but I can’t figure out why, or why it works randomly every now and then, despite changing nothing in the code.

I never destroy the waypoints and the waveconfig files do reference a path.

I’ll attach the code here if that would help. Here’s the EnemySpawner:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    // Config parameters
    [SerializeField] List<WaveConfig> waveConfigs;
    [SerializeField] int startingWave = 0;
    [SerializeField] bool loop = false;
    [SerializeField] float waveDelay = 2f;

    // Private variables

    // Start is called before the first frame update
    IEnumerator Start()
    {
        do
        {
            yield return StartCoroutine(SpawnAllWaves(startingWave));
        } while (loop);
    }
    private IEnumerator SpawnAllWaves(int startWave)
    {
        for (int i = startWave; i < waveConfigs.Count; i++)
        {
            yield return new WaitForSeconds(waveDelay);
            yield return StartCoroutine(SpawnAllEnemiesInWave(waveConfigs[i]));
        }
    }

    private IEnumerator SpawnAllEnemiesInWave(WaveConfig waveConfig)
    {
        for (int i = 0; i < waveConfig.GetNumEnemies(); i++)
        {
            GameObject newEnemy = Instantiate(
                waveConfig.GetEnemyPrefab(),                        // GameObject
                waveConfig.GetWaypoints()[0].transform.position,    // Position
                Quaternion.identity);                               // Rotation
            newEnemy.GetComponent<EnemyPathing>().SetWaveConfig(waveConfig);
            yield return new WaitForSeconds(waveConfig.GetWaveSpawnDelay());
        }
    }    
}

Here’s the WaveConfig:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "Enemy Wave Config")]
public class WaveConfig : ScriptableObject
{
    // Config Variables
    [SerializeField] GameObject enemyPrefab;
    [SerializeField] GameObject pathPrefab;
    [SerializeField] int numEnemies = 5;
    [SerializeField] float waveSpawnDelay = 1f;
    [SerializeField] float spawnRandomFactor = 0.3f;
    [SerializeField] float moveSpeed = 2f;

    // Private Variables
    private List<Transform> waypoints;
    private int waypointsSize;

    public GameObject GetEnemyPrefab() { return enemyPrefab; }
    public List<Transform> GetWaypoints()
    {
        Debug.Log("Howdy");
        waypointsSize = 0;
        foreach (Transform i in pathPrefab.transform)
        {
            waypoints.Add(i);
            waypointsSize++;
        }
        return waypoints;
    }
    public int GetWaypointsSize() { return waypointsSize; }
    public int GetNumEnemies() { return numEnemies; }
    public float GetWaveSpawnDelay() { return waveSpawnDelay; }
    public float GetSpawnRandomFactor() { return spawnRandomFactor; }
    public float GetMoveSpeed() { return moveSpeed; }
}

Here’s Enemypathing:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyPathing : MonoBehaviour
{
    // Config Parameters
    [SerializeField] bool usePathing = true;

    // Private Variables
    private WaveConfig waveConfig;
    private List<Transform> waypoints;
    private int waypointsSize;
    private int waypointIndex = 0;
    private Vector3 targetPos;
    private float deltaPos;
    private float moveSpeed;

    // Start is called before the first frame update
    void Start()
    {
        if (usePathing)
        {
            waypoints = waveConfig.GetWaypoints();
            waypointsSize = waveConfig.GetWaypointsSize();
            moveSpeed = waveConfig.GetMoveSpeed();
            transform.position = waypoints[waypointIndex].transform.position;
            targetPos = waypoints[waypointIndex].transform.position;
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (usePathing)
            Move();
    }

    private void Move()
    {
        if (waypointIndex < waypointsSize)
        {
            if (transform.position == targetPos)
            {
                waypointIndex++;
                if (waypointIndex != waypointsSize)
                    targetPos = waypoints[waypointIndex].transform.position;
            }
            deltaPos = moveSpeed * Time.deltaTime;
            transform.position = Vector2.MoveTowards(transform.position, targetPos, deltaPos);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void SetWaveConfig(WaveConfig WC)
    {
        waveConfig = WC;
    }
}

So I didn’t find a solution but I instead found a workaround. I started thinking the issue might be with the new nested prefabs that were introduced after this course came out. That doesn’t explain why it works when I launch the scene directly and stops working when loading from another scene though.

Anyway, my workaround was this: Create WaveConfig as a gameObject instead of a scriptableObject, give it the same exact parameters and everything. The main difference is instead of using a foreach loop to create the list of waypoints, I attach each waypoint manually in the inspector to a List variable. Worked like a charm.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Wave : MonoBehaviour
{
    // Config Variables
    [SerializeField] GameObject enemyPrefab;
    [SerializeField] List<Transform> waypoints;
    [SerializeField] int numEnemies = 5;
    [SerializeField] float waveSpawnDelay = 1f;
    [SerializeField] float spawnRandomFactor = 0.3f;
    [SerializeField] float moveSpeed = 8.5f;

    // Private Variables
    private int waypointsSize;

    // Start is called before the first frame update
    void Start()
    {
        waypointsSize = waypoints.Count;
    }

    public GameObject GetEnemyPrefab() { return enemyPrefab; }
    public List<Transform> GetWaypoints() { return waypoints; }
    public int GetWaypointsSize() { return waypointsSize; }
    public int GetNumEnemies() { return numEnemies; }
    public float GetWaveSpawnDelay() { return waveSpawnDelay; }
    public float GetSpawnRandomFactor() { return spawnRandomFactor; }
    public float GetMoveSpeed() { return moveSpeed; }
}

Of course this makes the project a bit less scalable, makes the hierarchy significantly uglier, and makes a whole mess of the prefabs folder, but hey, it works :slight_smile:

I still want to know if there’s a solution to fixing this problem while still using scriptable objects.

Good job! :slight_smile:

Don’t worry if your solution/workaround is not perfect. You could replace it at a later juncture.

That sounds like a perfectly fine approach to me. If Rick didn’t implement his current solution, I’m sure he would have done the same as you.

I would not rely too much on the Inspector. If the issue occurs during runtime, check values with Debug.Log during runtime.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms