Script communication failed - null reference exception

So, I have 4 scripts : GunInfo(where i store some information about gun entity uses), WeaponInfo(where i store some additional info about weapon entity in my game uses), PlayerShooting(where I instantiate my bullet and attempt to store that GunInfo to be accesed by the bullet in WeaponInfo script) and lastly BulletMovement(where I implement bullet movement based on that GunInfo script that I stored in WeaponInfo script).
Here are the scripts:

using UnityEngine;

public class GunInfo : MonoBehaviour
{
    [SerializeField] private int magazine, bulletCount;
    [SerializeField] private float bulletSpeed, reloadTime, flyTime;

    public int Magazine { get; private set; }
    public int BulletCount { get; private set; }
    public float BulletSpeed { get; private set; }
    public float ReloadTime { get; private set; }
    public float BulletFlyTime { get; private set; }

    private void Awake()
    {
        Magazine = magazine;
        BulletCount = bulletCount;
        BulletSpeed = bulletSpeed;
        ReloadTime = reloadTime;
        BulletFlyTime = flyTime;
    }

    public void Reload()
    {
        var reloadBullets = Mathf.Min(BulletCount, magazine);
        Debug.Log("Reloaded " + reloadBullets + " bullets");
        BulletCount -= reloadBullets;
        Magazine += reloadBullets;
    }

    public void HadShot()
    {
        Magazine--;
    }
}
using UnityEngine;

public class WeaponInfo : MonoBehaviour
{
    [SerializeField] private int startingDamage;
    [SerializeField] private bool destroyable;
    [SerializeField] private User user;

    public enum User { Ally, Enemy }
    public User UserName { get; private set; }
    public int Damage { get; private set; }
    public bool Destroyable { get; private set; }
    public GunInfo Gun { get; private set; }

    private void Awake()
    {
        Damage = startingDamage;
        Destroyable = destroyable;
        UserName = user;
    }

    public void UsedGun(GunInfo gun)
    {
        Gun = gun;
    }
}
using System.Collections;
using UnityEngine;

public class PlayerShooting : MonoBehaviour
{
    [SerializeField] private float fireRate;
    [SerializeField] private GameObject bullet;

    private float lastShot = 0f;
    private GunInfo gunInfo;
    private bool isReloading = false;

    private void Awake()
    {
        gunInfo = GetComponent<GunInfo>();
        if(gunInfo == null)
        {
            Debug.Log("Can't find weapon");
        }
    }

    private void Start()
    {
        UIManager.Instance.UpdateBullets(gunInfo.Magazine, gunInfo.BulletCount);
    }

    private void Update()
    {
        if (Input.GetMouseButtonUp(0))
        {
            Fire();
        }
    }

    private void Fire()
    {
        if((gunInfo.BulletCount == 0 && gunInfo.Magazine == 0) || isReloading)
        {
            return;
        }
        else
        {
            if (gunInfo.Magazine == 0)
            {
                StartCoroutine(ReloadCoroutine());
            }
            else if (Time.time > lastShot + fireRate)
            {
                var bulletObj = Instantiate(bullet, transform.position, transform.rotation);
                bulletObj.GetComponent<WeaponInfo>().UsedGun(gunInfo);
                if(bulletObj.GetComponent<WeaponInfo>().Gun == null)
                {
                    Debug.Log("No");
                }
                lastShot = Time.time;
                gunInfo.HadShot();
            }
            UIManager.Instance.UpdateBullets(gunInfo.Magazine, gunInfo.BulletCount);
        }
    }
 
    private IEnumerator ReloadCoroutine()
    {
        isReloading = true;
        yield return new WaitForSeconds(gunInfo.ReloadTime);
        gunInfo.Reload();
        Debug.Log("Reloaded");
        UIManager.Instance.UpdateBullets(gunInfo.Magazine, gunInfo.BulletCount);
        isReloading = false;
    }
}
using UnityEngine;

public class BulletMovement : MonoBehaviour
{
    float timer = 0f;
    private GunInfo gunInfo;

    private void Awake()
    {
        gunInfo = GetComponent<WeaponInfo>().Gun;
    }

    private void Update()
    {
        if(timer <= gunInfo.BulletFlyTime)
        {
            Debug.Log("Here");
            transform.position += gunInfo.BulletSpeed * Time.deltaTime * transform.right;
        }
        else Destroy(gameObject);

        timer += Time.deltaTime;
    }
}

Error occurs on line 15 in the BulletMovement script:
if(timer <= gunInfo.BulletFlyTime),
which means that gunInfo is null.
Furthermore, I identifed that each GetComponent works properly and lastly I realised that the Gun property on line 10:

