试图让Jslib函数与C#函数UNITY 2017一起使用 [英] Trying to get Jslib function to work with C# function UNITY 2017

查看:120
本文介绍了试图让Jslib函数与C#函数UNITY 2017一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在Unity上进行WebGL构建.我有一组图像,我试图将这些图像放入Firebase Cloud存储中,然后使用JSLIB文件编写Javascript代码以获取URL,然后在For循环中将其传递给Unity中的"WWW".我不断收到这个毫无意义的奇怪索引错误.我不知道问题出在哪里.
错误
IndexOutOfRangeException:索引超出数组的范围.
在System.String.get_Chars(System.Int32索引)处
[0x00000]在< 00000000000000000000000000000000>:0中
(文件名:当前在il2cpp行上不可用:-1)
这是代码.
Jslib文件

I am doing a WebGL build on Unity. I have a set of images, I am trying to put these images in the Firebase Cloud storage and then use a JSLIB file to write Javascript code to get the URL and then pass it to "WWW" in Unity in a For loop. I keep getting this weird index error that doesn't make any sense. I don't know where the problem is.
The error
IndexOutOfRangeException: Index was outside the bounds of the array.
at System.String.get_Chars (System.Int32 index)
[0x00000] in <00000000000000000000000000000000>:0
(Filename: currently not available on il2cpp Line: -1)
Here is the code.
Jslib file

mergeInto(LibraryManager.library, {


StringReturnValueFunction: function (name) {
const firebaseConfig = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: ""
};
firebase.initializeApp(firebaseConfig);
var storage = firebase.storage();
var name = Pointer_stringify(name);
var storageRef = storage.refFromURL('gs://jwellery-data.appspot.com');
storageRef.child(name).getDownloadURL().then(function(returnStr) {
  console.log(lengthBytesUTF8(returnStr));
    var bufferSize = lengthBytesUTF8(returnStr) + 1;
    var buffer = _malloc(bufferSize);
    stringToUTF8(returnStr, buffer, bufferSize);
    return buffer;
  }).catch(function(error) {
  // Handle any errors
});
  }});

Unity中的代码部分,我尝试从此函数获取此字符串URL,然后将其与WWW一起使用以获取实际图像.
我尝试了很多变化.使用yield而不使用yield时,直接从Jslib返回一个字符串.字符串URL而不是var URL.任何帮助都是真正的帮助.

The part of the code in Unity where I tried getting this string URL from this function then using it with WWW to get the actual image.
I have tried a lot of variations. With yield without yield directly returning a string from Jslib. string URL instead of var URL. Any help would be really help full.

IIEnumerator LoadUI ()
{

    for (int i = 0; i < jewelData.names.Count; i++) {
        GameObject prefab = (GameObject)Instantiate (jewelContentPrefab) as GameObject;
        // WWW www = new WWW (jewelImagePath + "/" + jewelData.SKU [i] + ".png");
        var url = StringReturnValueFunction(jewelData.SKU [i] + ".png");
        yield return url;
        WWW www = new WWW (url);
        //Debug.Log (jewelImagePath + jewelData.names [i] + ".png");
        yield return www;
        Sprite sprite = Sprite.Create (www.texture, new Rect (0.0f, 0.0f, www.texture.width, www.texture.height), new Vector2 (0.5f, 0.0f));
        //jewelIcons.Add (sprite);
        prefab.transform.Find ("JewelIcon").GetComponent<Image> ().sprite = sprite;

        prefab.transform.SetParent (grid, false);
        prefab.AddComponent<IconOnClick> ();


        IconOnClick ic = prefab.GetComponent<IconOnClick> ();
        ic.ji = new JewelItem ();


        ic.ji.name = jewelData.names [i];
        ic.ji.weight = jewelData.weight [i];
        ic.ji.price = jewelData.price [i];
        ic.ji.sku = jewelData.SKU [i];
        ic.ji.gameObject = prefab;
        if (jewelData.Type [i] == "1") {
            ic.ji.type = JewelType.Necklace;
        } else if (jewelData.Type [i] == "2") {
            ic.ji.type = JewelType.Tikka;
        } else if (jewelData.Type [i] == "3") {
            ic.ji.type = JewelType.EarRing;
        }
        ic.ji.sprite = sprite;
        ic.Assign ();
        currentLoadedScroll.Add (prefab);
        totalIcons.Add (prefab);
        allJewels.Add (ic.ji);
    }

}

