Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feedback Zero references using UnityEngine Events. If function change name no warning pops up.

Discussion in 'Scripting' started by AlanMattano, Sep 15, 2020.

?

Did you know that when you press Play you lose all your "Missing" references in Event inputs?

This poll will close on Oct 15, 2026 at 1:06 AM.
  1. Yes

    1 vote(s)
    10.0%
  2. No

    9 vote(s)
    90.0%
  1. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    upload_2020-9-14_21-11-58.png

    I use Unity Events (UI buttons) but if I change the name of a function, since there is 0 references to it, no warning pops up. Is difficult to debug and more important detect these bugs.
    Actually I'm afraid of improving the readability of functions because I do not know what can happen!


    Expected:
    I wish that the Unity framework check each UI button connection before pressing Play.
    Since after pressing the Play button the "missing reference" inside the UI button event will be lost.
     
    Last edited: Sep 15, 2020
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I agree. That's why I don't use that system at all. I rolled my own Datasacks system that basically links an arbitrary string into your game. Sure, you can mistype string names too, but you can also print them out trivially.

    The key is, you never drag and drop fiddly bits from your game into UI panels. You just emplace the various Datasack scripts on your UI and your program does not change.

    The nice part is you can completely revamp your UI and nothing in code changes, as long as the string messages are still the same.

    Here's an overview:

    20180724_datasacks_overview.png
    See example in there for how to handle responding to buttons, but basically it is something like this:

    Code (csharp):
    1.     void    OnUserIntent( Datasack ds)
    2.     {
    3.         // Here is where you service the notification
    4.         // of a user intent, probably from a button.
    5.         switch( ds.Value)
    6.         {
    7.         case "ButtonStartGame":
    8.             // call whatever you do to start the game
    9.             break;
    10.         case "ButtonShowOptions":
    11.             // call whatever you do to show options
    12.             break;
    13.         }
    14.     }
    15.  
    16.     void    OnEnable()
    17.     {
    18.         DSM.UserIntent.OnChanged += OnUserIntent;
    19.     }
    20.     void    OnDisable()
    21.     {
    22.         DSM.UserIntent.OnChanged -= OnUserIntent;
    23.     }
    24.  
    25.  
    Datasacks is presently hosted at these locations:

    https://bitbucket.org/kurtdekker/datasacks

    https://github.com/kurtdekker/datasacks

    https://gitlab.com/kurtdekker/datasacks

    https://sourceforge.net/projects/datasacks/
     
    AlanMattano likes this.
  3. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,193
    I agree that the engine should warn about this sort of thing.

    In the meantime, I wrote a thing to alert me of this kind of behavior. It's somewhat manual, in that you need to call something in the Start() on some object to "wire up" your desire for a warning. But here's what I do. Here's an example of setting up the warning in a script that has an OnMidpointReached UnityEvent:

    Code (CSharp):
    1. void Start()
    2. {
    3.     SceneUtil.ValidateMissingUnityEventImplementation(this, OnMidpointReached, requireHandler: true);
    4. }
    And here's what that's calling:

    Code (CSharp):
    1. public static void ValidateMissingUnityEventImplementation(MonoBehaviour controller, UnityEventBase unityEvent, bool requireHandler = false)
    2. {
    3. #if UNITY_EDITOR
    4.     var field = controller.GetType().GetFields().FirstOrDefault(f => f.GetValue(controller) == unityEvent);
    5.  
    6.     if (requireHandler)
    7.     {
    8.         if (unityEvent == null || unityEvent.GetPersistentEventCount() == 0)
    9.         {
    10.             throw new Exception($"GameObject '{controller.gameObject.name}' has no handler for UnityEvent '{field.Name}', but one is required.'");
    11.         }
    12.     }
    13.  
    14.  
    15.     if (unityEvent != null)
    16.     {
    17.         for (int eventIndex = 0; eventIndex < unityEvent.GetPersistentEventCount(); eventIndex++)
    18.         {
    19.             var methodName = unityEvent.GetPersistentMethodName(eventIndex);
    20.  
    21.             if (string.IsNullOrWhiteSpace(methodName))
    22.             {
    23.                 throw new Exception($"GameObject '{controller.gameObject.name}' has UnityEvent '{field.Name}' with no selected method name.");
    24.             }
    25.  
    26.             var target = unityEvent.GetPersistentTarget(eventIndex);
    27.             var method = target.GetType().GetMethods().FirstOrDefault(m => m.Name == methodName);
    28.             if (method == null)
    29.             {
    30.                 throw new Exception($"GameObject '{controller.gameObject.name}' has a UnityEvent '{field.Name}' with a missing method name ({methodName}).");
    31.             }
    32.         }
    33.     }
    34. #endif
    35. }
    Let's say the UnityEvent on this object is set to call "PrepareWarehouseRoom", and I change the name of the "PrepareWarehouseRoom" in code, I'll then get the following console error the next time I enter play mode:

    GameObject 'WarehouseRoomHallway' has a UnityEvent 'OnMidpointReached' with a missing method name (PrepareWarehouseRoom).


    I intended to improve this a bit so that it can handle cases where arguments changed better, but this mostly fill my needs.

    Again, I'm not suggesting this replaces the need for Unity to do a better job at alerting us to these issues, but this has helped me feel more confident about changing method names.
     
  4. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,085
    Do everything from scripts, problem solved.

    Code (CSharp):
    1. [RequireComponent(typeof(Button))]
    2. public class ButtonRestartGame : MonoBehaviour
    3. {
    4.     [SerializeField] Button button;
    5.  
    6.     void Awake ()
    7.     {
    8.         button.onClick.AddListener( RestartApp );
    9.  
    10.         void RestartApp()
    11.         {
    12.             Application.OpenURL( Application.dataPath.Replace( "_Data", ".exe" ) );
    13.             Application.Quit();
    14.         }
    15.     }
    16. }
    GetComponent<Button>() instead of button for worse performance but less code.
     
    Kurt-Dekker and AlanMattano like this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    This is often the best approach. Control the code!

    However, let me just point out @Kamyker that you are transacting against a Button object in your own script's Awake()... that may work for Buttons today, but it's always best to confine activities to yourself in Awake(), and use Start() for hitting other Components so you know they have experienced their own Awake() already.
     
    Dazaer and AlanMattano like this.