Triggers in XNA games using C#

jwatte's picture

"So," you may ask yourself, "what if I figure out a way to detect that the player has hit a button, or stepped on a landmine, or entered a goal zone? Then what?" If that's you, you've come to the right place -- I'll describe how to implement the "reactor pattern" using a few different techniques, and then I'll show you how you can make it all be driven through text, rather than hard-wired in code in your C# game. Don't say I never treat you right!

the actual "what do I do when I trigger" can be solved several different ways. One way is to use event handlers, just like the GUI does in Windows Forms:

  public delegate void MyEventHandler(object sender, object args);
  class MyTrigger {
    public event MyEventHandler Fire;
    public void OnFire() {
      if (Fire != null)
        Fire(this, null);
    }
  }
 
  class TriggerUser {
    public TriggerUser(MyTrigger t) {
      t.Fire += new MyEventHandler(t_Fire);
    }
    public void Fire(object sender, object args) {
      // OMG! Something happened!
    }
  }

The "MyTrigger" class is typically a virtual base class, or maybe even an interface, rather than just a plain class like this, to allow for easier discovery and hook-up of triggers polymorphically (which is just a fancy way of saying that different classes may all have triggers, and you may want to treat them all the same).

I think you actually don't need to do anything else, because writing a wrapper function for each thing that can be triggered isn't all that bad. However, if you need something more elegant, you can define an interface for "being triggered," and then have multiple implementations of that interface.

  public interface ITriggered {
    void Fire(object sender, object args);
  }
 
  public class MyTrigger {
    public ITriggered Fire;
    public void OnFire() {
      if (Fire != null)
        Fire.Fire(this, null);
    }
  }
 
  public class TriggerUser : ITriggered {
    public TriggerUser(MyTrigger t) {
      t.Fire = this;
    }
    public void Fire(object sender, object args) {
      // OMG! It's doing it again!
    }
  }
 
  public class InvisibleAdapter : ITriggered {
    public InvisibleAdapter(DrawableGameComponent comp, MyTrigger t) {
      this.comp = comp;
      t.Fire = this;
    }
    DrawableGameComponent comp; 
    public void Fire(object sender, object args) {
      comp.Visible = false;
    }
  }

OK, so that's not all that different from the delegate/event version -- which one you use is usually a subtle matter of style.

Finally, you can implement more generic triggering components. For example, you can define a component that sets a property of a given name to a given value when triggering, using reflection. This is where it gets fun!

using System.Reflection;
 
  public class SetPropertyAdapter : ITriggered {
    public SetPropertyAdapter(object obj, string propName, object val) {
      this.obj = obj;
      this.pi = obj.GetType().GetProperty(propname);
      this.val = val;
    }
    object obj;
    PropertyInfo pi;
    object val;
    public void Fire(object sender, object args) {
      // A-ha! Set the value now!
      pi.SetValue(obj, val, null);
    }
  }

You can do the same thing with calling functions, by using a MethodInfo instead of a PropertyInfo, and changing the code accordingly. The cool thing is that you can then configure what happens from a trigger using a text or XML file, because binding becomes name based, rather than explicit in code. You can also give a name to every object in your world, and have a Dictionary somewhere that maps from name to object. You can then set up a trigger based on an object name and a property name, entirely in text; no static type needed!

Note that you can use this same kind of adapter with delegates/events instead just as well. However, if some objects die, but others stay around in your world, you have to make sure to properly un-hook your triggered object from the trigger when it dies; I find that's a little easier to manage with interfaces than delegates. Your mileage may vary. On the other hand, the event system trivially supports multiple things triggered by a single trigger; with interfaces you'd have to build an explicit list of them.