If all you need is for a part to kill its dependent parts on death (ie. head -> body), we can do this with a single HealthPool
component with a reference to its dependent part(s):
public class HealthPool: MonoBehaviour {
public float startingHealth;
// Make current health show in the inspector, but protect it from direct editing
// by other scripts unless they go through TakeDamage()
[SerializeField]
float _currentHealth;
public float currentHealth { get { return _currentHealth; }}
public HealthPool killOnDeath;
public void TakeDamage(float amount) {
_currentHealth -= amount;
if(_currentHealth <= 0f)
Die();
}
// Kill our dependent object when we die:
public void Die() {
Destroy(gameObject);
if(killOnDeath != null)
killOnDeath.Die();
// This will naturally cascade down to the dependent part's dependent parts,
// eg. if you had a multi-part snake that should die backward from the head.
}
}
If you need any more complicated interactions between the body parts, I'd be inclined to still use a simple HealthPool
component on each body part, and have it report its damage up to a HealthPoolCollection
on their shared parent/grandparent/ancestor (even if that shared ancestor is the head or body itself):
public class HealthPool: MonoBehaviour {
// In case you need to make decisions based on whether it's a head/body/etc
// This exposes a list to the Inspector to select from.
[System.Serializable]
public enum HealthPoolType { Other, Head, Body };
public HealthPoolType type;
public float startingHealth;
[SerializeField]
float _currentHealth;
public float currentHealth { get { return _currentHealth; }}
HealthPoolCollection _collection;
void Start() {
// Assuming these sit under the same parent/grandparent/etc.
// If not, you can wire up this reference in the inspector.
_collection = GetComponentInParent<HealthPoolCollection>();
if(_collection != null)
_collection.Register(this);
}
public void TakeDamage(float amount) {
_currentHealth -= amount;
if(_collection != null)
_collection.ReportDamage(this, amount);
if(_currentHealth <= 0f)
Die();
}
public void Die() {
// If you want the shared object to die,
// consider moving this to the parent collection script instead.
Destroy(gameObject);
}
}
using System.Collections.Generic;
public class HealthPoolCollection : MonoBehaviour {
List<HealthPool> _pools = new List<HealthPool>();
public void Register(HealthPool pool) {
_pools.Add(pool);
}
public void ReportDamage(HealthPool pool, float damage) {
// Add logic here to pass the info along to other health pools / etc.
}
}
Both these approaches have some similar advantages:
Anything that deals damage only needs to know about a single Health Pool component type, not separate head & body flavours.
You only have to write damage taking & dying logic once, and maintain it in one place, following the principle "Don't Repeat Yourself"
Health pool relationships are hierarchical & one-way, so you don't end up with a spaghetti mess of sibling-to-sibling references to disentangle: there's a single place to look if you want to track a death to its source.