- Home /
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!
Answer by Azrapse · Oct 26, 2013 at 06:43 PM
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.
This is a great approach. $$anonymous$$ight help to know that in order to implement this your ObjectFactory will need access to the prefab, in Azrapse's example, sithPrefab. I can think of 3 options...
pass it into the CreateSith call (requires all dependent objects to have a reference to the prefab)
load it directly from resources (means Unity can't track the prefab usage)
make your ObjectFactory a $$anonymous$$onoBehaviour so that you can register the prefab on it using the inspector. (this requires a bit of a fiddle so that the CreateSith call remains static)
I have updated the code for the Factory in my answer following your suggestions, @karlhulme.
I had to set instance = this
in Awake()
ins$$anonymous$$d of Start()
.
Answer by olejuer · Dec 10, 2015 at 05:20 PM
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 http://devmag.org.za/2012/07/12/50-tips-for-working-with-unity-best-practices/
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;
}
}
Answer by tpainton · Jun 14, 2015 at 03:03 PM
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.
Couldn't disagree more.
Note that this isn't actually about initialising the object, it's about initialising its components. It could have any number of components, and any number of them (including, quite often, none of them) might require some sort of additional initialisation. To me it seems perfectly reasonable to put all this in the hands of the person who created the prefab. That's the person that knows what the components are and whether they need further initialisation.
A system for all this in the engine would have to be quite complicated in order to be of any real use, and it would still involve me having to write my Initialisation functions and then decide which components needed initialising and how. I can't see how it's going to end up being any simpler than my calling the initialisation function(s) explicitly straight after instantiating, and it'd certainly be less transparent.
I'd much rather the Unity devs spent their time on more useful things
Answer by hexagonius · Mar 15, 2017 at 02:07 PM
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>);
}
}