Invoke function dumps NPCs everywhere

So I am working on a script that spawns 5 customers into the store, each 15 seconds apart. Once those 5 have spawned, there is a 15 second break before 5 more customers spawn in. This goes on 5 times, and then night time begins and the NPCs stop spawning. I used the invoke function for this, but whenever the 45 second grace period between night and day ends, (the 30 seconds of dawn time plus 15 more) an insane amount of NPCs are dumped out into the game. I checked the code multiple times, but it has gotten too spaghetti-esque, so it is hard for me to see any errors. If anyone would be able to take the time to search for the error that would be greatly appreciated. I will respond to questions as soon as I can. Here’s the code:

if (dayNight.isNightTime == false && justDecreased == false && firstSpawn == true)

{

Invoke("SpawnNPC", dayNight.dawnTime + 15);

justDecreased = true;

firstSpawn = false;

}

if (justDecreased == true)

{

hasSpawned = false;

Invoke("SpawnNPC", 15);

}

if (npcNumberToSpawn == 0)

{

npcNumberToSpawn = 5;

numberOfNPCWaves += 1;

justDecreased = false;

hasSpawned = false;

Invoke("GracePeriodInacted", 15);

}

if (numberOfNPCWaves == 5)

{

justDecreased = false;

Invoke("MakeNightTime", dayNight.duskTime);

}

}



void SpawnNPC()

{

if (hasSpawned == false)

{

Instantiate(NPC);

hasSpawned = true;

}

npcNumberToSpawn -= 1;

}



void GracePeriodInacted()

{

justDecreased = true;

}

Generally you don’t “see errors” by staring at code. Yes, in simple errors this is certainly possible.

Instead when code misbehaves you debug it. Here’s how:

Time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

Here is how you can begin your exciting new debugging adventures:

You must find a way to get the information you need in order to reason about what the problem is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the names of the GameObjects or Components involved?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: https://discussions.unity.com/t/700551 or this answer for Android: https://discussions.unity.com/t/699654

If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

If your problem is with OnCollision-type functions, print the name of what is passed in!

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

https://discussions.unity.com/t/839300/3

If you are looking for how to attach an actual debugger to Unity: https://docs.unity3d.com/2021.1/Documentation/Manual/ManagedCodeDebugging.html

“When in doubt, print it out!™” - Kurt Dekker (and many others)

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.

1 Like

Yeah, that’s going to happen with Invoke()

Look, Invoke is not good. You fire off some code and then it happens at some point in the future, with no stack trace back to where you fired it from. You can’t log out the current state of the variables when things go wrong, because you decided what to do 15 seconds earlier or whatever.

Replace your Invoke implementation with a simple Update loop that just counts down to when the next time it should spawn. That will mean that when things go wrong, you can actually just do a Debug.Log of all the variables, and see their state when you actually do the spawning.

3 Likes

You can also make it dynamic, reusable and easily editable directly form the inspector, with something like this (code not tested)

    [System.Serializable]
    public class SpawnDefinition
    {
        public GameObject prefab;
        public int spawnAmount = 5;
        public float waitBetweenSpawns = 15f;
    }

    public SpawnDefinition[] spawnDefinitions;
    public float waitBetweenDefinitions = 15f;
    public int numberOfLoops = 5;

    private void Start()
    {
        // Start the spawn process
        StartCoroutine(SpawnObjects());
    }

    IEnumerator SpawnObjects()
    {
        for (var loopCount = 0; loopCount < numberOfLoops; loopCount++)
        {
            foreach (var spawnDef in spawnDefinitions)
            {
                for (var i = 0; i < spawnDef.numberOfObjects; i++)
                {
                    SpawnObject(spawnDef.prefab);
                    yield return new WaitForSeconds(spawnDef.waitBetweenSpawns);
                }

                yield return new WaitForSeconds(waitBetweenDefinitions);
            }
        }
    }

    void SpawnObject(GameObject prefab)
    {
        // Set the desired position and rotation of the spawned object...
        Instantiate(prefab, transform.position, Quaternion.identity);
    }
}

Like this, you can define everything in the Inspector, and reuse this in other places if needed. Just add the logic to stop the coroutine at night and starts it again at day, should be fairly straightforward.

2 Likes