XML Versus Lua for Game Data

Recent discussions with my friend and game-development partner Jeff prompted me to write about the pros and cons of using XML versus Lua to represent data in a game. At first glance this my seem like an ‘apples to oranges’ comparison. One is a data-representation language and the other is a programming language. We use Lua extensive in our game, which should help put the comparison into context. But I will still try to convince you that the comparison is not as asinine as it may sound.

Lua as a Data-Representation Language

I will assume you are familiar with XML and the basics of representing data with XML documents. But Lua can be useful for the same purpose. We do exactly that for our LNVL project. What we take advantage of some shortcuts made possible by Lua to create a domain-specific language (DSL) for representing conversation scenes.

I am not trying to explain the LNVL DSL. I am only using it as an example that Lua can serve as a nice and readable language for representing data. The authors of Lua discuss this very subject in the 1997 paper “Building Domain-Specific Languages over a Language Framework”.

Pros and Cons of XML

This is an example of XML in our game. We also use XML to represent a lot of data about enemies. We do not use it exclusively for enemies; we also mix in Lua. That is an issue I will discuss later in this article.

The biggest benefit to XML in my opinion is that we can validate the data. This is like compile-time checking; if there is a malformed enemy definition or a bullet pattern has a string where it should have a number then we can catch those errors before ever running the game. By combining our XML with a Document Type Definition (DTD) or XML Schema or RELAX NG schema we can create a domain-specific language for our data that is more strongly-typed than Lua. Or at least stronger than Lua is by default; I will talk about how we could achieve the same in pure Lua. But I still believe this is a big advantage for XML, particularly since it already has a healthy environment of existing tools to help automate validation and more.

The biggest downside to XML is the seemingly-inevitable verbosity. Here is an example from our game, an XML document that defines the bullet attack pattern of an enemy:

<?xml version="1.0"?>
<!DOCTYPE bulletml SYSTEM "bulletml.dtd">
<bulletml xmlns="http://www.asahi-net.or.jp/~cs8k-cyu/bulletml">
    <bullet label="turretBullet">
        <damage>20</damage>
        <speed>2.00 + 0.75 * ($rank - 1)</speed>
        <size>4</size>
        <shape>dot</shape>
        <color red="200" green="200" blue="250"/>
    </bullet>

    <offset label="turretOffset">
        <x>0</x>
        <y>20</y>
    </offset>

    <action>
        <wait>1.2</wait>
        <repeat>
            <times>9999</times>
            <fire>
                <bulletRef label="turretBullet"/>
                <direction type="relative">0</direction>
                <offsetRef label="turretOffset" />
            </fire>
            <wait>10</wait>
            <fire>
                <bulletRef label="turretBullet"/>
                <direction type="relative">0</direction>
                <offsetRef label="turretOffset" />
            </fire>
            <wait>10</wait>
            <fire>
                <bulletRef label="turretBullet"/>
                <direction type="relative">0</direction>
                <offsetRef label="turretOffset" />
            </fire>
            <wait>70</wait>
        </repeat>
    </action>
</bulletml>

One goal of XML is to be ‘human-readable’, and I would argue that the example above qualifies for that. However, I would also absolutely concede that the example is verbose if we image how we could represent it in Lua. It is simple to fathom how using Lua tables would remove the need for all of those closing tags, and that alone is a gain in brevity without an inherent loss of readability.

Pros and Cons of Lua

An obvious but non-trivial advantage to using Lua for representing data lets us use the same language for data as we do for our logic. It’s not like either of us struggle to think in terms of XML and Lua at the same time. But I do believe there is a tangible benefit to limiting the amount of languages on any programming project.

The best benefit of Lua for our project is that it we can plug Lua-defined data directly into the game. Data from XML must first go through a parser that translates it into tables. This is an extra layer of work for the game as well an extra source of bugs. That is not to say that using Lua for data gives us a bug-free environment, but it does free us from bugs related to translating data from one format into another.

Since Lua is a programming language it allows us to more easily define logic as data, or more generally code as data. Here is an abbreviated example from our game, from a Lua script that defines an enemy:

EnemyTemplates["battlefly"] = {}
table.insert(EnemyTemplates, EnemyTemplates["battlefly"])

EnemyTemplates["battlefly"].name = "Battlefly"
EnemyTemplates["battlefly"].namePlural = "Battleflies"
EnemyTemplates["battlefly"].description = 'An air-based midboss; a giant butterfly that shoots pollen and uses aerial magic.'

