Search code examples
c#unity-game-enginebuttonanimatorprefab

How do I assign a method that is inside a prefab to the On Click() event on a button?


I have a button that is supposed to switch beetween light and dark mode in my game by running the method "ToggleTheme" inside the ObjectTheme script, which all the objects that I want to be affected by light/dark mode have. ToggleTheme just changes the boolean "DarkMode", since all the objects' transitions use this DarkMode boolean. It all works fine if I just assign the objects and select ObjectTheme.ToggleTheme, but if I assign the objects' prefabs and select ObjectTheme.ToggleTheme I get the warning "Animation is not playing an AnimatorController" on button press. Is there any way around this, because assigning every object in every scene would just be to impractical and one of the objects has up to 30 copies in every level of the game?

P.S. I know It probably would have been easier if I just used a toggle instead of a button, but I'm new to Unity and I just couldn't get the toggle to work how I wanted it, so I'm using a button instead.

Here is the ObjectTheme script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectTheme : MonoBehaviour
{
    public Animator animator;

    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    public void ToggleTheme()
    {
         if(animator.GetBool("DarkMode") == true)
         {
            animator.SetBool("DarkMode", false);
         }
        else
         {
            animator.SetBool("DarkMode", true);
         }
    }
   
}

Solution

  • **Edited after Jonatan's comments below.

    I understand the desire to just assign the prefab as the button's event target. But the prefab itself is in some sense also just an instance that is just not living in the scene. While in edit mode, all changes in the prefab itself will reflect in the scene instances. But when you are in play mode (runtime) the prefab instances in the scene will no longer automatically update themselves with changes in the prefab file.

    In this case, we are trying to set a bool value on an Animator component, but the Animator on the prefab is not really playing - only the Animators on the scene instances are playing. That is why you get the 'not playing' warning.

    One option to solve the issue could be something like the following.

    First add a script to the button that has a function that can be hooked up with your button's OnClick() UnityEvent. The script will look for instances of another script, which is present on all the objects that should react to dark mode state. This other script could be your ObjectTheme script but here I call it DarkModeReceiver. When the button triggers the function, the script will simply call a function on all the script instances stored in its array.

    //Put this script on the Button,
    //and hook up the Button's OnClick event with the OnButtonClicked() function
    
    using UnityEngine;
    
    public class DarkModeHandler : MonoBehaviour
    {
        static bool isDarkMode;
        public static bool IsDarkMode => isDarkmode;//Public get property
    
        //Make your Button call this function in its OnClick() event
        public void OnButtonClicked()
        {
            isDarkMode = !isDarkMode;//Toggle bool
            SendIsDarkMode();
        }
    
        //Alternatively, if you choose to use a Toggle instead
        //you could hook this function up with the Toggle's OnValueChanged(Boolean) event
        //with the dynamic bool of that event.
        public void OnToggleValueChanged(bool isToggledOn)
        {
            isDarkMode = isToggledOn;
            SendIsDarkMode();
        }
    
        void SendIsDarkMode()
        {
            var darkModeReceivers = FindObjectsOfType<DarkModeReceiver>(true);
            foreach (var receiver in darkModeReceivers)
            {
                receiver.SetIsDarkMode(isDarkMode);
            }
        }
    }
    

    And then the receiving script (attached on all the game objects / prefabs that should react to dark mode state) could be something like this (or a modified version of your ObjectTheme script).

    using UnityEngine;
    
    public class DarkModeReceiver : MonoBehaviour
    {
        Animator myAnimator;
    
        void Awake()
        {
            myAnimator = GetComponent<Animator>();
        }
    
        void Start()
        {
            //Ensure that our state is in sync with the DarkModeHandler
            SetIsDarkMode(DarkModeHandler.IsDarkMode);
        }
    
        public void SetIsDarkMode(bool isDarkMode)
        {
            myAnimator.SetBool("DarkMode", isDarkMode);
        }
    }
    

    Alternatively, you could do something where the DarkModeReceivers/ObjectThemes register themselves on the DarkModeHandler on their Start() and unregister themselves again on their OnDestroy() - for example by subscribing to an event. Then the DarkModeHandler wouldn't have to look for receivers every time the button is clicked.