Data Driven Programming

In Data Driven Programming, the data itself drives the logic of the program. The actual code extremely general -- it is essentially a framework for interpeting the data. In Game Programming, often Data Driven Programming means using some form of scripting language. For this class, the term "Data Driven Programming" will mean something a little more general: the behavior of the game can me modified (often quite drastically) by only changing data files, and not doing any recompiling at all.

Step 1: Constants

The first step in data driven programming is to grab the low-hanging fruit. Consider the following code:
 velocity += Vector2[0,1] * elapsedTime * 9.8;   // Accelleration due to cravity is 9.8 m/s*s 
 
This isn't too bad -- but note that we have a magic number in our code: 9.8. Magic numbers are bad. We can do a little better by making that a constant:
 const float GravitationalConstant = 9.8;  // 9.8 m/s*s

...  

 velocity += Vector2[0,1] * elapsedTime * GravitationalConstant;  
 
We're getting a little better -- we've removed the magic number from the code, and made it a constant. That's better a better software engineering practice, but it is not data driven. To make this slightly more data driven, we can read this constant in from a data file (please excuse the pseudo-code, we'll do real XML parsing in a bit):
 readonly float GravitationalConstant = getDataFromFile("gravity");

...  

 velocity += Vector2[0,1] * elapsedTime * GravitationalConstant;  
 
Now we can change gravity in our world by just modifying a line in a data file. This gets us a little more flexibility, but we can do better! Consider:
 readonly Vector2 Gravity = getDataFromFile("gravity");

...  

 velocity += elapsedTime * Gravity;  
 
Now, we can change not only the magnitude, but also the direction of gravity in our game, by only modifying a data file. This brings use to Rule #1:

Rule #1: No Magic Numbers, No Constants

Your code should have no "magic numbers", that is, literal constants burried in the code. In fact, your code should have no literal constants at all -- any time that you are typing in any literal constant into code that you will compile (including any string literal!), consider moving that to a data file instead. What does this get us?

Brief diversion: Reading XML files in XNA

Ok, so you've decided that you want to take some small data-driven steps. First up -- how do you read in the data? XML is a handy format, especially since there are already a number of good XML parsers that you can use. Let's take a look at how we can create and use an .xml file in xna. First, add the appropriate file to the content directory. Right click on the content directory, and add a new item

Screenshot

Once we have the new item, we want to create a XML file. Use an appropriate name, here we picked world.xml:

screenshot

Now we have an XML file that we can access. Since XNA uses XML files for all of its content managment pipeline, some of the XML files (like spritefonts) will be compiled as part of the build step. Since we are (for the moment) doing all of the XML file management ourselves, we do not want the build system to try to build our file, or to try to create any binaries from our file. We need to tell the build system not to try to compile the XML, and to copy the file to the target, so that we can access it:

screenshot

Now we have and .xml file that we can access from both windows and xbox. How do we actually read in data from the xml file? There are a number of ways we can do this, let's look at one. Let's say that we have a very simple xml file:

 <?xml version="1.0" encoding="utf-8" ?>
 <XnaContent>    
      <constants>
        <pi type = "float">
          3.14159
        </pi>
        <gravity type = "Vector2">
          <x>
            0
          </x>
          <y>
            9.8
          </y>
        </gravity>
      </constants>
  </XnaContent>
    

The top level "root" node of our XML tree is XnaContent. That node has a single child, constants, and that node has two children. We can get the root of the XML as follows:

    XmlReader reader = XmlReader.Create(new StreamReader("Content/world.xml"))
    XDocument xml = XDocument.Load(reader);
    XElement root = xml.Root;    
    
An XElement object stores an element of the XML tree. We can traverse elements using the following properties: For example, for the following element
    <pi type = "float">
       3.141592
    </pi>
    
the name is "pi", the value is "\n 3.141592 \n ", it has a single attribute (with name "type" and value "float"), and no children

 

Let's see an example of using these methods to do a traversal of the constant XML above. Just to be "C#-y", we will use the "using" control construct. The "using" control construct works as follows:

    using (type var = init)
    {
       // body
    }
    

the variable "var" only exists during the body of the using statement, and will be immeditately garbage-collected, with all resources freed, when the block has finished. This construct is often utilized for file processing classes -- once they are done, all resources (file pointers, etc) will be freed up.

    using (XmlReader reader = XmlReader.Create(new StreamReader("Content/world.xml")))
    {
        XDocument xml = XDocument.Load(reader);
        XElement root = xml.Root;
        foreach (XElement elem in root.Elements())
        {
            AddElementToWorld(elem);
        }
    }
    

