You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:30:54