There is any way to know prior to the game execution if a GameObject has an specific set of components?

Hello,

I’m creating a simple 2D plataformer with Unity. In this plataformer I have a GameObject “Player” with a prefab like this:

90003-img1.png

In short, the player has life, can move and cast fireballs, with each component performing one of the desired behaviours.

However, at some point in the development I decided to remove the player’s ability to cast fireballs. Since I’m using the component based architecture, removing the behaviour was pretty easy from point of view the of GameObject, I just had to remove the “FireBall_Component” component.

90004-img2.png

However, when taking scripts in consideration, the things started to get messy. In my scripts, there were many GetComponent calls related to the Player’s “FireBall_Component”, and since it was removed, many null reference exceptions started to appear during run time. Many of these exceptions were caused when some script was trying to subscribe to the OnCastFireBall event.

if(go.tag == "Player")
{
	go.GetComponent<FireBall_Component>().OnCastFireBall += OnCastFireBall;
}

Since the “FireBall_Component” does not exist any more in the Player GameObject, the GetComponent calls will return null and null reference exceptions will happen. The null reference exceptions problem could be solved with an if statement, checking if the return of GetComponent is null.

FireBall_Component fireBall_ComponentTmp;
                
 if(go.tag == "Player")
{
    fireBall_ComponentTmp = go.GetComponent<FireBall_Component>();
    if(fireBall_ComponentTmp != null) fireBall_ComponentTmp.OnCastFireBall += OnCastFireBall;
}

But I would still have useless pieces of code all over my scripts. After all, if I’ll never have a “FireBall_Component” in my Player’s GameObject, there is no reason to check if it exists.

This problem has caused me to spend a lot of time executing the game until I was able to find all null reference exceptions and, after that, take a big look at all my code removing the incorrect GetComponent calls.
And this is not the first time I had this problem.

So some questions are:

There is any way to know prior to the game execution if a GameObject has an specific set of components?

There is some way to find any GetComponent calls that will return null before run time?

There is any best practices or desing paterns that I can use to avoid that problem?

This problem has been killing me for quite some time and I would be very thankfull to anyone who can help me.

OBS.: Please apologize any English mistakes. English is not a mother tongue in my country and I’m not used to speaking English in my daily life.

The answer for your first question is: yes, there is! Here’s a little ExecuteInEditMode script I wrote that checks for specific components that are attached to its gameobject, and tells you if it has one or not, all while inside the editor (outside of play mode). It updates when the editor updates (including when you change a value in the inspector of any GameObject), or when you press the Check button in its own inspector.

CheckForComponents.cs

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class CheckForComponents : MonoBehaviour
{
	public string[] componentTypes = new string[] { "Transform", "Example" };

	public void Check()
	{
		foreach (string T in componentTypes)
		{
			if (gameObject.GetComponent(T) == null)
			{
				Debug.LogError(name + " has no " + T + " component");
			}
			else
			{
				Debug.Log(name + " has a " + T + " component");
			}
		}
	}

	// for continual updating in editor
	private void Update()
	{
		Check();
	}
}

// inspector button
#if UNITY_EDITOR
[CustomEditor(typeof(CheckForComponents))]
public class CheckForComponents_Editor : Editor
{
	public override void OnInspectorGUI()
	{
		CheckForComponents script = (CheckForComponents)target;

		DrawDefaultInspector();

		if (GUILayout.Button("Check for Components"))
		{
			script.Check();
		}
	}
}
#endif

Or, if you just want an add-on to your existing play mode script that works outside of play mode, here is an example that checks the components of its GameObject every frame (but only when its GameObject is selected).

ExampleTwo.cs

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

// play mode script
public class ExampleTwo : MonoBehaviour
{
	// Cast a Fireball or something
}

#if UNITY_EDITOR
[CustomEditor(typeof(ExampleTwo))]
public class ExampleTwo_Editor : Editor
{
	ExampleTwo script;
	string[] componentTypes = new string[] { "Transform", "Example" };

	void Check()
	{
		foreach (string T in componentTypes)
		{
			if (script.gameObject.GetComponent(T) == null)
			{
				Debug.LogError(name + " has no " + T + " component");
			}
			else
			{
				Debug.Log(name + " has a " + T + " component");
			}
		}
	}
	
	private void OnEnable()
	{
		script = (ExampleTwo)target;

		EditorApplication.update += Check; // update every frame
	}

	private void OnDisable()
	{
		EditorApplication.update -= Check; // stop updating
	}
}
#endif

I don’t know if there is any practical way of checking each and every script to see if it will result in a NullReferenceException, since reference variables can be changed at any time during gameplay or in the editor. I’ll leave this for other people to answer.

My best suggestion for easy removal of scripts is custom scripting define symbols. Go to Edit > Project Settings > Player | Other Settings | Scripting Define Symbols, and type in, for example, FIREBALL_COMPONENT. Then use the following code (preprocessing directive) in your scripts, whenever you are referencing a Fireball_Component.

#if FIREBALL_COMPONENT
	// fireball code
#endif

Then, when you want to remove a component, simply remove the FIREBALL_COMPONENT string from the list of symbols, and you’re good to go; the scripts will still have the code referencing the fireball component, but it will not be executed.

I think an easier way would be to reattach the FireBall script to the character, then alter the script such that all methods that affect the character are removed. That way, the script will then become just a bunch of variables that are being changed by other scripts but aren’t really changing the character’s behavior.
There would be no null references, and the changes caused to the script wont affect the character. Problem solved.