Hi I have written a singleton script called DownloadImage which can be used by any class to download images from web. There is a scene with several images with an attached script WebImage. Webimage internally calls this DownloadImage Singleton to download the image and update its image. Webimage has url field in which we can pass in the image link.
This system works fine if url is fixed. However if if I change url for ecah image component in scene images are not downloaded properly or I get error saying Malfoemed url. What might be the issue here and how to solve this?
Below is singleton Download script, and this script is attached to empty gameobject component.
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using UnityEngine.UI;
public class DownloadImage : MonoBehaviour
{
public static DownloadImage instance;
[SerializeField] public string url;
[SerializeField] public Texture2D texture;
[SerializeField] public bool isCached;
private void Awake()
{
if (instance != null)
{
Destroy(this.gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this.gameObject);
}
void Start()
{
StartCoroutine(GetImageFromWeb(url));
}
public IEnumerator GetImageFromWeb(string refURL)
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(refURL);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError ||
File.Exists(Application.persistentDataPath "Image.jpg"))
{
Debug.Log("-------------------------" request.error);
byte[] byteArray = File.ReadAllBytes(Application.persistentDataPath "Image.jpg");
Texture2D tempText = new Texture2D(60, 90);
tempText.LoadImage(byteArray);
texture = tempText;
}
else
{
texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
if (isCached)
{
SaveDownloadedTexture(texture);
}
}
}
public void SaveDownloadedTexture(Texture2D refTexture)
{
bool clearCache;
byte[] byteArr = refTexture.EncodeToJPG();
File.WriteAllBytes(Application.persistentDataPath "Image.jpg", byteArr);
clearCache = Caching.ClearCache();
if (clearCache)
{
Debug.Log("Image deleted");
}
}
}
Below is script WebImage script which is attache on each of image component in scene that takes url and passes it to singleton.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class WebImage : MonoBehaviour
{
[SerializeField] public string imageURL;
[SerializeField] public bool cacheData;
void Start()
{
this.gameObject.AddComponent<RawImage>();
DownloadImage.instance.url = imageURL;
DownloadImage.instance.isCached = cacheData;
}
void Update()
{
this.gameObject.GetComponent<RawImage>().texture = DownloadImage.instance.texture;
}
}
CodePudding user response:
Besides the comments I made already timing can be a huge issue here!
You are starting the download right away in DownloadImage.Start
.
But there is no guarantee that your WebImage.Start
is actually executed before that => maybe the URL is even simply ""
.
Also check your local file path! By simply using
what you get is a path like e.g.
some/local/filePathImage.jpg
what you want would rather be
some/local/filePath/Image.jpg
So for system paths always use Path.Combine
which inserts the correct path separator depending on the OS the app is running on.
And then you probably would also not want to again and again overwrite the very same local file Image.jpg
but rather have unique names (for now I will assume based on the last part of the Url)
As said I would simply rather merge these to together into a single component and do something like
public class LoadedImage : MonoBehaviour
{
[SerializeField] private Image image;
[SerializeField] private string url;
[SerializeField] private bool isCached;
// Your path was also invalid!
// by simply using you end up with a path like e.g.
// some/local/filePathImage.jpg
// what you rather want is
// some/local/filePath/Image.jpg
// And then still - if you have multiple instancesthey will all overwrite the same local file
// I would simply use the url again
private string localPath = Path.Combine(Application.persistentDataPath, url.Split('/').Last());
// Unity will automatically run this as a Coroutine
private IEnumerator Start()
{
Texture2D texture;
// First of all why is the local file an error?
// I would rather expect it to be the default case and only trigger a download
// if it DOESN'T exist
// So as first option go for local cached file
if(File.Exists(localPath))
{
byte[] byteArray = File.ReadAllBytes(localPath);
texture = new Texture2D(1,1);
texture.LoadImage(byteArray);
}
// as fallback download
else
{
using(var request = UnityWebRequestTexture.GetTexture(refURL))
{
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.LogError("-------------------------" request.error);
return;
}
texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
if (isCached)
{
SaveDownloadedTexture(texture);
}
}
}
if(texture != null)
{
// Finally use Image which is way more performant than RawImage which adds DrawCalls to your UI
image.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f);
}
}
public void SaveDownloadedTexture(Texture2D refTexture)
{
var byteArr = refTexture.EncodeToJPG();
File.WriteAllBytes(localPath, byteArr);
var clearCache = Caching.ClearCache();
if (clearCache)
{
Debug.Log("Image deleted");
}
}
}
There is no need for a singleton. Just have this attached to your image components, configure them and you should be fine.