2
\$\begingroup\$

I would need some help with making two floats from two different classes, match their "data".

What I want to do here is to make it so when Body.healthb hits a value of 0, Head.health will also hit 0.

Any suggestions? I'm barely new to this.

The class from which the float comes from:

public class Body : MonoBehaviour 
{
  public GameObject body;

  public float healthb = 50f;

  public void TakeDamageb(float amountb)
  {
    healthb -= amountb;
    if (healthb <= 0f)
    {
      Dieb ();
    }
  }

  // Update is called once per frame
  public void Dieb() 
  {
    Destroy (gameObject);
  }
}

The class it has to reach:

public class Head : MonoBehaviour 
{
  public static float health = 10f;
  public void TakeDamage(float amount)
  {

    health -= amount;
    if (health <= 0f)
    {
      Die ();
    }
  }

  // Update is called once per frame
  public void Die() 
  {
    Destroy (gameObject);
  }
}
\$\endgroup\$
1
  • \$\begingroup\$ I'd say either create a static variable bodyHealth and headHealth, then I believe they'd be accessible between scripts, or create a public static function on each to return the health value. \$\endgroup\$
    – Dark Hippo
    Commented May 9, 2017 at 12:43

3 Answers 3

1
\$\begingroup\$

DMGregory's answer is all well and good but I think it might be over the heads of beginners, so here's a simpler solution.

What you want to do is reference these two with each other or in a separate script.

Example code:

public class Body : MonoBehaviour 
{
  public Head head;

  public float health = 50f;

  public void TakeDamage(float amount)
  {
    health -= amount;
    if (health <= 0f)
    {
      head.BroadcastMessage("TakeDamage", head.health); //if the body dies, damage the head as much hitpoints as the head has.
      Die ();
    }
  }

  // Update is called once per frame
  public void Die() 
  {
    Destroy (gameObject);
  }
}

Now the other part:

public class Head : MonoBehaviour 
{
  public Body body;

  public float health = 10f;

  public void TakeDamage(float amount)
  {

    health -= amount;
    if (health <= 0f)
    {
      body.BroadcastMessage("TakeDamage", body.health); //if the head dies, damage the body as much hitpoints as the head has.
      Die ();
    }
  }

  // Update is called once per frame
  public void Die() 
  {
    Destroy (gameObject);
  }
}

These will be your classes or, more likely, scripts on your objects. Add the scripts on the relevant objects. Drag the Head script to the Body's script slot in the editor and also drag the Body script to the Head's script slot in the editor.

The line where we call the method to damage the head or body could also easily be changed into body.BroadcastMessage("Die"); but this method runs the risk of not going through the proper take damage channel you are implementing and it means it will bypass anything you implement in that method.

\$\endgroup\$
5
  • \$\begingroup\$ Alternatively to the BroadcastMessage (which uses reflection) you can use GetComponent (or variant) and navigate the transform hierearchy. e.g. if the Head script is attached to a child object, then transform.parent.GetComponent<Body>() is what's needed (going the other direction is easier if you've only got the one copy: GetComponentInChildren<Head>()). \$\endgroup\$ Commented May 9, 2017 at 13:36
  • \$\begingroup\$ @Draco18s Yeah, that's an alternative too, but we don't really know which one is the parent or even either is a parent of the other or not. The head and the body could just very well be child objects of an empty gameobject called person, so I didn't go into detail on that part :) \$\endgroup\$ Commented May 9, 2017 at 13:59
  • \$\begingroup\$ The suggestion that @JohnHamilton brought here worked out, I had to remove the broadcastmessage from the head because it was the parent of the body, so when the head would die, the game would not understand where the body when \$\endgroup\$ Commented May 9, 2017 at 14:05
  • \$\begingroup\$ @CanadianHuey Like I noted in the comments, I was planning it out as two separate objects since we had no idea what the parent-child relationship in your case was ;) \$\endgroup\$ Commented May 9, 2017 at 14:14
  • 1
    \$\begingroup\$ @JohnHamilton Of course. :) I just mention it because of the performance impact that reflection has, and that in the more general case a user will want to use direct object references but that providing an answer using such is hard due to the transform hierarchy needing to be a known factor. \$\endgroup\$ Commented May 9, 2017 at 14:15
1
\$\begingroup\$

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.

\$\endgroup\$
0
\$\begingroup\$

You should have 1 health for the whole character and different parts would have referenced to it. When head gets hit by calling this.GetComponent<BodyPart>TakeDamage(amount); - it calls this.character.TakeDamage(amount * bodyPartHitBonus); inside.

// bodyPartHitBonus - head could have 5 for example, so if damage from weapon was 20, then 20 * 5 = 100 damage done to character.

Now you don't have to worry about referencing all the body parts in every script and you can get different damage depending on weapon damage and body part hit.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .