[SOLVED] Why does property return null? What am I doing wrong?

I’m doing a Free Fall level (infinitely falling), instead of implementing Custom Physics for this purpose, I decided to make Background move down while platforms move up to don’t mess with character movement.
Here is my problematic part of Platform Spawner script:

    (...)
    IEnumerator SpawnPlatform()
    {
        GameObject newPlatform = null;
        Vector3 lastPos = Vector3.zero,
            newPos = Vector3.zero;

        bool isThisFirstLoop = true;

        while(true)
        {
            yield return null;

            Debug.Log("> " + this.gameObject.name + " () Platform Count: " + spawnedPlatformCount);
           
            if (CheckIfPlatformLimitReached() == true)
                continue;

            newPos = CalculateNewPosition();
            if (!isThisFirstLoop && CheckIfNewPositionInDeadZone(lastPos, newPos) == true)
                continue;

            newPlatform = InstantiateNewPlatform(newPos);
            if (newPlatform == null)
                continue;

            lastPos = newPos;

            if(isThisFirstLoop)
                isThisFirstLoop = false;
            yield return new WaitForSecondsRealtime(spawnInterval);
        }
    }
    (...)
    GameObject InstantiateNewPlatform(Vector3 newPos)
    {
        if (backgroundSpawner.SelectedBackground == null)
            return null;

        GameObject selectedPlatform = backgroundSpawner.SelectedBackground.name.Contains("bg_0") ?
                            platform[1] : platform[0];

        return Instantiate(selectedPlatform, newPos, Quaternion.identity, platformListRootObject.transform);
    }
}

And related Background Spawner script:

public GameObject SelectedBackground { get; private set; }

    public GameObject InstantiateNewBackground(Transform bgSpawn)
    {
        SelectedBackground = bg[Random.Range(0, bg.Length)];

        return Instantiate(SelectedBackground, bgSpawn.position, Quaternion.identity, bgList);
    }

I tried to debug code and noticed “SelectedBackground” property is null in “Platform Spawner” script. I don’t know what is going wrong. Any ideas?

TY for taking time ^.^ !

Well, SelectedBackground is an auto-implemented property with a reference type, so it will be null if you haven’t set it to anything yet or if the last thing you set it to was null.

It looks like you’re trying to initialize it in InstantiateNewBackground(), but I guess either that function didn’t get called before you needed it, or else you set it to an element from your “bg” array that was, itself, null.

I debugged code ~10 times, by setting Breakpoints to that two Instantiate… methods. This is what happens each time;
First; InstantiateNewBackground() get called.
Second; InstantiateNewPlatform() get called, and surprisingly it spawns one Platform.
After that, SelectedBackground property always returns null in “PlatformSpawner”, but not in its own script (“BackgroundSpawner”).

  • No, “bg” array isn’t null, BackgroundSpawner keeps spawning new Backgrounds as it should do.
    - After spawning ONE platform (“InstantiateNewPlatform()”), function doesn’t get called again.

Isn’t this weird? Maybe customizing “Script Execution Order” , will change outcome. Any other ideas?

After changing the “Script Execution Order” according to; First execute “BackgroundSpawner” then execute “PlatformSpawner”. Set Breakpoints on same functions (“Instantiate… methods”), ran 6 times.
Three times debugger returned (0x1), this a is bad return value,
Two times the “InstantiateNewBackground()” method never got called, even weirder - Backgrounds were keep spawning one after one…
And in the last try Unity crashed, I got “Send a bug report” window.

If there is someone from Unity, I can share my project via Drive, if this is eligable. Can you take a look at my project and tell me what am I doing wrong?:face_with_spiral_eyes:

Antistone suggested making sure that the array doesn’t contain null entries. But if the background spawner is working, that may or may not be the problem. Things to check:

  • is there only one background spawner?
  • or, does the background spawner get deleted and recreated?
  • do any other methods set SelectedBackground?

A possible workaround is to expose a simpler method on BackgroundSpawner, like “bool Background0Selected()”. You can either derive that

public bool BackgroundSelected => SelectedBackground.name.Contains("bg_0");

or

public bool BackgroundSelected { get; private set; }

and update it in “InstantiateNewBackground()”.

You can debug a property being null by breaking in the setter. That would mean rewriting the property to use an explicit private field, instead of an implicit field:

private GameObject _selectedBackground;
public GameObject SelectedBackground {
  get => _selectedBackground;
  set {
    _selectedBackground = value;
    if (_selectedBackground == null) {
      Debug.Log("SelectedBackground was cleared."); // add a breakpoint here
    }
  }
}

Everything that happens in code happens for a reason. It’s just a question of tracing the cause back to its source.

@bgulanowski TY for the response. I still couldn’t solve problem;

