Evaluating paths in XNA games; how to run triggers and dynamically configure properties!

jwatte's picture

Inside an XNA game, you invariably start needing to wire things together. "When the player steps on this tile, run that action" or "when this timer expires, open that gate." For simple levels, you may be able to write this using code, but once your game reaches a dozen levels, each with hundreds of possible actions, hard-coding each and every one of them becomes a real nightmare! Not to mention that if you're working with non-programmer artists, you will need some way for them to add and configure actions without involving you all the time.

My solution to this problem is the "Path." It's somewhat similar to Xpath, or the Binding property found in the Windows Presentation Foundation. A Path might look something like:

Game.World.Entities('portal').Open()

Or something like:

TriggerEntity.TakeDamage(this.DamageAmount);Game.Sounds.Play('ouch');

To actually evaluate a path in your code, you simply call the static "Eval" method:

  object result = Path.Eval("My.Path.Here");

The implementation is actually quite straightforward, using reflection. Starting with a "root," which is a dictionary of words mapping to objects, the evaluator just reads one token at a time, and finds a property or method of the given name on the current context object, and evaluates that. For method calls, the parentheses start out re-evaluating from the root dictionary, up to a comma, and remembers that as a function argument. Semicolons start over from the beginning.

Running a Path each frame in your game is not recommended, because reflection does generate some garbage (every value type value is boxed, for example), but for things that happen once in a while, such as triggers and timers, it works fine, and is very flexible! Another great place to evaluate paths is when the level has just loaded, to set up initial game conditions without having to run custom code.

For certain cases, such as when you have a collection of objects that you want to expose as properties to path evaluation, but don't know the names of up front (say, a world full of entities each with a given ID), you can either use method invocation, or you can implement a custom interface that maps name to object, recognized by the path evaluator. The evaluator, when trying to find a property named 'foo' on the current object, will first attempt casting the current object to IPropertySource, and if successful, will call its method to try to get the object with the given name.

So, how does the Path know where to start looking inside your game? You have to configure it with the "roots" of your name space. You do this through the PropertyDictionary. For example, if inside a Trigger object, you want to make "TriggerEntity," "this" and "Game" available as root names, do this:

  public void OnTrigger(Entity triggerer) {
    PropertyDictionary dict = new PropertyDictionary();
    dict.Add("this", this);
    dict.Add("TriggerEntity", triggerer);
    dict.Add("Game", MyGlobalGameClass.Current);
    Path.Eval(MyExpression, dict);
  }
  public string MyExpression { get; set; }

When you call Eval() without passing in a specific PropertyDictionary, it uses the default dictionary, which you can configure through the Path.Defaults static property. You can also make your custom dictionary "inherit" values from the default, by passing it a an argument to the constructor.

The actual code is downloadable as a ZIP file at the bottom of this article. I hope you find it useful!

AttachmentSize
xna-path.zip4.85 KB