0

I have made an up and down movement RPG prototype and I wanted to have a fighting system.

I made it so that the enemy follows the player and that I get damage every time he collides with me. The problem was that he only gave me damage once and would have to stop colliding with me, to attack again. I fixed that by changing OnCollisionEnter2D to OnCollisionStay2D, but now he attacks every frame, which makes him way too strong.

I already asked some people how to change it and they said you could do it with coroutines. The thing is that I have no idea how to code that and it gets even more complicated, because the enemy is constantly touching the player (When you're not dodging). I would be really happy if someone could help me with that

EnemyAttack.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyAttack : MonoBehaviour { [SerializeField] private GameObject Player; [SerializeField] private int timer; public int damage; public PlayerHealth playerHealth; public float AttackCooldown; public float WaitForSecondsRealTime; float PlayerHealthh;

private void OnCollisionStay2D(Collision2D collision) {
    if(collision.gameObject.tag == "Player") {
        playerHealth.TakeDamage(damage);

    }
}

}

DMGregory
  • 134,153
  • 22
  • 242
  • 357
Hammynator
  • 33
  • 9

2 Answers2

0

I wouldn't use a coroutine for this - just tracking the time since the last attack is enough.

public class EnemyAttack : MonoBehaviour
{
    public int damage;
    public PlayerHealth playerHealth;
    public float attackCooldown;
float _lastAttackTime;

private void OnCollisionStay2D(Collision2D collision) {
    // Abort if we already attacked recently.
    if (Time.time - _lastAttackTime < attackCooldown) return;

    // CompareTag is cheaper than .tag ==
    if(collision.gameObject.CompareTag("Player")) {
        playerHealth.TakeDamage(damage);

        // Remember that we recently attacked.
        _lastAttackTime = Time.time;
    }
}

}

The advantage here is that we don't have to allocate and construct a new Coroutine instance. Just updating a float is dirt cheap. Subtracting float timestamps is not entirely precise, but for values that don't need sub-millisecond precision like an attack cooldown on the order of a half-second or more, this is "good enough".

But if you really want to do it with a coroutine, you can do it like this:

public class EnemyAttack : MonoBehaviour
{
    public int damage;
    public PlayerHealth playerHealth;
    public float attackCooldown;
Coroutine _attackInProgress;
int _playerContacts;

private void OnCollisionEnter2D(Collision2D collision) {

    if(collision.gameObject.CompareTag("Player")) {
        _playerContacts++;
        if (_attackInProgress == null)
             _attackInProgress = StartCoroutine(AttackLoop);
    }
}

private void OnCollisionExit2D(Collision2D collision) {

    if(collision.gameObject.CompareTag("Player")) {
        _playerContacts--;
    }
}

private IEnumerator AttackLoop() {
    while(_playerContacts > 0) {
        playerHealth.TakeDamage(damage);

        // You can construct this once and cache it if you like.
        yield return new WaitForSeconds(attackCooldown);
    }
    _attackInProgress = null;
}

}

I've used the _playerContacts counter to exit the coroutine instead of calling StopCoroutine in OnCollisionExit for a couple of reasons:

  • It preserves the cooldown even if we briefly separate and re-collide, instead of short-circuiting and letting another attack through early.

  • It correctly handles cases where the player or enemy have multiple colliders (and so might get an exit message for one pair of colliders while another pair is still touching).

DMGregory
  • 134,153
  • 22
  • 242
  • 357
  • I'm so thankful for that. I struggled with that since yesterday morning. I was at this problem for hours. I fricking love you, I can't thank you enough. You're Great! – Hammynator Jan 07 '23 at 18:43
  • 1
    I'm glad this was useful. If an answer solves your problem, you can click the ✔ icon in the top-left to mark it as "Accepted". – DMGregory Jan 07 '23 at 19:06
  • Your edit to change the title, was misleading, when combined with the text of the question. if there is any discrepancy, I go with the text of the question. "2 seconds" was an important part of the original question title and should not have been reduced. –  Jan 08 '23 at 05:10
  • @Strom you can always propose an edit of your own. – DMGregory Jan 08 '23 at 13:14
0

Use the following code to match the one off code, no co-routines required.

Note the damage time is now in terms of seconds, so adjust the values accordingly. I suggest damage/600 as a starting point.

This change entirely removes the FPS dependency that your original code had, and makes the attack speed machine independent.

I would add some random variation to increase replay value, as indicated in the commented line (adjust values as needed):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyAttack : MonoBehaviour { [SerializeField] private GameObject Player; [SerializeField] private int timer; public int damagePerSecond; public PlayerHealth playerHealth; public float AttackCooldown; public float WaitForSecondsRealTime; float PlayerHealthh;

private void OnCollisionStay2D(Collision2D collision) {
    if(collision.gameObject.CompareTag("Player")) { # attributed to DMGregory as faster.
        If (!player.Dodge) playerHealth.TakeDamage(Time.deltaTime * damagePerSecond);

//If (!player.Dodge) // playerHealth.TakeDamage(Time.deltaTime * (damagePerSecond * Random.Range(.9f, 1f)); //else // playerHealth.TakeDamage(Time.deltaTime * (damagePerSecond * Random.Range(.1f, .4f); } } }

  • "Periodically" means at regular intervals, like a beat, not continuously. Even lacking the 2 second guideline that was accidentally omitted from the title edit, this is still dealing damage every frame, which both the title and body text say is not wanted. – DMGregory Jan 08 '23 at 13:15
  • @DMGregory The question body text alone begs for a time independent solution.(Please re-read it) This answer provides a possible solution, with explanation as to why the change failed., and would be helpful to anyone coming across this question with different expectations. I felt an apology was in order , with an explanation. I did not provide for the dodge, and neither did you. –  Jan 13 '23 at 03:43
  • @Hammynator, I retract my earlier comment and contend that real-time dodge is important to your game mechanic, that adding a periodic delay will not solve. –  Jan 13 '23 at 03:59