I am reading many blogs posts about making game on the basis of small, independants components, with seems to be the right way to structure projects in Unity.
However, I have trouble understanding some of those decoupling patterns. Here, for example, this article Unity: Now You're Thinking With Components | Envato Tuts+
In the last part, the author presents the making of ennemies, using a set of small, independants components ; for example, a Health component, a Death component, a DropItem, and then some secondary components for variations. I can get the usefulness of such separation ; however, the source code is not provided, and as pure code terms, I don’t see how the decoupling is possible.
I am wondering :
The Health system must have some kind of reference to Death, to trigger it when health is zero, right ?
How the Death and DropItem can get fully decoupled ? Either the ItemDrop listens to an OnDeath event and is dependant on the Death script event, or the Death scripts calls the ItemDrop script to drop items. I don’t see how then can be fully independant, otherwise than a master Enemy scripts who listen to Death and calls actions on ItemDrop. Such Enemy will thus be dependant of the two others.
If anyone can helps me understand this concept, either with explainations or more links, I will be grateful.
Not necessarily. It’s possible that it’s only a GetComponent call at that time, either to the exact “Death” component, or to an “IDeath” interface if there is the possibility of multiple scripts that handle the death action, or even using an event/UnityEvent that has to be rigged up ahead of time (possibly in the inspector with UEs). All of which could have a problem if not setup correctly, though.
Events are probably the best choice. For one, there are likely to be several things that are listening for a death event (the item drop, experience collectors, wave triggers, etc.), you still need these things to subscribe on their own to the death event (which is also likely to be triggered by an event) that can also be done through an interface if need be.
You don’t need to fret that much about dependencies though. In most of these, you could run a GetComponent, check for null, and if it was then you don’t give a shit. You’re talking about one, maybe two, functions that don’t have to be run, ergo they aren’t dependent in the slightest.
Does it need to be an event though? How often is health going to reach zero without calling death? Part of the point of events is to be anonymous, where the relationship is based on the subscriber wanting something with the caller being unconcerned with who was listening, and that’s not the relationship here. These are two classes that are effectively part of the same system, so having them coupled isn’t a huge loss when they are closely related.
Well that is true but I also can think of games systems in the same game where they make sense being separated : an enemy who calls reinforcement at health < 2, or an enemy invulnerable directly but killable by other means.
I think I begin to see the interest of such decouping, but I still have a bit of trouble thinking how to wire things correctly. I will remember your idea of anonymous listener as a concept.
I need more information for such decouping between different GameObject, in my example with events.
Lets imagine now for the sake of example I have a player with a Damager script. Such script will do damage to a Health script. The environnement, for example a falling rock, may also damage enemies, so I want to use that same Damage script. I also want a Player to “damage” a switch to toggle its status. This one switch wont have Health, cause it just enable/disable. I will call for now this script Switch
How the same Damage script of both player/rock tell it has done damage to something, either a Health or a Switch, in a decoupled way
This isn’t that necessary though. Maybe at times you just want a script that detects physics collisions and passes damage on to the collided object. Other times you might need a script that controls the physic’s casts itself. What is more important here is having an architecture for passing damage, and this usually comes in the form of using an interface to detect all damageable objects (the interface lesson in the learn section even uses IDamageable as it’s example) and a damage struct that’s used to calculate more complicated damage types.