Unity’s rapid edit-compile-test cycle encourages developers to build game levels interactively in the editor. Game parameters are often saved directly in the scene, or on prefabs. However, sometimes it makes sense to read that data from a file at run-time.
For example: Pawns puzzles are defined by a simple arrangement of game elements on a grid. By putting this information in a text file, I was able to try changes to the puzzles without leaving the game. Even better, by giving public access to the file I have the option to allow other people to mod the game (i.e. make their own puzzles) even if they don’t have Unity or the Pawns source code!
Below I post the code for the two techniques I used to read text files into Pawns. I also cover some of the differences to be aware of when loading data on different platforms.
Method 1: Read a text file embedded in the game’s resources
The simplest technique is to read a file that Unity has compiled into your game, just as it does for the other game assets like textures, sounds, and models. This is simple because Unity encrypts the file into your game bundle automatically. However the file is not visible to would-be-level editors, so you lose some of the advantages I talked about earlier. But if you want to avoid exposing the file’s contents to the public in a shipping game, this may be just the thing.
Place the textfile (in this example, “puzzles.txt”) in the Resources folder of your Unity project. Unity will automatically package it into your game. To read it, your script just uses Unity’s built-in Resources class:
FileInfo theSourceFile = null;
StringReader reader = null;Â
TextAsset puzdata = (TextAsset)Resources.Load("puzzles", typeof(TextAsset));
// puzdata.text is a string containing the whole file. To read it line-by-line:
reader = new StringReader(puzdata.text);
if ( reader == null )
{
Debug.Log("puzzles.txt not found or not readable");
}
else
{
// Read each line from the file
while ( (string txt = reader.ReadLine()) != null )
Debug.Log("-->" + txt);
}
Method 2: Read unencrypted text file bundled with the game
This example reads text from a file that sits alongside other files of your compiled game. Anyone can see and edit the file, which makes it convenient for making changes without having to rebuild your game. The complication is that you have to remember to include the file with your game, either by manually adding it after every build, or by creating a Unity build post-processing script to do it for you.
The following example reads a file called “puzzles.txt” from the game’s data folder as pointed to by Application.dataPath. This location varies depending on where your game is running:
Where game is running | Application.dataPath |
---|---|
In the Unity editor | project’s Assets folder |
Mac build | ‘Contents’ folder in the package (right-click on the game, select “View Package Contents”) |
Windows build | Data subfolder |
iPhone app | In app’s writeable Documents directory; see comments below |
Webplayer build | URL to webplayer’s data folder; use WWW class to access |
(Since Application.dataPath returns a URL for webplayers, the following code will probably not work for them. You would have to use the WWW class instead.)
FileInfo theSourceFile = null;
StreamReader reader = null;
theSourceFile = new FileInfo (Application.dataPath + "/puzzles.txt");
if ( theSourceFile != null && theSourceFile.Exists )
reader = theSourceFile.OpenText();
if ( reader == null )
{
Debug.Log("puzzles.txt not found or not readable");
}
else
{
// Read each line from the file
while ( (string txt = reader.ReadLine()) != null )
Debug.Log("-->" + txt);
}
Method 1+2: Read from plain text file, with fallback to embedded resource
Pawns originally used method 2 exclusively, but since I plan to charge for the full set of puzzles I’d prefer not to have the final puzzle file exposed for all to see. On the other hand, I’d still like to have the ability to try out new puzzles occasionally, or allow others to design puzzles. A simple solution is to first look for the plain text file using method 2; if not found, load the default puzzles embedded in the game resources (method 1.)
In fact, since StringReader (used to read lines from the embedded text resource) and StreamReader (used to read the plain text file) are both subclasses of TextReader, once either file is opened the rest of the code can parse it without caring whether it’s reading from a file or an in-memory string:
FileInfo theSourceFile = null;
TextReader reader = null;Â // NOTE: TextReader, superclass of StreamReader and StringReader
// Read from plain text file if it exists
theSourceFile = new FileInfo (Application.dataPath + "/puzzles.txt");
if ( theSourceFile != null && theSourceFile.Exists )
{
reader = theSourceFile.OpenText();Â // returns StreamReader
}
else
{
// try to read from Resources instead
TextAsset puzdata = (TextAsset)Resources.Load("puzzles", typeof(TextAsset));
reader = new StringReader(puzdata.text);Â // returns StringReader
}
if ( reader == null )
{
Debug.Log("puzzles.txt not found or not readable");
}
else
{
// Read each line from the file/resource
while ( (string txt = reader.ReadLine()) != null )
Debug.Log("-->" + txt);
}
Parsing XML in Unity
People often use XML as a convenient text file format, and the Mono libraries contain powerful classes for parsing it. So this would seem to be a natural fit for Unity. But using those functions requires additional Mono libraries to be bundled with your game. You should particularly avoid this when building a webplayer or iPhone app. See the Unity documentation for details.
Fortunately most tasks only require a small subset of the XML standard, so writing or borrowing a simple parser is an alternative. I found some alternatives being discussed in the Unity forums here.
To use the Mono libraries to read and write XML, this article seems to cover the topic nicely.
Accessing text files on the iPhone
As mentioned above, iPhone apps each have a writeable Documents directory. The contents of this directory are preserved when the app is updated. However, that directory starts off empty and there is no way to install a file directly into that directory along with your app. The workaround is for your game to create whatever files it needs the first time it is run.
The following code was posted in the Unity3d forums (and I’ve seen it elsewhere) for finding the location of the writeable Documents directory:
string docsPath = Application.dataPath.Replace("/Data", "/Documents/");
However, I imagine this may change in the future. Application.dataPath is a bit strange with Unity iPhone 1.5.1 in that it does not appear to point directly to a usable directory. To read from the Documents directory you need to use the above code. To read asset bundles you need to replace “Data” with “myappname.app/Data”. If the folks at Unity Technologies ever decide to fix Application.dataPath to point directly to the Data directory in a future release of Unity iPhone, the code for finding the Documents directory would need to be updated. (Or perhaps they will also add a method like Application.documentPath as well.) [3/6/2010: Unity 1.6 for iPhone was just released, and I see in the release notes that they have added the application name to the Data directory, just as I predicted. So the code sample above needs adjusting- the application name will also need to be removed from the path.]
Unity Pro only: asset bundles
Developers of webplayers and online games should also look into asset bundles, a Unity Pro feature that lets you collect game objects into a file ahead of time, and then load them at run time from a local file or from a web server.
Comments and corrections welcome. Did you find an error? Did you have a particularly interesting use for runtime data in your game? I’d be interested to know.
This topic came up on answers.unity.com recently. For those seeking more information about the AssetBundle option, some sample code for creating an AssetBundle was posted:
http://answers.unity3d.com/questions/2885/creating-assets-on-the-fly
Pingback: Weekly Digest for April 11th — Hello. My name is Václav VanÄura.
Got email from someone recently, wanting to know why the code was crashing. The problem they’d found was that the code isn’t robust when it doesn’t actually find the file in Resources (both Method 1 and Method 1+2 omit this check.) The puzdata variable should be checked for null before accessing it.
For example: method 1, the line “reader = new StringReader(puzdata.text);” could be prefaced with “if (puzdata != null) “.
Very Helpful. Thanks.
Helpful indeed. Thanks alot!
I just made a blog post explaining how to obtain a valid file path in a platform-independent way (working on both PC, Mac, iOS and Android):
http://www.previewlabs.com/file-io-in-unity3d/
You should maybe mention to include ‘using System.IO;’ at the top. I figured this out pretty quick but it’d be nice to have it there from the start.
Jason & Berhard- good comments, thanks! I might update the article one day, but in the meantime, I’m sure folks will find your comments just as helpful.
Thanks for sharing this! Method 1 was exactly what I was looking for, and the explanation was perfect.
Awsome man, you cant figure out what you do for me with the example 2, just save my life, my game its complete and then, needs to be burned with diferent codes each….i just realize that a cant use a simple txt in the end of project, was sucking me…and now i can sleep!!!!
Thanks a lot, ill master the unity engine to be so helpfull as you did!!!!
Fantastic topic. You saved my day. Thanks for the nice article.
Perfect, just what I was after!
Thanks!
Had a bit of trouble with this. I was trying to use instantiate. Just dropping a line to say thanks and good work.
Thanks for the feedback! Glad it helped.