Trying to do UI events at runtime

Hey guys I’m building my menus out through script at runtime and I’m not sure how to use the event system.

I have a “menu” gameobject with the eventsystem and standalone input module components and I’m displaying a button as a child gameobject and adding an event trigger to it.

EventTrigger btn1_trigger = btn1.AddComponent<EventTrigger>();
EventTrigger.Entry btn1_entry = new EventTrigger.Entry();
btn1_entry.eventID = EventTriggerType.PointerClick;
btn1_entry.callback.AddListener((data) => { Onbtn1Click((PointerEventData) data); });
btn1_trigger.triggers.Add(btn1_entry);
public static void Onbtn1Click(PointerEventData e)
{
    Debug.Log("clicked");
}

But no click events are firing. The button has a recttransform and image components on it. I noticed that when I play in the editor the button has the event trigger component showing up but no functions are displayed on it.

Untitled

Runtime added listeners have never shown in in UnityEvents. They are handled differently to inspector added ones.

That said, not sure why this isn’t working, though haven’t used the old UI system for a while.

But at runtime you’re better off using regular C# delegates. Usually helps to have a custom monobehaviour middleman that you can hook into. Ideally you have a skeleton prefab with all these triggers hooked up via the inspector, calling methods that invoke your C# delegates.

EventTrigger does appear to be set up to be inherited from, so this could be an option too.

Also, as an aside, there’s no real point in using an anonymous method to call a method with the same signature. You should just be subscribing Onbtn1Click directly.

Thx for reply spiney199. Ya I was just testing it out to see if it would work. The problem was that I also needed a “GraphicRaycaster” object on my menu.

1 Like

If you want to build dynamic UI content, I’ve always found that a middle ground approach works best. Use the inspector to set up the UI as close as you need, including boilerplate crud like raycasters and eventsystems and whatnot, then have a very small surface of stuff that gets duplicated procedurally according to the dynamic needs.

Attached is a simple demo of such a system which looks for sprites, then builds as many clickable UI buttons with those sprites in it as necessary.

9557998–1351300–DynamicUIDemo.unitypackage (94.0 KB)

UI Toolkit is more oriented to runtime generation, as visual elements are all just plain C# objects and can be instanced as much as desired. Yes the framework isn’t perfect but neither is uGUI.

Otherwise for the old UI system, I agree with Kurt and think using prefabs are a must. Even if it’s skeleton ones, with specific variants of them. It is UI done the “Unity way” so you kinda need to follow the same patterns with how you use it.

@Kurt-Dekker, ya that’s kinda what I was going for. I’m going to have a crap load of menus so instead of keeping 1000 menu gameobjects in the heirarchy I thought it would be better for performance to just create them manually as they are needed. But then maybe that would contribute to some nasty GC, I dunno.

@spiney199, ya I tried the UI toolkit out first but I dunno man… I think it’s nowhere near ready for production. I come from a web developer background so it was nice to see them take a stylesheet approach but there just wasn’t enough features at all. And I wanted to use rounded corners and drop shadows so I bought some assets to help with that.

An altenative for prefabs that I tend to use alot are template panels that I hide upon Awake, and then instantiate when I need them. This way I get full wysiwyg from within the place they are tended to be used.

Doesnt replace prefabs though, because you might want to reuse the content on several places. The idea is the same though.

Example from our game

public class TutorialMenu : BaseIngameMenu
{
    [SerializeField] private Button tutorialButtonTemplate;

    protected override void Awake()
    {
        base.Awake();

        tutorialButtonTemplate.gameObject.SetActive(false);

        var manager = (TutorialGameManager)GameManager.Instance;
        foreach (var tutorial in manager.Tutorials.Where(t => !string.IsNullOrEmpty(t.FriendlyName)))
        {
            var instance = Instantiate(tutorialButtonTemplate);
            instance.transform.SetParent(tutorialButtonTemplate.transform.parent, false);
            instance.gameObject.SetActive(true);

            instance.GetComponentInChildren<Text>().text = tutorial.FriendlyName;
            instance.onClick.AddListener(() => manager.ForceTutorial(tutorial));
        }
    }

    protected override string GetMapName()
    {
        return "Tutorial";
    }
}

Another example, little more advanced. Using custom events instead of click handler

    public class TabMenu : MonoBehaviour
    {
        private TabItem template;

        public TabItemConfig[] Tabs;
        private readonly List<TabItemInstance> tabItemInstances = new List<TabItemInstance>();

        public ColorBlock Colors;

        private TabItemInstance selected;

        protected virtual void Awake()
        {
            template = GetComponentInChildren<TabItem>(true);
            template.gameObject.SetActive(false);

            foreach (var config in Tabs)
            {
                var instance = Instantiate(template);
                instance.gameObject.SetActive(true);

                instance.Image.sprite = config.Icon;

                instance.transform.SetParent(transform, false);

                var item = new TabItemInstance
                {
                    Tab = instance.GetComponent<Image>(),
                    Config = config,
                };
                tabItemInstances.Add(item);

                item.Tab.gameObject.AddComponent<EventTrigger>()
                    .triggers.AddRange(new Dictionary<EventTriggerType, Action>()
                    {
                        {EventTriggerType.PointerClick, () => SetActive(item)},
                        {EventTriggerType.PointerEnter, () => SetActiveColor(item, true)},
                        {EventTriggerType.PointerExit, () => SetActiveColor(item, item == selected)}
                    }.Select(h =>
                    {
                        var et = new EventTrigger.Entry
                        {
                            eventID = h.Key
                        };
                        et.callback.AddListener(o => h.Value());
                        return et;
                    }));
            }

            SetActive(tabItemInstances.First());
        }

        private void SetActive(TabItemInstance activate)
        {
            foreach (var item in tabItemInstances)
            {
                var active = item == activate;

                SetActiveColor(item, active);
                item.Config.Panel.gameObject.SetActive(active);
                if (active)
                {
                    selected = item;
                }
            }
        }

        private void SetActiveColor(TabItemInstance item, bool active)
        {
            item.Tab.color = active ? Colors.highlightedColor : Colors.normalColor;
        }

        private class TabItemInstance
        {
            public TabItemConfig Config { get; set; }
            public Image Tab { get; set; }
        }
        
    }

In both these exmaples I set up the meat of the UI in editor. Not from code. Never create UI from code. Its a one way to disaster. Doesnt matter what UI framework.

This part here was super helpful. That’s how I wanted to do it but wasn’t sure how. Thanks Anders.

1 Like