0
\$\begingroup\$

I'm implementing object-pooling, though that's not the issue, and a bit confused about the order of operations between Monobehaviours. In the InitGame() method of MyGameManager, I call SpawnEnemy() on the EnemyManager singleton. What I would expect to happen (but is not happening) is for the Awake() method in EnemyManager to be called, then the EnemyManager-> Start() method, and then and only then, would SpawnEnemy() be called. What actually happens is that EnemyManager Awake() is called, then MyGameManager calls SpawnEnemy(), and when that's done, EnemyManager's Start() method is called last.

The docs say,

Start is called on the frame when a script is enabled just before any of the Update methods is called the first time.

I'm guessing that while my EnemyManager is Active, it is not Enabled, and thus it's Start() method is not called until after the call to SpawnEnemy(). I found it surprising that a method can be called on an object before Start() is called on that object.

I can work around this situation by simply including the line gameObject.SetActive(false); in my EnemyManager's Awake() method, and thus the SpawnEnemy() method will find and spawn an object from the pool, but it would be helpful to have a deeper understanding of when Start() is actually called. I've read the docs and whatever I could find, but don't feel like I fully understand.


Here is the Manager Singleton designed to easily create XxxxManager classes:

using UnityEngine;

public class Manager<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _sharedInstance = null;

    public static T sharedInstance
    {
        get
        {
            if (_sharedInstance == null)
            {
                _sharedInstance = Object.FindObjectOfType<T>();
                if (_sharedInstance == null)
                {
                    Debug.Log("Can't find " + typeof(T));
                }
            }
            return _sharedInstance;
        }
    }

    public virtual void Awake()
    {
        // ...
    }
 }

An EnemyManager class:

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

public class EnemyManager : Manager<EnemyManager>
{    
    static private List<EnemyManager> enemyControllers;

    public EnemyManager SpawnEnemy(Vector3 location)
    {

        foreach (EnemyManager controller in enemyControllers)
        {
            if (controller.gameObject.activeSelf == false)
            {
                controller.gameObject.transform.position = location;

                // object available
                controller.gameObject.SetActive(true);
                return controller;
            }
        }

        Debug.Log("Not enough enemies available in pool.");
        return null;
    }

    public override void Awake()
    {
        if (enemyControllers == null)
        {
            enemyControllers = new List<EnemyManager>();
        }

        enemyControllers.Add(this);

        // If we SetActive(false) here, we DO get the desired behavior
        gameObject.SetActive(false); // <----- 

        base.Awake();
    }

    protected void Start()
    {
        // wake up and disable self
        // If we SetActive(false) here, we do NOT get the desired behavior
        gameObject.SetActive(false);  // <----- 
    }
}

And I'm using these classes in MyGameManager:

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

public class MyGameManager : Manager<MyGameManager> {

    private EnemyManager enemyManager;

    public override void Awake()
    {
        enemyManager = EnemyManager.sharedInstance;
        base.Awake();
    }

    private void Start()
    {
        InitGame();
    }

    void InitGame()
    {
        enemyManager.SpawnEnemy(new Vector3(0, 0, 0));
    }
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Script execution order is a Thing. (Edit -> Project -> Script Execution Order) \$\endgroup\$ Commented Apr 27, 2018 at 2:49
  • \$\begingroup\$ @Draco18s Thank you for the suggestion. Is this a common practice? What I'm describing feels like a pretty frequently seen situation. Is there a programmatic way to ensure that Start() on EnemyManager (or other Manager classes I create) is called before I start working with them? \$\endgroup\$
    – JaredH
    Commented Apr 27, 2018 at 2:55
  • 1
    \$\begingroup\$ Script execution order isn't regularly used, but it comes in handy when its needed and would be common thing to do. \$\endgroup\$ Commented Apr 27, 2018 at 4:06

1 Answer 1

4
\$\begingroup\$

Start is called on the frame when a script is enabled just before any of the Update methods is called the first time.

The key word here is "just" - Start is called when Unity is about to run a FixedUpdate or Update on the script that hasn't yet been Start()ed.

Since EnemyManager.SpawnEnemy() isn't called from inside EnemyManager's Update, there's no particular reason to expect EnemyManager's Start() to run first. MyGameManager's Start has as much right to run earlier, if that's what the engine decides to do (and it doesn't have to make the same decision during testing and in a built executable).

All you're guaranteed is that it's had Awake (and, if spawned in the enabled state, OnEnable) called, because this happens before the AddComponent / Instantiate methods return for dynamically-spawned instances, or before any Start methods for objects loaded with the scene.

As Draco18s mentions, when you have sensitive update orders, you can control the order in which scripts Start / Update under Edit => Project => Script Execution Order. Though generally it's better practice to have the object fully initialized and ready to use in Awake if you can, to avoid hard-to-diagnose update order bugs sneaking in later.

\$\endgroup\$
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .