Unity newbie - Confused about maintaining type safety with prefab GameObjects

Brand new to Unity, but coming from a background in both C# and Unreal Engine. I’m getting the hang of it, but one thing about Unity’s prefabs just isn’t clicking for me.

For an overly-simplified example, let’s say I have enemies in my game, and those enemies have health.

To start, I make this class:

public class Enemy : MonoBehaviour
{
    public int health { get; set; }
}

Then I create a prefab, EnemyPrefab that has that class as a component.

Now, to instantiate an enemy, I know to use the Instantiate() method. I set a variable (say, enemyPrefab) to contain a reference to the prefab, and then do this:

GameObject newEnemy = Instantiate(enemyPrefab);

That instantiates my enemy, which is great. But coming from a C# background, I feel like I’m missing some type safety here. The thing I created is just a GameObject? That feels like declaring everything as object in non-Unity C#.

If I have a list of these instantiated enemies, that’s just a List<GameObject>. What’s to stop me from putting unrelated GameObjects in there that aren’t enemies? If I want to reference the health variable of a member of the list, do I have to call .GetComponent<Enemy>() on it and hope that it isn’t some unrelated thing that snuck in with no Enemy component?

I know it’s perfectly easy to just, not add non-Enemies to the list, but that’s more of a fast-and-loose, Python/JS-style, dynamic typing school of thought. I would expect C# to enforce some type safety and make sure I can’t absentmindedly add the wrong thing to my enemy list.

The only thing I can think of is that maybe when I instantiate, I should do this instead:

Enemy newEnemy = Instantiate(enemyPrefab).GetComponent<Enemy>();

And then maintain a List<Enemy> instead of a List<GameObject>. But all the code examples I’m finding seem to work with bare GameObjects.

I feel like I must be missing something. How do you know what’s what?

This is how inheritance looks like for your Enemy class:

    flowchart
         Component-->UnityEngine.Object
         Behaviour-->Component
         MonoBehaviour-->Behaviour
         Enemy-->MonoBehaviour

And this is what Instantiate( prefab ); creates, assuming it contains Enemy,Collider and Rigidbody components:

    flowchart
         Transform<-->GameObject
         Enemy-->GameObject
         Collider-->GameObject
         RigidBody-->GameObject

As you can see this is not about inheritance. This is a graph of references between different objects that all inherit from UnityEngine.Object. Some inherit from Component, some from UnityEngine.Object and others from MonoBehaviour (most user-created scripts) - but still it’s a graph of references that connects them to this central/organizing GameObject. And this is why Instantiate give you reference to it.

Instantiate also appends this newly created GameObject instance to a scene graph structure, commonly referred to as Hierarchy. Hierarchy determines how Transform’s Matrix4x4 is calculated.

Nothing stops you. But also nothing stops you from creating a List<Enemy> and problem is solved.

Yes and yes. This is why List<Enemy> is a more precise option (type-wise).

Exactly! This is the way. Ignore all the basic code samples in this regard - most of these target utmost simplicity to accommodate beginners.