Saturday, July 13, 2013

Loading Connection Data from XML

Hi Reader! Let me have your attention for a little while. I've been working really hard on my networked game lately, and I thought the subject of this page would take me little more than 10 minutes to accomplish. I was wrong!

Let me tell you a scenario where loading XML is extremely useful. When testing my game, I am often switching between the remote connection and my own local SmartFox connection. I am also having to log in as different users to test the interactivity between two clients. I deploy the game as standalone to my PC and open it up. Oh no! I've forgotten to change a setting, and now I have to go back to my Unity editor; change the setting in my script; rebuild the game; and deploy it again. Whew! That is a huge pain. So, this week I decided to start loading all of these settings via XML. This enables you to change the settings without having to rebuild the game. Let me show you the XML.
<?xml version="1.0"?>
<config>
 <remoteConnection>False</remoteConnection>
 <localHost>127.0.0.1</localHost>
 <remoteHost>serverIP</remoteHost>
 <serverPort>9933</serverPort>
 <zone>Vineyard</zone>
 <username>AndyM</username>
 <password>********</password>
</config> 

So, this is obviously not the coolest XML syntax in the world. Lots of people would rather use attribute tags. Anything is possible, so format it how you want, I just found this to be pleasing to my eyes. You see that XML has a couple of differnt parts. First, you need to say what version it is. Then you always have to have a root object. I'm calling it config, but you can call it anything. Everything inside of the <Config> </Config> tags is a child of that object. So I made a function that can go through XML and find the next child of an object. It's based on the IsStartElement() method which will always detect the opening tags for an element. Study it and mess with it. But, if you don't care about it, move on to the next function that I have which is the heart of the solution to this post.

Let's not get ahead of ourselves though. How do we even get Unity to load this file. I initially placed it in my Assets folder, but when I deployed as standalone, I realized that Unity did not export the file with the build. I tried a few different options, and the solution is that you need to create a StreamingAssets folder in your project. This folder's contents will be exported as is to your final build. Edit: 8/21/2013 It has come to my attention that this LoadPath function will not work in all cases. Check my post on the Unity forums page for more updates as to what the problem is. Use the "GetPathUsingUnityWrapper(string fileName)" instead. http://forum.unity3d.com/threads/196948-StreamingAssets-folder-documentation-is-incorrect-for-Mac-standalone
public clas LoadXML{

// Todo: trim, check for attributes
private static bool MoveToNextChild(ref XmlReader xmlReader)
{ 
 xmlReader.Read(); // Moves past the current element
 
 while(xmlReader.IsStartElement() == false)
 {
  if(xmlReader.Read() == false)
  {           
   return false; //No more children
  }
 }
 
 //Found a child
 return true;
}

// For the streaming assets folder
public static string GetPath(string fileName)
{
#if UNITY_IPHONE
 string path = Application.dataPath + @"/Raw";
#elif UNITY_ANDROID
 string path = "jar:file://" + Application.dataPath 
          + "!/assets/";
#else
 string path = Application.dataPath + @"/StreamingAssets";
#endif
 //append the name of the actual file
 path += @"/" + fileName;
  
 return path;
}

//Update 8/21/2013 This is a more accurate function that will never fail
public static string GetPathUsingUnityWrapper(string fileName)
{
    string path = System.IO.Path.Combine(Application.streamingAssetsPath, fileName);
    return path;
}

public static Dictionary<string, string> 
       CreateDictionary(string fileName)
{
 Dictionary<string, string> xmlData = new 
                Dictionary<string, string>();
 XmlReader xmlReader = XmlReader.Create
                (GetPathUsingUnityWrapper(fileName));
 
 //Read to root
 xmlReader.MoveToContent();

 while(MoveToNextChild(ref xmlReader) == true)
 { 
  string key = xmlReader.Name;
  string value = xmlReader.ReadElementString();
  
  xmlData.Add(key, value);
  
  Debug.Log("key " + key + " value " + value);
 }
 
 return xmlData;
}
}

/* You can make this call from anywhere in your project
   Now though the question is, "What do I do with this file?" 
   I'm glad that you asked. I've created my own XML Loader fil 
   that can quickly parse this information and store it in a dictionary.
*/

Dictionary<string, string>
xml = LoadXML.CreateDictionary("config.xml");

foreach(string param in xml.Keys)
{
 switch(param)
 {
 case "remoteConnection" :
  remoteConnection = 
                Boolean.Parse( xml["remoteConnection"] );
  break;
 case "localHost" :
  localHost = xml["localHost"].ToString();
  break;
 case "remoteHost" :
  remoteHost = xml["remoteHost"].ToString();
  break;
 case "serverPort" :
  serverPort = Int32.Parse( xml["serverPort"].ToString() );
  break;
 case "zone" :
  zone = xml["zone"];
  break;
 case "username" :
  username = xml["username"];
  break;
 case "password" :
  password = xml["password"];
   break;
  default:
  Debug.Log ("XML parameter not added to loader list");
  break;
  
 }
}