How to read compiled XML from Resources.Load at runtime?

I’ve got XML data stuffing some Options Menu variables into a File, then deserializing when I need to load it again - ie when i restart the application/editor.

It works great in the editor, but I can’t get it to work in the build. The problem seems to be that there is no longer a file that I can specifically target, but rather I have to dig with Resources.Load to get my data and I can’t seem to do that properly.

        private static void LoadXml()
        {
            if (Application.isEditor)
            {
                FileInfo theFile = new FileInfo(FileLocation + "\\" + FileName);
                if (!theFile.Exists) CreateXml();
                StreamReader sr = File.OpenText(FileLocation + "\\" + FileName);

                string info = sr.ReadToEnd();
                sr.Close();
                _dataAsString = info;
            }
            else
            {
                TextAsset binary = Resources.Load(FileName) as TextAsset;
                _dataAsString = binary.text;
            }
        }

This is where I’m loading data and I’m separating the Editor method from the Build method for the time being. Once I get the runtime version working I’ll probably just use that instead of targeting the file anyway.

The Logs say that I get null ref on TextAsset binary so either it fails to read or it never stores the OptionsData.xml file in Resources when it does the build. Anyone have any suggestions on how to do this? I’ve read a bunch of threads, but I can’t seem to find an answer to why doing this with Resources.Load() does not work and I don’t know of an alternative.

Thanks

I can’t tell from your script what exact path you’re saving to, but it looks like you try to load from Resources using only the filename, which means the final folder in your FileLocation path has to be “Resources”. Can you verify that that’s the case before we go any further?

EDIT: Also, try using Resources.Load(“FileName”); instead. It might seem like a small difference, but on top of being faster and more reliable, it keeps the function from returning any types that won’t match- as apposed to returning a possible match which is then going to have a conversion error when hit with the “as TextAsset”.

TextAsset binary = Resources.Load<TextAsset>("Resources\\" + FileName);

No change from doing this, or using full path, or using just FileName.

It doesn’t seem to work if I use the full path or the asset name only. When I make the build the Resources are compiled into a single file, so I can’t access them directly with the path anymore. When I am creating the XML file from the data struct then of course I use the full path to place it in the correct location in the hierarchy but accessing the data from the Build seems to be a different matter because its compiled into a binary file and the path (seems to be) useless.

That doesn’t actually answer the initial question about whether the final folder in the FileLocation variable was "Resources". Resources.Load literally requires that the file be inside of a directory called “Resources” (any directory called Resources), or directories within a “Resources” directory. Unless you’re saving it to that directory, or a directory within that directory, etc… then it won’t be able to find it regardless. Using the full path would only look for the full path starting with any directory in your Assets folder called “Resources”.

Ah, yes. Of course.

The Resource.Load actually works fine in the Editor.

        /// <summary> Serialized data options, string/int/float/bool etc. Anything that can be pushed to XML.</summary>
        public static OptionsData Data;
        /// <summary> References to objects in the hierarchy and such. Anything that cannot be pushed to XML.</summary>
        public static OptionsRefs Refs;

        public static string FileLocation = Application.dataPath + "\\Deftly\\Core\\Resources";
        public static string FileName = "OptionsData.txt";
        private static string _dataAsString;

Kk, just making sure. Someone had a problem like this a few weeks ago and it took like 14 back-and-forth posts to realize that they didn’t actually understand how the function was supposed to work, lol.

Remove the .txt from the FileName. Extensions are not only not required, but they screw everything up… I can’t believe I didn’t notice that right away >_>;

1 Like

I tried that per some post on Answers, but it doesn’t seem to change anything. Tried with both Path + FileName and just FileName.

Well, leave it off, because as long as you’re trying things they’re guaranteed not to work properly if you leave the extension there. As long as you use the Resources(path) or Resources(path, typeof(TextAsset)) and you’re sure the path is correct as a relative position from the nearest Resources folder, and it’s not finding it, then it’s just not there to find. That means you’re having a problem with your saving method rather than your loading method, but as I’ve always used the XmlDocument class in these situations I’m not sure I can be of much more help than that…

Hope you figure it out!

The file is in a Resources folder so Unity should be compiling it into the Resources.assets binary… I can only assume I’m trying to access it incorrectly.

Aha!

It turns out that you must save the file with the extension but Resources.Load() the file without the extension. It seems to have something to do with how TextAsset looks at the extension to see if it is a type that it accepts, when there is no file extension then it is null however Resources.Load() does not like the file extension (or some madness).

Declare your stuff

        public static string FileName = "OptionsData";
        public static string FileNameExt = ".xml"; // only used to save/create the xml file
        private static string _dataAsString;

Load from resouces.asset in the Build (after you created/saved an xml file in a Resources folder in the Editor. If you put it in another subfolder, you’ll have to add that in the path before FileName…)

            TextAsset binary = Resources.Load<TextAsset>(FileName);
            _dataAsString = binary.text;

