Help? Getting derived class function of child object to activate AFTER its Start() method.

I wanted to make a base class component for animating sprites by a selected sequence, followed by a derived class that initializes the sequence data for each game object containing a sprite. I have succeeded in doing so and I wanted to apply this to some animated arrows that are children of a root menu screen. Upon instantiating the root menu screen, however, I have come across a problem with triggering a function that selects a sequence while compiling all the necessary data in the Start method of the root menu script. For some reason, the base component of the sprite animator is activating the assigned outside function BEFORE it runs its Start method. This causes the sequence to read as null and create null reference errors. Is there a specific reason the Start methods of both the base and derived animator classes are not being triggered in time before other functions? Does it have something to do with the derived component being attached to a child object while the root or parent is running first? Here is some example code to clarify what I’m going through:

The base class of the sprite animator:

public abstract class AnimateSprite : MonoBehaviour {
     [SerializeField] protected bool animateOn = true;
     [SerializeField] public int animState;
     public Dictionary<int, AnimData> animSeq;
     public Dictionary<int, AnimNext> animSkip;
     SpriteRenderer sprend;
     Sprite[] spr;
     protected bool loopOn = false;
     protected int loopCount = 0;
     int frame = 0;
     int renderFrame = 0;
     public class AnimData
     {
         public int[] phase;
         public int[] speed;
         public string spriteName;
     }
     public class AnimNext
     {
         public int nextPhase;
         public bool loopPhase;
         public int loopAmount;
     }
     // Use this for initialization
     protected virtual void Start () {
         StartCall();
         sprend = gameObject.GetComponent<SpriteRenderer>();
         spr = Resources.LoadAll<Sprite>(sprend.sprite.name.Substring(0, sprend.sprite.name.Length-2));
         frame = 0;
         renderFrame = 0;
         if (animSeq != null)
         {
             sprend.sprite = spr[animSeq[animState].phase[renderFrame]];
         } else
         {
             sprend.sprite = spr[0];
         }
     }
     protected abstract void StartCall();
     void FixedUpdate()
     {
         //Animation in progress
     }
     public void setAnimation(int nAnimState)
     {
         animState = nAnimState;
         //ERROR STARTS BELOW
         string nSprite = animSeq[nAnimState].spriteName;
         sprend = gameObject.GetComponent<SpriteRenderer>();
         spr = Resources.LoadAll<Sprite>(nSprite);
         sprend.sprite = spr[animSeq[animState].phase[0]];
         loopOn = animSkip[animState].loopPhase;
         loopCount = animSkip[animState].loopAmount;
         frame = 0;
         renderFrame = 0;
     }
}

The derived class of sprite animator:

public class AnimateGlowArrows : AnimateSprite {
     // Use this for initialization
     protected override void Start () {
         base.Start();
     }
     protected override void StartCall()
     {
         initializeAnimation();
     }
     public void initializeAnimation()
     {
         animSeq = new Dictionary<int, AnimData>
         {
             { 0, new AnimData{ phase = new int[1]{ 0 }, speed = new int[1]{ 1 }, spriteName = "MenuScreenArrow1" } },
             { 1, new AnimData{ phase = new int[6]{ 1, 2, 3, 4, 3, 2 }, speed = new int[6]{ 2, 2, 3, 3, 3, 2 }, spriteName = "MenuScreenArrow1" } },
             { 2, new AnimData{ phase = new int[1]{ 5 }, speed = new int[1]{ 1 }, spriteName = "MenuScreenArrow1" } }
         };
         animSkip = new Dictionary<int, AnimNext>
         {
             { 0, new AnimNext{ nextPhase = 0, loopPhase = false, loopAmount = 1 } },
             { 1, new AnimNext{ nextPhase = 1, loopPhase = false, loopAmount = 1 } },
             { 2, new AnimNext{ nextPhase = 2, loopPhase = false, loopAmount = 1 } }
         };
         loopOn = animSkip[animState].loopPhase;
         loopCount = animSkip[animState].loopAmount;
     }
}

Component of root menu screen where animator is being called within. The start method is basically calling for the derived class’s setAnimation() function:

public class MenuScreenRoot : MonoBehaviour {
     //Initializing fields here
     void Start()
     {
         //After certain code, assign animator HERE
        gameObject.transform.Find("MenuScreenArrows").gameObject.transform.Find("MenuScreenArrowLeft").GetComponent<AnimateGlowArrows>().setAnimation(0);
         //Continuing code here
     }
}

For the record, I can bypass this issue if I use a coroutine that waits for the sequence to no longer be null before activating, but I don’t want to rely on that as a default, as I believe this would eventually slow down processing time. Just what order does each method of each class trigger in exactly? I want the derived class to be initialized first before its parent game object finishes its starting call. Can it be done?

One thing that’s important to be aware of is that Unity handles the Start/Update/etc calls weirdly and it’s hard to predict which one will be called (I’ve pulled my hair out trying to figure out why Update was never being called on something before, only to figure out hours later that Unity was calling the base class’s Update instead), so it’s generally good practice to NEVER make them virtual/override when using inheritance with MonoBehaviour. If you must, create a Start in your base class which calls a virtual method of your own, but don’t make Start virtual. I don’t think that’s causing a problem here because your override just calls the base, just something to be aware of.

In fact, I don’t think this has anything to do with the derived class at all, it’s just normal Unity execution order stuff. You can’t rely on any particular object’s Start() to be called before or after any particular other object’s Start() in general. The usual way to resolve this sort of thing is to use both Awake() and Start() (all Awake’s are always called before all Start’s): In Awake, do all initialization that doesn’t depend on other objects, and then in Start, do any initialization that requires other objects to have stuff initialized. So, in AnimateSprite, move the initializeAnimation() call into Awake, rather than Start.

A second option is to perform initialization on demand. In this case, at line 49, you could check to see if animSeq is null, and if so, call your initialization at that point. This has some downsides (for example, if it manages to not get called during Start it could cause performance hiccups in game since you’re loading resources), and I don’t recommend it in this case, but it’s an option.

If you really, really need to force a specific order of execution between scripts, you can do that in project settings. This makes your scripts less portable and is generally only used as a last resort - I’d recommend relying on the other two methods before resorting to this.

Unfortunately, most of the suggestions did not work well, and using Awake seemed to involve the need of further alterations to the initialization of several scripts, so I had to resort to the last option of changing the script execution order. I placed MenuScreenRoot ahead of the default time by 100. This actually managed to bring my runtime to a success, and so the errors are now avoidable. It’s new to me, so I don’t fully understand why relying of execution order alteration would be considered unnecessary. Will it negatively affect possible future scripting?

In general, if your goal is to write reusable code (which is usually a good thing, and the sort of script you’re working on here is one that would be useful elsewhere), anytime you reuse this code, you’ll have to go into Project Settings and make this change for every project. Additionally, the more scripts you add, you’ll eventually start to get a confusing rat’s nest of dependencies in your execution order, and problems with it will become increasingly difficult to debug.

In contrast, if you get in the habit of separating your initialization into “stuff that depends on other objects” (in Start) and “stuff I can do on my own” (in Awake), the execution order of scripts which depend on each other will be reliable, consistent, and predictable, and it’s unlikely to get more confusing on bigger projects.