'In Unity3D, Parent Obj collide with Child Obj makes weird result. (OnTriggerEnter, OverlapBoxNonAlloc)

I'll describe the long long story as short as I can.

1.It's '3D Infinite Runner' Game. (Like 'Subway Surfer'.)

2.There's 'Player Character Object' , 'Coin Object' , 'Magnet Object'.

3-1.Player Character Object has boxcollider,rigidbody.

3-2.Magnet Item Object has capsulecollider(isTrigger),rigidbody(isKinematic).

3-3.Coin Object has capsulecollider(isTrigger),rigidbody(isKinematic). (I'll explain why these consumable objects have kinematic rigidbody below.)

4.When Player trigger magnet item, 'Player OnTriggerEnter' turn 'isMagnet local bool switch' to 'true'. And 'Player Update' check the switch in next frame and starts 'Magnet Coroutine Function'.

5.What the 'Magnet Coroutine Function' does is just 'yield return new WaitForSeconds(1.0f)' and '++magnetTimeCounter' every second and when magnetTimeCounter reaches the limit(which is <20), it just turn off the 'isMagnet' switch.

6.In 'Player Update', there's this code.

public List<GameObject> magnetCoins = new List<GameObject>();
private const float k_MagnetSpeed = 10f;
private Vector3 k_HalfExtentsBox = new Vector3(20.0f, 1.0f, 1.0f);
private int coinLayerMask = 1 << 8;

if (isManget)
{
       Collider[] returnColls = new Collider[20];
            
       int nb = Physics.OverlapBoxNonAlloc(transform.position, k_HalfExtentsBox, returnColls, transform.rotation, coinLayerMask);
            

       for (int i = 0; i < nb; ++i)
       {
           Controller_ConsumableCoinBase returnCoin = returnColls[i].GetComponent<Controller_ConsumableCoinBase>();

           if (returnCoin != null && !magnetCoins.Contains(returnCoin.gameObject) && returnCoin.gameObject.activeSelf)
           {
                    returnColls[i].transform.SetParent(transform);
                    
                    magnetCoins.Add(returnColls[i].gameObject);
           }
       }
}
if (0 < magnetCoins.Count)
      for (int i = 0; i < magnetCoins.Count; ++i)
      {
           magnetCoins[i].transform.position = Vector3.MoveTowards(magnetCoins[i].transform.position, transform.position, k_MagnetSpeed * Time.deltaTime);
       }

6-1.What it does is 'Use Physics.OverlapBoxNonAlloc to check certain area every 'Player Update' to collect 'Objects with LayerMask 8(which is 'Coin')' and Add them to List and make the collected coins 'MoveTowards Player Object'.

7.'Coin' and 'Magnet Item' has 'isKinematic rigidbody' because when the Player Object with Magnet pull the consumables toward itself, the consumables are set to 'child' to the player object. This was the only way to 'trigger the OnTriggerEnter in both child obj and parent obj' when they collide with each other.

8.When the 'Coin Obj' collide with Player Obj, 'Player OnTriggerEnter' checks below.

 if (other.tag.Equals("Coin"))
{
            if (magnetCoins.Contains(other.gameObject))
                magnetCoins.Remove(other.gameObject);

            ObjPooler.Destroy(other.gameObject);    
}

9.What 'ObjPooler' does is just 'Object Pooling'. The code is nothing sophisticated but very long so I'll just write pseudo code of it below.

Class ObjPooler
{
    void Destroy(GameObject obj)
    {
        1.check if the obj has 'Pooler Component'.
        2.No pooler, just Object.Destroy(obj).
        3.Yes pooler, 'ObjPool.Push(obj)'.
        // What 'ObjPool.Push function does is, set 'obj' to child of 'Object Pool transform(which is nothing. I do this only to make sure that the objects are 'Not DontDestroyOnLoad')' then Stack.Push to the object pool and SetActive(false).
        // There's also 'ObjPool.Pop' function. What it does is 'Stack.Pop' and 'SetActive(true)' and return the pooled obj. Long code but very simple and straightforward.
    }
}

10.Now there's 'Track Spawner'. What it does is 'Spawn Track'. And there's 'Track Controller'. What it does is 'in Update, check the distance between Player Obj and itself and if the 'distance' reaches certain point(which is >20f), it 'generates new Track(call 'SpawnTrack' function in 'TrackSpawner' class)' then destroys all the 'Consumables(coins and items)' inside of it using 'foreach' then destroy itself.

11.All the 'Tracks' and 'Consumables' are also 'Object Pooled'.

12.What 'SpawnTrack' does is 'Generate new Coin objs using ObjPool.Pop and set the coins child to an empty obj(hierachy filter), and generate new Track and put the 'empty obj with coin children' in middle of the Track. There are long and boring lines of codes which does the boring transform.position stuff but nothing special.

Now here's the question. While 'generate, destroy tracks and coins', I don't know where,when,why but the 'Coin Objects' are placed in a new track position but still 'is child of Player Object' and 'moves toward Player Object'. Which means it still is in the 'Player Object Magnet List'. The coins set to children of 'Object Pool' within the 'Destroy process' as I explained above but is still 'a child of Player Object' in a 'New Track Position'.

In Player's OnTriggerEnter, it certainly List.Remove the coin objects out of 'Player Magnet List' then set it to a child of 'Object Pool' through 'Object Pooling process' but is still a child of Player Object and still is in the Player Magnet List.

What makes it weirder is that this coin glitch happens time to time. It works as I intended in most of time but the 'weird coin glitch' happens 'sometimes'.

My assumption is that maybe there's something I don't fully know about 'Collision between parent/child' and 'OverlapBoxNonAlloc' so I've researched it for days but still haven't got a clue.

I've tried my best to describe my situation in short and straightforward way but still is pretty long. PLEASE HELP!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source