Firstly, I used explicit private field you suggested; also deleted the null check from PlatformSpawner. I didn’t get a message from Debug.Log that I wrote in BackgroundSpawner 's SelectedBackground properties setter, also Debugger was showing that SelectedBackground property is either bg_0 or bg_1. When it comes to PlatformSpawner, it is null. It is still spawning just ONE platform at the start of the game then stops.

  • Also there is no second BackgroundSpawner, never gets destroyed/recreated.
  • And, no, SelectedBackground isn’t being set from any other script than BackgroundSpawner, also not being get from any other script than PlatformSpawner.

Secondly, I used two bool fields, here is updated “BackgroundSpawer” and “PlatformSpawner”. (I’m posting full scripts for both of them, there will be something that I missed.)
PlatformSpawner:

using System.Collections;
using UnityEngine;

public class PlatformSpawner : MonoBehaviour
{
    [SerializeField] GameObject[] platform = new GameObject[2];
    [SerializeField] EndlessPitBackgroundSpawner backgroundSpawner = null;
    [SerializeField] Transform platformListRootObject = null;
    [SerializeField] Transform spawnAreaL = null, spawnAreaR = null;
    [SerializeField] float spawnInterval = 1f, spawnDeadZone = 0.1f;
    [SerializeField] int spawnedPlatformLimit = 10;
    int spawnedPlatformCount = 0;

    void Start()
    {
        StartCoroutine(SpawnPlatform());
    }

    IEnumerator SpawnPlatform()
    {
        GameObject newPlatform = null;
        Vector3 lastPos = Vector3.zero,
            newPos = Vector3.zero;

        bool isThisFirstLoop = true;

        while(true)
        {
            yield return null;

            Debug.Log("> " + this.gameObject.name + " () Platform Count: " + spawnedPlatformCount);
           
            if (CheckIfPlatformLimitReached() == true)
                continue;

            newPos = CalculateNewPosition();
            if (!isThisFirstLoop && CheckIfNewPositionInDeadZone(lastPos, newPos) == true)
                continue;

            newPlatform = InstantiateNewPlatform(newPos);
            if (newPlatform == null)
                continue;

            lastPos = newPos;

            if(isThisFirstLoop)
                isThisFirstLoop = false;
            yield return new WaitForSecondsRealtime(spawnInterval);
        }
    }

    bool CheckIfPlatformLimitReached()
    {
        spawnedPlatformCount = platformListRootObject.childCount;
        return (spawnedPlatformCount >= spawnedPlatformLimit) ? true : false;
    }

    Vector3 CalculateNewPosition()
    {
        return new Vector3(Random.Range(spawnAreaL.position.x, spawnAreaR.position.x),
                        spawnAreaL.position.y);
    }

    bool CheckIfNewPositionInDeadZone(Vector3 lastPos, Vector3 newPos)
    {
        Vector3 deltaVector = newPos - lastPos;

        bool isNewXInDeadZone = (Mathf.Abs(deltaVector.x) <= spawnDeadZone) ? true : false;
        bool isNewYInDeadZone = (Mathf.Abs(deltaVector.y) <= spawnDeadZone) ? true : false;

        return (!isNewXInDeadZone && !isNewYInDeadZone) ? false : true;
    }

    GameObject InstantiateNewPlatform(Vector3 newPos)
    {
        GameObject selectedPlatform = null;

        if (backgroundSpawner.BackgroundZeroSelected)
            selectedPlatform = platform[1];
        else if (backgroundSpawner.BackgroundOneSelected)
            selectedPlatform = platform[0];

        return Instantiate(selectedPlatform, newPos, Quaternion.identity, platformListRootObject.transform);
    }
}

BackgroundSpawner:

using UnityEngine;

public class EndlessPitBackgroundSpawner : MonoBehaviour
{
    [SerializeField] Transform bgList = null;
    [SerializeField] GameObject[] bg = new GameObject[2];

    private GameObject selectedBackground = null;
    public GameObject SelectedBackground
    {
        get => selectedBackground;
        private set
        {
            selectedBackground = value;
            if (selectedBackground == null)
                Debug.Log("> selectedBackground is NULL.");
        }
    }

    public bool BackgroundZeroSelected { get; private set; }
    public bool BackgroundOneSelected { get; private set; }

    public GameObject InstantiateNewBackground(Transform bgSpawn)
    {
        int index = Random.Range(0, bg.Length);

        BackgroundZeroSelected = (index == 0) ? true : false;
        BackgroundOneSelected = !BackgroundZeroSelected;

        SelectedBackground = bg[index];

        return Instantiate(SelectedBackground, bgSpawn.position, Quaternion.identity, bgList);
    }
}

This method doesn’t directly using SelectedBackground property, but result is same. Also I don’t know if it is important but, InstantiateNewBackground(…) method is being called by current background. When it reaches a point while moving down, it calls this method by giving bgSpawn argument that is child of background that calls this method. This way Backgrounds keep spawning one after one.

