How can the player jump on top of the enemy, damage the enemy and the player does not get damaged?

Currently, when the player jumps on top of an enemy, the player and the enemy take damage.

I’m trying to figure out a way that only the enemy gets damaged when the player jumps on top of the enemy and so the player does not get damaged. I have two colliders on the enemy (image below). One collider under the Enemy gameObject (wider, bottom collider) and then there’s a child gameObject under Enemy called Receive Damage Check that has a collider (top, smaller collider player jumps on). There is a hazard Layer that the enemy gameObject is on. The child gameObject, Receive Damage Check, underneath the enemy is not on the hazard Layer.

The goal is to have the Receive Damage Check collider register the player collided with it and only damage the Enemy.


8882433--1214130--Player jumps on enemy and gets damaged.gif

Base class:

public class Character : MonoBehaviour {

    public int maxHealth;
    public int currentHealth;
    public HealthBar healthBar;

    [SerializeField] protected float damageInterval = 2f; //in seconds
    protected float currentDamageInterval;

    protected SpriteRenderer sr;
    private Rigidbody2D rb;

    protected virtual void Start() {
        sr = GetComponent<SpriteRenderer>();
        rb = GetComponent<Rigidbody2D>();
        currentHealth = maxHealth;
        healthBar.SetMaxHealth(maxHealth);
    }

    protected virtual void Update() {
        //subtract 1 real life second
        if (currentDamageInterval >= 0)
            currentDamageInterval -= Time.deltaTime;
    }

    public bool CanTakeDamage() {
        return (currentDamageInterval < 0);
    }

    public virtual void TakeDamage(int damage) {
        currentHealth -= damage;
        healthBar.SetHealth(currentHealth);

        int yDamageVelocity = 15;
        rb.velocity = new Vector2(rb.velocity.x, yDamageVelocity);

        //set currentDamageInterval to start a new cooldown/delay period (2 seconds)
        currentDamageInterval = damageInterval;
    }
}

Children classes:

public class Player : Character {

   [SerializeField] private Transform groundCheck;
   [SerializeField] private LayerMask hazardLayer;

   private Color[] colors = { Color.red, Color.white };
   private Coroutine damageFlash;

   protected override void Start() {
       base.Start();
       maxHealth = 10;
   }

   protected override void Update() {
       base.Update();
       //if the current number in the currentDamageInterval is <= 0 or in other words if
       //2 seconds of delay has passed, take damage
       if (CanTakeDamage())
           if (Physics2D.OverlapCircle(groundCheck.position, 0.5f, hazardLayer)) {
               TakeDamage(1);
           }

       //for testing purposes
       if (Input.GetKeyDown(KeyCode.H))
           TakeDamage(1);
   }

   public override void TakeDamage(int damage) {
       base.TakeDamage(damage);
       //guarantee at most, one coroutine runs at a time
       if (damageFlash != null)
           StopCoroutine(damageFlash);
       damageFlash = StartCoroutine(DamageFlashing(1f, .1f));
   }

   IEnumerator DamageFlashing(float duration, float interval) {
       int index = 0;
       //var is a replacement for WaitForSeconds bc it would be redundant
       var wait = new WaitForSeconds(interval);

       for (float elapsedTime = 0; elapsedTime < duration; elapsedTime += interval) {
           //divides the index by 2 and returns the remainder
           sr.color = colors[index % 2];
           index++;
           //waits the interval time and then continues the next color in the flashing duration
           yield return wait;
       }
       damageFlash = null;
   }
}
public class Enemy : Character {

    protected override void Start() {
        maxHealth = 3;
        base.Start();
    }

    private void OnCollisionEnter2D(Collision2D collision) {
        if (collision.gameObject == GameObject.FindWithTag("Player"))
            if (CanTakeDamage())
                TakeDamage(1);
    }
}

FYI, you posted your Character code twice.

Firstly… you need to consider abandoning this inheritance structure. It doesn’t not benefit game development, particularly Unity with its component structure. Most of these could be separate discreet components, without the need for inheritance/repeated code.

Secondly, and I already suggested this to you , you need to invert the relationship of taking damage. The colliders should have their own components that check for certain components when hit with something, and call a method on said object. Not the player/enemy constantly checking if they’re overlapping with something.

1 Like

I updated the bottom code block to have the Enemy code. Thanks for catching that!

You think with my game at this point, I can wholly build it on composition?

Okay. I will invert how the damage works. I’m trying to understand composition…not quite sure where to start. I’ve watched a few in depth videos about it and read a few articles. :face_with_spiral_eyes:

With your current code, the reason this is happening is that you’re not distinguishing between the two colliders. The OnCollisionEnter is probably happening for both colliders. You can add an if statement and check what collider is what to determine if it’s safe or not. However, I don’t recommend doing this a lot, as it’s hacky, and there are cleaner ways to handle it. @spiney199 's suggestions are spot on – that is the more maintainable way to handle this behavior.

To give you some more hints, you can make a component called Hazard that damages any Character it collides with at a specified interval. To make it so that you can filter what Characters it damages, you can put them on different layers and filter the collisions by layer for each Hazard.

Damage flash sounds like something that can happen to many things, bosses, enemies, and players. You can make your own DamageFlash component (or maybe just call it Flash) that will flash a renderer’s color.

Player is then just a Character with max health of 10, and Enemy is then just a character with a max health of 3. These can be set in the Unity Editor. Using composition over inheritance directs you to use the Unity editor to add / remove features rather than using code. Think how you could create the behavior of the Player by adding gameobjects and components without touching any code.

1 Like

Components, rather than composition. Different things. (Composition should be used over inheritance, though, where possible)

Unity is component based after all.