#c# #unity3d
#c# #unity3d
Вопрос:
Недавно я заменил некоторые массивы на списки в большинстве своих скриптов, поскольку они проще в использовании. Проблема в том, что появились некоторые новые ошибки. Они :
- Исключение InvalidOperationException: коллекция была изменена; операция перечисления может не выполняться.
- Исключение 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
; вместо этого сделайте его циклом for5. Для ошибок вне диапазона аргументов убедитесь, что вы не обращаетесь к элементу массива за пределами длины массива. В частности, если вы выполняете итерацию массива, доходите до последнего элемента и решаете удалить его, не пытайтесь снова получить к нему доступ — ваш список заключенных может пострадать от этого, потому что вы принимаете решение удалить в 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 — нет. Ваши методы, похоже, каким-то образом влияют на систему таргетинга. Он больше не может переключать цели. Первая цель остается текущей целью даже после ее уничтожения. Есть ли какой-то другой способ исправить эти ошибки?