EnemyTemplates["battlefly"].init = function(enemy)
    enemy.collisionDamage = 16
    enemy.directionVector = Vector2D.create(0, 1)
    enemy.fireDirection = enemy.directionVector:toDegrees()
    enemy.defense[DamageTypes.astral.supernal] = -3

    enemy.name = EnemyTemplates["battlefly"].name
    enemy.namePlural = EnemyTemplates["battlefly"].namePlural
    enemy.description = EnemyTemplates["battlefly"].description

    enemy.windDirection = math.pi / 2
    enemy.windStrength = 250 -- pixels per second
    enemy.windDuration = 7 -- seconds

    enemy.positionIndex = 0 -- 0 = top middle of screen
                            -- 1 = top left of screen
                            -- 2 = top right of screen

    enemy.topCenterPosition     = Vector2D.create(960, 150)
    enemy.topLeftPosition       = Vector2D.create(Level.left + 240, 300)
    enemy.topRightPosition      = Vector2D.create(Level.right - 240, 300)

    enemy:behave(EnemyTemplates["battlefly"].enter)
end

EnemyTemplates["battlefly"].update = function(enemy, dt)
    if (enemy.windDuration > 0 and not enemy.dead) then
        if (ThePlayer.currentMagic == AirBlast and ThePlayer.currentMagic.activated) then
            -- While using "Air Blast", the player is immune to the Battlefly's wind push.
        else
            local windVector = Vector2D.create(enemy.windStrength * dt * math.cos(enemy.windDirection),
                enemy.windStrength * dt * math.sin(enemy.windDirection))
            ThePlayer.position = ThePlayer.position + windVector;
        end
        enemy.windDuration = enemy.windDuration - dt
    end
end

EnemyTemplates["battlefly"].enter = Behavior.create("Enter",
    function(behaver)
        -- Most of the Battlefly's entrance behavior is handled by the XML file settings.
        Behavior.wait(10.0)
        behaver:behave(EnemyTemplates["battlefly"].switchToRandomOffensiveBehavior)
    end
)

-- This causes the battlefly to stay in place for a few seconds.
EnemyTemplates["battlefly"].chill = Behavior.create("Chill",
    function(behaver)
        Behavior.wait(3.0)
        behaver:behave(EnemyTemplates["battlefly"].switchToRandomOffensiveBehavior)
    end
)

-- Switch to a random offensive behavior.
-- There are three offensive behaviors, each associated with a region of the screen:
--  0 = Attacking from the top center of the screen.
--  1 = Attacking from the top left of the screen.
--  2 = Attacking from the top right of the screen.
EnemyTemplates["battlefly"].switchToRandomOffensiveBehavior =
    Behavior.create("Switch to random offensive behavior",
    function(behaver)
        local i = math.random(2)
        -- ...
    end
)

It is easy for us to customize the behavior of enemies by writing code directly. Now note an important comment in the code, “Most of the Battlefly’s entrance behavior is handled by the XML file settings.” I said earlier that enemies in our game use a mixture of XML and Lua, and this is where we run into a problem. Initially our enemy definitions were nothing but XML; we wrote XML documents that defined their hit-points, where they spawned on screen, what image assets they used, things of that nature. And we used XML to define some simple behaviors common to all enemies, such as what to do when they spawn and when they die.

But the more complex and customized enemies become the more it highlights a problem with XML. We could take Lua code for enemies like the above and shove it into XML documents using <![CDATA[[ … ]]> but that would create new problems; for example, testing and even basic editing of Lua code is more difficult when that code is in the middle of an XML document.

The situation above makes it sound like using pure Lua would be best for enemies. And honestly we may make that change. However, it leads into what I feel is the biggest con against using Lua for game data: it becomes more difficult to validate the structure and content of said data. Using XML and schemata we can do things such as declare that hit-points not only must be numbers, but that they must be within a certain range. Couldn’t we do write some Lua code to perform these same tests? Absolutely. But the XML schemata tools already exist, and as guilty as I am of re-inventing the wheel I prefer to avoid creating tools that are already out there. With regards to type-checking, we have Lua’s built-in type() function, but we need a lot more to mimic the refined data-type definitions we can create with XML Schema Datatypes. We would need to write regular expressions, verification functions, and other things which would be entirely possible but also redundant in the face of existing XML linting software.

Conclusion

So which is better than the other?

Sorry—I have no answer for that. When it comes to defining game data XML gives us strong type-checking and data-definition rules along with existing tool-sets to validate that data; but it comes at the cost of verbosity and the lack of a good way to define anything imperative like the enemy behavior example above. Using Lua for data, on the other hand, makes it a lot easier to define and plug-in data since it’s the same language as the game itself, which means that data can be more ‘code-ish’ too; but we sacrifice a safety net of data validation unless we write our own layer or tool to do what programs like xmllint already do.

The truth is that the best choice may be somewhere in the middle. As I said, it seems likely that we will stop using XML for enemies since we have to write code for their behavior, and splitting data across multiple files like that causes bugs, oversights, and other mistakes. But XML seems to be a good fit for game data that is entirely or mostly declarative in nature, such as bullets that the player and enemies shoot.

I am sure I will revisit this with a more educated opinion once our game ships.

Advertisements

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s