Unity3D脚本列表越界问题:无法实现类Minecraft方块销毁逻辑
方块销毁功能的索引越界问题修复
问题背景
需求:实现类似Minecraft的方块销毁逻辑——碰撞体触碰到目标物体且点击左键时,销毁列表首位的物体;但触碰到WorldMap、Water、Berries三类物体时,不执行销毁操作。
报错情况:当碰撞体触碰到上述三类物体时,触发索引越界错误:
Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
原脚本代码:
using System.Collections.Generic; using System.Collections; using UnityEngine; public class GetTarget : MonoBehaviour { [Header("Координаты блока")] // block positions public int positionX; public int positionY; public int positionZ; [Header("Получение цели")] // set list for block to destroy public static List<GameObject> target = new List<GameObject>(); private void OnTriggerStay(Collider collider) { target.Insert(0, collider.gameObject); if(target[0].name == "WorldMap") // can't destroy world map { if(target.Count != 0) { Debug.Log("Вы не можете получить " + target[0]); // you can't get (test message) target.Remove(collider.gameObject); } } if(target[0].name == "Water") // you can't destroy water in structure { if(target.Count != 0) { Debug.Log("Вы не можете получить " + target[0]); // you can't get (test message) target.Remove(collider.gameObject); } } if(target[0].name == "Berries") // you can't destroy berries { if(target.Count != 0) { Debug.Log("Вы не можете получить " + target[0]); // you can't get (test message) target.Remove(collider.gameObject); } } } private void Update() // i get position for collider { Vector3 positions = transform.position; positionX = (int)Mathf.Round(gameObject.transform.position.x); positionY = (int)Mathf.Round(gameObject.transform.position.y); positionZ = (int)Mathf.Round(gameObject.transform.position.z); // set position for collider - positions = transfrom.position positions.x = positionX; positions.y = positionY; positions.z = positionZ; } }
问题分析与修复方案
核心问题点
- 索引越界根源:
OnTriggerStay每帧调用,先将碰撞体插入列表首位,判断后移除该物体。如果移除后列表为空,后续的if(target[0].name)判断会直接访问空列表的索引0,触发错误。 - 列表逻辑混乱:未处理重复插入同一物体的情况,且没有在碰撞体离开时移除对应物体,导致列表数据不准确。
修复步骤
- 提前过滤禁止销毁的物体:在插入列表前先判断当前碰撞体是否属于禁止类型,直接跳过插入,避免后续无效的列表操作。
- 简化判断逻辑:用HashSet存储禁止销毁的物体名称,提升判断效率,减少重复代码。
- 添加空列表检查:所有访问
target[0]的操作前,先确认列表不为空。 - 补充
OnTriggerExit逻辑:当碰撞体离开时,从列表中移除对应物体,保证列表数据的准确性。
修复后的完整代码
using System.Collections.Generic; using UnityEngine; public class GetTarget : MonoBehaviour { [Header("方块坐标")] public int positionX; public int positionY; public int positionZ; [Header("销毁目标列表")] public static List<GameObject> target = new List<GameObject>(); // 存储禁止销毁的物体名称集合 private readonly HashSet<string> _forbiddenObjects = new HashSet<string> {"WorldMap", "Water", "Berries"}; private void OnTriggerStay(Collider collider) { GameObject currentObj = collider.gameObject; // 如果是禁止销毁的物体,直接提示并返回 if (_forbiddenObjects.Contains(currentObj.name)) { Debug.Log("无法选中该物体:" + currentObj.name); return; } // 避免重复插入同一物体 if (!target.Contains(currentObj)) { target.Insert(0, currentObj); } } private void OnTriggerExit(Collider collider) { // 碰撞体离开时,从列表中移除对应物体 if (target.Contains(collider.gameObject)) { target.Remove(collider.gameObject); } } private void Update() { Vector3 positions = transform.position; positionX = (int)Mathf.Round(transform.position.x); positionY = (int)Mathf.Round(transform.position.y); positionZ = (int)Mathf.Round(transform.position.z); positions.x = positionX; positions.y = positionY; positions.z = positionZ; // 处理左键点击销毁逻辑 if (Input.GetMouseButtonDown(0) && target.Count > 0) { Destroy(target[0]); target.RemoveAt(0); } } }
额外说明
- 补充了原需求中缺失的左键点击销毁逻辑,确保点击时先检查列表不为空再操作。
- 使用
HashSet存储禁止名称,比多次字符串相等判断更高效。 - 添加
OnTriggerExit后,列表只会保留当前处于碰撞范围内的有效物体,避免无效数据积累。
内容的提问来源于stack exchange,提问作者RuslanKovtun
相关产品推荐
相关产品推荐

