Search Unity

Possible to import custom user textures from file system at runtime?

Discussion in 'Scripting' started by TheLurkingDev, Sep 1, 2014.

  1. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    I would like to allow users to drop image files into specific folders within their file system (i.e., within My Documents/SubFolderName in Windows) and then import these files for use as textures at runtime. Is the only option to use the WWW class for this?
     
  2. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
  3. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    Hmm... I must be doing something slightly wrong after referencing those pages. Here is the quick and dirty code I have trying to load a single image so far:

    Code (csharp):
    1.  
    2. void GetGameTileTextures()
    3.     {
    4.         string filePath = _gameTileTexturesPath + "Grass.jpg";
    5.         Debug.Log("filePath: " + filePath);
    6.         Byte[] byteFile = File.ReadAllBytes(filePath);
    7.  
    8.         var Tex = new Texture2D(4, 4);
    9.         Tex.LoadImage(byteFile);
    10.         Debug.Log("Name: " + Tex.name);
    11.     }
    12.  
    There are no exceptions being thrown, but when I try to display Tex.name, nothing is showing up. Any idea what I borked?
     
    Last edited: Sep 1, 2014
  4. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    Ok, I have made it a little further:

    Code (CSharp):
    1. void GetGameTileTextures()
    2.     {
    3.         string filePath = _gameTileTexturesPath + "Grass.jpg.bytes";
    4.         var imageTextAsset = new TextAsset();
    5.         imageTextAsset = System.Text.Encoding.Default.GetString(File.ReadAllBytes(filePath));
    6.         //Byte[] byteFile = File.ReadAllBytes(filePath);
    7.  
    8.         var Tex = new Texture2D(256, 256);
    9.         Tex.LoadImage(imageTextAsset.bytes);
    10.         Debug.Log("Name: " + Tex.name);
    11.     }

    Unfortunately, I have just read that a TextAsset represents a Unity asset that must be compiled. Therefore, it is impossible to create this at runtime. Is this correct or has anyone gotten something like this to work?
     
  5. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
    Are you sure that the Texture is still empty? Try making it a public variable and check in the inspector. I'm not sure what you'd expect from the name property of a new texture.

    How is _gamesTileTexturesPath being set? Application.dataPath is LOCAL to the project file and doesn't return a useable file path without modification.
     
  6. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    _gameTileTexturesPath is being set using the .net SpecialFolder enum off the Environment class. I don't have Unity open at the moment, but when I copied the path that is output to the debug log in unity to the address bar of a file system window it takes me directly to the file, which is located in the user's Documents directory.

    Additionally, I tried changing the extension of the file to .bytes but that did not work either.

    As far as the name property, I don't recall precisely what that returns at the moment but I do know it returned something in the past when I loaded from a Resource directory within the project (I have tried to solve this problem before and gave up - you can see my old posts here regarding the same issue some time ago).
     
  7. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
    I'm still thinking the Texture2D is actually working correctly, but since you're giving it only a byte array, it can't interpret a name for you like it could for a Resource.Load() method. Make Tex a public global variable and check it in the inspector after running the game.

    Edit: Use the first method, but move the Tex to a public global variable.
     
    TheLurkingDev likes this.
  8. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    Thank you very much for taking the time to help me. Looks like you are absolutely correct. Thank you for the advice. When I set the Texture2D variable to a public property I can see a very small version of the image in the Inspector.

    So now if I can just assign these textures to prefabs in-game. Have you done this also? Is it fairly straight-forward?
     
  9. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
    Well, its easy to apply the texture to a GameObject.

    Code (CSharp):
    1. gameObject.GetComponent<Renderer>().material.mainTexture = loadedTexture;
     
    TheLurkingDev likes this.
  10. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    Ha. My apologies for the useless question then. I suppose trying to solve this problem had me a bit anxious.

    Thanks a mint for your help.
     
  11. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    Ok, so now back to the same problem as the last time I dealt with this. After successfully getting the code above to work, if I now try to load the Texture2D instances into a List<Texture2D> I am receiving null reference exception on the Add attempt.

    Code (CSharp):
    1. void GetTextures()
    2.     {
    3.         foreach(string fileName in _gameTileTexturesFileNames)
    4.         {
    5.             string filePath = _gameTileTexturesPath + fileName;
    6.             Debug.Log("filePath: " + filePath);
    7.             Byte[] byteFile = File.ReadAllBytes(filePath);
    8.             Tex = new Texture2D(256, 256);
    9.             Tex.LoadImage(byteFile);
    10.             _gameTileTextures.Add(Tex);
    11.         }
    12. }
    The filepaths are absolutely correct and, if I only load one file into Tex and check it in the Inspector at runtime, I can see a thumbnail of the texture image.
     
  12. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
    Has _gameTileTextures been created?

    Code (CSharp):
    1. _gameTileTextures = new List<Texture2D>();
     
  13. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    Yes, it is defined as a private field within the class:

    Code (CSharp):
    1. private List<Texture2D> _gameTileTextures;
    And the List is instantiated at Start():

    Code (CSharp):
    1. _gameTileTextures = new List<Texture2D>();
    However, I *think* I finally have a solution to the problem. I don't think the images were fully loaded before the Add method was called on the List<Texture2D>. So I have set the call to Texture2D.LoadImage to be run in a coroutine:

    Code (CSharp):
    1. IEnumerator GetTextures()  // Coroutine.
    2.     {
    3.         foreach(string fileName in _gameTileTexturesFileNames)
    4.         {
    5.             string filePath = _gameTileTexturesPath + fileName;
    6.             Debug.Log("filePath: " + filePath);
    7.             Byte[] byteFile = File.ReadAllBytes(filePath);
    8.             Tex = new Texture2D(256, 256);
    9.             yield return Tex.LoadImage(byteFile);
    10.             _gameTileTextures.Add(Tex);
    11.         }
    12. }
    At this point I still have to assign them as textures to gameobjects, but hopefully this problem is solved.
     
  14. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
    I don't think Texture2D.LoadImage is synchronous, at least it doesn't mention it being so anywhere in the documentation. Try this instead.

    Code (CSharp):
    1. Tex.Apply();
    2. //use/add this texture now
     
  15. TheLurkingDev

    TheLurkingDev

    Joined:
    Nov 16, 2009
    Posts:
    91
    No, this does not work either. I still receive the null reference exception upon performing the Add to List<Texture2D>.

    Only after calling the LoadImage in a coroutine am I able to prevent this exception so far.