Search Unity

Checking for a specific collider. [SOLVED]

Discussion in 'Scripting' started by StarLightShadow, Jan 24, 2020.

  1. StarLightShadow

    StarLightShadow

    Joined:
    Jul 9, 2019
    Posts:
    7
    SOLVED: Example of fully functioning scripts.


    I'm a programmer who codes in a way that is heavily reliant on Public Variables in the inspector in order to connect different things together, and a big reason I have for doing this is so that I can make my Scripts easily modular.

    I'm trying to figure out a way to effectively create working functions that act as OnTriggerEnter, Exit, and Stay but for a specific collider that I can find and connect to the script.


    A theory I have for how to pull this off was to Make all the Colliders separate game objects, and to put a secondary script on those. This script would check for OnTriggerEnter, Exit and Leave as any normal script, but when it did, it would be called upon by the main Script to activate a specific function that is given the necessary data from the Trigger collision.

    But I don't want the secondary script to necessarily call on the Primary script's function through connecting to it by its own public Scriptclass,(That would mean I'd have to make all of the in-Editor connections in the trigger object, as well as making several more of them. A massive detour for my Modular method of work.)

    I want it to be the other way around that way I can just write the function as necessary, make a public gameObject/Scriptclass for the function to be activated by, plug it into the script, and it will work.

    So the best way I can explain it, is that I need a reverse of SendMessage that only runs when it retrieves a message from another script, which script it retrieves the message from being a public gameObject/Scriptclass that can be edited in the editor.


    There's another concept I've seen to do something similar to this, but it has the problem of forcing you to put all the colliders on the same game Object and is kinda broken in the first place. I'd much prefer the first idea.

    This idea is to make another script on everything the first script is supposed to be collidable with and in that script it stores and gives to the Main Script the data of what it hits to compare it and see which one it's colliding with.

    The reason it's broken is that I found out that it comes with a delay because the OnTriggerEnter on the second script always runs after the OnTriggerEnter from the first script has finished, thus storing the variable, but not giving it to the first script until you repeat the process.

    In order to fix this, I'd need a way To Force the Second Script's OnTriggerEnter function to Run Before going to the next line of code in the OnTriggerEnter in the first one's. is there way to do this?
     
    Last edited: Feb 8, 2020
  2. StarLightShadow

    StarLightShadow

    Joined:
    Jul 9, 2019
    Posts:
    7
    I haven't tried creating an example of what I'm looking for but with all the in-Editor connections being on the trigger Object, so I apologize if I'm so off base that not even doing it THAT way is possible.
     
  3. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    If I understand what you're looking for correctly, you can achieve this by broadcasting a callback during OnTriggerEnter etc. like normal, but instead of using SendMessage to target specific components you use delegates which allow the listeners to subscribe for the callback instead.

    So you have components that you can attach to the collider GameObjects that broadcast a callback (using a delegate) whenever a specific event (in this example OnTriggerEnter) occurs.
    Code (CSharp):
    1. [RequireComponent(typeof(Collider))]
    2. public class OnTriggerEnterBroadcaster : MonoBehaviour
    3. {
    4.     // This callback is broadcast to all listeners during OnTriggerEnter.
    5.     public Action<Collider, Collider> onTriggerEntered;
    6.  
    7.     private void OnTriggerEnter(Collider other)
    8.     {
    9.        // If there are any listeners...
    10.        if(onTriggerEntered != null)
    11.        {
    12.             // ...broadcast the callback.
    13.             onTriggerEntered(GetComponent<Collider>(), other);
    14.        }
    15.     }
    16. }
    Then you have a base class for components that can subscribe to these broadcasters and do something whenever the callback is broadcast.
    Code (CSharp):
    1. public abstract class OnTriggerEnterListener : MonoBehaviour
    2. {
    3.     // Link this to a specific broadcaster using the inspector.
    4.     public OnTriggerEnterBroadcaster trigger;
    5.  
    6.     private void OnEnable()
    7.     {
    8.         // Subscribes to the callback.
    9.         trigger.onTriggerEntered += OnTriggerEntered;
    10.     }
    11.  
    12.     // Extending classes override this method to determine what happens during OnTriggerEnter.
    13.     protected abstract void OnTriggerEntered(Collider trigger, Collider collider);
    14.  
    15.     private void OnDisable()
    16.     {
    17.         if(trigger != null)
    18.         {
    19.             // Unsubscribes from the callback.
    20.             trigger.onTriggerEntered -= OnTriggerEntered;
    21.         }
    22.     }
    23. }
    And here's an example class that implements the OnTriggerEnterListener base class:
    Code (CSharp):
    1. public class OnTriggerEnterDebug : OnTriggerEnterListener
    2. {
    3.     protected override void OnTriggerEntered(Collider trigger, Collider collider)
    4.     {
    5.         Debug.Log(collider.name + " entered trigger "+trigger.name);
    6.     }
    7. }
     
    Last edited: Jan 24, 2020
    StarLightShadow likes this.
  4. StarLightShadow

    StarLightShadow

    Joined:
    Jul 9, 2019
    Posts:
    7
    I am so sorry it took me so long to be able to test this out, it works, Thank you so much!
    One more question, however. A large part of my reason for wanting this is so that I can use it to
    set up the script to react differently to things entering multiple different triggers.

    How would it be possible to, working with this code, Make the public OnTriggerBroadcaster a set list(Size only editable in code, not in the inspector) of slots with each slot having a unique and permanent name, while still being able to use "For each" to deal with the Subscribe/Unsubscribe to callback functions without having to duplicate them for each Trigger added to the script?

    Also if a Script class is inheriting from a script class that is inheriting from Monobehavior, does it also inherit from Monobehavior? cause if not that's incredibly worrying because Abstracts can't be the main class.

    Edit: ALSO also, Whenever I make a duplicate of the Main script the OnTriggerListener class begins to conflict with the duplicate because it's in a global namespace.
     
    Last edited: Jan 30, 2020
  5. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    Glad to hear this worked for you!

    You'd want to create a wrapper class that contains a private array of OnTriggerEnterBroadcaster and their names.

    Then have the class implement the IEnumerable<OnTriggerBroadcaster> interface to make it support foreach.

    You could also implement a this[string name] indexer that could be used to retrieve a broadcaster with a certain name.

    I'm not really sure what you mean by "without having to duplicate them for each Trigger added to the script". If you mean that you'd want the same code to support multiple different broadcaster classes, you can make the array in the wrapper class contain interface or abstract types from which all the different broadcaster classes derive. You could also make it generic with a type constraint with pretty much the same result.

    Yes, derived classes always inherit the whole inheritance path. MonoBehaviour itself already inherits from Behaviour which inherits from Component which inherits from Object.

    I'm not sure what you mean by "Main script". If you duplicate a script a conflict will occur no matter what namespace the script is in. You need to change the name of the class to avoid this.
     
  6. StarLightShadow

    StarLightShadow

    Joined:
    Jul 9, 2019
    Posts:
    7
    1: While it would be cool to have the public Broadcaster field compatible with both Trigger and Collision versions of the Broadcaster class and activate different functions depending on which, Let's take this one step at at time, I was referring to in the Trigger Listener class, where the Subscribe/UnSubscribe from callback code have to be duplicated if you manually add more then one Broadcaster field.

    How do you set up a Wrapper class and IEnumerable in this context? nothing I try has worked.

    2: By "Main Script," I'm referring to the script listening out for orders from the Broadcasters. The one with the OnTrigger-Listener class. If I Make a new script and move over the code from the template I created, the OnTrigger-Listener class acts like it's in the global namespace and thus conflicts with the already previous one.

    In order to fix this I'd either need the Listener class to work while not being on the Global namespace so each Script can have its own iteration of OnTrigger-Listener, or I'd need a way to add an override to each script that overrides the Whole Array of Broadcasters As well as (Preferably)whether or not certain functions need to be included, allowing me to just put OnTrigger-Listener onto its own Script that everything else borrows its code from.
     
  7. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    Here's a simple example wrapper class that implements IEnumerable<OnTriggerEnterBroadcaster>.
    It doesn't have unique names for the triggers but maybe it'll still help you get started.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3.  
    4. public class OnTriggerEnterCallback : IEnumerable<OnTriggerEnterBroadcaster>
    5. {
    6.     private OnTriggerEnterBroadcaster[] triggers;
    7.  
    8.     public OnTriggerEnterCallback(int size)
    9.     {
    10.         triggers = new OnTriggerEnterBroadcaster[size];
    11.     }
    12.  
    13.     public IEnumerator<OnTriggerEnterBroadcaster> GetEnumerator()
    14.     {
    15.         return (triggers as IEnumerable<OnTriggerEnterBroadcaster>).GetEnumerator();
    16.     }
    17.  
    18.     IEnumerator IEnumerable.GetEnumerator()
    19.     {
    20.         return triggers.GetEnumerator();
    21.     }
    22. }
    You could also use Dictionary<string, OnTriggerEnterBroadcaster> instead of the array to hold the triggers, to allow you to give each trigger a unique name.


    And you could also add some helper methods for easily subscribing a listener to all the broadcasters, to avoid having to use foreach every time (you'll need to change OnTriggerEntered into a public method for this to work).
    Code (CSharp):
    1.     public void Subscribe(OnTriggerEnterListener listener)
    2.     {
    3.         for(int n = 0, count = triggers.Length; n < count; n++)
    4.         {
    5.             triggers[n].onTriggerEntered += listener.OnTriggerEntered;
    6.         }
    7.     }
    8.  
    9.     public void Unsubscribe(OnTriggerEnterListener listener)
    10.     {
    11.         for(int n = 0, count = triggers.Length; n < count; n++)
    12.         {
    13.             triggers[n].onTriggerEntered -= listener.OnTriggerEntered;
    14.         }
    15.     }

    Sorry but I still don't understand what you are doing that is causing name conflicts to occur. Creating new classes that derive from OnTriggerEnterListener should be very simple. Maybe if you'd post some code I could better understand what you're doing that is causing these problems.
     
  8. StarLightShadow

    StarLightShadow

    Joined:
    Jul 9, 2019
    Posts:
    7
    Thank you so much! I figured out how to have the base script override what needed to be overridden and it all works as intended!

    For those interested, Here is a fully assembled set of scripts that makes this setup work.
    With added instructions and OnCollision functionality.

    TestScript
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. //The Main Script class as usual. REMEMBER TO RENAME THIS TO THE SCRIPT'S FILE NAME WHEN YOU COPY EVERYTHING.
    7. public class MultiTriggerTestScript : OnTriggerListener
    8. {
    9.     public override void OnEnable() //Must Include.
    10.     {
    11.         TriggerInitialize(); //Must Include.
    12.     }
    13.  
    14.     #region Trigger Broadcasters to Listen for.
    15.     //Example Syntax of a TriggerBroadcaster object: ------- public OnTriggerBroadcaster trigger1;
    16.     public OnTriggerBroadcaster trigger1;
    17.     public OnTriggerBroadcaster trigger2;
    18.     public OnTriggerBroadcaster trigger3;
    19.    
    20.     //for every TriggerBroadcaster object you must add it to the 'triggers.Add' portion of this override.
    21.     void TriggerInitialize(){{if (triggers.Count == 0){
    22.                 //Here  VVVVVV
    23.                 triggers.Add(trigger1);
    24.                 triggers.Add(trigger2);
    25.                 triggers.Add(trigger3);
    26.             }base.OnEnable();}}
    27.     #endregion
    28.  
    29.     #region Surrogate Functions for OnTrigger/Collision Enter/Exit.
    30.     //Stay is not included cause it's more preformant have the intended code in Update, and for it to only run when a switch is on that is turned on by Enter and turned off by Exit.
    31.  
    32.     //Once called they will return two variables, "activator" which is what the collider hit, (just like the normal Trigger Functions,) and "zone" which is the collider itself.
    33.     //In order to tell which Collider is being activated check if zone = the intended TriggerBroadcasting object.
    34.     //Individual functions can be left out if not neccesary.
    35.  
    36.     public override void OnTriggerEntered(Collider zone, Collider activator)
    37.     {
    38.         Debug.Log(activator.name + " entered zone " + zone.name);
    39.     }
    40.     public override void OnTriggerExited (Collider zone, Collider activator)
    41.     {
    42.         Debug.Log(activator.name + " left zone " + zone.name);
    43.     }
    44.  
    45.     public override void OnCollisionEntered(Collider zone, Collision activator)
    46.     {
    47.         Debug.Log(activator.collider.name + " entered zone " + zone.name);
    48.     }
    49.     public override void OnCollisionExited(Collider zone, Collision activator)
    50.     {
    51.         Debug.Log(activator.collider.name + " left zone " + zone.name);
    52.     }
    53.  
    54.     //IN CASE OF FUNCTION DELETION.
    55.  
    56.     //public override void OnTriggerEntered(Collider zone, Collider activator)
    57.     //public override void OnTriggerExited (Collider zone, Collider activator)
    58.     //public override void OnCollisionEntered(Collider zone, Collision activator)
    59.     //public override void OnCollisionExited(Collider zone, Collision activator)
    60.  
    61.     #endregion
    62.  
    63. }
    64.  
    65. //The Thread this system is based on. https://forum.unity.com/threads/checking-for-a-specific-collider.815118/
    66.  
    OnTriggerBroadcaster
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5. using UnityEngine;
    6.  
    7. [RequireComponent(typeof(Collider))]
    8. public class OnTriggerBroadcaster : MonoBehaviour
    9. {
    10.     // This callback is broadcast to all listeners during OnTriggerEnter/Exit.
    11.     public Action<Collider, Collider> onTriggerEntered;
    12.     public Action<Collider, Collider> onTriggerExited;
    13.  
    14.     public Action<Collider, Collision> onCollisionEntered;
    15.     public Action<Collider, Collision> onCollisionExited;
    16.  
    17.     private void OnTriggerEnter(Collider other)
    18.     {
    19.         // If there are any listeners...
    20.         if (onTriggerEntered != null)
    21.         {
    22.             // ...broadcast the callback.
    23.             onTriggerEntered(GetComponent<Collider>(), other);
    24.         }
    25.     }
    26.     private void OnTriggerExit(Collider other)
    27.     {
    28.         // If there are any listeners...
    29.         if (onTriggerExited != null)
    30.         {
    31.             // ...broadcast the callback.
    32.             onTriggerExited(GetComponent<Collider>(), other);
    33.         }
    34.     }
    35.  
    36.     private void OnCollisionEnter(Collision other)
    37.     {
    38.         // If there are any listeners...
    39.         if (onCollisionEntered != null)
    40.         {
    41.             // ...broadcast the callback.
    42.             onCollisionEntered(GetComponent<Collider>(), other);
    43.         }
    44.     }
    45.     private void OnCollisionExit(Collision other)
    46.     {
    47.         // If there are any listeners...
    48.         if (onCollisionExited != null)
    49.         {
    50.             // ...broadcast the callback.
    51.             onCollisionExited(GetComponent<Collider>(), other);
    52.         }
    53.     }
    54.  
    55. }
    56.  
    57. //Honestly this script is basically plug and play. Just put it on the Game Object that contains the trigger you're trying to connect too.
    58.  
    59. //The Thread this system is based on. https://forum.unity.com/threads/checking-for-a-specific-collider.815118/
    60.  
    OnTriggerListener
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. //The Trigger Listener System. Mostly code that will work on it's own.
    7. [System.Serializable]
    8. public abstract class OnTriggerListener : MonoBehaviour
    9. {
    10.     //This is what the script uses to treat the triggers as if they were parts of a list.
    11.     [HideInInspector]
    12.     public List<OnTriggerBroadcaster> triggers = new List<OnTriggerBroadcaster>();
    13.  
    14.     //Handles the Activation and Deactivation of the Trigger system.
    15.     public virtual void OnEnable()
    16.     {
    17.  
    18.  
    19.         //Subscribes to the callback
    20.         foreach (OnTriggerBroadcaster trigger in triggers)
    21.         {
    22.             if (trigger != null)
    23.             {
    24.                 trigger.onTriggerEntered += OnTriggerEntered;
    25.                 trigger.onTriggerExited +=  OnTriggerExited;
    26.                 trigger.onCollisionEntered += OnCollisionEntered;
    27.                 trigger.onCollisionExited +=  OnCollisionExited;
    28.             }
    29.         }
    30.     }
    31.     private void OnDisable()
    32.     {
    33.         // Unsubscribes from the callback.
    34.         foreach (OnTriggerBroadcaster trigger in triggers)
    35.         {
    36.             if (trigger != null)
    37.             {
    38.                 trigger.onTriggerEntered -= OnTriggerEntered;
    39.                 trigger.onTriggerExited -=  OnTriggerExited;
    40.                 trigger.onCollisionEntered -= OnCollisionEntered;
    41.                 trigger.onCollisionExited -=  OnCollisionExited;
    42.             }
    43.         }
    44.     }
    45.  
    46.     //The Functions that are ran through the main Script once they recieve orders from the Broadcasters
    47.     public virtual void OnTriggerEntered(Collider zone, Collider activator) { }
    48.     public virtual void OnTriggerExited(Collider zone, Collider activator) { }
    49.     public virtual void OnCollisionEntered(Collider zone, Collision activator) { }
    50.     public virtual void OnCollisionExited(Collider zone, Collision activator) { }
    51.  
    52.  
    53. }
    54. //Pretty much plug and play except you don't actually have to plug it in to anywhere. Just leave it somewhere in the project and it will work.
    55.  
    56. //The Thread this system is based on. https://forum.unity.com/threads/checking-for-a-specific-collider.815118/
    57.  
     
    Last edited: Apr 29, 2020
    SisusCo likes this.
  9. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    Wouldn't having OnTrigger/CollisionStay in there be a performance problem? Maybe you can make smaller modular ones too.
     
  10. StarLightShadow

    StarLightShadow

    Joined:
    Jul 9, 2019
    Posts:
    7
    I suppose it would be better performance-wise to just put it in Update and use WithinTrigger flags. I'll update the code to remove all that garbage.

    Man do I WISH I could make modular ones, but I'm pretty sure there's nothing I could possibly do because the Main Script using it connects to this whole thing via inheritance.
     
    Last edited: Feb 9, 2020