While waiting for a response I’m going to reverse the script to change referencing mechanism. Maybe adjusting which platform is going to be next from BackgroundSpawner script will make a difference.

Finally, I solved the problem. Also, I noticed there was actually two problems; and that customizing Script Execution Order, doesn’t work as I thought.

First, to solve NULLReferenceException, I used a bool check for if platformIsReadyToSpawn, because it became clear that until my Entrance Background passes certain point it doesn’t call method for Instantiating Next Background. Also, because it is unclear if reference between BgSpawner and PltSpawner is working right, I added a method to call from BgSpawner that changes next platform to spawn. It still needs some tweak, though…

Second; after I implemented new mechanics for Free Falling Level, I forgot that I call all platforms from a point that is below Camera and Player, and all platforms where instantiating in same Y position…

Lastly, I’m sharing final scripts, in case any one will find them useful…

BackgroundSpawner:

using UnityEngine;

public class EndlessPitBackgroundSpawner : MonoBehaviour
{
    [SerializeField] Transform bgList = null;
    [SerializeField] GameObject[] bg = new GameObject[2];

    private GameObject selectedBackground = null;
    public GameObject SelectedBackground
    {
        get => selectedBackground;
        private set
        {
            selectedBackground = value;
            if (selectedBackground == null)
                Debug.Log("> selectedBackground is NULL.");
        }
    }

    [SerializeField] PlatformSpawner platformSpawner = null;

    public GameObject InstantiateNewBackground(Transform bgSpawn)
    {
        int index = Random.Range(0, bg.Length);

        platformSpawner.ChangeNextPlatform((index == 0) ? 1 : 0);
        platformSpawner.IsPlatformReadyToSpawn = true;

        SelectedBackground = bg[index];

        return Instantiate(SelectedBackground, bgSpawn.position, Quaternion.identity, bgList);
    }
}

PlatformSpawner:

using System.Collections;
using UnityEngine;

public class PlatformSpawner : MonoBehaviour
{
    [SerializeField] GameObject[] platform = new GameObject[2];
    GameObject selectedPlatform = null;
    [SerializeField] Transform platformListRootObject = null;
    bool isPlatformReadyToSpawn = false;
    public bool IsPlatformReadyToSpawn { get => isPlatformReadyToSpawn; set => isPlatformReadyToSpawn=value; }
    [SerializeField] Transform spawnAreaL = null, spawnAreaR = null;
    [SerializeField] float spawnInterval = 1f, spawnDeadZone = 0.1f;
    [SerializeField] int spawnedPlatformLimit = 10;
    int spawnedPlatformCount = 0;

    void Start()
    {
        StartCoroutine(SpawnPlatform());
    }

    IEnumerator SpawnPlatform()
    {
        GameObject newPlatform = null;
        Vector3 lastPos = Vector3.zero,
            newPos = Vector3.zero;

        bool isThisFirstLoop = true;

        while(true)
        {
            yield return null;

            if (!IsPlatformReadyToSpawn)
                continue;

            Debug.Log("> " + this.gameObject.name + " () Platform Count: " + spawnedPlatformCount);
           
            if (CheckIfPlatformLimitReached() == true)
                continue;

            newPos = CalculateNewPosition();
            if (!isThisFirstLoop && CheckIfNewPositionInDeadZone(lastPos, newPos) == true)
                continue;

            newPlatform = InstantiateNewPlatform(newPos);

            lastPos = newPos;

            if(isThisFirstLoop)
                isThisFirstLoop = false;
            yield return new WaitForSecondsRealtime(spawnInterval);
        }
    }

    bool CheckIfPlatformLimitReached()
    {
        spawnedPlatformCount = platformListRootObject.childCount;
        return (spawnedPlatformCount >= spawnedPlatformLimit) ? true : false;
    }

    Vector3 CalculateNewPosition()
    {
        return new Vector3(Random.Range(spawnAreaL.position.x, spawnAreaR.position.x),
                        spawnAreaL.position.y);
    }

    bool CheckIfNewPositionInDeadZone(Vector3 lastPos, Vector3 newPos)
    {
        Vector3 deltaVector = newPos - lastPos;
        return (Mathf.Abs(deltaVector.x) <= spawnDeadZone) ? false : true;
    }

    public void ChangeNextPlatform(int nextPlatformIndex)
    {
        if (selectedPlatform != platform[nextPlatformIndex])
            selectedPlatform = platform[nextPlatformIndex];
        if (selectedPlatform == null)
            Debug.LogWarning("selectedPlatform is NULL. What is going on?");
    }

    GameObject InstantiateNewPlatform(Vector3 newPos)
    {
        return Instantiate(selectedPlatform, newPos, Quaternion.identity, platformListRootObject.transform);
    }
}

Also, TY @bgulanowski and @Antistone for responses.