5

is there a way to get some kind of info or warning, when any Button component has a missing method?

What I mean by that is that when you implement a method for a Button, assign that method in the scene and then e.g. rename the method, the rename won't update the method call in the scene and so it says "< Missing ScriptName.OldMethodName >".

When this happens I'd like to get notified about that - at least when pressing play, or at the very least when deploying the application.

1
  • 1
    I avoid this by setting the onClick Event in Code and not in the designer. Commented Mar 14, 2017 at 11:10

2 Answers 2

4

Scott's answer is very close to what you are doing and led to this answer. Although it is missing so many things. You need to do more work to get that script working.

1.You need to get all the Buttons in the scene (including inactive/disabled ones) with Resources.FindObjectsOfTypeAll.

2.Loop through the buttons and check if the class/script that holds that function exist with reflection. You do this because sometimes, we rename scripts. This could cause problems. Show message of the script does not exist.

You can do this by simply checking if Type.GetType(className); is null.

If it is null then don't even both the test below because the component that holds that Button's onClick function has been re-named. Display an error message that says that this script has been deleted or re-named.

3.If the class exist, now check if the function exist with reflection in that class that is registered to the Button's onClick event.

This can be done by simply checking if type.GetMethod(functionName); is null.

If the function exist, that Button is fine. You don't have to show a message if the function exist. Stop here.

If that returns null then continue to #4.

4.Check if the function exist, but this time, check if the function is declared with a private access modifier.

This is a typical mistake people make. They declare function as public, assign it through the Editor, then mistakenly change it from public to private. This should work but can cause problem in the future.

This can be done by simply checking if type.GetMethod(functionName, BindingFlags.Instance | BindingFlags.NonPublic); is null.

If the function exist, show a message that warns you that this function's access modifier has been changed from public to private and should be changed back to public.

If the function does not exist, show a message that warns you that this function does no longer exist or has been re-named.

Below is a script that performs everything I mentioned above. Attach it to an empty GameObject and it will do its job anytime you run the game in the Editor.

using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;

public class MissingOnClickDetector : MonoBehaviour
{
    void Awake()
    {
        //Debug.Log("Class exist? " + classExist("ok.ButtonCallBackTest"));
        searchForMissingOnClickFunctions();
    }

    void searchForMissingOnClickFunctions()
    {
        //Find all Buttons in the scene including hiding ones
        Button[] allButtonScriptsInScene = Resources.FindObjectsOfTypeAll<Button>() as Button[];
        for (int i = 0; i < allButtonScriptsInScene.Length; i++)
        {
            detectButtonError(allButtonScriptsInScene[i]);
        }
    }

    //Searches each registered onClick function in each class
    void detectButtonError(Button button)
    {
        for (int i = 0; i < button.onClick.GetPersistentEventCount(); i++)
        {
            //Get the target class name
            UnityEngine.Object objectName = button.onClick.GetPersistentTarget(i);

            //Get the function name
            string methodName = button.onClick.GetPersistentMethodName(i); ;

            //////////////////////////////////////////////////////CHECK CLASS/SCRIPT EXISTANCE/////////////////////////////////////////

            //Check if the class that holds the function is null then exit if it is 
            if (objectName == null)
            {
                Debug.Log("<color=blue>Button \"" + button.gameObject.name +
                    "\" is missing the script that has the supposed button callback function. " +
                    "Please check if this script still exist or has been renamed</color>", button.gameObject);
                continue; //Don't run code below
            }

            //Get full target class name(including namespace)
            string objectFullNameWithNamespace = objectName.GetType().FullName;

            //Check if the class that holds the function exist then exit if it does not
            if (!classExist(objectFullNameWithNamespace))
            {
                Debug.Log("<color=blue>Button \"" + button.gameObject.name +
                     "\" is missing the script that has the supposed button callback function. " +
                     "Please check if this script still exist or has been renamed</color>", button.gameObject);
                continue; //Don't run code below
            }

            //////////////////////////////////////////////////////CHECK FUNCTION EXISTANCE/////////////////////////////////////////

            //Check if function Exist as public (the registered onClick function is ok if this returns true)
            if (functionExistAsPublicInTarget(objectName, methodName))
            {
                //No Need to Log if function exist
                //Debug.Log("<color=green>Function Exist</color>");
            }

            //Check if function Exist as private 
            else if (functionExistAsPrivateInTarget(objectName, methodName))
            {
                Debug.Log("<color=yellow>The registered Function \"" + methodName + "\" Exist as a private function. Please change \"" + methodName +
                    "\" function from the \"" + objectFullNameWithNamespace + "\" script to a public Access Modifier</color>", button.gameObject);
            }

            //Function does not even exist at-all
            else
            {
                Debug.Log("<color=red>The \"" + methodName + "\" function Does NOT Exist in the \"" + objectFullNameWithNamespace + "\" script</color>", button.gameObject);
            }
        }
    }