Update1: 通过JSlib文件登录时,URL存在并且可以使用.但是没有出现在C#中,并且代码因错误而崩溃.
Update2:应用以下解决方案后,出现两个错误.一种是将[MonoPInvokeCallback(typeof(Action)))添加为回调的属性.另一个是关于这段代码中的声明表达式.

Update1: The URL when logged through the JSlib file exists and works. But doesn't come into the C# and the code crashes with the error.
Update2: After apply the below solution, there were two errors. One was to add a [MonoPInvokeCallback(typeof(Action))] as an attribute to the callback. And the other one was regarding the "Declaration Expression in this part of the code.

            if(callbacksBook.TryGetValue(requestID, out Action<string,string> callback))
            {
                callback?.Invoke(url, error);

            }
            // Remove this request from the tracker as it is done.
            callbacksBook.Remove(requestID);
        }

我通过声明如下的回叫来解决了这个问题

I solved this by declaring the call back above as following

Action<string,string> callback;
        if(callbacksBook.TryGetValue(requestID, out callback))
        {
            callback?.Invoke(url, error);

        }
        // Remove this request from the tracker as it is done.
        callbacksBook.Remove(requestID);
    }

我通过在全局回调函数上方添加一行[MonoPInvokeCallback(typeof(Action))]解决了第一个问题. URL是一个空字符串.我不断收到没有错误但也没有URL",尽管该URL存在于console.logged时看到的JSlib文件中.

I solved the first one by adding a the line [MonoPInvokeCallback(typeof(Action))] above the Global Callback function. The URL is an empty string. I keep getting "No errors but No URL either" although the URL exists in the JSlib file which I saw when console.logged.

推荐答案

长答案,集中精力.

首先,您的StringReturnValueFunction返回void,实际上返回字符串的是promise的then函数,就像C#中的回调一样,您的javascript函数中的情况与C#中的情况相同

First things first, your StringReturnValueFunction returns void, what actually returns the string is the promise's then function, which is like in C# a callback, the situation in your javascript function is equivalent to this in C#

public void MyOuterFunction(string parameter)
{
       Task someTask = Task.Run(SomeAsyncFunction);

       // This is equivalent to the 'then' in javascript

       someTask.ContinueWith(result=>
       {
           // inside here we're in another function other than the outer function (MyOuterFunction)
           // So anything returned in this function is the return of this function not the outer one.
       });

       // We reach this point immediately same in your javascript function, returning void.
       // while the 'someTask' is doing its work, in your example, getting the download url.
}

这意味着您错误地认为它会返回一个字符串.

Which means you wrongfully assumed it'd return a string.

所以您现在要做的是,当诺言在JavaScript端完成时,调用一个静态C#函数.

So what would you do now, is to call a static C# function when the promise finishes on the javascript side.

此过程分为三个部分:

1)我们需要编辑javascript库,以添加一个上下文对象以与C#进行双向通信,您的名为firebaseStorage.jslib的jslib文件将类似于

1) We need to edit the javascript library to add a context object for two-way communication with the C# side, your jslib file named firebaseStorage.jslib would look something like

