I am downloading some sprites from my server and store them in Application.persistentDataPath.
However, I cannot load the controller using Resources.Load (controllerPath) because the path is outside the Resources folder.
Additionally, I get a MissingComponentException when I try to add the animation controller to the GameObject.
Here is my code:
private GameObject SideSprite;
// ...
string controllerPath = Application.persistentDataPath+"/"+aux+"/"+aux+"Controller";
controller = (RuntimeAnimatorController)Resources.Load (controllerPath); // Returns null
// Below I get:
// MissingComponentException: There is no 'Animator' attached to the
// "Missing Prefab (Dummy)" game object, but a script is trying to access it.
SideSprite.GetComponent<Animator> ().runtimeAnimatorController = controller;
How should I load the resources from the persistent data path?
persistentDataPath is used as any regular folder. I would not store a Sprite but more likely a texture and next time you need it you unroll the process of applying a texture to a sprite:
public static void StoreCacheSprite(string url, Sprite sprite)
{
if(sprite == null || string.IsNullOrEmpty(url) == true) { return; }
SpriteRenderer spRend = sprite.GetComponent<SpriteRenderer>();
Texture2D tex = spRend.material.mainTexture;
byte[] bytes = tex.EncodeToPNG();
string path = Path.Combine(Application.persistentDataPath, url);
File.WriteAllBytes(Application.persistentDataPath, bytes);
}
public static Sprite GetCacheSprite(string url)
{
if( string.IsNullOrEmpty(url) == true) { return; }
string path = Path.Combine(Application.persistentDataPath, url);
if(File.Exists(path) == true)
{
bytes = File.ReadAllBytes(path);
Texture2D texture = new Texture2D(4, 4, TextureFormat.RGBA32, false);
texture.LoadImage(bytes);
Sprite sp = Sprite.Create(texture, new Rect(0,0 texture.width, texture.height, new Vector2(0.5f,0.5f));
return sp;
}
return null;
}
The first method stores the texture using the File class from .NET. It converts and writes a byte array onto the ROM of the device (File.WriteAllBytes). You need a path to a Sprite and a name for it. That name needs to comply with file and folder path naming.
The second method does the inverse process, checking if it is already stored and turning the byte array found on the RAM into a usable Sprite.
You could also simply use WWW to get the data.
string controllerPath = Application.persistentDataPath+"/"+aux+"/"+aux+"Controller";
IEnumerator Start ()
{
WWW www = new WWW("file:///" + controllerPath);
yield return www;
Debug.Log(www.texture); //or www.bytes
}
Related
In my Unity2D project, I made a prefab contains RawImage gameObject to rendering thumbnail images.
Here is the structure of the prefab:
I'm using two methods to load images from web sources with URLs, and rendering to the Texture property of RawImage.
void ReadData()
{
foreach (GameObject thumbnailGrouping in thumbnailList)
{
Destroy(thumbnailGrouping.gameObject);
}
thumbnailList.Clear();
GroupingConfigration.Page.PageResponse people = webConfig.page.pageResponse;
TextMeshProUGUI title = thumbnailGrouping.GetComponentInChildren<TextMeshProUGUI>();
title.text = people.collection.name;
// Store the thumbnails urls
List<string> thumbnails = new List<string>();
foreach (var documentNode in people.classifier.classifierNode[0].documentNode)
{
string content = documentNode.metadataList.metadata[0].content;
int startIndex = content.IndexOf("src=\"") + "src=\"".Length;
int length = content.IndexOf("\"", startIndex) - startIndex;
string url = content.Substring(startIndex, length);
thumbnails.Add("https://cs-turns-50.interactwith.us" + url);
}
GameObject grouping = Instantiate(thumbnailGrouping);
grouping.name = title.text;
grouping.transform.SetParent(GameObject.Find("Canvas/FloatingMedias/ThumbnailView").transform, false);
RawImage[] rawImages = grouping.transform.GetComponentsInChildren<RawImage>(true);
// Set the texture of each RawImage
for (int i = 0; i < rawImages.Length; i++)
{
StartCoroutine(LoadImage(thumbnails[i], rawImages[i]));
}
}
IEnumerator LoadImage(string url, RawImage rawImage)
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.Log(request.error);
}
else
{
Texture2D texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
rawImage.texture = texture;
Debug.Log("Width: " + rawImage.texture.width + ", Height: " + rawImage.texture.height);
}
}
However, I caught the errors from last debug: "UnassignedReferenceException: The variable m_Texture of RawImage has not been assigned.
You probably need to assign the m_Texture variable of the RawImage script in the inspector." which because the texture is null.
I have checked it got the correct image URL with "gif" extension. the parent object of rawImage[i] is ThumbnailGrouping. the rawImage[i] itself is the RawImage component, it contains the properties of a common RawImage component, and all correct except the Texture value, which is null.
Why does this "null" problem happen? and how do I fix it?
Its a VN style game with user generated content and I need to load the image without delay.
Due to it being user generated content the images will be siting in a folder with the game.
Due to the same reason I cant preload the images, since I cant know which will be the next image to load.
I have tried UnityWebRequest, WWW or File.ReadAllBytes and all have more delay than I expected, even thou Im running it on a SSD.
Is there a faster way?
The code im using for testing the loading time of the images
using UnityEngine;
using System.IO;
using UnityEngine.Networking;
using System.Collections;
using UnityEngine.UI;
using System.Threading.Tasks;
/// <summary>
/// 2020/19/05 -- Unity 2019.3.3f1 -- C#
/// </summary>
public class itemCreatorImageLoad : MonoBehaviour
{
public Image image; // this is referencing to a UI Panel
private Texture2D texture2D;
private UnityWebRequest uwr;
public RawImage rawImage; // this is referencing to a UI rawImage
// path = #"C:\UnityTests\Referencing\Referencing\Assets\StreamingAssets\Items\image.png"
// the # handles the / and \ conventions that seem to come from the program using paths for the web
// C:/.../.../... web
// C:\...\...\... pc
public void LoadImageWWW(string path)
{
if (texture2D)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
texture2D = new Texture2D(1, 1);
texture2D = new WWW(path).textureNonReadable as Texture2D;
image.sprite = Sprite.Create(texture2D, new Rect(0.0f, 0.0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
public void LoadImageWWWv2(string path)
{
// "http://url/image.jpg"
StartCoroutine(setImage(path));
}
IEnumerator setImage(string url) // this comes from https://stackoverflow.com/questions/31765518/how-to-load-an-image-from-url-with-unity
{
Texture2D texture = image.canvasRenderer.GetMaterial().mainTexture as Texture2D;
WWW www = new WWW(url);
yield return www;
// calling this function with StartCoroutine solves the problem
Debug.Log("Why on earh is this never called?");
www.LoadImageIntoTexture(texture);
www.Dispose();
www = null;
}
public void LoadImageReadAllBytes(string path)
{
byte[] pngBytes = File.ReadAllBytes(path);
if (texture2D)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
texture2D = new Texture2D(1, 1);
texture2D.LoadImage(pngBytes);
image.sprite = Sprite.Create(texture2D as Texture2D, new Rect(0.0f, 0.0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
public void LoadImageUnityWebRequest(string path)
{
StartCoroutine(LoadImageCorroutine());
IEnumerator LoadImageCorroutine()
{
using (uwr = UnityWebRequestTexture.GetTexture(#path))
{
yield return uwr.SendWebRequest();
// I would always check for errors first
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Could not load texture do to {uwr.responseCode} - \"{uwr.error}\"", this);
yield break;
}
// Destroy the current texture instance
if (rawImage.texture)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
rawImage.texture = DownloadHandlerTexture.GetContent(uwr);
image.sprite = Sprite.Create(rawImage.texture as Texture2D, new Rect(0.0f, 0.0f, rawImage.texture.width, rawImage.texture.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
StopCoroutine(LoadImageCorroutine());
}
}
public void LoadImageUnityWebRequestv2(string path)
{
StartCoroutine(LoadImageUnityWebRequestv2Coroutine(path));
}
IEnumerator LoadImageUnityWebRequestv2Coroutine(string MediaUrl) // this comes from https://stackoverflow.com/questions/31765518/how-to-load-an-image-from-url-with-unity
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.Log(request.error);
}
else
{
rawImage.texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
}
}
// a async version that I grabbed from somewhere, but I dont remember where anymore
[SerializeField] string _imageUrl;
[SerializeField] Material _material;
public async void MyFunction()
{
Texture2D texture = await GetRemoteTexture(_imageUrl);
_material.mainTexture = texture;
}
public static async Task<Texture2D> GetRemoteTexture(string url)
{
using (UnityWebRequest www = UnityWebRequestTexture.GetTexture(url))
{
//begin requenst:
var asyncOp = www.SendWebRequest();
//await until it's done:
while (asyncOp.isDone == false)
{
await Task.Delay(1000 / 30);//30 hertz
}
//read results:
if (www.isNetworkError || www.isHttpError)
{
//log error:
#if DEBUG
Debug.Log($"{ www.error }, URL:{ www.url }");
#endif
//nothing to return on error:
return null;
}
else
{
//return valid results:
return DownloadHandlerTexture.GetContent(www);
}
}
}
}
I'm trying to write a program in unity. I'm quite new to unity, so i don't know much.i want to be able to make a new material in my code with only a texture2d to pass into it. So far im trying this:
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
public class Search : MonoBehaviour
{
public void SearchEnter()
{
MyObject.GetComponent<Image>().material = LoadImage(name.ToLower());
}
Texture2D LoadImage(string ImageName)
{
string url = "file:///C:/MyFilePath" + ImageName + ".jpg";
Texture2D tex;
tex = new Texture2D(4, 4, TextureFormat.DXT1, false);
using (WWW www = new WWW(url))
{
www.LoadImageIntoTexture(tex);
GetComponent<Renderer>().material.mainTexture = tex;
}
return tex;
}
}
I cannot do this as it cannot convert from texture2d to material.
Can anyone help me?
First of all you are not waiting until the WWW request finishes so you are trying to access the result to early.
Instead of the WWW you should rather use a UnityWebRequestTexture.GetTexture in a Coroutine like
public class Search : MonoBehaviour
{
public void SearchEnter()
{
StartCoroutine(LoadImage(name.ToLower()))
}
IEnumerator LoadImage(string ImageName)
{
using (UnityWebRequest uwr = UnityWebRequestTexture.GetTexture("file:///C:/MyFilePath" + ImageName + ".jpg"))
{
yield return uwr.SendWebRequest();
if (uwr.isNetworkError || uwr.isHttpError)
{
Debug.Log(uwr.error);
}
else
{
// Get downloaded texture
var texture = DownloadHandlerTexture.GetContent(uwr);
//TODO now use it
...
}
}
}
}
The second issue: You can not simply convert a Texture2D to Material. You rather have to either create a new Material from a given shader or a given Material using new Material() e.g. using Shader.Find like
//TODO now use it
// ofcourse again you can not simply create a new material from a texture
// you would rather need to pass in a shader like
var material = new Material(Shader.Find("UI/Default"));
material.mainTexture = texture;
MyObject.GetComponent<Image>().material = material;
GetComponent<Renderer>().material.mainTexture = texture;
However, for an Image you would actually probably rather simply stick with the default UI material but use a Sprite instead
MyObject.GetComponent<Image>().sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f);
I have a png image inside of my StreamingAssets folder. When I try to retrieve the image, it returns an image with a red question mark (this is not the right image).
I am using this script:
public string url;
IEnumerator Start()
{
url = Application.dataPath + "/StreamingAssets/shareImage.png";
using (WWW www = new WWW(url))
{
yield return www;
Renderer renderer = GetComponent<Renderer>();
renderer.material.mainTexture = www.texture;
}
}
What should I do the get the right image?
Location of the image:
The Inspector tab of the image I want to change:
While reading files in the StreamingAssets folder, you have to use WWW on some platforms or File.ReadAllBytes some others. You check if the path contains :// or :/// then determine which one to use. This should solve the question mark issue. Note that you should now be using UnityWebRequest where WWW is required unless there is a bug with UnityWebRequest.
Another issue that will arise is the SpriteRenderer image not being upadte. The material will reflect the images changes but the SpriteRenderer will not. The proper way to change a sprite of a SpriteRenderer is to change the SpriteRenderer.sprite property. This means that you will have to Convert the Texture2D to Sprite then assign that sprite to the SpriteRenderer.sprite property.
Finally, use Application.streamingAssetsPath when accessing files in the StreamingAssets folder. This is not the issue now since you're running it in the Editor but will be in a build.
Below is what your code for reading image file in the StreamingAssets should look like:
using UnityEngine.Networking;
public string url;
IEnumerator Start()
{
///url = Application.dataPath + "/StreamingAssets/shareImage.png";
url = Path.Combine(Application.streamingAssetsPath, "shareImage.png");
byte[] imgData;
Texture2D tex = new Texture2D(2, 2);
//Check if we should use UnityWebRequest or File.ReadAllBytes
if (url.Contains("://") || url.Contains(":///"))
{
UnityWebRequest www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();
imgData = www.downloadHandler.data;
}
else
{
imgData = File.ReadAllBytes(url);
}
Debug.Log(imgData.Length);
//Load raw Data into Texture2D
tex.LoadImage(imgData);
//Convert Texture2D to Sprite
Vector2 pivot = new Vector2(0.5f, 0.5f);
Sprite sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), pivot, 100.0f);
//Apply Sprite to SpriteRenderer
SpriteRenderer renderer = GetComponent<SpriteRenderer>();
renderer.sprite = sprite;
}
I found a tutorial on YouTube that accurately added File Explorer and image upload to a 'RawImage' on a canvas using Unity 2017.3.1f1.
What I'm trying to do is add the same image after 'button press' to a 3D object like a cube or plane as shown by the colored cube. When I run the below code, it registers as being present on the cube but doesn't render. Any help is appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
public class Explorer : MonoBehaviour
{
string path;
public RawImage image;
public void OpenExplorer()
{
path = EditorUtility.OpenFilePanel("Overwrite with png", "", "png");
GetImage();
}
void GetImage()
{
if (path != null)
{
UpdateImage();
}
}
void UpdateImage()
{
WWW www = new WWW("file:///" + path);
image.texture = www.texture;
}
}
There is a tiny bug in your code. It should work sometimes and fail other times. The chances of it working or not depends on the size of the image. It will work if the image is really small but fail when it is a large image.
The reason for this is because of the code in your UpdateImage function. The WWW is supposed to be used in a coroutine function because you need to yield or wait for it to finish loading or downloading the file before accessing the texture with www.texture. Your are not doing this now. Change it to a coroutine function then yield it and it should work fine,.
void GetImage()
{
if (path != null)
{
StartCoroutine(UpdateImage());
}
}
IEnumerator UpdateImage()
{
WWW www = new WWW("file:///" + path);
yield return www;
image.texture = www.texture;
}
If some reason you can't use a coroutine because it's an Editor plugin then forget about the WWW API and use use File.ReadAllBytes to read the image.
void GetImage()
{
if (path != null)
{
UpdateImage();
}
}
void UpdateImage()
{
byte[] imgByte = File.ReadAllBytes(path);
Texture2D texture = new Texture2D(2, 2);
texture.LoadImage(imgByte);
image.texture = texture;
}
To assign the image to a 3D Object, get the MeshRenderer then set the texture to the mainTexture of the material the renderer is using:
//Drag the 3D Object here
public MeshRenderer mRenderer;
void UpdateImage()
{
byte[] imgByte = File.ReadAllBytes(path);
Texture2D texture = new Texture2D(2, 2);
texture.LoadImage(imgByte);
mRenderer.material.mainTexture = texture;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using System.IO;
public class Explorer : MonoBehaviour
{
string path;
public MeshRenderer mRenderer;
public void OpenExplorer()
{
path = EditorUtility.OpenFilePanel("Overwrite with png", "", "png");
GetImage();
}
void GetImage()
{
if (path != null)
{
UpdateImage();
}
}
void UpdateImage()
{
byte[] imgByte = File.ReadAllBytes(path);
Texture2D texture = new Texture2D (2, 2);
texture.LoadImage(imgByte);
mRenderer.material.mainTexture = texture;
//WWW www = new WWW("file:///" + path);
//yield return www;
//image.texture = texture;
}
}