问题描述
我正在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一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!