- Home /
What's the "right" way to deal with inheritance and constructor parameters?
I have a whole inheritance tree of game object types I need to be able to instantiate, activate and deactivate (or enable and disable) at run time, and I want to be able to use constructor parameters when I instantiate them so I don't have to use a bunch of boilerplate code every time I do that. That grows in size with each level in the inheritance tree. =P
What I've resorted to is (this seems kind of embarrassing and awful), I've created a single behavior script, which has a reference to the base class for my inheritance tree. So it has Update(), FixedUpdate(), OnTriggerEnter(), etc., and those functions just pass along those events to the same functions of the referenced object. So I assign that single behavior script to every prefab. So that way I can instantiate my objects with constructor parameters however I want to. The base class constructor constructs a prefab of a passed in name, gets the script component and sets the reference to itself, and sets it's own gameObject and transform properties, et etc, just to make things easier in the subclass code. But then I have to call GameObject.Instantiate(..) instead of just Instantiate(..), and do a few other less-than-optimal things like that. So this approach seems a bit clumsy and rigid to me. I've got a whole inheritance tree of objects which basically represent the behavior scripts for prefabs, but they're not monobehaviors. Seems kinda wrong.
Hmm, what about this: I create a base class that extends monobehavior. Then I derive my inheritance tree from that. Each subclass has an Initialize(...) function that does what my current classes do in their constructors. So.. I would assign one of my subclasses to a prefab, then where ever I consume that, I instantiate the prefab instead of the class, and then get the script component and call the Initialize(..) function. Meh. Still too much code per consumption.
Ok, maybe instead of having an Initialize(), or in addition, each subclass has a static Instantiate(...) function that instantiates the prefab, gets the script component, and sets the various variables or calls it's own non-static Initialize(...) to do that. Tch, but then I basically have a version of my current base class constructor for every subclass. In fact I would have a bunch of multipli-duplicated code in that Instantiate function.
Could I mutate Instantiate(..) and pass the prefab name up the inheritance chain like I do now in the constructors? Let's say Instantiate(..) returns the script component, so the base class would return that to the subclass, and so on down the chain. Can I recast that as the subclass in the subclass's Instantiate(..) so I can do further initialization? Maybe pass parameters to a mutated Initialize(..) instance function, so I don't have to prefix everything with "theobject."..
That seems like that could be a reasonable solution, if it works.
I dunno, what do you do? How do you handle the need for constructor parameters? Is there some easy/obvious/"right" way to do this that I'm missing?
If what I'm gathering from reading answers from some of you more Unity-experienced people is correct, it seems like a lot of you don't really use anything analogous to constructor parameters, in fact you may not really use inheritance at all. Yet you feel your code is easy to create and maintain? It's like there's some kind of paradigm or pattern that I'm not quite getting. Involving assigning multiple different behavior scripts to a single prefab, for example. Kind of an "I'm a this, but I'm also a that, and I have this modifier, too" kind of a thing. So the set of scripts is basically flat, or as flat as possible, and each one is.. well, tries to be small. But you might end up with 5 different Update() functions associated with a single prefab. (!?) So I think I sort of get that, but I still don't see how, doing that, you avoid ending up with quite a lot of basically-duplicated-but-slightly-different code sitting around in various places. And what about all the potential for stepping on each other's toes that all those different shavings-of-behavior have? There would be this mass of rapidly-fading-to-gray knowledge of which ones can be mixed with which other ones. Seems like you'd have to be some kind of gestalt genius to pull that paradigm off very well for very long. Either that, or you'd evolve some kind of organizational system for naming them and placing them in the assets tree or something, that makes it clear which ones can be used with which other ones. But then aren't you basically back to a hierarchy at that point?
Answer by whydoidoit · Nov 03, 2012 at 10:48 AM
So you are really trying to come to terms with two things:
Composition rather than inheritance
MonoBehaviours being special and having special requirements for construction
FIrstly - no inheritance architecture should have many levels - more than 3 or 4 and it's just going to start being hard to manage - I know some tutorial examples have many more than - but they aren't fitting with best practices if they do - there's so much refactoring involved in adding new things - then along comes another type and it just doesn't fit.
C# (and Unity Script) fix some of those problems in the traditional way using Interfaces. I'm guessing you know about them? Basically allows you to treat a number of classes that don't inherit from each other as if they were the same thing. The IWeapon class has a Hit method that is implemented differently by all weapons.
When working with C# I often mix inheritance and interfaces which give you a way of not repeating code but still able to model the worlds increasing complexities. I define interfaces first, then write base classes that implement them and use those for the most obvious and common groupings.
Then there's composition - which is Unity's hard to grasp at the beginning ability to have multiple things attached to the same physical game object. It's actually not composition in the original sense, but it's close.
The Unity model is actually very powerful and I disagree with your summation of scripts interacting with scripts being hard to do - it does take thinking about though.
You could think of a script as a set of code describing a behaviour you could give to some object.
Imagine you have a set of enemies - some walk, some fly, some have better eyesight than others, some have better hearing and they can carry different weapons.
Imagine when you instantiate an enemy you need to add some script that moves it, a script for eyesight and a script for hearing. You set the relative ranges or hearing and eyesight as simple variables. You decide on either Walk or Fly scripts - these would take a target position from your core enemy script and get there. The hearing and eyesight would raise events when they detected the player, that an attack script turned into a position to move to that would be in range of the player etc etc etc
You would add an weapon as a child of the enemy's transform and populate the "weapon" slot in the enemy script with it - that weapon knows how to respond to a "Attack" message etc.
My Finite State Machine tutorial on Unity Gems is building towards this kind of operation using Sense scripts etc.
At the end of the day you may have to try for a moment to stop thinking about how to structure code the way you do it now - I had this problem when I first used interfaces, spent ages trying to make them be able to inherit implementation, but it's not the right model - I was thinking about it wrong.
As for constructors - either have a public virtual void Initialize(params) method that calls up the chain and you invoke after instantiate - or configure the prefab before instantiation if you need variables to be available in an Awake function inside the script. Instantiate makes a copy of the current state of the object and you can change it.
Write factory methods to initialize individual scripts if that's the way you want to go (adding things to an instantiated object) - that method is going to create the script by doing an AddComponent to a GameObject and then you will configure the returned instance.
Unity is opinionated software - fighting it isn't going to get you anywhere, adapting both it and yourself is...
And one final point - I believe deeply in writing DRY code (Dont Repeat Yourself) - you can definitely make Unity scripts DRY - if you find yourself repeating code frequently then there's something wrong with your architecture or design.
Thanks very much for your thoughtful and helpful reply..
So you have static factory functions, somewhere, I guess they would be part of one of your "core" general object type scripts. The factory functions take parameters, piece together a specific version on the fly, including assigning add-on scripts, and return freshly instantiated and configured prefabs.
A core script is kind of a general type that has a bunch of slots for things that can be added and/or switched out, and it has state variables and FS$$anonymous$$ style logic in it that manages its state, and then a given add on might also have state variables and FS$$anonymous$$ logic. A given core script's factory functions might even use entirely different prefabs, depending on which specific version they were manufacturing.
Rather than create sub types of the core that inherit, just add some states, state variables, FS$$anonymous$$ code, variables (slots) and factory functions to your core, or if what you're after is really different enough, make a different core that doesn't inherit at all.
A given add-on might implement an interface or two, and be designed to work with any core that has a slot for an add-on that implements one of those interfaces. I guess your add-ons might inherit, or they might be cores themselves. I can imagine a "projectile weapon" being a core, that has factory functions to piece together a "rifle", "pistol", "sub machine gun," etc. And the "projectile weapon" core implements the "gun" interface, so that any version of it fits into any "gun" slot.
The gun interface might have "pull trigger" and "release trigger" functions, and some variables that let the core know if its gun is auto-repeating or not, and what the repeat rate is, and such, if it cares. But the general idea is that rather than subclassing and inheriting, as long as you're still working with the same basic type of animal, just grow the complexity of your core animal. Under this kind of a design, an Eagle and a Canary are the same basic animal, in fact an Eagle and an Elephant might even be the same basic animal - they just have a bunch of swapped out features.
Somewhere you must have an "enemy manager" script, that is assigned to your main camera or maybe some central terrain object or something, that has its own FS$$anonymous$$, and spawns specific types of enemies and/or other objects in particular places under particular circumstances. Another thing which just grows in complexity rather than getting subclassed. I guess you might have a different one for each different core type that you're managing for a scene.
A given core script might maintain a cache of created objects that it has factory functions for, if there might be a lot of them. Like machine gun bullets, for example. I can imagine having a GameObject cache class, and a given core script having some static references to instances of it.
Your answer
Follow this Question
Related Questions
Inventroy Problem- Using different parameters in inherited classes 1 Answer
Loading large maps - 30000x30000 pixels (threading / parallel loading) 3 Answers
Is it possible to create game object/prefab from file? 2 Answers
How to create inheritance for unity GameObjects 2 Answers
Prefab inheritance without scripts. Is it possible? 2 Answers