Unity 使用 IENumerator 设置另一个对象将使用的 class

Unity Using IENumerator to set a class that another object will use

我正在尝试从我的数据库中创建另一个 class 使用的对象。 但是由于 IEnumerator 的延迟响应(也许?),似乎存在问题。

我们有一个非常简单的敌人 class 将 json 解析为:

[System.Serializable]
public class Enemy{
 public string EnemyID;
 public string Name;
}

BattleManager 附加到场景中的对象

public class BattleManager : Monobehaviour{

 public Enemy debugEnemy;

 void start()
 {
   //get a reference to the DBAccess
   DBA = GameObject.Find("ManagerDB").GetComponent<DBAccess>();

   debugEnemy = DBA.GetEnemy(1);
   //debugEnemy EnemyID=0 and Name=""
   //This is where the Problem is! Why is this not set from my DB?
 }
}

现在这是我从数据库中获取信息的地方。除了 GetEnemy return 是一个默认的 Enemy 对象,而不是从 IEnumerator GetEnemyFromDB

中的 json 加载的变量之外,一切似乎都正常进行
public class DBAccess: Monobehaviour{

 private Enemy enemy;

 public Enemy GetEnemy(int EnemyID)
 {
   enemy = new Enemy();
   StartCoroutine(GetEnemyFromDB(EnemyID));

   //HERE enemy.EnemyID is 0 and enemy.Name is ""
   return enemy;
 }
 private IEnumerator GetEnemyFromDB(int EnemyID)
 {
     WWWForm postData = new WWWForm();
     postData.AddField("EnemyID", EnemyID);

     WWW dbProc = new WWW(GetEnemyURL, postData);
     yield return dbProc;

     if (string.IsNullOrEmpty(dbProc.error)) //error is null or empty so: SUCCESS!
     {
         string jsonstring = "{\"Items\":" + dbProc.text + "}";

         Enemy[] EnemiesFromDB;
         EnemiesFromDB = JsonHelper.FromJson<Enemy>(jsonstring);

         if (EnemiesFromDB.Length > 0)
         {
             enemy = EnemiesFromDB[0];
             //HERE enemy.EnemyID is 1 and enemy.Name is "Evil Enemy Monster Man!"
             //So it is working here!
         }
         else throw new System.Exception("No Enemy Found When Reading Json: " + JsonUtility.FromJson<Enemy>(jsonstring));

     }
     else throw new System.Exception("DB ERROR: " + dbProc.error);
 }

}

我在 Whosebug 上找到了这个助手 class。我忘了在哪里,否则我会给予适当的信任,但效果很好!

public static class JsonHelper
{
 public static string RemoveBrackets(string s)
 {
    s = s.Replace("[", string.Empty);
    s = s.Replace("]", string.Empty);
    return s;
 }

 public static T[] FromJson<T>(string json)
 {

    Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(json);
    return wrapper.Items;
 }

 public static string ToJson<T>(T[] array)
 {
    Wrapper<T> wrapper = new Wrapper<T>();
    wrapper.Items = array;
    return JsonUtility.ToJson(wrapper);
 }

 public static string ToJson<T>(T[] array, bool prettyPrint)
 {
    Wrapper<T> wrapper = new Wrapper<T>();
    wrapper.Items = array;
    return JsonUtility.ToJson(wrapper, prettyPrint);
 }

 [System.Serializable]
 private class Wrapper<T>
 {
    public T[] Items;
 }
}

超级沮丧,我想我已经阅读了 IEnumerator 上的一百万篇文章。 我希望我可以只使用 IEnumerator 方法 return 我的 Enemy 对象,而不是将私人敌人作为受 IEnumerator 影响和 return 由 getter 方法编辑的变量。

非常感谢您的帮助!

非协程函数不能等待协程函数。如果你试图强制这样做,那么你需要在每一帧的 Update 函数中使用一个布尔变量来做到这一点。我不建议那样做。使用 WWW API 发出网络请求需要几帧左右。这意味着在您尝试访问该值之前 GetEnemyFromDB 函数调用尚未完成或返回。

对于您的情况,您必须进行以下更改:

1。您必须使 GetEnemy 函数成为协程函数,以便您可以等待 GetEnemyFromDB 函数完成。这是通过 yield return 语句完成的。

