Search Unity

Losing reference to objects - maybe because of prefab link?

Discussion in 'Scripting' started by my_username, May 28, 2015.

  1. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    I'm having an issue with an object pool that I've created where it 'loses' its references to the instantiated objects. When I check the debugger after populating the array it shows that the objects initialized fine, but as soon as I try to access them, a NullReferenceException is thrown. I created another object before this to spawn characters using the same idea (filling an object pool using cloned prefabs), and it works without issue.

    Additionally, the object pools shows up in the inspector, so they do exist, the PickupManager just doesn't keep the references to them for some reason.

    Code (CSharp):
    1. //relevant code from PickupManager.cs
    2. public GameObject bouquet;
    3. public GameObject[] bouquetPool = new GameObject[5];
    4.  
    5. void Start () {
    6.     for (int i = 0; i < bouquetPool.Length; i++) {
    7.         bouquetPool[i] = GameObject.Instantiate(bouquet);
    8.         bouquetPool[i].SetActive(false);
    9.     }
    10. }
    11.  
    12. // called by objects that generate pickups
    13. public void RollForPickup(Vector2 pos) {
    14.     if(Random.Range(0, 101) <= pickupChancePct) {
    15.         for (int i = 0; i < bouquetPool.Length; i++) {
    16.             if (!bouquetPool[i].activeInHierarchy) {
    17.                 bouquetPool[i].transform.position = pos;
    18.                 bouquetPool[i].SetActive(true);
    19.                 break;
    20.             }
    21.         }
    22.     }
    23. }
     
  2. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    Are you sure RollForPickup is being called after Start()? Try renaming Start to Awake and throwing in a Debug.Log for both to see when they're called. Also your Random.Range logic is not quite right, should probably be (1, 101) but I'd just use floating point numbers to make it easier to understand.
     
  3. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    Just tried that, but RollForPickup is called after collisions that only happen after 4-5 seconds of the game running.
     
  4. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    And what line is the NullReferenceException thrown on?
     
  5. Defero

    Defero

    Joined:
    Jul 9, 2012
    Posts:
    200
    I guess if it works in some cases as you mentioned, it's probably worth looking at the difference in the object you're instantiating
     
  6. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    16: if (!bouquetPool.activeInHierarchy) {...}

    I've run the debugger before and checked to see if they were initialized and they were. Just added this to Start to verify, and it works perfectly. But for whatever reason as soon as RollForPickup is called they go AWOL.

    Code (CSharp):
    1. void Start() {
    2.         Debug.Log("PickupManager.Start()");
    3.         for (int i = 0; i < bouquetPool.Length; i++) {
    4.             bouquetPool[i] = GameObject.Instantiate(bouquet);
    5.             bouquetPool[i].SetActive(false);
    6.         }
    7.         // Test to make sure the object was instantiated
    8.         bouquetPool[0].transform.position = new Vector2(0, 0);
    9.         bouquetPool[0].SetActive(true);
    10.     }
     
  7. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    The only difference is that the Bouquet object doesn't have a script attached to it. It has an empty parent object, a child with a SpriteRenderer and an Animation, and another child with a SpriteRenderer and PolygonCollider. The only script that has access and/or changes the bouquet objects is in the code I posted.

    I'm thinking that there's some sort of Unity quirk (maybe related the prefab system) that I don't know about that's causing this because I can usually pinpoint where a problem is or find a solution after some googling, but, I can't seem to come up with anything.
     
  8. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    Instantiate returns Object, you have to cast to GameObject
     
  9. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Is it possible that somewhere in your script (perhaps when you pick up the object), you accidentally call Destroy()?

    Usually (read: almost always) it is a small mistake in code causing problems, not Unity.
     
  10. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    I tried multiple ways (GameObject.Instantiate(bouquet), Object.Instantiate(bouquet) as GameObject, Object.Instantiate(bouquet)), and get the same result.

    I checked all the other relevant collision methods (OnTriggerEnter2D(), in this case) of the other objects, and the only one that would change anything in this script is the Player object, but it only sets the bouquet object to inactive if it collides with it, but that's irrelevant in this case.
    Code (CSharp):
    1.  
    2. // called in player object's OnTriggerEnter2D method
    3.  if (collider.tag == "Bouquet") {
    4.  collider.transform.root.gameObject.SetActive(false);
    5.  }
    6.  
    Here's the full PickupManager.cs script:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. // Acts as a messenger between RPM and the dead peds
    5. // Objects that can drop pickups will access the RollForPickup(Vector2) method
    6. public class PickupManager : MonoBehaviour {
    7.     public int pickupChancePct = 10;
    8.     public GameObject bouquet;
    9.     public GameObject[] bouquetPool;
    10.     public int poolSize = 5;
    11.    
    12.     void Start() {
    13.         bouquetPool = new GameObject[poolSize];
    14.         for (int i = 0; i < bouquetPool.Length; i++) {
    15.             bouquetPool[i] = GameObject.Instantiate(bouquet) as GameObject;
    16.             bouquetPool[i].SetActive(false);
    17.         }
    18.     }
    19.  
    20.     void Update() {
    21.        
    22.     }
    23.  
    24.     // called by objects that generate pickups
    25.     public void RollForPickup(Vector2 pos) {
    26.         if(Random.Range(1, 101) <= pickupChancePct) {
    27.             for (int i = 0; i < bouquetPool.Length; i++) {
    28.                 if (!bouquetPool[i].activeInHierarchy) {
    29.                     bouquetPool[i].transform.position = pos;
    30.                     bouquetPool[i].SetActive(true);
    31.                     break;
    32.                 }
    33.             }
    34.         }
    35.     }
    36. }
    37.  
     
  11. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    So I tried the script myself and It works flawlessly. (Casting wasn't the issue! o_O)
    Something else is going on here.
    Perhaps you can show us the bouquet and the PickupManager and how they are setup in the editor?
     
  12. Defero

    Defero

    Joined:
    Jul 9, 2012
    Posts:
    200
    Sorry, this line is confusing me a bit. Are you trying to instantiate a child prefab?
     
  13. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    I don't think this describes much, but, it's showing that the objects are all there. The overlaid inspector views are how the Bouquet prefab is set up which is the object that the PickupManager is holding. But, for whatever reason, if I go into the debugger in MonoDevelop it's showing that all the elements of the bouquetPool array are null.
     
  14. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    See the picture above, it's empty in that it only has a transform component.
     
  15. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    Does logging using something like this give you any more information?
    Code (csharp):
    1.  
    2. public void RollForPickup(Vector2 pos) {
    3.     if(bouquetPool == null) {
    4.         Debug.Log("(bouquetPool == null)");
    5.     } else {
    6.         if(Random.Range(1, 101) <= pickupChancePct) {
    7.             for (int i = 0; i < bouquetPool.Length; i++) {
    8.                 if (bouquetPool[i] == null) {
    9.                     Debug.Log("(bouquetPool["+i+"] == null)");
    10.                 } else {
    11.                     if (!bouquetPool[i].activeInHierarchy) {
    12.                         bouquetPool[i].transform.position = pos;
    13.                         bouquetPool[i].SetActive(true);
    14.                         break;
    15.                     }
    16.                 }
    17.             }
    18.         }
    19.     }
    20. }
    21.  
     
  16. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    if (bouquetPool == null) never triggers. For whatever reason, I couldn't get (bouquetPool[0] == null) to return null until I tried it again just now. It didn't tell me anything besides that by the time RollForPickup() is called all the elements are null.

    I added an if(bouquetPool[0] == null) in Update() (again, this didn't return null until I tried it now, for whatever reason), and it triggers once the game starts, but, the debugger is showing that the elements of the array are holding GameObjects.
     
  17. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    I see bouquetPool is public now, does changing that line to
    Code (csharp):
    1.     [System.NonSerialized] public GameObject[] bouquetPool;
    yield different results?

    It may have a size in the inspector from the beginning. Which would lead me to think that it is an order of initialization issue.
     
  18. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    Now the bouquetPool array becomes null. It still exists up until the point that RollForPickup is called.
     
  19. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    I cant see why this is happening, short of looking at the whole thing I ccant think of what to do
     
  20. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    Got tired of playing with it and just settled on this. It's not the most efficient way to do it but considering it only happens once every minute or so it I don't imagine it will be a problem - and, most importantly - it works.

    Code (CSharp):
    1. // called by objects that generate pickups
    2.     public void RollForPickup(Vector2 pos) {
    3.         if(Random.Range(1, 101) <= pickupChancePct) {
    4.             GameObject.Instantiate(bouquet).transform.position = pos;
    5.         }
    6.     }
     
  21. my_username

    my_username

    Joined:
    Feb 13, 2015
    Posts:
    11
    I definitely appreciate you taking the time to try and help, thank you. I think for now since it's a relatively trivial piece of code, I'll just stick with what I have and move on with the project, but it still kind of bugs me that I don't know why that's happening.
     
  22. vbs

    vbs

    Joined:
    Sep 8, 2014
    Posts:
    24
    Same thing is happening to me, I've tried many things: passing in the game object prefab from assigned public GO reference on GameManager class/instance to another TerrainManager GO/Script, to having it a public class on TerrainManager, The GameManager calls a method "InitializePool()", and I've tried passing the GO prefab there. The funny thing is I have asserts checking that the expected prefab exists before being passed into the method, as well as within the receiving method and they never trigger. The InitializePool() method loops and is supposed to clone the prefab, and it successfully Instantiates a new clone ONCE, then the reference is lost. So after "Instantiate", the returned object reference gets lost, as I try to add it to a Dictionary.

    Edit: So what I've found is that as long as I don't try to do anything except Instantiate the GO prefab, it works fine, as soon as I try to assign it to the Dictionary it does not work anymore.

    Error:

    GameObject.cs:

    Code (CSharp):
    1.     //Awake is always called before any Start functions
    2.     void Awake()
    3.     {
    4.         //Check if instance already exists
    5.         if (Instance == null)
    6.  
    7.             //if not, set instance to this
    8.             Instance = this;
    9.  
    10.         //If instance already exists and it's not this:
    11.         else if (Instance != this)
    12.  
    13.             //Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
    14.             Destroy(gameObject);
    15.  
    16.         //Sets this to not be destroyed when reloading scene
    17.         DontDestroyOnLoad(gameObject);
    18.  
    19.  
    20.         if (UsePrefabGenerator == true)
    21.         {
    22.             Debug.Log("UsePrefabGenerator");
    23.             Debug.Assert(ChunkPrefab);
    24.             PrefabGenerator = PrefabGeneratorInstance.GetComponent<TerrainManager>();
    25.             PrefabGenerator.InitializePool(Radius, ChunkPrefab);
    26.             PrefabGenerator.GenerateGrid(Radius, PlayerTransform.position);
    27.         }
    28.     }
    TerrainManager.cs:

    Code (CSharp):
    1. private Dictionary<Vector2, GameObject> Chunks;
    2.  
    3.         public void InitializePool(int radius, GameObject prefab)
    4.         {
    5.             Debug.Assert(prefab);
    6.             Debug.Log(radius);
    7.             // Create grid of prefabs
    8.             for (int x = 0; x < radius; x++)
    9.             {
    10.                 for (int z = 0; z < radius; z++)
    11.                 {
    12.                     Vector2 position = new Vector3(x, z);
    13.                     GameObject newPrefab = Instantiate(prefab, transform.position, transform.rotation);
    14.                     Debug.Assert(newPrefab);
    15.                     newPrefab.name = "Chunk_" + x + "_" + z;
    16.                     newPrefab.transform.SetParent(transform);
    17.                     Debug.Assert(newPrefab);
    18.                     Chunks.Add(position, newPrefab);
    19.                 }
    20.             }
    21.         }
     

    Attached Files:

    Last edited: Sep 12, 2017
  23. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    This thread is quite old...
    Where is the code for "Chunks" ?

    I think .. the OP (when this was written) had the error that he was trying to initialize the array outside of any method. The array would then be non-null (I think), but all of its elements are null.
    In any event.. that may or may not be your situation :)