2

所以我想做一个有一堆静态方法的对象。这些方法是远程服务器的 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";
        }
}
4

2 回答 2

3

您在使用 Unity 时经常遇到的技巧是,您的代码运行的唯一上下文是在脚本回调期间。

这基本上意味着最终在某个地方,您必须在场景中拥有一个游戏对象,并将 MonoBehaviour 附加到所述对象,该对象调用您想要重复运行的代码。

除了在继承自 MonoBehaviour 的类的实例中没有可用的 StartCoroutine 之外,您还存在一个问题,即您需要在每一帧左右执行代码以检查某些内容。除了制作一个 MonoBehaviour 之外,没有真正的解决方法,只需接受它。

一个常见的模式是创建一个单例游戏对象,它要么调用了 DontDestroyOnLoad,要么根据每个场景的需要重新创建。您甚至可以将一个对象指定为与特定类或任何东西无关的“协程存储桶”。从字面上看,它是一个空的 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;

(单身很恶心,但在这种情况下这是必要的邪恶)

然后通过将所有StartCoroutine调用更改为在单例 MonoBehaviour 实例上调用:

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

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

如果您的 Server 类将保持很多状态,或者它不是必须保留在场景中的一部分,则建议您不要调用DontDestroyOnLoad它。如果场景发生变化并且 ServerObject 游戏对象被删除,对 _serverObject 的后续检查将显示为 null 并因此被重新实例化(因为 Unity 重载了 null 转换运算符)。

于 2013-01-26T10:40:41.287 回答
0

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

于 2013-01-26T10:31:44.987 回答