(Note that we don't need to utilize the "using" construct, it just makes our code a little better at cleaning up after itself). Once we have an XML reader, we use it to create an XML document (which is just a data struture containing all of the elements in the XML file). Once we have this data structure in memory, we can traverse it fairly easily. "xml.root" gives us the root element of the document. This would be the <XnaContent> element (which contains everything else as descendants). We go through each of our descendants (we have only one, <constants>), and processs them, by adding them to the world. Let's take a look at our function to add elements to the world

    protected void AddElementToWorld(XElement elem)
    {
        if (elem.Name.ToString() == "constants")
        {
            foreach (XElement nextElem in elem.Elements())
            {
                AddConstant(nextElem);
            }
        }
        // Other cases, assuming our XML file has other elements tham
        // just constants
    }
    

Now, we just need to read in values for each constant:

        protected void AddConstant(XElement elem)
        {
            if (elem.Name.ToString() == "Pi")
            {
                Constants.GetInstance().Pi = float.Parse(elem.Value);
            }
            if (elem.Name.ToString() == "Gravity")
            {
                Vector2 grav = new Vector2(float.Parse(elem.Element("x").Value),
                                    float.Parse(elem.Element("y").Value));

                Constants.GetInstance().Gravity = grav;
            }
            // Code for any other constant that you want to add
        }
    

This will work just fine, but ... every time you add a constant, you will need to update your parsing code to parse it correctly. Also, the code you all will all be very similar (every time you add a new float constant, you will be doing almost the same thing that you did to parse "Pi") -- which will tempt you to do cut-and-paste coding. Any time you are tempted to cut and paste, STOP! There is (almost) always a better way. If only there was a way to use a string representing the name of an attribute to get at the attribute itself, we'd be set. Fortuneatly, C#'s type reflection lets us do just that. Given a type, we can get all properties of that type, and given an instance we can set it. Given an instance of a class, we can get (and set) information on properties as follows

            // Given an instance of a class foo with a float property "Prop", so that the following is legal
            // foo.Prop = 3.4, we can:
            PropertyInfo propertyInfo =  foo.GetType().GetProperty("Prop");
            propertyInfo.SetValue(foo, 3.4 ,null);
    
This gives us a nice, generic way to set constants from XML. Given an object to add a Property to, and an XML element that describes a property's value, we can go ahead and set the value as follows:
        protected static void AddVector2ToClassInstance(XElement elem, Object obj)
        {
            PropertyInfo propertyInfo = obj.GetType().GetProperty(elem.Name.ToString());
            Vector2 valueToSet = new Vector2(float.Parse(elem.Element("x").Value),
                    float.Parse(elem.Element("y").Value));
            propertyInfo.SetValue(obj, valueToSet, null);
        }


protected static void AddFloatToClassInstance(XElement elem, Object obj) { PropertyInfo propertyInfo = obj.GetType().GetProperty(elem.Name.ToString()); propertyInfo.SetValue(obj, float.Parse(elem.Value), null); } public static void AddValueToClassInstance(XElement elem, Object obj) { string type = GetAttributeValue(elem, "type").ToLower(); if (type == "float") { AddFloatToClassInstance(elem, obj); } else if (type == "vector2") { AddVector2ToClassInstance(elem, obj); } // We could always add more supported types here ... else { throw new FormatException("Unknown Type attribute " + type + " in XML file"); } } }

Now, all we need to do to add a new constant is add the constant to our Constants singleton, and then add the appropriate fields to our XML file. The parsing will all be done (somewhat) automagically. A complete version of the code to load constants (slightly different than appears here, to encapsulate everything within the constant class) along with the XML file in the correct format, can be found here:

Back to Data Driven Design: Levels

One way in which almost all games are data driven is in creating levels. The game code (or engine) contains the logic for each of the elements of the game, but how those elements are put together in an actual game is determined by a data file which describes the level. Let's look at a simple example for a platformer. First, we will assume that our level is made up of a number of platforms that the character can jump between. Our world will just be a list of platforms that the player can jump on. What makes up a basic platform? We can create a basic world element for a platform:
    class WorldElement
    {
        public WorldElement() { }

        public Vector2 Position { get; set; }
        public Texture2D Texture { get; set; }
        public string TextureName { get; set; }
        public virtual void Update (GameTime time) { }
    }
Our world will just be a list of these platforms. To load our world (assuming that there are just platforms in the world), we just need to go through an XML representing the worlds, and extract all of the platforms. This turns out to be fairly easy:
        void AddPlatformToWorld(XElement platformElement)
        {
            Platform newPlatform = new Platform();

            string platformType = platformElement.Name.ToString();



            foreach (XElement elem in platformElement.Elements())
            {
                XMLParse.AddValueToClassInstance(elem, newPlatform);
            }
            mPlatforms.Add(newPlatform);
        }
        

        protected void AddElementToWorld(XElement elem)
        {
            if (elem.Name.ToString().ToLower() == "platform")
            {
                AddPlatformToWorld(elem);
            }
            else
            {
                // Other cases for adding other elements to the world
            }
        }


        public void LoadWorld(String worldFile)
        {
            using (XmlReader reader = XmlReader.Create(new StreamReader(worldFile)))
            {
                XDocument xml = XDocument.Load(reader);
                XElement root = xml.Root;
                foreach (XElement elem in root.Elements())
                {
                    AddElementToWorld(elem);
                }
            }
            foreach (WorldElement e in mPlatforms)
            {
                e.Texture = mContentManager.Load(e.TextureName);
            }
        }
Our input file would look something like:

   <Level>
      <Position type ="Vector2">
        <x>
          100
        </x>
        <y>
          100
        </y>
      </Position>
      <TextureName type ="string">platform1</TextureName>
    </Platform>
    
    <Platform>
      <Position type ="Vector2">
        <x>
          200
        </x>
        <y>
          100
        </y>
      </Position>
      <TextureName type ="String">platform1</TextureName>
    </Platform>
   </Level>
Assuming our draw method for the world just draws the spirtes in the proper location, we're done. Now, what if we want to have moving platforms? It turns out that we need to do very little to make them work properly. We will expand our definition of a platform to include information about how the platform should move, as well as an update method which actually moves the platform:
  class Platform : WorldElement
    {
        public bool IsMoving {get; set;}
        public List%lt;Vector2> Path { get; set; }
        public bool ReverseOnPathEnd { get; set; }
        public float Speed { get; set; }


        protected int direction = 1;
        protected int targetIndex;


        protected void incrementTargetIndex()
        {
            targetIndex += direction;
            if (targetIndex >= Path.Count)
            {
                if (ReverseOnPathEnd)
                {
                    direction = -1;
                    targetIndex += direction + direction;
                }
                else
                    targetIndex = 0;
            }
            else if (targetIndex < 0)
            {
                targetIndex = 1;
                direction = 1;
            }
        }

        public override void Update(GameTime time)
        {
            if (IsMoving)
            {
                Vector2 target = Path[targetIndex];
                float moveDistance = Speed * (float)time.ElapsedGameTime.TotalSeconds;
                if (moveDistance == 0)
                {
                    return;
                }
                while ((target - Position).LengthSquared() < moveDistance * moveDistance)
                {
                    Position = target;
                    incrementTargetIndex();
                    target = Path[targetIndex];
                }
                Vector2 moveVector = (target - Position);
                moveVector.Normalize();
                moveVector = moveVector * moveDistance;
                Position = Position + moveVector;

            }
        }

OK, so we've defined how the platform should move, but we haven't yet done anything to read in the proper information from our data file to initialize our moving platforms. It turns out that we don't have to add a single line of code, as long as we are clever about how we structure our data file! Why not? Note that our parsing code grabs XML elements, looks for properties that have those names, and sets values -- this will work for any property, not just Position and TextureName!

Taming data files

You may notice that your data files are now getting pretty complicated, and it is easy to introduce nasty run-time errors by having poorly formed data. The examples in this docuemnt pretty much assume that the data is all correct without doing any data verification at all. While adding some extra verification for the data is a good idea, you should also consider creating a tool to manage your data files -- essentially a level editor. Large-scale games tend to have teams of tools programmers that create tools that help artists create these data files, as well as verify that the data files are correct based on the current state of the engine

Scripting

A big part of data driven design -- having the data influcence the control flow of your program -- is allowing actual commands to be added to the data files, usually as scripts. We'll cover basic scripting (really basic scripting) next week. We will be using many of the reflection tools described here to get a lightweight scripting system going.