Search Unity

Create a Component that accepts scripts like Buttons do?

Discussion in 'Scripting' started by protopop, May 22, 2019.

  1. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    In Unity's (very useful) UI you can drag any script/component into one of the event slots. Then a dropdown with all public functions inside that script appear.

    I like how the button and script dont need to be explicitly liked, The Button component accepts any script and somehow finds all the public functions inside of it. You dont have to specifically declare a script type in the code.

    Is it possible to make a c# script that does something similar? Like with a slot i could drag any script into, and then call any of the public functions inside that script from it?

    buttons.png
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    That would potentially create tons of hard to track and debug, spaghetti of dependencies.
    You are better to stick, of how C# links other scripts.
    Is easy this way, to track dependencies and references via scrip editor, like VS for example.
     
    protopop likes this.
  3. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    Thank you - I see what you are saying, and that does make sense that it could be potentially to spaghetti-ish

    But we use it with buttons and it doesn't seem to create a project that's too unwieldy.

    Maybe this something special that unity Buttons can do only? Like maybe Button script has some kind of special connection in unity?

    For arguments sake though, i wonder if anyone knows if its possible to do in a script? Im also super curious about what kind of C# code could replicate this kind of functionality.
     
  4. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,408
    protopop likes this.
  5. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    Its called UnityEvent, and you can access it after include #using UnityEngine.Events.
    Then in your code you just invoke the event where needed.
    Its pretty handy, but as suggested, use it for simple tasks mostly, or it can get unwieldy pretty quick.
     
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,144
  7. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    So you can use UnityEvent if you want to just expose methods you can call.

    If you want to do something more crazy, you can use reflection to step through the various classes and members. Its a fair bit of work to get your head around, but it does work.

    I did something rather similar to make an inspector that accepted any float value, which you can use as your basis. Fair warning: I haven't touched this project since 2015, so don't expect it to run first time.

    https://forum.unity.com/threads/run-time-trending.346504/
    https://bitbucket.org/BoredMormon/trend.me/src/master/Assets/TrendMe/Editor/RemoteFieldDrawer.cs
     
    protopop likes this.
  8. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,144
    That said you don't have to use just UnityEvent and you're not limited solely to methods. You can create derivatives of UnityEvent. Below is an example of a Vector3 event that allows you to hook up and pass a Vector3, and because you've specified a type it will show every field compatible with that type and allow you to hook the field itself up.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3.  
    4. [System.Serializable]
    5. public class VectorEvent : UnityEvent<Vector3> {}
    upload_2019-5-23_0-54-4.png
     
    Last edited: May 23, 2019
  9. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560

    Oh. My. God!


    Wow - this is exactly what i was looking for.

    I wish i had known about Unity events 5 years ago.

    I have so many micro scripts to invoke functions in my various main scripts.

    I just did a test and i can basically replace them all with a generic unity event script.

    This is my test and it works amazing.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Events;
    5.  
    6. public class TestEvent : MonoBehaviour
    7. {
    8.  
    9.     public bool onEnable;
    10.  
    11.     public UnityEvent genericEvent;
    12.  
    13.     void OnEnable ()
    14.     {
    15.  
    16.         if (onEnable == true) {
    17.  
    18.             genericEvent.Invoke ();
    19.         }
    20.     }
    21.  
    22.  
    23. }
    24.  
    Plus i can see that i can even remove my multi events off my buttons and onto a component, then i can invoke the events from the button. This means i can setup multiple events on one object and call them from a button or other sources. This is great because sometimes i want to recreate what my button does from another source, and i can change multievents and not worry about setting them up on every caller.

    I will use with great care, but it seems really powerful.

    btw any suggestions what i could call this script? I want to call it GenericEvent.cs but im concerned that may already or in the future conflict with something native in unity, because it sounds like a very generic name.

    Thank you everyone for this - its going to make my next game that much more manageable.

    nimianlegendsbrightridge.jpg
     
    StarManta likes this.
  10. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Just use appropriate own name space, to avoid potential conflicts.
    I think should work in such case as well.
     
    protopop likes this.
  11. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    Also, check out the EventTrigger component. It has some premade events for handling the mouse.
     
    protopop likes this.
  12. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    Thank you:) This is a real game changer for me.

    I didn't use a namespace because i wanted to keep it simple. But i named it GeneralEvent.cs - a quick Google showed at least this doesn't exists publicly. If it ever does conflict i could ad a namespace after the fact, because its functionality doesn't seem to change if it's in a namespace or not.
     
  13. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Namespace doesn't add to complexity.
    Only help in organising your code.
     
    protopop likes this.
  14. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    If i add a namespace, can i keep the same script name?

    Like if i have a namespace Protopop (my company name) and then i could call the script genericevent.cs the name i prefer) without worry.

    But i hate using brand names in code, so what would i call my namespace? Actually, how do you decide what to call namespaces in the firstplace?

    And then isnt there more complexity if i call my genericevents script fro another script, because I have to make sure it includes the namespace too?

    I think i prefer flatter - so no namespace but a more unique name (generalevent vs genericevent for example). But i can see the value in namespaces. I just need to give it some thought.
     
  15. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    You can call it whatever you like. You can refactor later at any time, with 2 clicks.
    Then you can have categorized namespaces with dot character '.'.
    so for example
    namespace protopop.core
    and use that name space in for example, math scripts / classes, some public variables, or methods etc.
    Then you can have
    namespace protopop.character.skills
    Here for example scripts with classes speed, agility, dexterity etc.

    And if it happens to have controller.cs with protopop.core, then you download some asset, having controller.cs, they wont conflict. Chances are, that other controller.cs has also own namespace. If don't that would be probably bad written asset anyway.
     
    protopop likes this.
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Think about events as notifications to other code that a particular thing has occurred. For instance "this door has been opened". In response to that notification, other code which cares about doors being opened now has the opportunity to react to it if needed.

    With that in mind the convention is to name the event after the thing that happened to cause the event.

    For instance, if doors are important in my game I might have an event called "OnDoorOpened". Then, things that care about doors opening can subscribe to that event or be hooked up to it in the Inspector, and things are nicely self descriptive.

    While UnityEvent doesn't do this by default, it's convention in general .NET that the first parameter passed when an event is raised is a reference to the object raising the event. This is super useful for a two key reasons - you know what object raised the event, and you don't have to anticipate what data to pack into the event because you can access whatever (public) stuff you want via that reference.

    Code (csharp):
    1. // Event type
    2. [System.Serializable]
    3. public class DoorOpenedEvent : UnityEvent<Door> {}
    4.  
    5. // Creating the event
    6. public DoorOpenedEvent OnDoorOpened = new DoorOpenedEvent();
    7.  
    8. // Event being raised
    9. OnDoorOpened.Invoke(this);
    10.  
    11. // Event being listened to in code. As you know, can also be hooked up in the Inspector.
    12. // This goes on the receiving object, generally in Start or Awake.
    13. // Here, "OnDoorOpened" is the name of the method called when this event is received
    14. otherObject.OnDoorOpened.AddListener(OnDoorOpened);
    15.  
    16. // Don't forget to write the method you just named!
    17. // Often referred to as the event's "handler".
    18. // The function parameter types here need to match those in your event type.
    19. private void OnDoorOpened(Door door)
    20. {
    21.     // Do whatever this object needs to do when the door is opened.
    22. }
    23.  
    24. // And when you don't need to listen any more...
    25. otherObject.OnDoorOpened.RemoveListener(OnDoorOpened);
    26.  
    Note: Typed on the fly, not tested. Beware typos and such.

    Combine this with nicely designed Interfaces and you've got some really flexible, powerful options. Maybe instead of Door you have IOpenable, and this could be applied to doors, drawbridges, force-fields... even if they otherwise have nothing in common, without breaking the code that listens for when things are opened.
     
    protopop likes this.
  17. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    I just want everyone to know this has already been super helpful. I went back to my game and ive been able to use this technique several times - so i now have one script replacing literally a dozen separate ones - its makes things much more manageable, extensible and clean, so thank you all:)
     
    angrypenguin likes this.
  18. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    While it's not UnityEvent in particular, I use this general approach to basically not ever need anything like a "GameManager".

    Do be careful not to end up with monolithic / God classes. That said, if your designs are improving then don't sweat it if they're not perfect yet. You'll keep getting better, and perfection isn't really needed anyway.

    I did make a game a few years ago where, for experimentation's sake, I deliberately avoided having any kind of "Manager" class. I did everything 100% with events, as long as it made sense to do so. My general rules were as follows:
    1. If an object has state changes which other objects might care about, it is responsible for exposing these via an event.
    2. If an object cares about other objects, it is responsible for subscribing to relevant events on those objects.
    3. No "Managers". :)
    I didn't really expect this to work, because I usually find that following a paradigm to its absolutes really exposes its flaws. Also, in the past I'd found Managers to be a handy place to take explicit control of some things. However, in this case I didn't run into any meaningful flaws, it just made everything more flexible.

    That said, I'd already used events enough to know how to structure things to: a) avoid cases where event order matters, and b) ensure that you don't get infinite loops by events (indirectly) causing themselves to get called again. I imagine that if you're not experienced at designing your code those things could become problematic.
     
  19. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    The big risk for me with events its it can make it hard to follow your code. Its possible to end up with a lot of project that is a lot of single line methods that just jump into another method. Tracing it where code disappears into the inspector then back into code multiple times is a pain.

    You can avoid it with care and experience. As long as you are aware its a pitfall.
     
    protopop likes this.
  20. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    I don't use the Inspector part very often, so I haven't really had that issue. "Find All References" in Visual Studio is very helpful if your events are hooked up by code.
     
    protopop likes this.
  21. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    I have another question. This events caller worked so well.

    Can i do something similar to set public variables in an external script?

    The same as above - drag an instance of an unknown script into my event manager script, but right now i need to explicitly have a function in the scripts to change a variable.

    like right now i have to add something like

    Code (CSharp):
    1. public void SetFloat(float f){
    2. speed = f;
    3. }
    I am wondering if i can expose public variables the same way ive using exposed public functions above? That way i can also set variables in an unknown script without having to add the function to explicitly do so.
     
  22. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    unity events don't seem to display public variables, but they can display public accessors. so if you have a public float myFloat, you can't access it using a unity event in the editor, but if you have a public float MyFloat { get { return myFloat; } set { myFloat = value; }} then you can set it using a unity event in the editor.
     
    protopop likes this.
  23. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    Ahhhhhhh - cool. Thank you:)

    I didn't now about get and set, although ive seen it in code a million places. I guess its like a shorthand for writing a custom SetFloat function like i did above?

    so i added it and this works, lets me access the float damage from the unity event in the inspector:

    Code (CSharp):
    1. public float damage { get; set; }
    Screen Shot 2019-06-05 at 4.22.02 PM.png

    But then it no longer appears in the inspector as a variable i can change (damage variable no longer appears exposed in the inspector)

    Screen Shot 2019-06-05 at 4.22.10 PM.png

    Is it an either or kind of thing? Like i can use get:set to make public floats accessible, or i can forego it if i want to keep it accessible in the inspector and write a custom get:set function instead?
     
  24. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    Properties don't show in the inspector. The way you are writing it is an auto-property which is shorthand so you don't have to make a field. If you use the longhand version you can see the field's value in the inspector. And, yes, a property is like merging GetFloat/SetFloat functions into one.

    Latest C# in .NET 4.5
    Code (csharp):
    1. [SerializeField]
    2. private float speed;
    3.  
    4. public float Speed { get => speed; set => speed = value; }
    Older C# in .NET 3.x
    Code (csharp):
    1. [SerializeField]
    2. private float speed;
    3.  
    4. public float Speed
    5. {
    6.     get { return speed;}
    7.     set { speed = value;}
    8. }
    Medieval times:
    Code (csharp):
    1.  
    2. [SerializeField]
    3. private float speed;
    4.  
    5. public float GetSpeed()
    6. {
    7.     return speed;
    8. }
    9.  
    10. public float SetSpeed(float value)
    11. {
    12.     speed = value;
    13. }
    14.  
    However, changing the field in the inspector will not execute 'set', so any code you have in there will only work using the event. EDIT: See post below about OnValidate().
     
    Last edited: Jun 6, 2019
    protopop and angrypenguin like this.
  25. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Yeah, in my mind this is a bit of an oversight on Unity's behalf. An understandable one, but still something I'd like to see addressed.

    Where I need something to both have a property and be in the Inspector, I use an OnValidate method to basically repeat whatever the setter does. It's not ideal, but for most of the stuff that you would do in a setter it's workable. In many cases that's just a matter of...

    Code (csharp):
    1. void OnValidate()
    2. {
    3.     // Call the property setter with the current value.
    4.     Variable = _variable;
    5. }
     
    Stardog, protopop and Kiwasi like this.
  26. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    Thank you:)

    I can see it starts to be a lot of code.

    I think for me, for simplicity, id rather just keep the float variable simple and exposed in the inspector,

    And add a function to handle setting the variables.

    so like
    Code (CSharp):
    1. public float speed = 1.0f;
    2.  
    3. public void SetSpeed(float f){
    4.     speed = f;
    5. }
    6.  
    This way i can:

    • keep the variable declarations simple
    • change values in the inspector
    • I can set an initial value in code if i need to
    • i dont have to handle two similarly named variables (ex: speed and Speed) which for me becomes a bit confusing

    the get:set if it let me change in the inspector too would be perfect - its such a small line of code and seems clean, its sooooo close to what i was looking for.

    But i did learn something new today:) Thank you very much for helping me understand this.
     
  27. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Keep in mind that a part of the purpose of get/set functions is that you can "sanity check" or "guard" the contents of those variables, making sure they don't get set to invalid states. Making something straight up public robs you of this benefit.

    Having a get/set function (or using properties, which are really just fancy looking get/set functions) and separately making something visible in the Inspector means that the Inspector can set something to an invalid value.
     
    protopop likes this.
  28. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    I just realized this is a function.

    I kind of thought functions had to be called void - i am coming from JavaScript/UnityScript where we call functions "functions". Now i see you just put the return type, so the syntax for a function and a variable is actually very similar, just functions add the code block after the variable declaration?

    So either way you have to write a small function to set a variable.

    Why do people use get functions when you could just go scriptName.variableName to get the value of a variable? Is it less writing?
     
  29. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    Interesting. What do you mean an invalid state? What is that?

    Like if i have a speed variable that's a float, i can only ever set it as a float type anyways. So how could i ever set my speed float to something invalid even if its public?

    For the fancy, i guess you mean if i write "public speed = 1.0f" then c# is actually using a kind of get:set behind the scenes to set my float vale to 1.0f?
     
  30. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    See in my last post where I talked about "sanity checking" or "guarding" variables.

    It's about "encapsulating" data. Basically, the principle is that a variable should only be modified by the object that owns it. That sounds academic and arbitrary, but it considering it appropriately helps you to a) reduce the chance of certain types of error, and b) track down errors more effectively when they do arise.
     
    protopop likes this.
  31. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    OK i just googled why use get:set and it says in my case its not needed as much, but the power is you can add functions to the get and set.

    So i could for example make sure my speed is always more than 0 if that was a requirement. Or i could broadcast that the variable had changed, which i guess is a cpu friendly way to "watch" a variable for change?

    I find it really fascinating. But i still set many values in the inspector so i dont think get:Set will work for me in most of my cases. but its good to know.
     
  32. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    ooooo i get what you mean now:

    Code (CSharp):
    1.  
    2. [SerializeField]
    3. private float speed;
    4.    
    5. public float Speed
    6.     {
    7.         get { return speed;}
    8.         set { speed = value;}
    9.     }
    10.  
    11. void OnValidate()
    12.     {
    13.         speed = Speed;
    14.     }
    15.  
    For people like me who don't know, OnValidate runs when the script is loaded of the variable is changed in the inspector.

    This all makes sense, but i think i will stick with

    Code (CSharp):
    1. public float speed = 1.0f;
    2. public void SetSpeed(float f){
    3.     speed = f;
    4. }
    At least for now since its less code. But i do get what you mean.

    You know, already the help above with the UnityEvents really improved my code so much already. And even if im not using get:set, taking the mystery out of it does help me get my next code based ready and cleaner.
     
  33. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Well, "get" and "set" are just different looking functions. Personally I'm not a fan, because they make it harder to tell what's going on by making a function call look like accessing a variable.

    A couple of things to note in your example above:
    • In OnValidate it should be "Property = backingValue;" rather than "backingValue = Property;". You want to call the setter, not read from the getter.
    • By keeping the backing variable public you're still allowing any code to directly change it, which defeats any logic you may put in the setter to ensure its correctness. That's why it's considered "best practice" to make variables private or protected whenever possible and, in Unity, use [SerializeField] to make it visible in the Inspector. This means any code that accesses the variable must do so through properties or functions you provide.
     
    protopop and Kiwasi like this.
  34. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    Yeah, stopping the number from being negative is a good example of what you can do. Giving access to the raw variables means you can't error check for anything like that as mentioned above.

    I updated my post with an older example. A language like Java doesn't have properties, so you have to do it the way you are doing it with a Set function.
     
    angrypenguin and protopop like this.
  35. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    Thank you for updating the post. I think that's valuable information and comparisons right there.

    Yeah im using your medieval example as my goto actually:)

    The reason get:set attracted me was because it was less verbose, and it allowed me to set variables from events, BUT it removed my ability to set in in the inspector. And by the time i was serializing private fields and validating it was more code than the medieval version.

    I dont really need a GetFloat type function because i make my essential variables public. I know there is this classic idea to make as much private as possible, but i actually like many public and exposed variables (i am a transparent person in general). In my grand scheme, it opens everything up to a kind of unimagined and unplanned possibility for mutation that goes well with my procedurally generated projects. I think i can see though how making the variable private and using functions (are those methods?) to alter them does protect them and can provide error checks.(EDIT: the more i think about this part the more i can see the error check value here... )

    I guess in the end its all very similar just different syntax. As long as i think things through, which is helped immensely by learning from coders like you here, and keep it simple and clean ill have good code. And my main takeaway is im so happy to have Unity event scripts now and a way to call functions and set variables from them.
     
  36. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    if i put a [SerializeField] in one of my scripts, will it screw up any of my other scripts or compilation?

    Like its a safe code word to use that doesnt affect anything outside of my script?
     
  37. protopop

    protopop

    Joined:
    May 19, 2009
    Posts:
    1,560
    oh like it should be Speed = speed? Because it will read the value ive set in the serialized private speed variable right?
     
  38. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Yes, exactly.

    Sure, it seems all nice at first. You can do anything from anywhere, wheee!

    But then if you actually follow through on that you end up with the worst of what's called "spaghetti code". Bugs are easy to make and a nightmare to investigate and fix. You can't be sure of the results of making even trivial changes. Code execution order can make a big difference.

    Keep in mind the "Single Responsibility Principle". This goes nicely with encapsulation, because basically only the object which is responsible for a piece of data should have direct access to it. If I want to change the speed of my car I should be doing it by making appropriate calls to that object, not by looking inside and messing with its variables myself.

    The idea is that this is really simple code to write that's fairly easy to get right, that will save you much more effort later by avoiding parts of the "spaghetti code" scenario.

    Here's a scenario: I've written a Car class with a speed variable. I've made that variable public. In a few different places in my code, the car is controlled by directly modifying that variable. What happens when you decide to change how acceleration works? Are you going to implement it in multiple different places that aren't even in the Car class? If you do, you're giving yourself opportunities to make mistakes which will lead to inconsistent behaviour. It also makes tweaking harder, which makes iteration slower and could impact on the quality of your game.

    I absolutely agree with this. However... think about more than just the definition of your variables. Consider how they're used. A little more code when you create a variable and set up how it's used can save a heck of a lot of complexity elsewhere in your code.

    With my example above, having a Car class with an Accelerate() and a Brake() method controlling a private speed variable would save a heck of a lot of spaghetti. It's a little more complex up front, but it forces the rest of the code to be dead simple, and it encourages you to solve problems in one place rather than many.