- Home /
How to get UnityWebRequest to work for downloading JSON
Hello,
I am trying to download a simple JSON file that is stored in the Streaming Assets Folder in my WEBGL Build. The problem is that I have to wait for the download to finish, since this data is neccessary for starting the game. I've tried many methods of loading the file via UnityWebRequest in a WebGL Development Build, but nothing seems to work - it's just loading endlessly, without any error message. The UnityWebRequest.progress that is logged to console is always 0.
Notes: Loading the file in the editor via the File class works fine, so the JSON file is correct. The URL I use is logged to console and if I try to open it in the browser, it shows me the right file, so the URL is correct too.
The approaches I've tried so far are:
1. Waiting for UnityWebRequest.isDone to be true
public string Load (string streamingAssetLocalPath)
{
string filePath = Path.Combine (Application.streamingAssetsPath, streamingAssetLocalPath);
UnityWebRequest request = GetRequest (filePath);
return request.downloadHandler.text;
}
private UnityWebRequest GetRequest (string filePath)
{
UnityWebRequest request = UnityWebRequest.Get (filePath);
while (!request.isDone && (request.error == null || request.error.Equals (""))) {
Debug.Log (request.downloadProgress + " - " + request.downloadedBytes);
}
if (request.error != null && !request.error.Equals ("")) {
Debug.LogError ("Request error: " + request.error);
}
return request;
}
2. Waiting for DownloadHandler.isDone to be true Same as above, but with !request.downloadHandler.isDone
instead of !request.isDone
.
3. Waiting for UnityWebRequestAsyncOperation.isDone to be true Same as above, but saving the result of request.SendWebRequest ()
to an UnityWebRequestAsyncOperation and checking for !asyncOperation.isDone
.
4. Yielding SendWebRequest As far as I know I cannot log the progress with this approach, but this method fails, too: After some time the console logs an error that the script timed out.
private DownloadHandler downloadHandler;
public string Load (string streamingAssetLocalPath)
{
string filePath = Path.Combine (Application.streamingAssetsPath, streamingAssetLocalPath);
StartCoroutine (SendRequest (filePath));
while (downloadHandler == null) {
Debug.Log ("waiting for " + filePath);
}
string data = downloadHandler.text;
downloadHandler = null;
return data;
}
private IEnumerator SendRequest (string url)
{
using (UnityWebRequest request = UnityWebRequest.Get (url)) {
yield return request.SendWebRequest ();
if (request.isNetworkError || request.isHttpError) {
Debug.LogError ("Request Error: " + request.error);
} else {
downloadHandler = request.downloadHandler;
}
}
}
I have absolutely no idea what I am doing wrong, has anybody had similar problems? Am I overseeing something? Or do I have do use a completely different approach? Any help is appreciated.
Answer by Bunny83 · May 04, 2018 at 12:15 PM
You can't load a resource synchronously in WebGL as explained here. To quote:
Do not block on WWW or WebRequest downloads
Do not use code which blocks on a WWW or WebRequest download, like this:
while(!www.isDone) {}
Blocking on WWW or WebRequest downloads does not work on Unity WebGL. Because WebGL is single threaded, and because the XMLHttpRequest class in JavaScript is asynchronous, your download never finishes unless you return control to the browser; instead, your content deadlocks. Instead, use a Coroutine and a yield statement to wait for the download to finish.
Your only option is to use a coroutine and wait for the download to finish. If you require this resource to continue you have to delay the subsequent actions until the download is finished. This is usually done by a loading scene which wil switch to the actual scene once ready.
Thanks for the answer, now I know at least why nothing happens at all while downloading. The problem is that I have to wait for the Coroutine to finish before I can start the game. And waiting for a Coroutine to finish with yield can only be done inside a Coroutine, am I right? Which means my whole game initialization has to be inside a Coroutine and start the game from inside the Coroutine in the end. That would mean rebuilding most of my code to Coroutines, since almost everything in my game is created dynamically. Is there really no other way?
And why can't you just use a loading scene? If you create everything dynamically, where is your actual kick-off point? Or do you use many scene objects and have them initialize themselfs. This would not be "everything is created dynamically" ^^. Since you prefer creating things dynamically don't you use some prefabs which simply get instantiated when ready? $$anonymous$$eep in $$anonymous$$d that you can load scenes additively. Though without more details it's difficult to suggest any workarounds.
Well every GameObject that is shown in the scene is created with prefabs and scripts that require data loaded from Strea$$anonymous$$gAssets or a web server, and I would like to only enable the player to actually play when everything's loaded (since the game is rather small, so I figured it would be okay to wait until everything is loaded).
I rebuild my initializing scripts so that I can use Coroutines to download the JSON data and now it works. It was a lot easier than I thought it would be (which was why I searched for another way). Thank you so much for your help! I never found the link you provided although I searched for a long time.