Как исправить эти ошибки Unity?

#c# #unity3d

#c# #unity3d

Вопрос:

Недавно я заменил некоторые массивы на списки в большинстве своих скриптов, поскольку они проще в использовании. Проблема в том, что появились некоторые новые ошибки. Они :

  1. Исключение InvalidOperationException: коллекция была изменена; операция перечисления может не выполняться.
  2. Исключение ArgumentOutOfRangeException: индекс был вне диапазона. Должно быть неотрицательным и меньше размера коллекции.

Согласно ошибкам, проблема находится в строке 96 Gorean Turret и строке 37 POW_Camp. У пары других скриптов также есть эта проблема, но если я выясню, что не так с этими двумя, я смогу выяснить, как исправить остальное.

Я искал, что делать с этими ошибками, и нашел пару решений. Я изменил циклы foreach на циклы стиля for (var i=0;). Это должно было сработать, но, к сожалению, нет.

Моя проблема в том, как мне исправить эти ошибки. Как мне нужно изменить мои сценарии, чтобы не было ошибок. В качестве примечания, если вы найдете что-то, что можно сократить, пожалуйста, скажите мне. У меня такое ощущение, что код также длиннее, чем должен быть.

Вот сценарий POW_Camp :

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

public class POW_Camp : MonoBehaviour
{
    public GameObject AmmunitionsDump;
    public GameObject Explosion;
    public GameObject Fences;
    public List<GameObject> Prisoners;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (AmmunitionsDump.GetComponent<Health>().CurrentHealth<=0)
        {
            AmmunitionsDump.SetActive(false);
            Explosion.SetActive(true);
        }
        //If the ammunitiondump is destroyed then destroy fences
        if (AmmunitionsDump.activeSelf == false)
        {
            Destroy(Fences);
            //Activate POWs
            for (var i=0;i<Prisoners.Count; i  )
            {
                if (Prisoners[i] == null)
                {
                    Prisoners.Remove(Prisoners[i]);
                }
                if (Prisoners[i] != null)
                {
                    Prisoners[i].GetComponent<PlayerData>().Captured = false;
                }
            }
        }
    }
}
 

Вот сценарий GoreanTurret:

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

public class GoreanTurret : MonoBehaviour
{
    public GameObject Projectile;
    public GameObject FiringPoint;
    public GameObject GunTower;

    public GameObject HealthBar;
    public List<GameObject> TargetsWithinRange = new List<GameObject>();
    public float FiringRange = 100;
    public GameObject CurrentTarget;
    public bool TargetLocked;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("Shoot", 1, 0.5f);
        HealthBar.GetComponent<Slider>().maxValue = gameObject.GetComponent<Health>().MaxHealth;
        HealthBar.GetComponent<Slider>().value = gameObject.GetComponent<Health>().CurrentHealth;
    }

    // Update is called once per frame
    void Update()
    {
        FindTargetsWithinRange(FiringRange, TargetsWithinRange);
        TargetManager(TargetsWithinRange, FiringRange);
        HealthBar.GetComponent<Slider>().value = gameObject.GetComponent<Health>().CurrentHealth;
        //Look at Target if Target is Locked
        if (TargetLocked)
        {
            GunTower.transform.LookAt(CurrentTarget.transform);
        }
        // If target is destroyed then set targetLocked to false and target to null;
        if (CurrentTarget != null)
        {
            if (CurrentTarget.GetComponent<Health>().CurrentHealth <= 0)
            {
                TargetLocked = false;
                CurrentTarget = null;
            }
        }
        // If one of the targets in the target list is destroyed then remove that
        foreach (GameObject possibleTarget in TargetsWithinRange)
        {
            if (possibleTarget == null || possibleTarget.GetComponent<Health>().CurrentHealth<=0 || possibleTarget.GetComponent<Health>()==null)
            {
                TargetsWithinRange.Remove(possibleTarget);
                
            }
        }
        if (CurrentTarget == null)
        {
            TargetLocked = false;
            CurrentTarget = null;

        }
        // If target is lost and there are still targets, then switch target
        if (TargetLocked == false amp;amp; (CurrentTarget == null || CurrentTarget.GetComponent<Health>().CurrentHealth<0) amp;amp; TargetsWithinRange.Count>0)
        {
            if (TargetsWithinRange[0] != null)
            {
                CurrentTarget = TargetsWithinRange[0];
                TargetLocked = true;
            }
        }
    }



    void FindTargetsWithinRange(float range, List<GameObject> TargetList)
    {
        Collider[] colliders = Physics.OverlapSphere(gameObject.transform.position, range);
        foreach (Collider collider in colliders)
        {
            if (collider.gameObject.GetComponent<PlayerData>())
            {
                if (collider.gameObject.GetComponent<PlayerData>().Captured == false amp;amp; TargetList.Contains(collider.gameObject) == false)
                {
                    TargetList.Add(collider.gameObject);
                }
            }
        }
    }
    void TargetManager(List<GameObject> TargetList, float MaxRange)
    {
        for (var i = 0; i < TargetList.Count; i  )
        {
            //If the Target is null or inactive then remove that target or if it has no health
            if (TargetList[i] == null || TargetList[i].activeSelf == false || TargetList[i].GetComponent<Health>().CurrentHealth <= 0)
            {
                TargetList.Remove(TargetList[i]);
                if (TargetList[i] == CurrentTarget)
                {
                    TargetLocked = false;
                }
            }
            //If the target is too far
            if (Vector3.Distance(gameObject.transform.position, TargetList[i].transform.position) > MaxRange)
            {
                TargetList.Remove(TargetList[i]);
                if (TargetList[i] == CurrentTarget)
                {
                    CurrentTarget = null;
                    TargetLocked = false;
                }
            }
        }

        //If there is no target and the TargetLocked is false then Set a new target
        if (CurrentTarget == null amp;amp; TargetLocked == false amp;amp; TargetList.Count > 0)
        {
            CurrentTarget = TargetList[0];
            TargetLocked = true;
        }

        // If target is destroyed then set targetLocked to false and target to null;
        if (CurrentTarget != null amp;amp; CurrentTarget.activeSelf == true amp;amp; CurrentTarget.GetComponent<Health>().CurrentHealth > 0)
        {
            if (CurrentTarget.GetComponent<Health>().CurrentHealth <= 0)
            {
                TargetLocked = false;
                CurrentTarget = null;
            }
        }
    }

    public void Shoot()
    {
        if (TargetLocked == true)
        {
            Instantiate(Projectile, FiringPoint.transform.position, FiringPoint.transform.rotation);
        }
    }
}
 

