Search Unity

Check if object is modified by CanvasGroup

Discussion in 'Scripting' started by Antistone, Sep 12, 2019.

  1. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Is there a good way to determine (in script) whether a given object is transparent and/or interactable and/or blocks raycasts, taking into account any Canvas Groups in its ancestry that might be modifying these settings?

    I could obviously climb up the object hierarchy all the way to the root while checking for Canvas Group components at every step and manually calculate the compound of all of their effects, but I imagine Unity is already calculating that somehow for its own purposes, so I'd like to avoid the duplicated effort if possible.

    (The end goal is to have a MonoBehaviour that responds to keyboard input in its Update function, but that automatically stops responding if it's been hidden/disabled by an upstream Canvas Group.)
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,738
    Are you aware that the Unity UI source is all open? You can see it here:

    https://bitbucket.org/Unity-Technologies/ui

    If there isn't an obvious documented way to do what you're doing, you might find that with some finagling you could achieve it, given the intel provided in the source above. At least you can see what Unity is even doing to get the CanvasGroup to do its magic.
     
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    A search for "canvasgroup" didn't find anything in there.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,738
    Well that's annoying. And of course we're not the first ones to notice:

    https://forum.unity.com/threads/where-is-canvasgroup-source.310276/

    Hm. Well, wish I could respond to your original answer but I got nuthin. I guess you could throw the DLL into a decompiler and see if you get any useful insights...
     
  5. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Well, the Raycast code in Graphic.cs kind of implies that Unity is just tracing the entire object hierarchy up to the root every time it needs to know (like I suggested as a naive solution in my OP), so maybe there's no better option.
     
    Kurt-Dekker likes this.
  6. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    You're in luck cause I wrote a class a while back that does exactly that. iirc, the implementation is pretty close to that in Unity's Selectable class. btw I think its the OnCanvasGroupChanged message that you were looking for
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. // Input Handlers are classes that where each concrete class is built for a specific type of Input.
    5. // This is done so that one class doesn't have to handle cases for every possible type of input that
    6. // may be in the UI. If the UI has a unique type of input (EventSystem Onclick, Input keybinding,
    7. // or third party support like Remix), it gets its very own Handler class. This decouples each input
    8. // case and simplifies the code for each class.
    9. public abstract class InputHandlerBase : MonoBehaviour
    10. {
    11.     #region statics + type definitions
    12.     private static readonly List<CanvasGroup> m_CanvasGroupCache = new List<CanvasGroup>();
    13.     #endregion
    14.  
    15.     #region properties
    16.     public bool IsInteractable { get; private set; }
    17.     #endregion
    18.  
    19.     #region Unity Messages
    20.  
    21.     private void OnCanvasGroupChanged()
    22.     {
    23.         //default to true incase no canvas group to root
    24.         bool interactibleCheck = true;
    25.  
    26.         Transform cg_transform = transform;
    27.         while(cg_transform != null)
    28.         {
    29.             cg_transform.GetComponents(m_CanvasGroupCache);
    30.             bool ignoreParentGroups = false;
    31.  
    32.             for(int i = 0, count = m_CanvasGroupCache.Count; i < count; i++)
    33.             {
    34.                 var canvasGroup = m_CanvasGroupCache[i];
    35.  
    36.                 interactibleCheck &= canvasGroup.interactable;
    37.                 ignoreParentGroups |= canvasGroup.ignoreParentGroups || !canvasGroup.interactable;
    38.             }
    39.  
    40.             if(ignoreParentGroups)
    41.             {
    42.                 break;
    43.             }
    44.  
    45.             cg_transform = cg_transform.parent;
    46.         }
    47.  
    48.         IsInteractable = interactibleCheck;
    49.     }
    50.     #endregion
    51. }
    52.  
    I then have another set of components called CanvasGroupHandlers where each concrete class monitors for a specific condition and works with other CanvasGroupHandlers to generate what state a CanvasGroup should be in. for example one handler can wait for a specific gamestate before it shows a certain hud, while another could disable certain inputs while progressing through a tutorial. Personally I'm pretty proud of it compared to the spaghetti code it was before cause now I can see each canvasgroup rule as a separate component in the editor without having to dig through some monolithic code.
     
  7. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    OnCanvasGroupChanged doesn't seem to be documented. There's a legacy page for UIBehaviour.OnCanvasGroupChanged, but it simply tells you to look up MonoBehaviour.OnCanvasGroupChanged, which doesn't seem to exist. Annoying.
     
  8. kamicazer7

    kamicazer7

    Joined:
    Oct 27, 2012
    Posts:
    7
    I took @JoshuaMcKenzie's code and made a standalone static method just to use on demand.
    If anyone wants here it is:

    Code (CSharp):
    1.     public static bool IsInteractable(this GameObject gameObject)
    2.     {
    3.         if (gameObject == null)
    4.             return false;
    5.  
    6.         Selectable selectable = gameObject.GetComponent<Selectable>();
    7.         if (selectable != null && !selectable.interactable)
    8.             return false;
    9.  
    10.         List<CanvasGroup> m_CanvasGroupCache = new List<CanvasGroup>();
    11.         bool interactibleCheck = true;
    12.  
    13.         Transform cg_transform = gameObject.transform;
    14.         while (cg_transform != null)
    15.         {
    16.             cg_transform.GetComponents(m_CanvasGroupCache);
    17.             bool ignoreParentGroups = false;
    18.  
    19.             for (int i = 0, count = m_CanvasGroupCache.Count; i < count; i++)
    20.             {
    21.                 var canvasGroup = m_CanvasGroupCache[i];
    22.  
    23.                 interactibleCheck &= canvasGroup.interactable;
    24.                 ignoreParentGroups |= canvasGroup.ignoreParentGroups || !canvasGroup.interactable;
    25.             }
    26.  
    27.             if (ignoreParentGroups)
    28.             {
    29.                 break;
    30.             }
    31.  
    32.             cg_transform = cg_transform.parent;
    33.         }
    34.  
    35.         return interactibleCheck;
    36.     }