Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Dynamic Menu that disappears after PointerExit

Discussion in 'UGUI & TextMesh Pro' started by Master_Aricitic, Oct 30, 2021.

  1. Master_Aricitic

    Master_Aricitic

    Joined:
    Sep 17, 2017
    Posts:
    6
    So I'm trying to create a dynamic menu that changes depending on what is clicked. I got most of it going after some trouble with buttons appearing off-screen, but I still have a problem.
    To make the menu disappear when the mouse is no longer above it I use "OnPointerExit()" and within the code I destroy the gameObject that parents the menu. This 'works' unless I have more than one button, at which point it vanishes as soon as the mouse moves.

    I have tried searching Google, but everyone else's issues seem to be very different - usually multiple 3D game objects rather than UI game objects.

    So, how do I make the OnPointerExit function work through child objects? Or... Is there a better way to do what I'm trying to do?

    Also, if I've missed a tutorial that's out there, or a thread, etc, feel free to post a link and I'll get the information from there.

    For the sake of clarity, and in case anyone has better ideas as to how to handle this, what I'm trying to accomplish is the following:
    When a game object is clicked a menu appears at the mouse (working); the menu changes depending on what thing was clicked (working), the menu disappears when the mouse is no longer hovering over any part of the menu (i.e. outside the bounds) - (Not working).

    The way I implemented the above is as follows:
    For each game item that has a relevant menu add the following code:
    Code (CSharp):
    1. Vector3 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    2.         pos.z = 0;
    3.         if (go == null)
    4.         {
    5.             go = Instantiate(objectMenu, pos, Quaternion.identity) as GameObject;
    6.             go.transform.SetParent(canvas.transform.GetChild(0).transform);
    7.             go.transform.position = pos;
    8.             go.GetComponent<ObjectMenu>().setObj(gameObject);
    9.             go.GetComponent<RectTransform>().localScale = new Vector3(1,1,1);
    10.         }
    11.         else if (go != null)
    12.         {
    13.             go.transform.SetParent(canvas.transform);
    14.             go.transform.position = pos;
    15.         }
    16.  
    To change the menu's contents implement a standard menu gameObject devoid of buttons and add them via code when the above code is run:
    Code (CSharp):
    1. protected void CreateTopMenu()
    2.     {
    3.         pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    4.         buttons = new List<GameObject>();
    5.         canvas = Camera.main.transform.GetChild(0).GetComponent<Canvas>();
    6.         transform.SetParent(canvas.transform.GetChild(0));
    7.         canvas.transform.GetChild(0).gameObject.SetActive(true);
    8.         button = Instantiate(buttonPrefab);
    9.         button.GetComponentInChildren<TextMeshProUGUI>().text = "Look at " + gameObj.name;
    10.         button.transform.SetParent(transform);
    11.         button.GetComponent<Button>().onClick.AddListener(delegate { OnPointerClick(); });
    12.     }
    The above code is changed/added to/a new method is created for new clickable objects (i.e. "protected void CreateTreeMenu()), within a new gameObject.

    To close the game object... well, this is explained above, and thus far only works correctly for the initial one button gameObject 'look at', otherwise it closes as soon as the mouse moves.

    Is there a better way to do this? Again, tutorials that I have not found or threads that I have not read -- i.e. anything you know of --- would be appreciated as well as direct code/responses.

    PostScript: I would honestly prefer a single global object that registers what is clicked on the screen then generates the menu, but I don't know how to do that.
     
    Last edited: Oct 30, 2021
  2. Master_Aricitic

    Master_Aricitic

    Joined:
    Sep 17, 2017
    Posts:
    6
    So, I haven't figured it out yet, as of this post. Still can't get the menu to disappear when the mouse leaves it.
    However, I have changed how I spawn the menu(s / items).
    I'm now using ScriptableObjects to hold each set of instructions for spawning the buttons, with a main script that looks like this:

    Code (CSharp):
    1.  
    2.     internal GameObject GameTree;
    3.     internal GameObject MenuTree;
    4.     [SerializeField]
    5.     internal GameObject MenuTreeDefault;
    6.     [SerializeField]
    7.     internal GameObject gameObj;
    8.     [SerializeField]
    9.     internal List<MenuScript> menus;
    10.     internal List<GameItemObject> items;
    11.     internal Canvas canvas;
    12.     internal GraphicRaycaster raycaster;
    13.     internal RaycastHit2D[] hit;
    14.     internal List<GameObject> gameObjects;
    15.     internal GameObject player;
    16.     internal Player Player;
    17.  
    18.     [SerializeField]
    19.     internal GameObject buttonPrefab;
    20.     internal GameObject button;
    21.     internal Vector3 pos;
    22.  
    23.     void Awake()
    24.     {
    25.         canvas = Camera.main.GetComponentInChildren<Canvas>();
    26.         GameTree = GameObject.FindGameObjectWithTag("GameTree");
    27.         if (GameTree == null)
    28.         {
    29.             GameTree = GameObject.Find("GameTree");
    30.         }
    31.  
    32.         MenuTree = GameObject.Find("Menu");
    33.         gameObjects = new List<GameObject>();
    34.         player = GameObject.Find("Player");
    35.         Player = player.GetComponent<Player>();
    36.     }
    37.  
    38.     // Start is called before the first frame update
    39.     void Start()
    40.     {
    41.        
    42.     }
    43.  
    44.     // Update is called once per frame
    45.     void Update()
    46.     {
    47.        
    48.     }
    49.  
    50.     public void OnPointerClick(PointerEventData eventData)
    51.     {
    52.         pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    53.         MenuTree.GetComponent<RectTransform>().SetPositionAndRotation(pos, Quaternion.identity);
    54.         List<GameObject> objects = new List<GameObject>();
    55.  
    56.         hit = Physics2D.RaycastAll(pos, Vector2.zero);
    57.  
    58.         // If nothing hit destroy the menu
    59.         if(hit.Length == 0)
    60.         {
    61.             foreach (MenuScript menu in menus)
    62.             {
    63.                 menu.DestroyThis();
    64.             }
    65.         }
    66.  
    67.         // Get list of objects
    68.         foreach (RaycastHit2D item in hit)
    69.         {
    70.             objects.Add(item.collider.gameObject);
    71.         }
    72.  
    73.         // Cleanup previous menu
    74.         foreach (MenuScript menu in menus)
    75.         {
    76.             menu.DestroyThis();
    77.         }
    78.  
    79.         // Spawn menu items for hit objects
    80.         for (int i = 0; i < hit.Length; i++)
    81.         {
    82.            
    83.  
    84.             gameObjects.Add(hit[i].transform.gameObject);
    85.             Debug.Log("Click" + hit[0].transform.gameObject);
    86.  
    87.             gameObj = hit[i].transform.gameObject;
    88.  
    89.  
    90.             Debug.Log(gameObj);
    91.             if (hit[i].transform.gameObject.name.Contains("Player"))
    92.             {
    93.                 Debug.Log("Contains player.");
    94.  
    95.                 menus[0].setObjects(objects);
    96.                 menus[0].Awake();
    97.             } else
    98.             {
    99.                 menus[0].setObjects(objects);
    100.                 menus[0].Awake();
    101.             }
    102.  
    103.             if (hit[i].transform.gameObject.name.Contains("Tree"))
    104.             {
    105.                 Debug.Log("Contains Tree.");
    106.                 gameObj = hit[i].transform.gameObject;
    107.  
    108.                 menus[1].setObj(gameObj);
    109.                 menus[1].Awake();
    110.                 Debug.Log(menus[1].buttons.Capacity);
    111.             }
    112.  
    113.             if (hit[i].transform.gameObject.GetComponent<GameItem>() != null)
    114.             {
    115.                 Debug.Log("Contains item.");
    116.                 menus[2].setObjects(objects);
    117.                 menus[2].Awake();
    118.             }
    119.         }
    120.     }
    121.  
    122.     public void OnPointerEnter(PointerEventData eventData)
    123.     {
    124.         foreach (MenuScript menu in menus)
    125.         {
    126.             // menu.DestroyThis();
    127.         }
    128.     }
    129.  
    130.     public void OnPointerExit(PointerEventData eventData)
    131.     {
    132.         foreach (MenuScript menu in menus)
    133.         {
    134.             // menu.DestroyThis();
    135.         }
    136.     }
    137.  
    138.     public void setObj(GameObject gameObjct)
    139.     {
    140.         gameObj = gameObjct;
    141.     }
    142.  
    143.     public void DestroyTree()
    144.     {
    145.         foreach (MenuScript menu in menus)
    146.         {
    147.             menu.DestroyThis();
    148.         }
    149.     }
    and a base spawning ScriptableObject that looks like:

    Code (CSharp):
    1. public GameObject gameObj;
    2.     public List<GameObject> objects;
    3.     [SerializeField]
    4.     protected GameObject MenuTree;
    5.     protected Canvas canvas;
    6.     [SerializeField]
    7.     protected GameObject buttonPrefab;
    8.     protected GameObject button;
    9.     internal List<GameObject> buttons;
    10.    
    11.  
    12.     internal virtual void Awake()
    13.     {
    14.         canvas = Camera.main.transform.GetChild(0).gameObject.GetComponent<Canvas>();
    15.         MenuTree = canvas.transform.GetChild(0).gameObject;
    16.     }
    17.  
    18.     public void setObj(GameObject gameObjct)
    19.     {
    20.         gameObj = gameObjct;
    21.     }
    22.  
    23.     public void setObjects(List<GameObject> list)
    24.     {
    25.         objects = list;
    26.     }
    27.  
    28.     internal virtual void DestroyThis()
    29.     {
    30.         foreach (GameObject gameObject in buttons)
    31.         {
    32.             Destroy(gameObject);
    33.         }
    34.         buttons = new List<GameObject>();
    35.     }
    The first menu item spawns a single button for each item hit:
    Code (CSharp):
    1. internal override void Awake()
    2.     {
    3.         canvas = Camera.main.transform.GetChild(0).gameObject.GetComponent<Canvas>();
    4.         MenuTree = canvas.transform.GetChild(0).transform.GetChild(0).gameObject;
    5.         CreateTopMenu();
    6.     }
    7.  
    8.     internal void CreateTopMenu()
    9.     {
    10.         MenuTree.SetActive(true);
    11.         // MenuTree.transform.SetPositionAndRotation(pos,Quaternion.identity);
    12.  
    13.         foreach (GameObject obj in objects)
    14.         {
    15.             // Look at...
    16.             button = Instantiate(buttonPrefab);
    17.             button.GetComponentInChildren<TextMeshProUGUI>().text = "Look at " + obj.name;
    18.             button.transform.SetParent(MenuTree.transform);
    19.             button.GetComponent<Button>().onClick.AddListener(() => Debug.Log("You are looking at " + obj.name + "."));
    20.             buttons.Add(button);
    21.         }
    22.     }
    23.  
    24.     internal override void DestroyThis()
    25.     {
    26.         foreach (GameObject gameObject in buttons)
    27.         {
    28.             Destroy(gameObject);
    29.         }
    30.         buttons = new List<GameObject>();
    31.     }
    Sadly, it didn't resolve the issue of making the menu disappear when the mouse leaves it.
    I tried creating a parent object and having it "onMouseEnter" remove the menu, but that causes it to vanish immediately as well...