如何注册多个实例“攻击”单个敌人?

huangapple go评论62阅读模式
英文:

How to register multiple instances "attacking" single enemy?

问题

以下是您提供的代码的翻译部分:

最近我尝试编写了一个脚本,其中敌人会受到多个实例的伤害。

让我感到困惑的是,该脚本不考虑其他实例的伤害。

我在敌人脚本中添加了冷却时间、伤害和攻击速度,以加快调试过程,但我无法弄清楚如何注册多个实例同时攻击。
我需要创建某种“战士”列表,其中列出了当前正在接触敌人并执行 foreach 循环吗?

请注意,代码部分没有翻译,只翻译了您提供的注释和文本。

英文:

I recently I tried to write a script in which enemy would be receiving damage by multiple instances.

What baffled me is that the script does not consider other instances damage

I added cooldown, damage and attack speed inside enemy script to speed up the debugging process, but I can't figure out how to register multiple instances attacking at the same time.
do i need to make some kind of a list of "Warriors" that are currently touching the enemy and do a foreach loop?

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

public class HealthSystemScript : MonoBehaviour
{
    
    [SerializeField]
    private int AttackPower;
    public int maxHealth = 100;
    public int currentHealth;
    private float _TimeSinceLastAttack = 10f;
    private float attackCooldown = 5f;

    public HealthBar HealthBar;

    private void Start()
    {
        currentHealth = maxHealth;
        HealthBar.SetMaxHealth(maxHealth);
    }
    
    void Update()
    {
        HealthBar.MaxHealth(currentHealth);
        if (currentHealth <= 0 || Input.GetKey(KeyCode.F))
        {
            Destroy(gameObject);
        }
    }
    
    private void OnCollisionStay2D(Collision2D collision)
    {
        //Check if enemy was hit by Warrior
        if (collision.gameObject.tag == "Warrior" && Time.time > attackCooldown)
        {
            // Abort if we already attacked recently.
            if (Time.time - _TimeSinceLastAttack < attackCooldown) return;
            {
                    //if collided object has "Warrior" Tag then:
                    if (collision.gameObject.CompareTag("Warrior"))
                    {
                        TakeDamage(20);
                        Debug.Log("Boss took 20dmg!");
                        // Remember that we recently attacked.
                        _TimeSinceLastAttack = Time.time;

                                     if (currentHealth <= 0 || Input.GetKey(KeyCode.F))
                                     {
                                            Destroy(gameObject);
                                     }
                    }
            }
        }
    }

    public void TakeDamage(int damage)
    {
        currentHealth -= damage;
        HealthBar.SetHealth(currentHealth);
    }
}

答案1

得分: 1

Here is the translated code portion:

> do i need to make somekind of a list of "Warriors" that are currently touching the enemy and do a foreach loop?

Yes!

`OnCollisionStay2D` is called for each individual touching collider yes, **however**, your entire code within it is purely time based!

Since you already re-assign

    _TimeSinceLastAttack = Time.time;

for the first touching warrior the other calls to `OnCollisionStay2D` for the remaining warriors won't pass the

     if (Time.time - _TimeSinceLastAttack < attackCooldown) return;

in that same frame anymore!

---

If I understood correctly you want to track individual attack times for each warrior so I would rather do something like e.g.

    public class HealthSystemScript : MonoBehaviour
    {
        [SerializeField]
        private int AttackPower;
        public int maxHealth = 100;
        public int currentHealth;
        public HealthBar HealthBar;

        private float attackCooldown = 5f;

        // maps each attacking warrior to its individual last attack time
        private readonly Dictionary<GameObject, float> _lastAttackTimes = new();

        private void Start()
        {
            currentHealth = maxHealth;
            HealthBar.SetMaxHealth(maxHealth);
        }

        private void OnCollisionStay(Collision2D collision)
        {
            // Check if enemy was hit by Warrior -> no -> ignore
            if (!collision.gameObject.CompareTag("Warrior")) return;

            // This means either this didn't touch before or the previous attack is too recent -> ignore
            if(_lastAttackTimes.TryGetValue(colision.gameObject, out var lastAttackTime) && Time.time - lastAttackTime > attackCooldown) return;

            // otherwise inflict damage once now and track last attack time for this warrior
            TakeDamage(20);
            Debug.Log("Boss took 20dmg!");
            _lastAttackTimes[colision.gameObject] = Time.time;
        }

        private void OnCollisionExit2D(Collision2D collision)
        {
            //Check if enemy was hit by Warrior
            if (!collision.gameObject.CompareTag("Warrior")) return;

            // remove from the map of warriors so next first touch is applied immediately
            _lastAttackTimes.Remove(collision.gameObject);
        }

        public void TakeDamage(int damage)
        {
            currentHealth -= damage;

            // by moving this check here you can be sure i is always applied when receiving damage
            // and you don't need to poll check anything each frame
            if (currentHealth <= 0)
            {
                Destroy(gameObject);
            }
            else
            {
                HealthBar.SetHealth(currentHealth);
            }
        }
    }

---