Комментарии:

1. Привет, Маран, обе ошибки говорят сами за себя, сначала говорится, что вы повторяете коллекцию, которая меняется внутри for/foreach цикла. Вторая ошибка говорит, что вы пытаетесь получить доступ к элементу, который находится вне индекса, т.е. index > array.Length Было бы здорово, если бы вы обновили свой вопрос с минимальным воспроизводимым примером.

2. Ни одна из этих ошибок не возникнет просто путем переключения объявления X из массива в список

3. for (var i=0;i<Prisoners.Count; i ) Я рекомендую вам выполнить итерацию этого массива в обратном направлении (i=Length-1, i—), если вы собираетесь удалять из него элементы по мере выполнения итерации

4. Вы не можете вызывать TargetsWithinRange.Remove(possibleTarget); из цикла, который есть foreach... in TargetsWithinRange ; вместо этого сделайте его циклом for

5. Для ошибок вне диапазона аргументов убедитесь, что вы не обращаетесь к элементу массива за пределами длины массива. В частности, если вы выполняете итерацию массива, доходите до последнего элемента и решаете удалить его, не пытайтесь снова получить к нему доступ — ваш список заключенных может пострадать от этого, потому что вы принимаете решение удалить в i, а затем сразу же пытаетесь получить доступ к i снова (я мог бы добавить без необходимости; поставитьif else вместо if null, если не null). Если вы удалите последнего заключенного, в i больше не будет заключенного, вызывающего исключение IOOB при следующем доступе

Ответ №1:

В обоих ваших классах у вас есть циклы, в которых вы удаляете элементы из своих коллекций во время итерации по ним.

Первый вызывает ArgumentOutOfRangeException , потому что после удаления определенного количества элементов вы будете выполнять итерации до тех пор, пока не достигнете слишком высокого индекса, поскольку ваш список / массив теперь меньше!

Другое исключение не требует пояснений: вы изменяете коллекцию, повторяя ее с помощью a foreach .

Вы можете решить оба случая, используя Linq Where , чтобы отфильтровать null записи (требуется using System.Linq; поверх вашего файла)

 // in general use the bool operator of UnityEngine.Object
// never do check for "== null" !
Prisoners = Prisoners.Where(p =>  p).ToList();
// This basically equals doing something like
//var p = new List<GameObject>();
//foreach(var prisoner in Prisoners)
//{
//    if(prisoner) p.Add(prisoner);
//}
//Prisoners = p;

foreach (var prisoner in Prisoners.Count)
{
    prisoner.GetComponent<PlayerData>().Captured = false;
}
 

Точно то же самое можно сделать в вашем втором скрипте

 // so first again we use the bool operator to check if the element target "existed
// then directly use TryGetComponent instead of GetComponent twice
// your order to check also made little sense ;) you should first Check 
// the existence of the component before accessing a value
TargetsWithinRange = TargetsWithinRange.Where(target => target amp;amp; target.TryGetComponent<Health>(out var health) amp;amp; health.CurrentHealth > 0).ToList();
 

В качестве альтернативы, если важно, чтобы сама коллекция оставалась той же ссылкой, которую вы могли бы использовать RemoveAll вместо этого, например

 Prisoners.RemoveAll(prisoner => ! prisoner);
 

и, соответственно, в вашем другом скрипте

 TargetsWithinRange.RemoveAll(target => !target || !target.TryGetComponent<Health>(out var h) || h.CurrentHealth <= 0);
 

Комментарии:

1. Привет, я пытался использовать оба ваших метода, но это создает больше проблем, чем решений. Скрипт POW_Camp работает нормально, а скрипт Turret — нет. Ваши методы, похоже, каким-то образом влияют на систему таргетинга. Он больше не может переключать цели. Первая цель остается текущей целью даже после ее уничтожения. Есть ли какой-то другой способ исправить эти ошибки?