// This is a whole global object, representing the file.
var firebaseStorage =
{
    // a communicator object inside the global object, the dollar sign must be before the name.
    $contextObject:
    {

         // Notifies C# that the link is received for the function that request it.
         NotifyCSharpAboutDownloadURLResult : function(requestID, link, error, callback)
         {
             var linkBytes = this.AllocateStringHelperFunction(link);
             var errorBytes = this.AllocateStringHelperFunction(error);

             // Calls a C# function using its pointer 'callback',
             // 'v' means it is a void function, the count of 'i's if the count of arguments this function accepts in our case 3 arguments, request id, the link and an error.
             Runtime.dynCall('viii', callback, [requestID, linkBytes,errorBytes]);

             // Free up the allocated bytes by the strings

             if(linkBytes)
                _free(linkBytes);
             if(errorBytes)
                _free(errorBytes);

         },

         // Utility function to convert javascript string to C# string.
         // Handles if the string is null or empty.
         AllocateStringHelperFunction: function (str)
         {
            if (str) 
            {
                var length = lengthBytesUTF8(str) + 1;
                var buff = _malloc(length);

                stringToUTF8Array(str, HEAPU8, buff, length);

                return buff;
            }
            return 0;
         },

    }, // this comma is important, it separates the communicator from other members of this global object, e.g global functions called from the C# side.

    // Initialize firebase from the C# side once, not everytime you attempt to get link.
    InitializeFirebase_Global : function()
    {
        const firebaseConfig = {
           apiKey: "",
           authDomain: "",
           databaseURL: "",
           projectId: "",
           storageBucket: "",
           messagingSenderId: "",
           appId: "",
           measurementId: ""
         };
        try
        {
            firebase.initializeApp(firebaseConfig);
            return true;
        }
        catch(err)
        {
            console.log("couldn't initialize firebase error" + err);
            return false;
        }

    }, // our comma again

    GetStorageDownloadURL_Global : function (requestID, namePtr, callback)
    {

         var storage = firebase.storage();
         // This was an error cause your parameter was named 'name' and you declared a var named 'name' in the same scope.
         var name = Pointer_stringify(namePtr);

         var storageRef = storage.refFromURL('gs://jwellery-data.appspot.com');

         // Start the async promise to get the link
         storageRef.child(name).getDownloadURL().then(function(url) 
         { 
             // link received, we're inside the callback function now.
             console.log(lengthBytesUTF8(url));

             // Send the link to the communicator to send it to a C# callback.
             // Pass the link and a null error.
             contextObject.NotifyCSharpAboutDownloadURLResult(requestID, url, null, callback);
         }).catch(function(error) 
         {
              // Handle any errors
              // Send the error to the communicator to send to a C# callback.
              // Pass a null link and and convert the error javascript object to a json string
              contextObject.NotifyCSharpAboutDownloadURLResult(requestID, null, JSON.stringify(error), callback);

         });

    }, // another comma, get in the habit of adding this for future members.


}; // end of the global object inside the javascript file.

autoAddDeps(firebaseStorage, '$contextObject'); // tell emscripten about this dependency, using the file name and communicator object name as parameters.
mergeInto(LibraryManager.library, firebaseStorage); // normal unity's merge into to merge this file into the build's javascript.

2)在C#端,有一个管理器来处理双方之间的下载URL请求流量.

2) On the C# side, have a manager that handles download url requests traffic between both sides.

    public static class FirebaseWebGLTrafficManager
    {
        // Must be same name and parameters as that in the jslib.
        // Note that only global functions are callable from here.
        [DllImport("__Internal")]
        private static extern bool InitializeFirebase_Global();

        [DllImport("__Internal")]
        private static extern void GetStorageDownloadURL_Global(int requestID, string namePtr, DownloadURLCSharpCallback callbackPtr);


        // This is the callback, whose pointer we'll send to javascript and is called by emscripten's Runtime.dynCall.
        public delegate void DownloadURLCSharpCallback(int requestID, string url, string error);

        /// <summary>
        /// Everytime a request is issued, give it the current id and increment this for next request.
        /// </summary>
        static int requestIDIncrementer = 0;

        /// <summary>
        /// Keeps track of pending callbacks by their id, once callback is received it is executed and removed from the book.
        /// </summary>
        static Dictionary<int, Action<string, string>> callbacksBook = new Dictionary<int, Action<string, string>>();


        /// <summary>
        /// Called from the javascript side, this is the function whose pointer we passed to <see cref="GetStorageDownloadURL_Global"/>
        /// This must match the return type and arguments of <see cref="DownloadURLCSharpCallback"/>
        /// </summary>
        /// <param name="requestID"></param>
        /// <param name="url"></param>
        /// <param name="error"></param>
        private static void GlobalCallback(int requestID, string url, string error)
        {
            if(callbacksBook.TryGetValue(requestID, out Action<string,string> callback))
            {
                callback?.Invoke(url, error);

            }
            // Remove this request from the tracker as it is done.
            callbacksBook.Remove(requestID);
        }

        /// <summary>
        /// Initializes firebase, on the javascript side.
        /// Call this once, or enough until it returns true.
        /// </summary>
        /// <returns>True if success, false if error occured.</returns>
        public static bool InitializeFirebase()
        {
            return InitializeFirebase_Global();
        }


        /// <summary>
        /// Gets a storage url using the javascript firebase sdk.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="onURLOrError">callback when the link is received or an error.</param>
        public static void GetDownloadUrl(string name, Action<string, string> onURLOrError)
        {
            int requestID = requestIDIncrementer;
            // For next request.
            requestIDIncrementer++;

            callbacksBook.Add(requestID, onURLOrError);

            // Now call the javascript function and when it is done it'll callback the C# GlobalCallback function.
            GetStorageDownloadURL_Global(requestID, name, GlobalCallback);
        }

    }

