- Home /
Unexpected behavior - allowed to call method before Start() is called
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));
}
}
Answer by Harinezumi · Apr 27, 2018 at 07:23 AM
I know this confusion quite well, I have been there many times. The docs are very confusing, saying similar things, but somewhat cryptic. This is the best official description I've found so far: https://docs.unity3d.com/Manual/ExecutionOrder.html
In a nutshell, Awake()
is called when the MonoBehaviour is instantaited (IF enabled), and Start()
is only called on the first frame the object is already active, that is just before Update()
is called on it for the first time (or would be called, if it doesn't have an Update()
).
Or you could also say that Awake()
is for internal initialization (it is the closest thing to a constructor), while Start()
is for initialization with other components (when you can expect them to have correct internal state). At least, that's how I use it.
Finally, you can also modify the script execution order in Edit -> Project Settings -> Script Execution Order, where you can move scripts relative to the general execution time. Can be very useful if you have a primary manager class that needs it's Awake()
to be called before everyone else's.
Answer by ZuhairGhias · Apr 27, 2018 at 07:03 AM
That is likely happening because MyGameManager.Start() is called before EnemyManager.Start(). Therefore enemyManager.spawnEnemy() gets called. I don't think it is possible to determine which gameObject.Start() method is called first.
One thing you could try is putting your EnemyManager.Start() logic in EnemyManager.Awake().