When creating/saving the file as an asset you’ll use the extension, but when loading you omit it.

What a weird gotcha…

1 Like

C# Version!
This is for those people like myself - Who are searching google endlessly looking for a way to parse Xml data into a List of Objects. In my case I was trying to playback position and rotation data that had been recorded in Xml format as a list Object. The filename “moves” when recorded will be in the folder above your Assets folder. Once recorded you will need to Import and place into the Assets/Resources folder. The filename is actually moves.xml but as you may have read above the extension is not needed in Resources.Load().

I am building to Android in Unity 2018.3.5f1. I was trying to create a tutorial helper for my game Balloon Rescue which is live on google. So I recorded xml data to a file using the first script which is attachec to the GameObject that I wanted to record.

(Tip: You will need to populate the 'public GameObject fakeballoon;’ in the Inspector with a duplicate object of the one you are recording.)

Keys to control recording etc:
Record = r
Stop Recording and Play = e
If you want to redo the recording just press ‘r’ again and the old recording will be deleted and a new one will be made.

When you are happy with your recording:
Write to XML file = q
Once the file is written - stop unity playing (not pause - Stop) then press play again.

To Test and Play Xml Recording from file. = p

Writing Xml Object to File (Filename: FakeBalloonMovement.cs)
```csharp
**using System.Collections.Generic;

using UnityEngine;

using System.IO;

using System.Xml.Serialization;

public class FakeBalloonMovement : MonoBehaviour

{

List movements;

public GameObject fakeballoon;

public bool recording;

int i;

bool finished;

void Start()

{

movements = new List<FBPositionRotation>();

}

void Update(){

if (Input.GetKeyDown(KeyCode.R)) {

  movements = new List<FBPositionRotation>();

  recording = true;

}

if (Input.GetKeyDown(KeyCode.E)) {

  recording = false;

}

if (Input.GetKeyDown(KeyCode.Q)) {

  WriteFilex();

}

if (Input.GetKeyDown(KeyCode.P)) {

  ReadFilex();

}

}

void FixedUpdate()

{

if (recording) {

  Record();

} else {

  Playback();

}

}

void Record(){

FBPositionRotation tempFBPR = new FBPositionRotation();

tempFBPR.position = transform.position;

tempFBPR.rotation = transform.rotation;

movements.Add(tempFBPR);

}

void Playback(){

if (movements.Count > 0) {

  fakeballoon.transform.position = movements[i].position;

  fakeballoon.transform.rotation = movements[i].rotation;

  i++;

  if (i == movements.Count) {

    i = 0;

  }

}

}

void WriteFilex(){

XmlSerializer writer = new XmlSerializer(typeof(List<FBPositionRotation>));

StreamWriter wfile = new StreamWriter(@"moves.xml");

writer.Serialize(wfile, movements);

wfile.Close();

}

void ReadFilex(){

  XmlSerializer reader = new XmlSerializer(typeof(List<FBPositionRotation>));

  StreamReader file = new StreamReader(@"moves.xml");

  movements = (List<FBPositionRotation>)reader.Deserialize(file);

  file.Close();

  Playback();

}

}

public class FBPositionRotation

{

public Vector3 position { get; set; }

public Quaternion rotation { get; set; }

}**
```

=====================================================================================
Once the xml file has been made you can remove the above script from the recorded object and place the scipt below onto the duplicate Object that was called with 'public GameObject fakeballoon;’
You will need to import the moves.xml into Unity and place it in a folder called ‘Resources’ as one of your assets folders. Playback is automatic.

Reading Xml Object from File (Filename: FBMoves.cs)

using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Xml;
using System.Text;
using System.Xml.Serialization;

public class FBMoves : MonoBehaviour
{
  List<FBPositionRotation> movements;
  TextAsset ta;
  bool readyToRoll;
  int i;

  void Start()
  {
    ReadFilex();
  }

  void FixedUpdate(){
    if (readyToRoll) {
      Playback();
    }
  }

  void Playback(){
    if (movements.Count > 0) {
      transform.position = movements[i].position;
      transform.rotation = movements[i].rotation;
      i++;
      if (i == movements.Count) {
        i = 0;
      }
    }
  }

  void ReadFilex(){
    ta = Resources.Load("moves") as TextAsset;
    movements = new List<FBPositionRotation>();
    XmlSerializer reader = new XmlSerializer(typeof(List<FBPositionRotation>));
    byte[] byteArray = Encoding.UTF8.GetBytes(ta.text);
    MemoryStream stream = new MemoryStream(byteArray);
    movements = (List<FBPositionRotation>)reader.Deserialize(stream);
    readyToRoll = true;
    }
}

public class FBPositionRotation
{
  public Vector3 position { get; set; }
  public Quaternion rotation { get; set; }
}