为什么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;
}
该脚本是 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;
}