3)最后,您一直在等待的那一刻,如何在脚本中使用它

3) Finally, the moment you've been waiting for, how to use this in your script

public class JewelUIManager : MonoBehaviour
{
    private void Start()
    {
        bool initialized = FirebaseWebGLTrafficManager.InitializeFirebase();
        if(!initialized)
        {
            // attempt to retry if you want.
            Debug.LogError("Failed to initialize javascript firebase for some reason, check the browser console.");
        }

        LoadUI();

    }
    private IEnumerator DownloadImage(int index, string url, string error)
    {
        if(!string.IsNullOrEmpty(error))
        {
            Debug.LogError($"Failed to download jewel at index {index}");
            yield break;
        }
        if(string.IsNullOrEmpty(url))
        {
            Debug.LogError($"No errors occured but no url found either.");
            yield break;
        }
        WWW www = new WWW(url);
        //Debug.Log (jewelImagePath + jewelData.names [index] + ".png");
        yield return www;
        Sprite sprite = Sprite.Create(www.texture, new Rect(0.0f, 0.0f, www.texture.width, www.texture.height), new Vector2(0.5f, 0.0f));
        //jewelIcons.Add (sprite);
        prefab.transform.Find("JewelIcon").GetComponent<Image>().sprite = sprite;

        prefab.transform.SetParent(grid, false);
        prefab.AddComponent<IconOnClick>();


        IconOnClick ic = prefab.GetComponent<IconOnClick>();
        ic.ji = new JewelItem();


        ic.ji.name = jewelData.names[index];
        ic.ji.weight = jewelData.weight[index];
        ic.ji.price = jewelData.price[index];
        ic.ji.sku = jewelData.SKU[index];
        ic.ji.gameObject = prefab;
        if (jewelData.Type[index] == "1")
        {
            ic.ji.type = JewelType.Necklace;
        }
        else if (jewelData.Type[index] == "2")
        {
            ic.ji.type = JewelType.Tikka;
        }
        else if (jewelData.Type[index] == "3")
        {
            ic.ji.type = JewelType.EarRing;
        }
        ic.ji.sprite = sprite;
        ic.Assign();
        currentLoadedScroll.Add(prefab);
        totalIcons.Add(prefab);
        allJewels.Add(ic.ji);
    }
    private void LoadUI()
    {
        for (int i = 0; i < jewelData.names.Count; i++)
        {
            GameObject prefab = (GameObject)Instantiate(jewelContentPrefab) as GameObject;
            string name = jewelData.SKU[i] + ".png";
            // This is so C# captures the variable into the annonymous method, otherwise all callbacks will use the last 'i'
            int jewelIndex = i;
            FirebaseWebGLTrafficManager.GetDownloadUrl(name, (url, error)=>
            {
                StartCoroutine(DownloadImage(jewelIndex, url, error));
            });
        }
    }
}

关于统一的C#-Javascript交互的文献很少,这是使用firebase javascript sdk编写统一的webgl firebase sdk时数月反复试验的结果.

Complex C#-Javascript interactions in unity is poorly documented, this is the result of months of trial and error while writing my unity webgl firebase sdk using the firebase javascript sdk.

这篇关于试图让Jslib函数与C#函数UNITY 2017一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