Unity中按列表顺序实例化Prefab失败问题求助
Unity父容器中Prefab顺序异常的解决方案
问题原因
异步加载的模型完成时间不一致,先加载完成的模型会先被添加到父容器并设置兄弟索引,后完成的模型设置索引时,父容器的子物体状态已变化,导致最终顺序不符合预期。SetSiblingIndex本身功能正常,但调用时机错误——无法保证按预期顺序执行。
解决方法
方案1:批量加载后统一排序(推荐)
先发起所有模型的异步加载请求,将加载完成的模型暂存,等所有模型加载完毕后,再按预期顺序依次添加到父容器并设置索引。既保留异步加载的效率,又能严格保证最终顺序。
修改后的代码示例:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking; public class LoadModelManager : MonoBehaviour { // 批量加载模型并保证顺序 public static IEnumerator LoadModelsInOrder(List<int> modelIds, GameObject self, GameObject parent = null, bool active = false) { // 缓存加载完成的模型及其目标索引 var loadedModels = new Dictionary<int, GameObject>(); int loadedCount = 0; // 遍历ID列表,发起所有加载请求 for (int i = 0; i < modelIds.Count; i++) { int index = i; int id = modelIds[i]; StartCoroutine(LoadModelCoroutine(id, self, (model) => { loadedModels[index] = model; loadedCount++; }, active)); } // 等待所有模型加载完成 while (loadedCount < modelIds.Count) { yield return null; } // 按索引顺序将模型添加到父容器 for (int i = 0; i < modelIds.Count; i++) { if (loadedModels.TryGetValue(i, out GameObject model)) { model.transform.SetParent(parent.transform, false); model.transform.SetSiblingIndex(i); model.SetActive(active); } } } // 内部加载协程,完成后回调 private static IEnumerator LoadModelCoroutine(int id, GameObject self, System.Action<GameObject> onLoaded, bool active) { string url = $"{GlobalVariable.server_url}/3ds/download/{id}"; using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(url)) { yield return webRequest.SendWebRequest(); if (webRequest.result != UnityWebRequest.Result.Success) { Debug.Log(webRequest.error); onLoaded?.Invoke(null); yield break; } AssetBundle remoteAssetBundle = DownloadHandlerAssetBundle.GetContent(webRequest); if (remoteAssetBundle == null) { onLoaded?.Invoke(null); yield break; } var model = Instantiate(remoteAssetBundle.LoadAsset(remoteAssetBundle.GetAllAssetNames()[0])) as GameObject; if (model != null) { model.transform.position = self.transform.position; model.transform.rotation = self.transform.rotation; model.transform.localScale = self.transform.localScale; model.SetActive(false); // 先隐藏,统一设置时再激活 } remoteAssetBundle.Unload(false); onLoaded?.Invoke(model); } } // 保留原单个加载方法(可选) static public IEnumerator LoadModel(int? id, GameObject self, GameObject parent = null, bool active = false, int indexOrder = 0) { if (id == null) { id = 0; // skin default } string url = $"{GlobalVariable.server_url}/3ds/download/{id}"; using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(url)) { yield return webRequest.SendWebRequest(); if (webRequest.result != UnityWebRequest.Result.Success) { Debug.Log(webRequest.error); } else { AssetBundle remoteAssetBundle = DownloadHandlerAssetBundle.GetContent(webRequest); if (remoteAssetBundle == null) { yield break; } var model = Instantiate(remoteAssetBundle.LoadAsset(remoteAssetBundle.GetAllAssetNames()[0])) as GameObject; model.transform.position = self.transform.position; model.transform.rotation = self.transform.rotation; model.transform.localScale = self.transform.localScale; if (parent != null) { model.transform.SetParent(parent.transform, false); } if (indexOrder != 0) { model.transform.SetSiblingIndex(indexOrder); } model.SetActive(active); remoteAssetBundle.Unload(false); } } } }
方案2:顺序加载(简单但效率低)
如果对加载速度要求不高,可以逐个加载模型,加载完成一个后再开始下一个的加载,天然保证顺序:
public static IEnumerator LoadModelsSequentially(List<int> modelIds, GameObject self, GameObject parent = null, bool active = false) { for (int i = 0; i < modelIds.Count; i++) { yield return LoadModel(modelIds[i], self, parent, active, i); } }
使用说明
- 方案1适合需要高效加载且保证顺序的场景,调用
LoadModelsInOrder方法传入ID列表即可。 - 方案2适合加载模型数量少、对加载速度要求低的场景,调用
LoadModelsSequentially方法。
内容的提问来源于stack exchange,提问作者Châu Đặng
相关产品推荐
相关产品推荐

