- Home /
[C#] Contructors, Polymorphism and Abstracting.
Hello dear community!
I am asking you guys for help today because, well i am running into problems with unity and C#(Well mostly just Unity).
The problems i am having mostly have to do with the fact that when creating(Instantiating) Game objects, i feel like i loose a certain amount of control, that i feel i normally have using constructors. An example could be "spawning" a enemy, and add him to a list of type Enemy. In the case of Unity it seems "dangerous" that i have to Instantiate a Prefab, hope it has a script of type Enemy (With abstract classes, this fear of mine is even worse) and add that script to the List? Normally you would just create a new Instance, and be done with it. Like in the following example(Overly simplified ofc)
List<Enemy> enemies;
Enemy newEnemy = new Enemy(params);
enemies.Add(newEnemy);
Where in Unity it seems like the "way" of doing it would be:
List<Enemy> enemies;
GameObject newEnemy = (GameObject)Instantiate(enemyPrefab, position, rotation);
Enemy enemyScript = newEnemy.getComponent<Enemy>();
enemies.Add(enemyScript);
This, seems, not only clumsy but reminds me of the old AS2 days of attachMovieClip(And for those of you who remember, or has to work with scaleform, can maybe understand why sweat is starting to form on my head)
I have tried my "own" way of "solving" the issue, using init() methods, or pseudo constructors, i tried classes that upon creation(the class not the GameObject) instantiate the prefab on its own. Or Prefabs, that register themselves to other classes, upon creation. But it just does not feel right.
So i keep thinking that there must be a way around this. A better way of working with GameObjects and MonoBehaviors. And i hope you guys might have some tips
Answer by whydoidoit · Apr 21, 2013 at 05:46 PM
It's dangerous to "fight" with an opinionated framework like Unity, and I'm afraid you just have to get used to it. I know where you are coming from because I similarly fought with it for a while :) I have all sorts of clever init functions that I just never use any more.
You do have less control, because you are using a framework, but you get lots of benefit for that too and the model really works.
As you are in control of the prefabs you really should be able to know that they have the right components. If you want to be sure then just assert that it has the components you need:
var enemyTransform = Instantiate(something) as Transform;
var enemy = enemyTransform.GetComponent<Enemy>();
if(!enemy) Debug.LogError("No enemy attached");
There's a very good reason it isn't clumsy - and that's because these are associated behaviours with a particular object - completely encapsulated pieces of behaviour that can be added to anything - it's very cool. You really shouldn't be using deep class hierarchies with it as its a composition model - you shouldn't need them.
Feel free to use real classes as variables within scripts if that makes sense to your game, then you can use what you like.
So rather than using a class hierarchy to create a series of overridden virtual methods for things like CastMagicSpell, HitWith - you use either the SendMessage pattern or interfaces to create behaviours that do that kind of dynamic functionality.
Here's a generic method to create an instance of a prefab and verify it:
public static class InstantiationHelper
{
public T Instantiate<T,TP>(this TP prefab, Vector3 position = default(Vector3), Quaternion rotation = default(Quaternion)) where T : UnityEngine.Component, TR : UnityEngine.Component
{
TP instance = Instantiate(prefab, position, rotation) as TP;
return instance != null ? instance.GetComponent<T>() : null;
}
public T Instantiate<T,TP>(this TP prefab, Vector3 position = default(Vector3), Quaternion rotation = default(Quaternion), params Type[] verify) where T : UnityEngine.Component, TR : UnityEngine.Component
{
var instance = prefab.Instantiate(prefab, position, rotation);
foreach(var t in verify)
{
if(instance.GetComponent(t) == null)
{
Debug.LogError("Did not contain " + t.FullName);
}
}
return instance;
}
}
And use it like this:
var enemy = somePrefab.Instantiate<Enemy>();
var anotherEnemy = somePrefab.Instantiate<Enemy>(transform.position, transform.rotation);
var verifiedEnemy = somePrefab.Instantiate<Enemy>(transform.position, Quaternion.identity, typeof(Rigidbody), typeof(SomeOtherScript));
Or you could use AddComponent to create an instance and also send an initialisation message:
public static class InstantiationHelper
{
public T InstantiateWithParam<T,TP>(this TP prefab, Vector3 position = default(Vector3), Quaternion rotation = default(Quaternion), object parameter = null) where T : UnityEngine.Component, TR : UnityEngine.Component
{
TP instance = Instantiate(prefab, position, rotation) as TP;
if(instance != null)
{
var returnValue = instance.GetComponent<T>();
if(returnValue == null) returnValue = instance.AddComponent<T>();
instance.Send$$anonymous$$essage("Initialize", parameter);
return returnValue;
}
return null;
}
}
Which allows your to write a "constructor" called Initialize
void Initialize(Transform target)
{
//Do something
}
var enemy = somePrefab.InstantiateWithParam<Enemy>(Vector3.zero, Quaternion.identity, transform);
That was a great answer, and pretty much nailed what i wanted to know. thanks a bunch!
I guess in the end, i was the one trying to force a more "deep" class structure hehe. But yes creating helper classes for instantiating of prefabs seems very nice, and I completely forgot about AddComponent too, which could work nice in a lot of circumstances.
But so many solid, and fast replies so fast, that made my $$anonymous$$onday morning the best in a while!
Answer by fafase · Apr 21, 2013 at 03:47 PM
I guess you should be able to do:
Enemy enemy = (Enemy)Instantiate(bla,blo,blu);
enemies.Add(enemy);
Answer by Loius · Apr 21, 2013 at 04:27 PM
Unity needs MonoBehaviour's constructor, and you can't have it (na-na-na-na-boo-boo, as it were).
Some alternatives which can keep your main code at a single line:
Build a wrapper class:
class Enemy {
EnemyBehaviour myObject;
public Enemy(Number one) {
myObject = (EnemyBehaviour)Instantiate(...):
}
}
Use typing in the Inspector: (ensures type safety, does not provide initialization)
public Enemy spawnable;
...
Enemy enemy = (Enemy)Instantiate(...);
Use a static function in the Enemy class:
class Enemy : MonoBehaviour {
public static Enemy NewEnemy(...) {
Enemy enemy = (Enemy)Instantiate(...);
// ...
return enemy;
}
...
}
I would not go for the first one as it would mean you cannot do all kind of GetComponent on it.
Answer by DESTRUKTORR · Apr 21, 2013 at 06:24 PM
When it comes to efficiency in Unity, there's a very delicate balance of use of custom classes (classes that do -not- extend Monobehaviour) and Monobehaviour-derived classes. Typically, you want to involve as few Monobehaviour classes as possible, to improve efficiency, making a class extend Monobehaviour only when it's actually necessary for the programming, or if your project needs it for added readability.
Generally, I like to make relatively few Monobehaviours that will manage a multitude of serializable, custom classes, in my scene.
However, to more directly answer your question, you will need to have at least one GameObject with an attached Monobehaviour in your scene, in order to instantiate the rest of the objects needed.
I'm not sure what exactly you meant by being worried about "Abstract classes," as an abstract class would never be instantiated, so I will assume you meant that you wouldn't be able to make an abstract Monobehaviour, which, to the best of my knowledge, is impossible, anyway.
However, it is possible to use abstract classes to create custom classes, and for those monobehaviours that you want to use an abstract-ish framework on, there's always interfaces.
However, when it comes to instantiating prefabs, or attaching monobehaviours, this process is actually quite simple.
void InstantiateDesiredMonobehaviour()
{
GameObject go = new GameObject("Desired GameObject Name");
DesiredMonobehaviour dm = go.AddComponent<DesiredMonobehaviour>();
dm.someVariable = new SomeVariable();
dm.someOtherVariable = new SomeOtherVariable();
}
The other option would be to simply instantiate a prefab object (which is MUCH more convenient, and arguably suggested, as opposed to manually instantiating every value). There are some examples of this in Unity's documentation, found here.