如何从附加到不同游戏对象的另一个脚本调用列表?

How do I call a List from another Script that is attached to a different GameObject?

我有一个包含所有敌人 GameObject 的列表,当敌人死亡时,他们的 GameObject 会被销毁,但是这会在我的脚本中留下一个空引用,因为列表中缺少一个 GameObject。要解决此问题,我知道我需要在销毁对象之前从列表中删除游戏对象。但是,我处理敌人的生命值和销毁 GameObject 的地方与初始脚本不同。

我不确定如何调用第一个脚本以便我可以引用所用的列表。

脚本 1:包含原始列表。

脚本 2:我想销毁其中的游戏对象并从中引用的脚本。

public class FriendlyManager : MonoBehaviour
{
    public NavMeshAgent navMeshAgent;
    public Transform player;



    public float health;
    public float minimumDistance;

    public int damage;

    public List<GameObject> enemies;
    private GameObject enemy;
    private GameObject enemyObj;

    // animations
    [SerializeField] Animator animator;
    bool isAttacking;
    bool isPatroling;

    // attacking
    [SerializeField] Transform attackPoint;
    [SerializeField] public GameObject projectile;
    public float timeBetweenAttacks;
    bool alreadyAttacked;

    private void Awake()
    {

        navMeshAgent = GetComponent<NavMeshAgent>();

        animator = GetComponent<Animator>();

        enemyObj = new GameObject();
    }

    private void Start()
    {
        isAttacking = false;
        isPatroling = true;
        animator.SetBool("isPatroling", true);
    }

    private void Update()
    {

        for(int i = 0; i < enemies.Count; i++)
        {
            if(Vector3.Distance(player.transform.position, enemies[i].transform.position) <= minimumDistance)
            {
                enemy = enemies[i];
                Attacking(enemy);
            }
        }
    }

    private void Attacking(GameObject enemy)
    {
        // stop enemy movement.
        navMeshAgent.SetDestination(transform.position);

        enemyObj.transform.position = enemy.transform.position;

        transform.LookAt(enemyObj.transform);

        if (!alreadyAttacked)
        {
            isAttacking = true;

            // attack player.
            animator.SetBool("isAttacking", true);
            animator.SetBool("isPatroling", false);

            Rigidbody rb = Instantiate(projectile, attackPoint.position, Quaternion.identity).GetComponent<Rigidbody>();
            rb.AddForce(transform.forward * 32f, ForceMode.Impulse);

            alreadyAttacked = true;
            Invoke(nameof(ResetAttack), timeBetweenAttacks);

        }
    }

    private void ResetAttack()
    {
        alreadyAttacked = false;
        animator.SetBool("isAttacking", false);
    }
}
public class Damageable : MonoBehaviour
{
    [SerializeField] float maxHealth = 100f;
    float currentHealth;

    public static int numOfEnemies = 5;

    Rigidbody rb;

    // get list.

    public void Start()
    {
        rb = GetComponent<Rigidbody>();

        currentHealth = maxHealth;
    }

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

        if (currentHealth <= 0)
        {
            Die();
        }
    }


    public void Die()
    {
        numOfEnemies--;
        List.Remove(gameObject);
        Destroy(gameObject);
    }
}

使用Singleton 模式:

public class FriendlyManager : MonoBehaviour
{
    public static FriendlyManager singleton; // you can save single user class on this field.
    public List<GameObject> enemies;

    void Start()
    {
        singleton = this;
    }
}

对敌人 class:

public void Die()
{
    FriendlyManager.singleton.enemies.Remove(gameObject); // remove it from singleton class
    Destroy(gameObject);
}

由于您的 FriendlyManager 包含所有敌人的列表,您可以传递对他们的引用,这样他们就知道他们属于哪个经理。这也消除了周围有 static int 的必要性,因为他们可以通过 属性.

获得敌人的数量。
public class FriendlyManager : MonoBehaviour
{
    [SerializeField]
    private List<Damagable> enemies;

    public int EnemyCount => enemies.Count;

    private void Start()
    {
        for (int i = 0; i < enemies.Count; i++)
            enemies.Manager = this;
    }

    public void EnemyDied(Damagable enemy)
    {
        enemies.Remove(enemy);
        // Maybe do more notifications here
    }
}
public class Damageable : MonoBehaviour
{
    public FriendlyManager Manager { get; set; }

    public void Die()
    {
        Manager.EnemyDied(this);
        Destroy(gameObject);
    }
}

第二种选择是使用事件。然而,经典的 C# 事件在 Unity 中可能会很棘手,因为它们往往会留下悬空引用,使对象保持活动状态,尽管它们已经被引擎销毁了。但是,这可以更轻松地解耦您的逻辑,同时仍保持信息流。

public class Damageable : MonoBehaviour
{
    public delegate void OnDiedHandler(Damagable sender);
    public event OnDiedHandler Died;

    public void Die()
    {
        Died?.Invoke(this);
        Destroy(gameObject);
    }
}
public class FriendlyManager : MonoBehaviour
{
    [SerializeField]
    private List<Damagable> enemies;

    public int EnemyCount => enemies.Count;

    private void Start()
    {
        for (int i = 0; i < enemies.Count; i++)
            enemies.Died += EnemyDied;
    }

    private void EnemyDied(Damagable enemy)
    {
        enemies.Remove(enemy);
        // Maybe do more notifications here
    }
}

在不更改代码结构的情况下处理此问题的一种简单方法是在迭代过程中遇到 null 时将其删除。

private void Update()
{
    for (int i = 0; i < enemies.Count; i++)
    {
        var enemy = enemies[i];
        if (enemy == null)
        {
            // remove the null at this index
            enemies.RemoveAt(i);

            // back up the index by 1 
            // next iteration will increase i by 1 and continue at this same index
            i--;

            // move to the next iteration
            continue;
        }

        if (Vector3.Distance(player.transform.position, enemy.transform.position) <= minimumDistance)
        {
            Attacking(enemy);
        }
    }
}