gunInfo = GetComponent<WeaponInfo>().Gun;

wasnt accesed correctly. I cant seem to find out why it is null.

You’ve run into a problem that is tricky to spot.
The key thing you have to know is that when GameObject.Instantiate is called then Awake() will be called IMMEDIATELY on the object.

Line 49ff in PlayerShooting with comments:

var bulletObj = Instantiate(bullet, transform.position, transform.rotation);
// Here Awake() has already been called on all your components.
// That's why the Gun is not yet set in BulletMovement.Awake().
// Setting it in the line below is too late.
bulletObj.GetComponent<WeaponInfo>().UsedGun(gunInfo);

How to solve this? → You have three options:

A) Do NOT use Awake() to initialize your components but instead make initialization explicit with a custom method. This way you control when things are set up and in what order. You will find some articles online advocating against using Unity’s magic methods for initialization at all. One of the reason is that ensuring proper order is tricky as the Awake() call order is not defined.

B) You DISABLE your prefab before instantiation via SetActive(false);.
Then instantiate it.
Then ENABLE your prefab (not the instance) again. We do this to avoid changing the active state of the prefab. Actually, you should get whether or not is was active, store that in a variable, and then restore that state.
Then you set your “Gun” and only …
Then set the instance active which triggers Awake().

C) You remove your gun code from Awake() and put it in Start() since that is run later. Though this will only work until you run into the same problem with conflicting Start() methods. Oh what joy :smile:

What you choose is up to you. I can tell you that for most complex systems, where you wire together many components, approach A will be better (more maintainable). A is the easiest solution to reason through once you return to your code in a year or so.

There sorta even is an option D where you ADD your components on demand via code, but I assume since yours are already on the prefab you have some data set in the inspector.

Hope that helps you out.
Now you decide A, B, C or D ?

^ ^ ^ This is excellent advice. In fact, I have a snippet pattern for exactly this:

Factory Pattern in lieu of AddComponent (for timing and dependency correctness):

Either way, now that you know this weird timing edge case, for nullrefs in general:

The answer is always the same… ALWAYS!

How to fix a NullReferenceException error

https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

Three steps to success:

  • Identify what is null ← any other action taken before this step is WASTED TIME
  • Identify why it is null
  • Fix that

Could I use Script Execution Order from Project Settings - variation of A option you suggested? I had alike problem yesterday when making a singleton. I wanted to be consistent and use Awake anytime possible.

Yes you could, but that’s an even worse solution because it lives outside of the codebase.

That doesn’t make sense. Awake() isn’t appropriate and @_geo1 just explained why. Why would you insist on using something inappropriate? It is your project I suppose, but … well, it’s not really a win if you force blind arbitrary consistency over actual functional correctness.

I want to keep things simple and not make code too much long. And yeah, thanks for such a detailed reply.

The execution order will not help you here since the wrong order is NOT due to two Awake() calls being called out of order but the order of Instantiate() → Awake() → your code, when what you need is Instantiate() → your code → Awake().

Avoid relying on the Script Execution order whenever possible. It’s a one trick fix and is very inflexible. It has it’s uses (intializing singletons may be one of them).

Alright, I will see what I can do.

I disagree. :slight_smile:

The following pattern always works for all singletons in Unity, without exception. More-critically, this pattern respects and was engineered with the Unity timing diagram in mind.

Simple Singleton (UnitySingleton):

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}

There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

And finally there’s always just a simple “static locator” pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

WARNING: this does NOT control their uniqueness.

WARNING: this does NOT control their lifecycle.

public static MyClass Instance { get; private set; }

void OnEnable()
{
  Instance = this;
}
void OnDisable()
{
  Instance = null;     // keep everybody honest when we're not around
}

Anyone can get at it via MyClass.Instance., but only while it exists.

I do too (with myself). That’s why I put the “may” in there as I personally never do it but I coud imagine it might be used for getting some things that are unique initialized. I remember TextMeshPro doing it. Though, that’s not the hill I’m gonna die on :smile:

I changed every Awake to Start(except I will keep Awake for singletons) and now everything works perfectly - tried running my game a few times. I will keep everything as it is for now and hopefully not get any exceptions again(although I know I will so I will keep what both of you said in mind). Thanks for accurate and detailed advices!

Glad it’s working now :slight_smile:

Cya, when your Start() methods start conflicting :wink:

I wouldn’t change every Awake to Start. They serve two different purposes in my books:

  • Awake: Self Initialisation (relies on nothing else)
  • Start: Initialisation that relies on other objects being initialised

Sometimes you want to use Awake, sometimes you want to use Start, sometimes both, and sometimes neither, depending on the situation.