Looks reasonable… you might get some benefit by extracting common methods into some kind of interface, which is a pattern that works really well with Unity and MonoBehaviors. MBs can then implement specific interfaces and be found because they implement those interfaces.
For instance, you might have an IAttackable
interface that can represent a target that can be attacked. Some of its methods might be “what can hurt you?” and “you have been attacked by …” This also means when a projectile hits a collider it can say “Hey, do you implement the IAttackable interface?” and if you do, have I got some business for you. A wall might not implement IAttackable, unless it makes sense.
And of course you can always make Darkness implement IAttackable so that you can attack the darkness, a time-honored way of irritating your GM.
Personally I prefer something like an IDamageable
, then something before that which decides how much damage, perhaps considering information other interfaces related to the thing attacking, the attack itself and the target, as well as any other concerns like buffs, debuffs, environment, etc…
At some point in any complex game there has to be some level of linkage. You can extract and abstract it to the point where it becomes almost impossible to reason about when it malfunctions, or you can hard-wire all of it which becomes a sort of brittle bugs and replicated code. Somewhere in between lies a happy medium, and everybody works differently. But it’s always good to consider all your tools, such as interfaces.
Other folks like inheritance, but in Unity that always seems brittle and far more irritating to work with than interfaces, but your mileage may vary.