如何在OnEnable执行前将实例化对象添加至目标列表?
实例化对象列表索引越界问题解决方案
问题描述
实例化游戏对象时遇到逻辑顺序冲突:
- 维护一个目标列表
targetsList,实例化对象后将其加入列表,同时通过PlayerPrefs为对象分配ID - 对象的
OnEnable生命周期方法会先于“添加至列表”的代码执行,导致OnEnable中调用的方法访问列表时抛出ArgumentOutOfRangeException - 不想通过禁用预制体规避,担心误激活引发其他错误
实例化脚本代码
private void CreateTargetOnNetwork(Vector3 position, Quaternion rotation) { PlayerPrefs.SetInt("Index", index); targetsList.Add(Instantiate(Resources.Load("TargetPrefabNetwork") as GameObject, position, rotation, areaTargetManager.GetCurrentArea().transform)); index++; } [PunRPC] private void UpdateTargetOnOffOnNetwork(int id, bool status) { targetsList[id].SetActive(status); }
对象挂载脚本代码
private void Awake() { _targetIndex = PlayerPrefs.GetInt("Index"); PlayerPrefs.DeleteKey("Index"); targetManager = FindObjectOfType<TargetManager>(); targetManagerPhotonView = targetManager.GetComponent<PhotonView>(); } private void OnEnable() { targetManagerPhotonView.RPC("UpdateTargetOnOffOnNetwork", RpcTarget.AllBuffered, _targetIndex, true); }
注:使用Photon,但问题与Photon无关,RPC可视为普通方法调用
解决方案
方法1:实例化时先禁用,加入列表后再激活
修改实例化逻辑,先让对象处于禁用状态,确保加入列表后再触发OnEnable:
private void CreateTargetOnNetwork(Vector3 position, Quaternion rotation) { PlayerPrefs.SetInt("Index", index); // 实例化后立即禁用,避免提前触发OnEnable GameObject newTarget = Instantiate(Resources.Load("TargetPrefabNetwork") as GameObject, position, rotation, areaTargetManager.GetCurrentArea().transform); newTarget.SetActive(false); // 添加到列表后再激活,此时OnEnable执行时列表已包含该对象 targetsList.Add(newTarget); index++; newTarget.SetActive(true); }
方法2:直接传递索引,弃用PlayerPrefs
取消PlayerPrefs的全局状态传递,实例化后直接给对象脚本赋值索引,彻底规避顺序问题:
首先修改对象脚本,将索引改为可外部赋值的公开字段:
public int _targetIndex; private TargetManager targetManager; private PhotonView targetManagerPhotonView; private void Awake() { targetManager = FindObjectOfType<TargetManager>(); targetManagerPhotonView = targetManager.GetComponent<PhotonView>(); } private void OnEnable() { targetManagerPhotonView.RPC("UpdateTargetOnOffOnNetwork", RpcTarget.AllBuffered, _targetIndex, true); }
然后修改实例化脚本:
private void CreateTargetOnNetwork(Vector3 position, Quaternion rotation) { GameObject newTarget = Instantiate(Resources.Load("TargetPrefabNetwork") as GameObject, position, rotation, areaTargetManager.GetCurrentArea().transform); // 先添加到列表,再赋值索引 targetsList.Add(newTarget); newTarget.GetComponent<TargetScript>()._targetIndex = index; index++; }
这种方式逻辑更直接,完全避免了全局状态带来的不确定性。
方法3:延迟执行OnEnable中的逻辑
如果不想修改实例化流程,可通过延迟调用确保列表已完成添加:
private void OnEnable() { // 下一帧执行,给实例化脚本足够时间将对象加入列表 Invoke(nameof(SendUpdateRPC), 0); } private void SendUpdateRPC() { targetManagerPhotonView.RPC("UpdateTargetOnOffOnNetwork", RpcTarget.AllBuffered, _targetIndex, true); }
注:该方法依赖帧执行顺序,稳定性不如前两种,仅作为临时方案使用
内容的提问来源于stack exchange,提问作者Noam Riahi
相关产品推荐
相关产品推荐

