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

时间:2018-08-21 05:21:31

标签: c# json unity3d ienumerator

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

我们有一个非常简单的敌人类,可以将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返回默认的敌人对象,而不是从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);
 }

}

我在stackoverflow上找到了这个帮助器类。我忘记了在哪里或应该给予应有的荣誉,但是效果很好!

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方法返回我的Enemy对象,而不是让私有敌人作为受IEnumerator影响并由getter方法返回的变量。

非常感谢您的帮助!

2 个答案:

答案 0 :(得分:3)

非协程函数不能等待协程函数。如果尝试强制执行此操作,则需要在每一帧的Update函数中使用布尔变量来执行此操作。我不建议那样。使用WWW API发出Web请求大约需要几帧。这意味着GetEnemyFromDB函数调用在您尝试访问该值之前尚未完成或返回。

对于您而言,您必须进行以下更改:

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

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

3 。将Start函数更改为协程函数。是的,你可以这么做。它是少数几个可以变成协程函数的回调函数之一。请注意,在您输入问题代码时,它是Start而不是start

您的新BattleManager类:

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类:

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);
    }
}

答案 1 :(得分:0)

您需要了解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
      }
   });
 }
}