    //Checks if class exit or has been renamed
    bool classExist(string className)
    {
        Type myType = Type.GetType(className);
        return myType != null;
    }

    //Checks if functions exist as public function
    bool functionExistAsPublicInTarget(UnityEngine.Object target, string functionName)
    {
        Type type = target.GetType();
        MethodInfo targetinfo = type.GetMethod(functionName);
        return targetinfo != null;
    }

    //Checks if functions exist as private function
    bool functionExistAsPrivateInTarget(UnityEngine.Object target, string functionName)
    {
        Type type = target.GetType();
        MethodInfo targetinfo = type.GetMethod(functionName, BindingFlags.Instance | BindingFlags.NonPublic);
        return targetinfo != null;
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the extensive response, Programmer and Scott. I'll have a closer look at this asap.
Found a little bug and fixed it. Added support for namespace. Replaced classExist(objectName.name) with !classExist(objectFullNameWithNamespace). Please check the updated code. Even the first code should work fine. This updated code should work better. Let me know if there is a problem.
Thanks for this script! However there is an exception being thrown with Unity 2018.2.5f1: "AmbiguousMatchException: Ambiguous matching in method resolution" on the line for MethodInfo targetinfo = type.GetMethod(functionName);
@Dihedral I suspect that you have the-same function name with different overloads. If that's true then you will have to re-write the code to support that. Really don't have time to update this code but that would nice if anyone else wants to do it
@Programmer Thanks. I created a gist with updated code which I believe works for my case. I just wrapped the "problem lines" in a try block. I'm not sure if this covers all the cases, it may break something else, but maybe it'll also help someone out. gist.github.com/AaronV/3fd7cc22039cf34f536ab98db47d044a
|
2

you can extend the button class and check for event counts + method names, if the name contains missing, fire a error

using UnityEngine.UI;

public class SafeButton : Button
{
    override protected void Awake()
    {
        for (int i = 0; i < onClick.GetPersistentEventCount(); i++)
        {
            var methodName = onClick.GetPersistentMethodName(i);
            // if method name contains "missing"
                // -> Log Error
        }
    }
}

4 Comments

No, it doesn't work. The GetPersistentMethodName(index) returns the original method name at the moment of registration (i.e.: when you add it via the drop-down menu), and won't change if that name is changed in a script.
@Galandil This answer is correct. OP was talking about assigning event from the Editor. "What I mean by that is that when you implement a method for a Button, assign that method in the scene and then e.g. rename the method, the rename won't update the method call in the scene and "....
@Programmer, try it by yourself, dropbox.com/s/nuucvxzbp49tad4/PersistentEvent.zip?dl=0 - run it once, and you'll see in the console the Test method name. Now go inside the script and change it to Test1, run again the project and you'll see that the method name in the console hasn't changed, while in the Inspector you can clearly see the <Missing SafeButton.Test>.
@Galandil I thought your first comment is saying that onClick.AddListener will not work on this so I made a comment that says that OP wanted to assign event from the Editor not from code. I get what you are saying now and that's right. I will still keep my upvote because this answer is what led to my answer. You can check it out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.