Recently, I’ve been working on building a tooltip pop-up function for my project. I have a script called TooltipTrigger.cs attached to various UI game objects, which triggers the tooltip on hover. In this script, I’ve implemented the IPointerEnter and IPointerExit interfaces.
public class TooltipTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
//binding data
public TooltipData data;
//static action
public static Action<bool, TooltipData> OnTrigger;
public void OnPointerEnter(PointerEventData eventData)
{
//send action
Debug.Log("Enter");
OnTrigger?.Invoke(true, data);
}
public void OnPointerExit(PointerEventData eventData)
{
Debug.Log("Exit");
OnTrigger?.Invoke(false, data);
}
}
In another script called UIManager.Event.cs , I registered a handler method to this action:
public partial class UIManager
{
public class Event : Singleton<Event>
{
public Event()
{
TooltipTrigger.OnTrigger += UIManager.GetInstance().TooltipTriggerHandler;
}
}
}
However, when I went back to the Unity editor and hit play, I encountered a NullReferenceException error. This is confusing because the console output indicates that the object isn’t null, and the UIManager is a singleton, which should also not be null. Yet, it still throws a null reference error.
Does anyone know how to resolve this issue? Any help would be greatly appreciated!
Thanks for the suggestion! I’ve managed to locate the problem. It turns out that this line of code: TooltipTrigger.OnTrigger += UIManager.GetInstance().TooltipTriggerHandler; doesn’t work as intended because UIManager.GetInstance() is returning null.
This is still quite puzzling, though. Every other singleton class that inherits from MonoSingleton.cs works perfectly fine. I suspect it might have something to do with the fact that I manually dragged UIManager.cs into the scene, but I still don’t fully understand why that would cause this issue.
Here’s the MonoSingleton script:
public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
private static readonly object lockObj = new object();
public static T GetInstance()
{
if (instance == null)
{
lock (lockObj)
{
if (instance == null)
{
GameObject obj = new GameObject();
instance = obj.AddComponent<T>();
//Regexp to format name
string input = typeof(T).ToString();
string pattern = @".*\.";
string replacement = "";
string formattedString = Regex.Replace(input, pattern, replacement);
obj.name = formattedString;
GameObject systemObj = GameObject.Find("System");
if (systemObj == null)
{
systemObj = new GameObject("System");
}
obj.transform.SetParent(systemObj.transform);
}
}
}
return instance;
}
protected virtual void OnDestroy()
{
if (instance == this)
{
instance = null;
}
}
}
And here’s how I modified the UIManager.cs script:
public partial class UIManager : MonoSingleton<UIManager>
{
//Binding
[SerializeField] private GameObject uiCanvas;
[SerializeField] private GameObject eventSystem;
//Modified
private static UIManager instance;
public static new UIManager GetInstance() { return instance; }
private void Awake()
{
instance = this;
}
private void Start()
{
DontDestroyOnLoad(uiCanvas);
DontDestroyOnLoad(eventSystem);
//Instance nested class
UIManager.Event.GetInstance();
}
public void TooltipTriggerHandler(bool isTriggered, TooltipData tooltipData)
{
Debug.Log("Display Tooltip");
}
}
What confuses me is that I thought whenever a script is attached to a game object, Unity Engine would automatically create an instance of that class. So, in this case, an instance of UIManager should already exist in memory even before I call the GetInstance method. But apparently, that’s not happening.
Can anyone explain why this is the case? Thanks a lot!
No, sorry, but it doesn’t work like that. Sure there’s an instance in memory, but doesn’t mean it gets magically assigned to this static reference. Something on your user code side still needs to make that happen.
And if you’re using a monobehaviour singleton, do not drag it into a scene. It should only ever self-factory itself, or pull itself out of Resources or Addressables. Putting them in scenes is asking for trouble.
Thanks for your help! I think I’ve figured out part of the issue. However, I still have a question. Even though the instance created by Unity Engine isn’t assigned to the static instance, in the specific line of code TooltipTrigger.OnTrigger += UIManager.GetInstance().TooltipTriggerHandler; , the GetInstance method should still return an instance of UIManager . It shouldn’t be returning null. I’m really struggling to understand where the null is coming from.
Either the code that tries to call Getinstance() runs before UIManager.Awake runs, or you don’t have an instance in the scene when Getinstance() runs. Or you have two instances and one is destroyed or something.
MonoSingleton looks like overengineered garbage, but it at least handles there being a guarantee that there’s always a singleton ready when you call it, so why are you changing it into a drag-into-scene setup?
I was just trying to save some time. I wanted to assign a tooltip game object to a variable, so I thought, “Why not just drag it into an object and assign the tooltip directly in the editor?” That’s why I made this stupid decision.
However, regarding the code above, since TooltipTrigger.OnTrigger += UIManager.GetInstance().TooltipTriggerHandler; is in the constructor of UIManager.Event, and I’m calling the UIManager.Event.GetInstance() method in the Start() method of UIManager, the instance of UIManager should never be null. I still can’t understand where the null is coming from.
Try this (and for future reference, post code as text and not images, so we can copy-paste it to show you what to do instead of having to retype it by hand):
public class UIManager {
private static UIManager _instance;
private static UIManager instance {
get => _instance;
set {
Debug.Log("Set instance to: " + value);
_instance = value;
}
....
}
See where that shows up in relation to the exceptions.
I think the bottom line is you need to debug throught this issue yourself. It could be specific to your project set up, and none of us can help with that.
This all just smells like extremely over-engineered defective singletons to me.
You have this Singleton type… is that a MonoBehaviour? If it is then you could NEVER ever ever implement constructor for it the way you have in your first screenshot.
I ask because I see that other MonoSingleton. I’m also not gonna waste time looking through photographs of code when the forum supports properly-posted code.
Meanwhile, if anything is going to be marked DDOL, as Spiney points out, NEVER put it in a scene. NEVER do it. It won’t help you , no matter if ten thousand tutorial makers drag singletons into scenes. It’s bad practice and gets you nothing: you can’t drag it onto stuff and you can’t drag stuff onto it because of lifecycle mismatch.
Simple Unity3D Singleton (no predefined data):
Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:
These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance
Alternately you could start one up with a [RuntimeInitializeOnLoad] attribute.
There is never a reason to drag a GameObject into a scene if it will be DontDestroyOnLoad.
If you do:
you may drag it into something else that isn’t DDOL. FAIL
you may drag something else into it that isn’t DDOL. FAIL
Just DO NOT drag anything into a scene that will be marked DontDestroyOnLoad. Just Don’t Do It!