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. Dismiss Notice

Ignore multiple OnTriggerEnter for same parent colliders

Discussion in 'Scripting' started by Daybreaker32, Oct 30, 2014.

  1. Daybreaker32

    Daybreaker32

    Joined:
    Jun 11, 2014
    Posts:
    73
    Umm, I'm having some issues with triggers and colliders.



    I decided to use multiple collider on enemy (A and B) so that I could have more complex damage detection than single collider with better performance than mesh collider. It worked well when I shoot a bullet which has small collider (single enemy damaged once every single bullet hit). But didn't work for bigger trigger damage source.

    When a bomb (C) explodes and activate it's trigger, single enemy can be damaged multiple times because OnTriggerEnter on the bomb's script is called for every single parts of the enemy that inside the trigger (then I realize that this can also happen if the bullet has a big collider). The expected outcome is that an enemy damaged once for a single explosion source.

    The flow is: a bomb with disabled trigger detonates when it hit the ground and enables it's trigger, the monobehaviour on the bomb call OnTriggerEnter and get the enemy script on the triggered enemy (parts) and then call the Hit method on enemy monobehaviour.

    The solution that came in mind is that the OnTriggerEnter on the bomb flag an "isDamage" bool and send the damage int to the enemy monobehaviour without do the damage calculation and then the enemy do the damage calculation every update (and re-flag the bool so that there can only be one damage for every frame). Isn't this a bit difficult? Am I missing something obvious or some basic workaround?
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,184
    A very, very easy workaround is to enumerate all of your damage sources.

    Simply have a publicly available int counter that you grab every time you send out damage from a damage source, and when something takes damage, it remembers the damage instances it's received, and ignores damage if it has already taken that damage.

    So something like:

    Code (csharp):
    1. //DamageCounter script:
    2. static int _counter
    3. public static int counter {
    4.   get {
    5.     return _counter++;
    6.   }
    7. }
    8.  
    9. //explosion script:
    10. void explode {
    11.   int counter = DamageCounter.counter;
    12.   foreach(BodyPart hit in TheListOfStuffTheExplosionHit) {
    13.     hit.damaged(myDamage, counter);
    14.   }
    15. }
    16.  
    17. //BodyPart script:
    18. void damaged(int damage, int counter) {
    19.   parent.damaged(damage, counter);
    20. }
    21.  
    22. //Enemy script:
    23. void damaged(int damage, int counter) {
    24.   if(counterList.contains(counter)
    25.     return;
    26.   hp -= damage;
    27.   counterList.Add(counter);
    28. }
     
    Daybreaker32 likes this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    What I do is I have a tag called 'root'.

    Every 'entity' in my game (player, NPC, so on so forth) is made up of several GameObjects, of which there is some top most parent that could be considered the 'root' or 'base' of that individual entity.

    I then have an extension method that searches up the parent chain until it finds the 'root', if none is found it returns the self.

    You can see this in action here (note, I have a component called 'MultiTag' that allows more than one tag on the same GameObject, this handles that as well):
    Code (csharp):
    1.  
    2.         public static GameObject FindRoot(this GameObject go)
    3.         {
    4.             if (go == null) return null;
    5.  
    6.             var root = FindParentWithTag(go.transform, SPConstants.TAG_ROOT);
    7.             return (root != null) ? root : go; //we return self if no root was found...
    8.         }
    9.      
    10.         public static GameObject FindParentWithTag(this Transform t, string stag)
    11.         {
    12.             if (t.tag == stag) return t.gameObject;
    13.             MultiTag multitag = t.GetComponent<MultiTag>();
    14.             if (multitag != null && multitag.ContainsTag(stag)) return t.gameObject;
    15.  
    16.             Transform p = t.parent;
    17.             while (p != null)
    18.             {
    19.                 if (p.tag == stag) return p.gameObject;
    20.  
    21.                 multitag = p.GetComponent<MultiTag>();
    22.                 if (multitag != null && multitag.ContainsTag(stag)) return p.gameObject;
    23.  
    24.                 p = p.parent;
    25.             }
    26.  
    27.             return null;
    28.         }
    29.  
    use is simple:

    Code (csharp):
    1.  
    2. var someRoot = someGameObject.FindRoot();
    3.  
    Now all colliders inside some entity will return the same exact GameObject for its root. So in the explosion script every collider that is hit during the explosion I will 'FindRoot' on and place in a temporary list, if the list already contains that GameObject, then I've already dealt with this entity and I don't do anything, otherwise I apply damage.

    Code (csharp):
    1.  
    2. private List<GameObject> _hits = new List<GameObject>();
    3.  
    4. void OnTriggerEnter(Collider coll)
    5. {
    6.     var root = coll.gameObject.FindRoot();
    7.     if(_hits.Contains(root)) return; //we already dealt with this guy
    8.  
    9.     _hits.Add(root);
    10.     //I have a special method called 'FindComponent' that will search an entire entity for a script as well
    11.     var health = root.FindComponent<HealthScript>();
    12.     if(health != null) health.Damage(DamageAmount);
    13. }
    14.  
    You can see this all here:

    Constants (where my tags have a const representation for clean coding):
    https://code.google.com/p/spacepupp...trunk/SpacepuppyUnityFramework/SPConstants.cs

    MultiTag (allows more than one tag on a GameObject):
    https://code.google.com/p/spacepupp...se/trunk/SpacepuppyUnityFramework/MultiTag.cs

    GameObjectUtil (all GameObject extension methods, where 'FindRoot' is):
    https://code.google.com/p/spacepupp...SpacepuppyUnityFramework/Utils/GameObjUtil.cs

    ComponentUtil (all Component extension methods, where 'FindComponent' is):
    https://code.google.com/p/spacepupp...acepuppyUnityFramework/Utils/ComponentUtil.cs


    Of course there's several parts to my solution because it falls out of a larger design.

    You can scale it back for yourself. You don't need 'MultiTag' for instance.
     
    Daybreaker32 likes this.
  4. Daybreaker32

    Daybreaker32

    Joined:
    Jun 11, 2014
    Posts:
    73
    Thanks guys! yeah so like mapping/flagging and ignoring same incoming damage or same "top root" target damage right?
    So there is no "easy-basic-automatic" way like treat multiple collider on same parents as a single collider or something?
    I mean this is no beginner collider problem right? I'm afraid I miss something like a tick on collider component
     
  5. A.Killingbeck

    A.Killingbeck

    Joined:
    Feb 21, 2014
    Posts:
    483
    Can't you just put a rigidbody component on the root, then have your OnCollisionEnter logic on that same gameobject? That way, any collisions which occur beneath that rigidbody in the hierarchy will get sent to the gameobject with the rigidbody.
     
    Daybreaker32 likes this.
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    That works on the side of the Rigidbody getting hit.

    But the bombs trigger collider's OnTriggerEnter still fires for every Collider.
     
  7. Daybreaker32

    Daybreaker32

    Joined:
    Jun 11, 2014
    Posts:
    73
    Yeah, it still called multiple times

    Gotta go with a hashset of root enemy inside the trigger to check before do the damage calculation on the bullet script