为什么Unity C#协程在点击一个不相关的按钮时执行

Why does Unity C# Coroutine execute upon click of an unrelated button

该脚本是 Unity/Vuforia AR 应用程序的一部分,用户可以在其中通过 Vuforia 地面检测系统放置各种预制模型。这些预制件被加载到 AssetBundle 中以管理内存开销。

完成此操作的脚本正在过早执行。

这个协程脚本附在每个按钮上。在 "onClick" 之后,脚本旨在加载包含预制件的 AssetBundle,然后将加载的预制件对象实例化到 AR 世界中。 当前的问题是脚本是在单击 UI 面板打开按钮时执行的,这使得可以访问脚本附加到的实际位置 UI 按钮。

我通过@derHugo 的输入得出了这个优秀的脚本。该线程可以在这里找到:

这是附加到放置按钮的脚本

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Vuforia;

public class anchorManagerBundles : MonoBehaviour
{
    public PlaneFinderBehaviour plane;
    public ContentPositioningBehaviour planeFinder;
    private AnchorBehaviour model;
    public string nameOfAssetBundle;
    public string nameOfObjectToLoad;
    private static bool alreadyLoading;
    private static AssetBundle assetBundle;

    void Start()
    {
        // only load the bundle once
        if (!alreadyLoading)
        {
            // set the flag to make sure this is never done again
            alreadyLoading = true;
            StartCoroutine(LoadAsset(nameOfAssetBundle, nameOfObjectToLoad));
        }
        else
        {
            LoadObjectFromBundle(nameOfObjectToLoad);
        }
    }

    private IEnumerator LoadAsset(string assetBundleName, string objectNameToLoad)
    {
        string filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "AssetBundles");
        filePath = System.IO.Path.Combine(filePath, assetBundleName);

        if (assetBundle == null)
        {
            Debug.Log("Failed to Load assetBundle!!");
            yield break;
        }

        { 
            var assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(filePath); 
            yield return assetBundleCreateRequest; assetBundle = assetBundleCreateRequest.assetBundle; 
        }

    private IEnumerator LoadObjectFromBundle(string objectNameToLoad)
    {
        AssetBundleRequest assetRequest = assetBundle.LoadAssetAsync<GameObject>(objectNameToLoad);
        yield return assetRequest;

        GameObject loadedAsset = (GameObject)assetRequest.asset;

        model = loadedAsset.GetComponent<AnchorBehaviour>();
    }

    public void create()
    {
        planeFinder.AnchorStage = model;
    }
}

desired/expected 结果是,单击 UI 按钮后,将加载选定的 AssetBundle,并加载命名的 PreFab,以便在点击屏幕时放置到 AR 世界中。

我添加了一个调试中断,以便确定资产包是否已成功加载。然后在按下不相关的 UI 面板打开按钮时注册了以下错误,该按钮没有附加脚本,表明脚本过早 运行。

Failed to Load assetBundle!!
UnityEngine.Debug:Log(Object)
<LoadAsset>d__8:MoveNext() (at Assets/Scripts/anchorManagerBundles.cs:38)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
anchorManagerBundles:Start() (at Assets/Scripts/anchorManagerBundles.cs:23)

然后我继续实际放置按钮,当然因为过早执行没有放置预制件。

There is no content to place at the anchor. Set the "Anchor Stage" field to the content you wish to place.
UnityEngine.Debug:LogError(Object)
Vuforia.ContentPositioningBehaviour:CreateAnchorAndPlaceContent(Func`2, Vector3, Quaternion)
Vuforia.ContentPositioningBehaviour:PositionContentAtPlaneAnchor(HitTestResult)
UnityEngine.Events.UnityEvent`1:Invoke(HitTestResult)
Vuforia.PlaneFinderBehaviour:PerformHitTest(Vector2)
UnityEngine.Events.UnityEvent`1:Invoke(Vector2)
Vuforia.AnchorInputListenerBehaviour:Update()

问题是为什么这个脚本会过早执行,从而阻碍资产包及其选定 PreFab 的加载

尽管你 { 太多了


你的支票

if (assetBundle == null)

在这一点上没有任何意义..它总是 null 所以你告诉你的例程 "If the assetBundle was not loaded yet .. then please do not load it".


你应该把支票移到最后。如果它 已经 已经加载,我个人会反转你的检查以跳过加载。

注意:您还错过了最后一行

LoadObjectFromBundle(objectNameToLoad);

LoadAsset 的末尾,因此永远不会为调用 LoadAsset 的第一个实例加载对象。对于其他人来说,在 Start 中调用 LoadObjectFromBundle 实际上是没有意义的(对不起,我第一次没有注意到它)。协同程序尚未完成,但他们将尝试从尚未设置的 assetBundle 加载对象。

所以我会把它改成

private IEnumerator LoadAsset(string assetBundleName, string objectNameToLoad)
{
    // can be done in one single call
    var filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "AssetBundles", assetBundleName);

    // if at this point the assetBundle is already set we can skip the loading
    // and directly continue to load the specific object
    if (!assetBundle)
    {
        var assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(filePath);
        yield return assetBundleCreateRequest;

        assetBundle = assetBundleCreateRequest.assetBundle;

        if (!assetBundle)
        {
            Debug.LogError("Failed! assetBundle could not be loaded!", this);
        }
    }
    else
    {
        Debug.Log("assetBundle is already loaded! Skipping", this);
    }

    // YOU ALSO MISSED THIS!!
    yield return LoadObjectFromBundle(objectNameToLoad);
}

private IEnumerator LoadObjectFromBundle(string objectNameToLoad)
{
    // This time we wait until the assetBundle is actually set to a valid value
    // you could simply use
    //yield return new WaitUntil(() => assetBundle != null);
    // but just to see what happens I would let them report in certain intervals like
    var timer = 0f;
    while (!assetBundle)
    {
        timer += Time.deltaTime;
        if (timer > 3)
        {
            timer = 0;
            Debug.Log("Still waiting for assetBundle ...", this);
        }

        yield return null;
    }

    var assetRequest = assetBundle.LoadAssetAsync<GameObject>(objectNameToLoad);
    yield return assetRequest;

    var loadedAsset = (GameObject)assetRequest.asset;

    model = loadedAsset.GetComponent<AnchorBehaviour>();

    // add a final check
    if(!model)
    {
        Debug.LogError("Failed to load object from assetBundle!", this);
    }
    else
    {
        Debug.Log("Successfully loaded model!", this);
    }
}

// and now make sure you can't click before model is actually set
public void create()
{
    if(!model)
    {
        Debug.LogWarning("model is not loaded yet ...", this);
        return;
    }

    planeFinder.AnchorStage = model;
}