2。要在协程函数的参数中设置对象,请使用 Action。在这种情况下,Action<Enemy> enemyResult 是合适的。

3。将 Start 函数更改为协程函数。是的,你可以这样做。它是 Unity 为数不多的可以做成协程函数的回调函数之一。请注意,它是 Start 而不是 start,因为您在问题中输入了代码。

你的新 BattleManager class:

public class BattleManager : MonoBehaviour
{

    public Enemy debugEnemy;

    IEnumerator Start()
    {
        //get a reference to the DBAccess
        DBAccess DBA = GameObject.Find("ManagerDB").GetComponent<DBAccess>();
        yield return StartCoroutine(DBA.GetEnemy(1, (result) => { debugEnemy = result; }));

        //YOU CAN NOW USE debugEnemy below

    }
}

你的新 DBAccess class:

public class DBAccess : MonoBehaviour
{
    public IEnumerator GetEnemy(int EnemyID, Action<Enemy> enemyResult)
    {
        yield return StartCoroutine(GetEnemyFromDB(EnemyID, enemyResult));
    }

    private IEnumerator GetEnemyFromDB(int EnemyID, Action<Enemy> enemyResult)
    {
        WWWForm postData = new WWWForm();
        postData.AddField("EnemyID", EnemyID);

        WWW dbProc = new WWW(GetEnemyURL, postData);
        yield return dbProc;

        if (string.IsNullOrEmpty(dbProc.error)) //error is null or empty so: SUCCESS!
        {
            string jsonstring = "{\"Items\":" + dbProc.text + "}";

            Enemy[] EnemiesFromDB;
            EnemiesFromDB = JsonHelper.FromJson<Enemy>(jsonstring);

            if (EnemiesFromDB.Length > 0)
            {
                //Pass result back to param
                if (enemyResult != null)
                    enemyResult(EnemiesFromDB[0]);

                //HERE enemy.EnemyID is 1 and enemy.Name is "Evil Enemy Monster Man!"
                //So it is working here!
            }
            else throw new System.Exception("No Enemy Found When Reading Json: " + JsonUtility.FromJson<Enemy>(jsonstring));

        }
        else throw new System.Exception("DB ERROR: " + dbProc.error);
    }
}

你需要明白StartCorutine不会阻塞执行,问题在这里:

 public Enemy GetEnemy(int EnemyID)
 {
   enemy = new Enemy();
   StartCoroutine(GetEnemyFromDB(EnemyID)); //this is executed asynchronously
   return enemy;
 }

您需要将其更改为:

public void GetEnemy(int EnemyID, Action<Enemy> callback)
{
    StartCoroutine(GetEnemyFromDB(EnemyID,callback));
}

private IEnumerator GetEnemyFromDB(int EnemyID, Action<Enemy> callback)
{
    WWWForm postData = new WWWForm();
    postData.AddField("EnemyID", EnemyID);

    WWW dbProc = new WWW(GetEnemyURL, postData);
    yield return dbProc; //code below is executed later, after after receiving the response from the server

    if (string.IsNullOrEmpty(dbProc.error)) //error is null or empty so: SUCCESS!
    {
        string jsonstring = "{\"Items\":" + dbProc.text + "}";
        Enemy[] EnemiesFromDB;
        EnemiesFromDB = JsonHelper.FromJson<Enemy>(jsonstring);
        if (EnemiesFromDB.Length > 0)
        {
            var enemy = EnemiesFromDB[0];
            callback(enemy); //return enemy
            yield break;
        }
        else
            Debug.LogError("Enemy does not exist");
    }
    else
        Debug.LogError("WWW request failed: " + dbProc.error);
    callback(null); //call empty calbback to inform that something has failed
 } 

并以这种方式使用:

public class BattleManager : Monobehaviour{

 public Enemy debugEnemy;

 void Start()
 {
   //get a reference to the DBAccess
   DBA = GameObject.Find("ManagerDB").GetComponent<DBAccess>();

   debugEnemy = DBA.GetEnemy(1,(e)=>{
      if(e!=null)
      {
      //Do something with enemy here, it will be couple frames later
      }
   });
 }
}