Unity Использование 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 возвращает объект Enemy по умолчанию вместо объекта с его переменными, загруженными из json в IEnumerator GetEnemyFromDB.

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 и которая возвращается методом получения.

Большое спасибо за любую помощь!


person Captain Noah    schedule 21.08.2018    source источник


Ответы (2)


Функция без сопрограммы не может ждать функцию сопрограммы. Если вы попытаетесь сделать это принудительно, вам нужно будет делать это с помощью логической переменной в функции Update каждый кадр. Я бы не стал этого предлагать. Выполнение веб-запроса с помощью WWW API занимает несколько кадров или около того. Это означает, что вызов функции GetEnemyFromDB еще не был завершен или возвращен до того, как вы попытались получить доступ к значению.

В вашем случае необходимо внести следующие изменения:

1. Вы должны сделать функцию GetEnemy функцией сопрограммы, чтобы вы могли ждать функцию GetEnemyFromDB, пока она не будет выполнена. Это делается с помощью оператора yield return.

2. Чтобы установить объект в параметре функции сопрограммы, используйте Action. В данном случае подходит Action<Enemy> enemyResult.

3. Измените функцию Start на функцию сопрограммы. Да, вы можете это сделать. Это одна из немногих функций обратного вызова Unity, которую можно превратить в функцию сопрограммы. Обратите внимание, что это 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);
    }
}
person Programmer    schedule 21.08.2018
comment
Это сделало это! Это просто потрясающе, вы можете вернуть объект из IEnumerator. Пришло время изучить, что, черт возьми, такое действие! Спасибо! - person Captain Noah; 21.08.2018
comment
Да, я использовал Action и лямбду, чтобы вернуть результат в функцию сопрограммы. Это то, что вы должны делать, когда вам нужно вернуть результат из функции сопрограммы. - person Programmer; 21.08.2018
comment
На самом деле Start и GetEnemy не обязательно должны быть сопрограммами. Вы можете просто вызывать их как базовые методы с текущим кодом. Теперь он зависает при запуске, хотя в этом нет необходимости, так как код ожидания находится в обратном вызове. Использование метода Start и GetEnemy void приведет к тому же результату. Это не значит, что в настоящее время это неправильно, просто, на мой взгляд, это можно упростить. - person Everts; 21.08.2018
comment
@Everts Это совершенно верно. Хотя у меня есть свои причины так поступать. Причина в том, что GetEnemy должна быть функцией void, поскольку она не может вернуть Enemy, как в вопросе. Вместо того, чтобы сказать OP изменить тип возврата на void, я решил изменить его на IEnumerator, что похоже на убийство двух зайцев одним выстрелом. - person Programmer; 21.08.2018
comment
Если вы посмотрите на код в функции Start, вы увидите, что OP хочет использовать результат после вызова GetEnemy(1) и под ним. Я также ожидаю, что OP будет делать больше вещей по порядку после получения данных. Если бы я сделал это так, как вы упомянули, остальную часть кода OP после выполнения запроса необходимо было бы разместить внутри лямбды, подобной этой DBA.GetEnemy(1,(result)=>{ LONG CODE } . - person Programmer; 21.08.2018
comment
Если OP захочет сделать еще один веб-запрос с вашим предложением, это будет лямбда внутри лямбды, и оттуда все станет грязным и уродливым. Это более организованный способ сделать это, чем использование вложенных лямбда-выражений. См. этот ответ, который я сделал, например, затем посмотрите на код Javascript в этом вопросе. Вот как это будет выглядеть. Я просто хотел, чтобы OP мог использовать его так же, как он хотел, вместо того, чтобы перемещать остальную часть кода, который зависит от этих данных, в лямбда. Никаких задержек. Корутина просто ждет запроса, прежде чем вы сможете использовать результат. - person Programmer; 21.08.2018

Вы должны понимать, что 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
      }
   });
 }
}
person zibizz1    schedule 21.08.2018