Unity数组初始长度7运行时变0触发索引越界异常排查
问题根因
- 核心逻辑错误:
PlayerMovement继承了GameRules,但两个脚本作为独立组件挂载在场景不同对象上时,会各自持有一套独立的序列化字段(生成点数组、预制体引用等),数据完全不互通。- 挂载在全局管理器对象上的
GameRules组件会正常执行自身Awake逻辑,通过FindGameObjectsWithTag获取到7个生成点,首次Start调用生成逻辑时日志正常输出数组长度为7。 - 挂载在玩家对象上的
PlayerMovement组件重写了Awake方法且没有调用base.Awake(),自身持有的两个生成点数组从未执行查找赋值逻辑,且被Unity序列化机制覆盖为Inspector中默认的空数组(长度0)。后续碰撞回调、死亡逻辑调用RandomLeft()/RandomRight()时,实际调用的是玩家身上PlayerMovement组件的方法,访问长度为0的数组直接触发越界报错。
- 挂载在全局管理器对象上的
- 附带逻辑bug1:生成开关时将Instantiate返回的场景对象直接赋值给了预制体引用字段,会覆盖原有预制体引用,且仅单个字段无法存储3个生成的开关对象,调用销毁方法时只会删除最后生成的1个开关,剩余2个开关会永久残留在场景中。
- 附带逻辑bug2:随机索引写法错误,Unity中
Random.Range(int min, int max)为左闭右开区间,传入array.Length -1作为最大值时永远无法取到数组最后一个点位。
修复方案
- 取消不合理的继承关系:
PlayerMovement不需要继承GameRules,将GameRules做成单例全局管理类,所有生成、销毁开关的逻辑统一通过单例调用,避免多实例数据不互通的问题。
改造后的GameRules示例代码:using UnityEngine; using System.Collections.Generic; public class GameRules : MonoBehaviour { public static GameRules Instance; public GameObject[] rightSwitchSpawns; public GameObject[] leftSwitchSpawns; public GameObject rightSwitchPrefab; public GameObject leftSwitchPrefab; // 存储生成的开关,方便后续统一销毁 private List<GameObject> _spawnedRightSwitches = new List<GameObject>(); private List<GameObject> _spawnedLeftSwitches = new List<GameObject>(); void Awake(){ // 单例初始化 Instance = this; rightSwitchSpawns = GameObject.FindGameObjectsWithTag("RightSpawns"); leftSwitchSpawns = GameObject.FindGameObjectsWithTag("LeftSpawns"); } void Start() { RandomLeft(); RandomRight(); } public void RandomLeft() { // 生成前先清理上一轮的开关 DestroyLeftSwitches(); Debug.Log("This Left array length is " + leftSwitchSpawns.Length); // 修正随机范围,直接传Length即可覆盖所有索引 int leftRandom1 = Random.Range(0, leftSwitchSpawns.Length); Debug.Log("left 1 index is " + leftRandom1); Vector2 leftOne = leftSwitchSpawns[leftRandom1].transform.position; _spawnedLeftSwitches.Add(Instantiate(leftSwitchPrefab, leftOne, Quaternion.identity)); int leftRandom2 = Random.Range(0, leftSwitchSpawns.Length); while(leftRandom2 == leftRandom1) { leftRandom2 = Random.Range(0, leftSwitchSpawns.Length); } Vector2 leftTwo = leftSwitchSpawns[leftRandom2].transform.position; _spawnedLeftSwitches.Add(Instantiate(leftSwitchPrefab, leftTwo, Quaternion.identity)); Debug.Log("Left 2 index is: " + leftRandom2); int leftRandom3 = Random.Range(0, leftSwitchSpawns.Length); while(leftRandom3 == leftRandom1 || leftRandom3 == leftRandom2) { leftRandom3 = Random.Range(0, leftSwitchSpawns.Length); } Vector2 leftThree = leftSwitchSpawns[leftRandom3].transform.position; _spawnedLeftSwitches.Add(Instantiate(leftSwitchPrefab, leftThree, Quaternion.identity)); Debug.Log("Left 3 index is: " + leftRandom3); } public void RandomRight() { // 生成前先清理上一轮的开关 DestroyRightSwitches(); Debug.Log("This Right array length is " + rightSwitchSpawns.Length); int rightRandom1 = Random.Range(0, rightSwitchSpawns.Length); Vector2 rightOne = rightSwitchSpawns[rightRandom1].transform.position; _spawnedRightSwitches.Add(Instantiate(rightSwitchPrefab, rightOne, Quaternion.identity)); Debug.Log("Right 1 index is: " + rightRandom1); int rightRandom2 = Random.Range(0, rightSwitchSpawns.Length); while (rightRandom2 == rightRandom1) { rightRandom2 = Random.Range(0, rightSwitchSpawns.Length); } Vector2 rightTwo = rightSwitchSpawns[rightRandom2].transform.position; _spawnedRightSwitches.Add(Instantiate(rightSwitchPrefab, rightTwo, Quaternion.identity)); Debug.Log("Right 2 index is: " + rightRandom2); int rightRandom3 = Random.Range(0, rightSwitchSpawns.Length); while (rightRandom3 == rightRandom1 || rightRandom3 == rightRandom2) { rightRandom3 = Random.Range(0, rightSwitchSpawns.Length); } Vector2 rightThree = rightSwitchSpawns[rightRandom3].transform.position; _spawnedRightSwitches.Add(Instantiate(rightSwitchPrefab, rightThree, Quaternion.identity)); Debug.Log("Right 3 index is: " + rightRandom3); } public void DestroyRightSwitches(){ Debug.Log("Right Switches Destroyed"); foreach(var sw in _spawnedRightSwitches){ Destroy(sw); } _spawnedRightSwitches.Clear(); } public void DestroyLeftSwitches(){ Debug.Log("Left Switches Destroyed"); foreach(var sw in _spawnedLeftSwitches){ Destroy(sw); } _spawnedLeftSwitches.Clear(); } } - 改造
PlayerMovement脚本,去掉对GameRules的继承,所有逻辑调用走GameRules.Instance单例,修改后的代码如下:using UnityEngine; public class PlayerMovement : MonoBehaviour { private float speed = 5f; private float jump = 5f; private Rigidbody2D rb; private bool isPlaying = false; private Transform trans; void Awake() { trans = gameObject.GetComponent<Transform>(); rb = gameObject.GetComponent<Rigidbody2D>(); rb.simulated = false; } void Update() { if(Input.GetMouseButtonDown(0)){ if(isPlaying == false){ isPlaying = true; rb.simulated = true; rb.velocity = new Vector2(speed,0f); } else{ rb.velocity = new Vector2(speed,jump); } } } void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.tag == "Wall") { OnDeath(); } if(collision.gameObject.tag == "RightSwitch"){ speed = -speed; rb.velocity = new Vector2(speed,0f); GameRules.Instance.RandomRight(); } if(collision.gameObject.tag == "LeftSwitch"){ speed = -speed; rb.velocity = new Vector2(speed,0f); GameRules.Instance.RandomLeft(); } } void OnDeath(){ isPlaying = false; rb.simulated = false; trans.position = new Vector2(0,0); GameRules.Instance.RandomLeft(); GameRules.Instance.RandomRight(); } } - 检查场景中静态生成点的标签设置,确保所有左右生成点分别挂载
LeftSpawns、RightSpawns标签,且不会被运行时逻辑误销毁。
内容的提问来源于stack exchange,提问作者Damion Russell
相关产品推荐
相关产品推荐

