How do I get a function of a derived class of a child game object to activate AFTER the Start method from during the instantiation of its parent gameobject?
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:
//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;
}
}
//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
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);
StartCoroutine("summonMenu", endMoveY);
}
}
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. Could I get a solution regarding the order around functions and Start methods sometime soon?
Answer by Kishotta · Jan 13, 2020 at 05:20 PM
A couple of things:
It's generally a bad idea to implement the MonoBehaviour
event methods ( Awake
, Start
, Update
, etc) as abstract/virtual or to override them. Unity calls these methods via reflection, so you can just add the vanilla event methods to your AnimateGlowArrows
class directly.
As for guaranteeing that the subclass fires it's Start
event first, you could do a couple things:
Edit the script execution order for your base class and all its inheritors
Remove the
Start
event from the base entirely and move that logic to aprotected void Initialize ()
method to do your initialization, then have the derived classes call that from theirStart
events.
It would seem the only way to resolve my issue was to edit the script execution order, which I have never been introduced to until now. Thanks to a solution for my question in the forums section in addition to this answer, I have the process running orderly. I do hope this execution order change does not bring any future issues upon new script developments.
Your answer

Follow this Question
Related Questions
Car from Unity's standard assets giving me of null reference errors 0 Answers
Changing positions of all GameObjects in an array 0 Answers
Problem with random music script 1 Answer
How do I set the texture on a child object of an instantiated prefab object? 2 Answers
How to start animation of child 2 Answers