Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Destroy collectible (e.g. coin) on collision

Discussion in 'Scripting' started by Fuzzytron, Mar 17, 2018.

  1. Fuzzytron

    Fuzzytron

    Joined:
    Feb 22, 2018
    Posts:
    13
    Hi people, I have a collision system in which if the player collides with a collectible coming towards them from the right hand side (parralaxed objects) then they gain a point. However the collectible currently does not disappear, the user simply faces through it and gets their point. I would like to change this so that the collectible disappears.

    I can get the user/character to disappear on collision however if i use
    Code (CSharp):
    1.  
    2.     void OnTriggerEnter2D(Collider2D col)
    3.     {
    4.         if (col.gameObject.tag == "Collectible")
    5.         {
    6.             PlayerScored();
    7.             Destroy(col.gameObject);
    8.         }
    9.         if (col.gameObject.tag == "Death")
    10.         {
    11.             rigidbody.simulated = false;
    12.             PlayerDied();
    13.         }
    14.  
    15.     }
    16.  
    17. }
    18.  
    the Destroy(col.gameObject); line destroys every instance of the object resulting in this error:

    MissingReferenceException: The object of type 'Transform' has been destroyed but you are still trying to access it.
    Your script should either check if it is null or you should not destroy the object.
    UnityEngine.Transform.get_localPosition () (at C:/buildslave/unity/build/artifacts/generated/common/runtime/TransformBindings.gen.cs:41)
    Parallaxer.Shift () (at Assets/scripts/Parallaxer.cs:140)
    Parallaxer.Update () (at Assets/scripts/Parallaxer.cs:88)

    If I change it to Destroy(gameObject); the player is destroyed instead but no errors occur and the game continues. If i remove the destroy line of code completely it returns to how I described it at the beginning, going through the collectible, getting the point but it does not disappear.

    Does anyone have advice on how to get the single instance of the collectible to disappear without breaking it? I am a novice in Unity.

    Many thanks.
     
  2. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
    You may need to make it a prefab by dragging it from the hierarchy into your assets folder. Then delete it, and drag the prefab onto a public variable to be instantiated.
     
  3. Fuzzytron

    Fuzzytron

    Joined:
    Feb 22, 2018
    Posts:
    13
    Yah it is already a prefab as the parralaxing spawning of the collectible functions just the deletion of only one instance of it:

    upload_2018-3-17_16-32-38.png
     
  4. CDMcGwire

    CDMcGwire

    Joined:
    Aug 30, 2014
    Posts:
    133
    The error is a result of your Parallaxing script trying to access the coin after it has been destroyed. Because that script is maintaining its own list of game objects, it will need to be updated when an object on the list has been destroyed. The simple way would be to leave the list as is, and add a null check to the parallax script before it tries to modify any object on the list. You could also trigger an event when a coin is picked up, then the parallax script can listen for it and remove all destroyed objects when triggered.

    Although, if it's a foreground object, why does the parallax script even need to know about the coin? Normally, foreground objects don't parallax and the camera movement is enough.
     
    Fuzzytron likes this.
  5. Fuzzytron

    Fuzzytron

    Joined:
    Feb 22, 2018
    Posts:
    13
    I think I understand what you are saying but unsure how to add a null check in the write place. The reason the collectibles are parallax is that they are randomly generating off screen in the same way as the background and the enemies. This is the parralax script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Parallaxer : MonoBehaviour
    6. {
    7.  
    8.     class PoolObject
    9.     {
    10.         public Transform transform;
    11.         public bool inUse;
    12.         public PoolObject(Transform t)
    13.         {
    14.             transform = t;
    15.         }
    16.         public void Use()
    17.         {
    18.             inUse = true;
    19.         }
    20.         public void Dispose()
    21.         {
    22.             inUse = false;
    23.         }
    24.     }
    25.  
    26.     [System.Serializable]
    27.     public struct YSpawnRange
    28.     {
    29.         public float min;
    30.         public float max;
    31.     }
    32.  
    33.  
    34.     public GameObject Prefab;
    35.     public int poolSize;
    36.     public float shiftSpeed; //how fast the object is moving
    37.     public float spawnRate; //how often it spawns
    38.  
    39.     public YSpawnRange ySpawnRange;
    40.     public Vector3 defaultSpawnPos; //its starting position
    41.     public bool spawnImmediate; //whether it will spawn immediately
    42.     public Vector3 immediateSpawnPos; //the immediate spawn position
    43.     public Vector2 targetAspectRatio; //target aspect ratio, accessibility stuff
    44.  
    45.     float spawnTimer;
    46.     float targetAspect;
    47.     PoolObject[] poolObjects;
    48.     GameManager game;
    49.  
    50.     void Awake() //for initialisation
    51.     {
    52.         Configure();
    53.     }
    54.  
    55.     void Start() //initialisate game
    56.     {
    57.         game = GameManager.Instance;
    58.     }
    59.  
    60.     void OnEnable()
    61.     {
    62.         GameManager.GameOverConfirmed += GameOverConfirmed;
    63.     }
    64.  
    65.     void OnDisable()
    66.     {
    67.         GameManager.GameOverConfirmed -= GameOverConfirmed;
    68.     }
    69.  
    70.     void GameOverConfirmed()
    71.     {
    72.         for(int i = 0; i < poolObjects.Length; i++)
    73.         {
    74.             poolObjects[i].Dispose();
    75.             poolObjects[i].transform.position = Vector3.one * 1000; //off screen
    76.         }
    77.  
    78.         if (spawnImmediate)
    79.         {
    80.             SpawnImmediate();
    81.         }
    82.     }
    83.  
    84.     void Update()
    85.     {
    86.         if (game.GameOver) return;
    87.  
    88.         Shift();
    89.         spawnTimer += Time.deltaTime;
    90.         if (spawnTimer > spawnRate)
    91.         {
    92.             Spawn();
    93.             spawnTimer = 0;
    94.         }
    95.     }
    96.  
    97.     void Configure()
    98.     {
    99.         targetAspect = targetAspectRatio.x / targetAspectRatio.y;
    100.         poolObjects = new PoolObject[poolSize];
    101.         for (int i = 0; i < poolObjects.Length; i++)
    102.         {
    103.             GameObject go = Instantiate(Prefab) as GameObject;
    104.             Transform t = go.transform;
    105.             t.SetParent(transform); //has the parent of object script is linked to
    106.             t.position = Vector3.one * 1000; //offscreen again to avoid user seeing it
    107.             poolObjects[i] = new PoolObject(t);
    108.         }
    109.         if (spawnImmediate)
    110.         {
    111.             SpawnImmediate();
    112.         }
    113.     }
    114.  
    115.     void Spawn()
    116.     {
    117.         Transform t = GetPoolObject();
    118.         if (t == null) return; //true means pool size is too small
    119.         Vector3 pos = Vector3.zero;
    120.         pos.x = (defaultSpawnPos.x * Camera.main.aspect)/targetAspect;
    121.         pos.y = Random.Range(ySpawnRange.min, ySpawnRange.max);
    122.         t.position = pos;
    123.     }
    124.  
    125.     void SpawnImmediate()//two objects to avoid gaps between objects
    126.     {
    127.         Transform t = GetPoolObject();
    128.         if (t == null) return; //true means pool size is too small
    129.         Vector3 pos = Vector3.zero;
    130.         pos.x = (immediateSpawnPos.x * Camera.main.aspect)/targetAspect;
    131.         pos.y = Random.Range(ySpawnRange.min, ySpawnRange.max);
    132.         t.position = pos;
    133.         Spawn();
    134.     }
    135.  
    136.     void Shift()
    137.     {
    138.         for (int i = 0; i < poolObjects.Length; i++)
    139.         {
    140.             poolObjects[i].transform.localPosition += -Vector3.right * shiftSpeed * Time.deltaTime;
    141.             CheckDisposeObject(poolObjects[i]); //checking if the position is less than spawn position
    142.         }
    143.     }
    144.  
    145.     void CheckDisposeObject(PoolObject poolObject)
    146.     {
    147.         if (poolObject.transform.position.x < ( -defaultSpawnPos.x * Camera.main.aspect)/targetAspect) //should spawn offscreen so assume negative x is out of sight so can be removed
    148.         {
    149.             poolObject.Dispose();
    150.             poolObject.transform.position = Vector3.one * 1000; //high value the user wont see
    151.         }
    152.     }
    153.  
    154.     Transform GetPoolObject()
    155.     {
    156.         for (int i = 0; i < poolObjects.Length; i++)
    157.         {
    158.             if (!poolObjects[i].inUse)
    159.             {
    160.                 poolObjects[i].Use();
    161.                 return poolObjects[i].transform;
    162.             }
    163.         }
    164.         return null;
    165.     }
    166. }
    167.  
    I think I found someone with a similar issue here https://answers.unity.com/questions/51331/how-to-check-if-something-is-null.html that you are talking about, but unsure how to translate over to my code?
     
  6. CDMcGwire

    CDMcGwire

    Joined:
    Aug 30, 2014
    Posts:
    133
    So, in your Shift() function, you'd replace:
    Code (CSharp):
    1. poolObjects[i].transform.localPosition += -Vector3.right * shiftSpeed * Time.deltaTime;
    with:
    Code (CSharp):
    1. if (poolObjects[i] != null) {
    2.     poolObjects[i].transform.localPosition += -Vector3.right * shiftSpeed * Time.deltaTime;
    3. }
    You may also need to move your CheckDispose() function if that causes an error too.
     
    Doug_B likes this.
  7. Fuzzytron

    Fuzzytron

    Joined:
    Feb 22, 2018
    Posts:
    13
    Yeah still getting the same error:

    MissingReferenceException: The object of type 'Transform' has been destroyed but you are still trying to access it.
    Your script should either check if it is null or you should not destroy the object.
    UnityEngine.Transform.get_localPosition () (at C:/buildslave/unity/build/artifacts/generated/common/runtime/TransformBindings.gen.cs:41)
    Parallaxer.Shift () (at Assets/scripts/Parallaxer.cs:142)
    Parallaxer.Update () (at Assets/scripts/Parallaxer.cs:88)

    Code (CSharp):
    1.  
    2.     void Shift()
    3.     {
    4.         for (int i = 0; i < poolObjects.Length; i++)
    5.         {
    6.             if (poolObjects[i] != null)
    7.             {
    8.                 poolObjects[i].transform.localPosition += -Vector3.right * shiftSpeed * Time.deltaTime;
    9.             }
    10.             CheckDisposeObject(poolObjects[i]); //checking if the position is less than spawn position
    11.         }
    12.     }
    13.  
    14.     void CheckDisposeObject(PoolObject poolObject)
    15.     {
    16.         if (poolObject.transform.position.x < ( -defaultSpawnPos.x * Camera.main.aspect)/targetAspect) //should spawn offscreen so assume negative x is out of sight so can be removed
    17.         {
    18.             poolObject.Dispose();
    19.             poolObject.transform.position = Vector3.one * 1000; //high value the user wont see
    20.         }
    21.     }
    22.  
    What do you mean by move the CheckDispose() function? Where to?
     
  8. Fuzzytron

    Fuzzytron

    Joined:
    Feb 22, 2018
    Posts:
    13
    Code (CSharp):
    1. void Shift()
    2.     {
    3.         for (int i = 0; i < poolObjects.Length; i++)
    4.         {
    5.             if(poolObjects[i] != null)
    6.             {
    7.                 poolObjects[i].transform.localPosition += -Vector3.right * shiftSpeed * Time.deltaTime;
    8.                 CheckDisposeObject(poolObjects[i]); //checking if the position is less than spawn position
    9.             }
    10.            
    11.         }
    12.     }
    13.  
    14.     void CheckDisposeObject(PoolObject poolObject)
    15.     {
    16.         if (poolObject.transform.position.x < ( -defaultSpawnPos.x * Camera.main.aspect)/targetAspect) //should spawn offscreen so assume negative x is out of sight so can be removed
    17.         {
    18.             poolObject.Dispose();
    19.             poolObject.transform.position = Vector3.one * 1000; //high value the user wont see
    20.         }
    21.     }
    Either position of CheckDisposeObect in the shift function gives the same error :(
     
  9. CDMcGwire

    CDMcGwire

    Joined:
    Aug 30, 2014
    Posts:
    133
    Could you post the error you get with the Check inside the if statement?
     
  10. Fuzzytron

    Fuzzytron

    Joined:
    Feb 22, 2018
    Posts:
    13
    Error:

    MissingReferenceException: The object of type 'Transform' has been destroyed but you are still trying to access it.
    Your script should either check if it is null or you should not destroy the object.
    UnityEngine.Transform.get_localPosition () (at C:/buildslave/unity/build/artifacts/generated/common/runtime/TransformBindings.gen.cs:41)
    Parallaxer.Shift () (at Assets/scripts/Parallaxer.cs:142)
    Parallaxer.Update () (at Assets/scripts/Parallaxer.cs:88)


    Code:

    Code (CSharp):
    1.  
    2.     void Shift()
    3.     {
    4.         for (int i = 0; i < poolObjects.Length; i++)
    5.         {
    6.             if (poolObjects[i] != null)
    7.             {
    8.                 poolObjects[i].transform.localPosition += -Vector3.right * shiftSpeed * Time.deltaTime;
    9.                 CheckDisposeObject(poolObjects[i]); //checking if the position is less than spawn position
    10.             }
    11.  
    12.         }
    13.     }
    14.  
    15.     void CheckDisposeObject(PoolObject poolObject)
    16.     {
    17.         if (poolObject.transform.position.x < ( -defaultSpawnPos.x * Camera.main.aspect)/targetAspect) //should spawn offscreen so assume negative x is out of sight so can be removed
    18.         {
    19.             poolObject.Dispose();
    20.             poolObject.transform.position = Vector3.one * 1000; //high value the user wont see
    21.         }
    22.     }
    23.  
     
  11. CDMcGwire

    CDMcGwire

    Joined:
    Aug 30, 2014
    Posts:
    133
    Ah, okay. So the destroy operation didn't remove the entire GameObject, only its Transform. So if you null check, you'll have to be more specific and do:

    Code (CSharp):
    1. if (poolObjects[i].transform != null) { }
     
  12. Fuzzytron

    Fuzzytron

    Joined:
    Feb 22, 2018
    Posts:
    13
    Great so now the collectible disappears as intended and points are still awarded, however its now made in even larger error when game over occurs. E.g. when the user dies, they are presented with the game over screen and you click the menu button at the bottom to return to the menu, this now breaks everything:

    MissingReferenceException: The object of type 'Transform' has been destroyed but you are still trying to access it.
    Your script should either check if it is null or you should not destroy the object.
    UnityEngine.Transform.set_position (Vector3 value) (at C:/buildslave/unity/build/artifacts/generated/common/runtime/TransformBindings.gen.cs:28)
    Parallaxer.GameOverConfirmed () (at Assets/scripts/Parallaxer.cs:75)
    GameManager.ConfirmGameOver () (at Assets/scripts/GameManager.cs:168)
    UnityEngine.Events.InvokableCall.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:165)
    UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:58)
    UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:36)
    UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:45)
    UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:50)
    UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:261)
    UnityEngine.EventSystems.EventSystem:Update()
     
  13. CDMcGwire

    CDMcGwire

    Joined:
    Aug 30, 2014
    Posts:
    133
    You still have something trying to access the destroyed objects. Seems to be in the parallax script again. You're going to want to remove the null entries from the list. One way to do it, since you're already looping through all the objects, is to mark each null index, then delete them all once the loop finishes.

    However, as long as you're deleting your objects from outside of the parallax script, you're going to have to keep manually syncing the data, so if you change something after fixing the issue, it might come back.

    If you're going to continue with the strategy of the parallax script controlling coin movement, then you could have the coins mark themselves to-be-deleted, which the parallax script would pick up later, or they could ask the parallax script directly to remove them from the list. Something like:

    Code (CSharp):
    1. public class Coin : MonoBehaviour {
    2.     private ParallaxController controller;
    3.     public ParallaxController Controller { private get; set; }
    4.    
    5.     private void OnCollide(Collider other) { // This line probably not accurate
    6.         controller.GetRidOf(this);
    7.     }
    8. }
    Then the parallax script would give each spawned coin a reference to itself. Thus your config method would become this:

    Code (CSharp):
    1. void Configure()
    2.     {
    3.         targetAspect = targetAspectRatio.x / targetAspectRatio.y;
    4.         poolObjects = new PoolObject[poolSize];
    5.         for (int i = 0; i < poolObjects.Length; i++)
    6.         {
    7.             GameObject go = Instantiate(Prefab) as GameObject;
    8.             Transform t = go.transform;
    9.             t.SetParent(transform); //has the parent of object script is linked to
    10.             t.position = Vector3.one * 1000; //offscreen again to avoid user seeing it
    11.             poolObjects[i] = new PoolObject(t);
    12.            
    13.             // NEW STUFF
    14.             t.GetComponent<Coin>().Controller = this;
    15.         }
    16.         if (spawnImmediate)
    17.         {
    18.             SpawnImmediate();
    19.         }
    20.     }
    Lastly, you'd add that GetRidOf() method in the parallax script which finds the object in the pool and removes it. You currently have a fixed size array, so that might be a bit of a finicky operation. You may want to consider implementing your pool with a typed List<>, since you're iterating in order and removing from the middle.
     
  14. Newca_Coder

    Newca_Coder

    Joined:
    Apr 27, 2020
    Posts:
    2
    Was this post ever resolved? I am having the same issue with my parallax script, once I collect the coins
     
  15. unity_JD2J4nIYAFE_vw

    unity_JD2J4nIYAFE_vw

    Joined:
    Jul 22, 2020
    Posts:
    1
    Not Sure if this was ever resolved but I kinda figuered it out with trying all the above stuff as per trial and error, and finally got to where you guys are right now.Soif You got stuck like me
    like at this below point ...

    instead of doing the (GetRidOf) part which (CDMcGwire) suggested...not that he was wrong. he just gave an alternate approach and it tested out fine .... but if you guys get stuck at this part where you get the bellow errors.

    That just means that when you hit the replay button and the bellow error popsup...

    i guess we are all following the flappy bird project. so weshould all have the same files...and functions..

    On replay you will get the big chunk of errors.which just means that the

    the replay button calls(ConfirmeGameOver() function) which intern calls the (OnGameOverConfirmed()) which is in the Paralaxer script.

    just replace that with
    void OnGameOverConfirmed()
    {
    for (int i = 0; i < poolObjects.Length; i++)
    {
    if (poolObjects.transform != null) // call the null check function on this line and everything should be fine
    {
    poolObjects.Dispose();//
    poolObjects.transform.position = UnityEngine.Vector3.one * 1000;//
    }
    }//
    if (spawnImmediate)
    {
    SpawnImmediate();//
    }//
    }//

    the entire big chunk of error dissapears...
    Hope it helps the next person that jumps to this page in the future....

    Happy flappybird to you too...
    Took me a long time to figure it out since Visual studioc doesnt let me debug unity projects on the go... wish they could find a way to let us debug all the codes via VS things would have been easier to find.

    But what the hell...
    old school (Trial and Error) is the best way.
    Just saved 3 hours of your time, hope you guys pay it forward 3 folds.

    this officially ends the unsolved mystery




     
    Last edited: Jul 22, 2020