if you rather want to just have one single timer for all warriors combined it gets a bit simpler:

    public class HealthSystemScript : MonoBehaviour
    {
        [SerializeField]
        private int AttackPower;
        public int maxHealth = 100;
        public int currentHealth;
        public HealthBar HealthBar;

        private float attackCooldown = 5f;

        private int touchingWarriorsAmount;

        private float lastAttackTime;

        private void Start()
        {
            currentHealth = maxHealth;
            HealthBar.SetMaxHealth(maxHealth);
        }

        private void FixedUpdate()
        {
            // run one shared timer for all attacks
            if (Time.time - lastAttackTime > attackCooldown)
            {
                TakeDamage(20 * touchingWarriorsAmount);

                lastAttackTime = Time.time;
            }
        }

        private void OnCollisionEnter2D(Collision2D collision)
        {
            //Check if enemy was hit by Warrior
            if (!collision.gameObject.CompareTag("Warrior")) return;

            // simply increase the counter on enter
            touchingWarriorsAmount++;
        }

        private void OnCollisionExit2D(Collision2D collision)
        {
            //Check if enemy was hit by Warrior
            if (!collision.gameObject.CompareTag("Warrior")) return;

            // simply decrease the counter on exit
            touchingWarriorsAmount--;
        }

        public void TakeDamage(int damage)
        {
            currentHealth -= damage;

            if (currentHealth <= 0)
            {
                Destroy(gameObject);
            }
            else
            {
                HealthBar.SetHealth(currentHealth);
            }
        }
    }

if each warrior has different damage power later (just a guess due to the `AttackPower`) you again might want to use a list and iterate over them of course but still do the time check only once - as said depends on your needs

---

In general I would rather go the other way round and instead of checking collisions and receive damage I would rather make the attacker the responsible/driving instance and inflict damage on the thing it is colliding with. This way it would be more trivial to have those attack intervals.
英文:

> do i need to make somekind of a list of "Warriors" that are currently touching the enemy and do a foreach loop?

Yes!

OnCollisionStay2D is called for each individual touching collider yes, however, your entire code within it is purely time based!

Since you already re-assign

_TimeSinceLastAttack = Time.time;

for the first touching warrior the other calls to OnCollisionStay2D for the remaining warriors won't pass the

 if (Time.time - _TimeSinceLastAttack < attackCooldown) return;

in that same frame anymore!


If I understood correctly you want to track individual attack times for each warrior so I would rather do something like e.g.

public class HealthSystemScript : MonoBehaviour
{
    [SerializeField]
    private int AttackPower;
    public int maxHealth = 100;
    public int currentHealth;
    public HealthBar HealthBar;

    private float attackCooldown = 5f;

    // maps each attacking warrior to its individual last attack time
    private readonly Dictionary<GameObject, float> _lastAttackTimes = new();

    private void Start()
    {
        currentHealth = maxHealth;
        HealthBar.SetMaxHealth(maxHealth);
    }

    private void OnCollisionStay(Collision2D collision)
    {
        // Check if enemy was hit by Warrior -> no -> ignore
        if (!collision.gameObject.CompareTag("Warrior")) return;

        // This means either this didn't touch before or the previous attack is too recent -> ignore
        if(_lastAttackTimes.TryGetValue(colision.gameObject, out var lastAttackTime) && Time.time - lastAttackTime > attackCooldown) return;
        
        // otherwise inflict damage once now and track last attack time for this warrior
        TakeDamage(20);
        Debug.Log("Boss took 20dmg!");
        _lastAttackTimes[colision.gameObject] = Time.time;
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        //Check if enemy was hit by Warrior
        if (!collision.gameObject.CompareTag("Warrior")) return;

        // remove from the map of warriors so next first touch is applied immediately
        _lastAttackTimes.Remove(collision.gameObject);
    }

    public void TakeDamage(int damage)
    {
        currentHealth -= damage;

        // by moving this check here you can be sure i is always applied when receiving damage
        // and you don't need to poll check anything each frame
        if (currentHealth <= 0)
        {
            Destroy(gameObject);
        }
        else
        {
            HealthBar.SetHealth(currentHealth);
        }
    }
}

if you rather want to just have one single timer for all warriors combined it gets a bit simpler:

public class HealthSystemScript : MonoBehaviour
{
    [SerializeField]
    private int AttackPower;
    public int maxHealth = 100;
    public int currentHealth;
    public HealthBar HealthBar;

    private float attackCooldown = 5f;

    private int touchingWarriorsAmount;

    private float lastAttackTime;

    private void Start()
    {
        currentHealth = maxHealth;
        HealthBar.SetMaxHealth(maxHealth);
    }

    private void FixedUpdate()
    {
        // run one shared timer for all attacks
        if (Time.time - lastAttackTime > attackCooldown)
        {
            TakeDamage(20 * touchingWarriorsAmount);

            lastAttackTime = Time.time;
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        //Check if enemy was hit by Warrior
        if (!collision.gameObject.CompareTag("Warrior")) return;

        // simply increase the counter on enter
        touchingWarriorsAmount++;
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        //Check if enemy was hit by Warrior
        if (!collision.gameObject.CompareTag("Warrior")) return;

        // simply decrease the counter on exit
        touchingWarriorsAmount--;
    }

    public void TakeDamage(int damage)
    {
        currentHealth -= damage;

        if (currentHealth <= 0)
        {
            Destroy(gameObject);
        }
        else
        {
            HealthBar.SetHealth(currentHealth);
        }
    }
}

if each warrior has different damage power later (just a guess due to the AttackPower) you again might want to use a list and iterate over them of course but still do the time check only once - as said depends on your needs


In general I would rather go the other way round and instead of checking collisions and receive damage I would rather make the attacker the responsible/driving instance and inflict damage on the thing it is colliding with. This way it would be more trivial to have those attack intervals.

huangapple
  • 本文由 发表于 2023年5月10日 23:15:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76220119.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定