Class Constructor on Instantiated objects

I’m encountering a recurring design problem in my Unity coding, and I’d like to see if others have found a satisfactory solution. To understand the problem, let’s start by comparing the traditional c# class constructor with ‘Object.Instantiate’:

  • C# class constructors can accept
    generic input parameters which can be
    customized by the developer.

  • Object.Instantiate, on the other hand, allows only a fixed set of
    input parameters. These parameters
    are used to define the transform for
    the instantiated object, we can’t
    customize beyond that.

So, we can’t pass data of our choosing to the object created by Object.Instantiate in the same way that we could pass to a class constructor. Which leads to the question, how do you pass such data?

I’ve been using a few patterns to pass this data. My current solution is to Object.Instantiate the object, then pass in needed data after Instantiation:

// instantiate our prefab
GameObject instantiatedObject = Object.Instantiate(prefabResource, Vector3.zero, Quaternion.identity) as GameObject;

// get the MonoBehavior that's attached to this object 
SomeMonoBehavior mono = instantiatedObject.GetComponent(typeof(SomeMonoBehavior)) as SomeMonoBehavior;

mono.field1 = new foobar();
mono.field2 = new foobaz();
...

this works, but It doesn’t feel elegant, and I end up creating a lot of ‘boilerplate’ code to handle what would typically be handled by a class constructor.

I’m not blocked on this, but I feel like there’s a better solution for this that I’m missing. Thanks!

Make all your prebabs have a script with a method Initialize.

Design that method as you please, accepting as many parameters as you wish, and that runs the initializing logic you wish.

Instatiante your game objects like this:

SpecialEnemy enemy = Object.Instantiate(enemyPrefab, Vector3.zero, Quaternion.identity).GetComponent<SpecialEnemy>();

enemy.Initialize("Darth Vader", 9000, "lightsaber", true);

It would be even better if, instead of instantiating the object directly, you create instead some Factory where you specify a type, and then the Factory creates the gameobject and returns to you the particular component object you want.

Something like

SpecialEnemy enemy1 = ObjectFactory.CreateSith("Darth Vader", 9000, "lightsaber", true);
SpecialEnemy enemy2 = ObjectFactory.CreateStormTrooper("Stormtrooper", 30, "blaster", false);
SpaceShip ship1 = ObjectFactory.CreateXWing("X-Wing", 350, "quad lasers", false);

The object factory could be something like this

public class ObjectFactory : MonoBehaviour 
{
    protected static ObjectFactory instance; // Needed

    public GameObject sithPrefab;
    public GameObject stormtrooperPrefab;
    public GameObject xwingPrefab;

    void Start()
    {
        instance = this;
    }

    public static SpecialEnemy CreateSith(string name, int health, string weapon, bool forceUser)
    {
        var enemy = Object.Instantiate(instance.sithPrefab, Vector3.zero, Quaternion.identity).GetComponent<SpecialEnemy>();
        enemy.Initialize(name, health, weapon, forceUser);
        return enemy;
    }

    [...]  // Do the same for the other kinds of prefabs.

}

Attach it to some empty gameobject, and fill up the different prefab slots by dragging them in the inspector.

Another solution is to create a class MonoBehaviourBase that extends MonoBehaviour and have all Scripts extend that one. That way you can define custom Insantiate-methods for each of your prefabs that require one. I got the idea from this handy article 50 Tips for Working with Unity (Best Practices) – Dev.Mag

using UnityEngine;

public class MonoBehaviourBase : MonoBehaviour
{
    public static Object InstantiatePrefabA(GameObject original, ParamType param,
        Vector3 position = default(Vector3), Quaternion rotation = default(Quaternion))
    {
        GameObject result = (GameObject) Instantiate(original, position, rotation);
        SomeComponent someComponent = result.GetComponent<SomeComponent>();
        // handle someComponent = null
        someComponent.Foo(param);
        return result;
    }
}

Really a more elegant solution needs to be provided in the platform. we are creating objects. Anytime an object is created, there needs to be a constructor. A great option would be a parameter in the Instantiate method that called for a Delegate constructor.

I’d like to throw another solution in the ring (based off of @Azrapse solution):

public class Test : MonoBehaviour{
  public Test Init(<parameters>){
    ...;
    return this;
  }
}

public class Instantiator : MonoBehaviour {
  void Start(){
    var newTest = Instantiate<Test>(prefab).Init(<param>);
  }
}