如何屈服 - 返回使用我不能使用线程的回调系统?

时间:2013-01-26 10:21:40

标签: c# unity3d yield-return

所以我想创建一个包含一堆静态方法的对象。这些方法是远程服务器的API。我正在阅读并认为我可以使用统一StartCoroutine方法,但在这种情况下不可用,所以现在我不知道该去哪里。

一般的想法是我希望能够调用我的一个对象的方法,传递一个委托并让id去做它的工作。完成后,使用结果调用委托。我不能使用线程,因为Unity3D不是线程安全的。

我知道c#有这个yield的东西,我已经读了好几个地方,但它仍然让我感到困惑。我如何重构下面的代码,以便完成我正在尝试做的事情?

public class Server
{
        private static string baseURL = "http://localhost/game.php";
        private static Hashtable session_ident = new Hashtable();

        //--- Public API
        public delegate void DeviceSeenCallback(bool seen);
        public static void DeviceSeen(DeviceSeenCallback callBack) {
                StartCoroutine(DoDeviceSeen(callBack));
        }

        public delegate void AuthenticateCallback(bool authenticated, string errorMessage);
        public static void Authenticate(string username, string passwordHash, AuthenticateCallback callBack) {
                StartCoroutine(DoAuthenticate(username, passwordHash, callBack));
        }


        //--- Private API
        private static IEnumerator DoDeviceSeen(DeviceSeenCallback callBack)
        {
                WWWForm form = new WWWForm();
                form.AddField("deviceID", SystemInfo.deviceUniqueIdentifier);

                WWW www = new WWW(baseURL + "?cms=seen", form.data, session_ident);
                        yield return www;

                // Check for errors
                callBack(ResultIsOk(www.text));
        }

        private static IEnumerator DoAuthenticate(string username, string passwordHash, AuthenticateCallback callBack)
        {
                WWWForm form = new WWWForm();
                form.AddField("deviceID", SystemInfo.deviceUniqueIdentifier);
                form.AddField("deviceType", SystemInfo.deviceType.ToString() + "||" + SystemInfo.deviceModel);
                form.AddField("user", username);
                form.AddField("pass", passwordHash);

                WWW www = new WWW(baseURL + "?cms=auth", form.data, session_ident);
                        yield return www;

                if (ResultIsOk(www.text)) {
                        callBack(true, "");
                } else {
                        int code;
                        string message;
                        ResultGetError(www.text, code, message);
                        callBack(false, message);
                }
        }

        private static bool ResultIsOk(string resultText) {
                return false;
        }

        private static void ResultGetError(string resultText, out int code, out string message) {
                code = -1;
                message = "Some Error Message";
        }
}

2 个答案:

答案 0 :(得分:3)

你总是遇到Unity的诀窍,就是你的代码运行的唯一上下文是在脚本回调期间。

这基本上意味着最终在某个地方,你必须在场景中有一个GameObject,其中MonoBehaviour附加到所谓的对象上,该对象调用你想要重复运行的代码。

除了从MonoBehaviour继承的类的实例之外没有可用的StartCoroutine这样的事实,你有一个问题,你需要让代码执行每一帧左右来检查一些东西。除了制作MonoBehaviour之外,没有其他方法可以解决这个问题。只需拥抱它即可。

一种常见的模式是创建一个单独的GameObject,它可以在其上调用DontDestroyOnLoad,也可以根据需要为每个场景重新创建。您甚至可以将一个对象指定为“coroutine bucket”,它不依赖于特定的类或任何东西。字面上是一个空洞的MonoBehaviour,其唯一目的就是开始协同程序。

您可以通过将Server类设置为MonoBehaviour来快速解决此问题:

然后添加一个单例属性:

    private static Server ServerObject {
        get { 
            if (_serverObject == null) {
                var gameObj = new GameObject("Server Object");
                _serverObject = gameObj.AddComponent<Server>();
                GameObject.DontDestroyOnLoad(_serverObject); // Optional
            }
            return _serverObject;
        }
    }
    private static Server _serverObject;

(单身人士很粗暴,但在这种情况下这是必要的邪恶)

然后更改要在单身MonoBehaviour实例上调用的所有StartCoroutine次调用:

    public static void DeviceSeen(DeviceSeenCallback callBack) {
            this.ServerObject.StartCoroutine(DoDeviceSeen(callBack));
    }

GoKit使用此模式并生成一个名为“Go”的GameObject,它处理补间的所有逻辑。由于Unity没有提供一种方法来指定经常运行而不附加到游戏对象的脚本,因此这是我们能做的最好的。

如果您的服务器类会保持很多状态,或者它不是必不可少的,那么建议您不要在其上调用DontDestroyOnLoad。如果场景发生更改并删除了ServerObject游戏对象,则对_serverObject的后续检查将显示为null,从而进行重新实例化(因为Unity会重置空转换运算符)。

答案 1 :(得分:0)

对于统一,您通常必须进行手动线程处理(除非您的线程不对Unity API进行任何调用)。也就是说,例如, Authenticate推送一个函数,该函数轮询其结果(无阻塞调用!)到私有静态列表上。然后提供一个静态Update方法,该方法遍历列表并调用每个轮询方法,如果它指示它已完成则删除它。当每个轮询方法完成时,它应该调用相关的回调。安排让您的应用程序定期调用此静态Update方法(例如,在通常的统一更新传递中)。