Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 20, Plug-in Manager)

Introduction

Welcome to Part 20 of the XNA Framework GameEngine Development series.  This is the first article of my new game engine series Roe3. 

This article will focus on creating a plug-in architecture for game components and managers.

Creating the plug-in socket

While writing this series I found a few instances when I wanted to be able to turn off a component or manager in order to test or even to create a game that didn’t use all of bells and whistles that the engine had to offer.  I normally would go into the base engine code and remove the components rebuild and move on.  Of course this works, but it is not exactly a very elegant solution.

So I created a configuration management system that would allow me to instantiate managers and components using an xml file that looks something like the following.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <components>
    <component value="RoeEngine3.GameComponents.Cursor"/>
    <component value="RoeEngine3.GameComponents.FpsCounter"/>
    <component value="RoeEngine3.GameComponents.DebugOverlay"/>
  </components>
  <managers>
    <manager value="RoeEngine3.Managers.AudioManager"/>
    <manager value="RoeEngine3.Managers.ScreenManager"/>
  </managers>
</configuration>

Much nicer!  In this example there are 3 components and 2 managers that will be created as the engine starts up… without the need to recompile the engine.  Don’t want to show a cursor or fpscounter?  Simply remove the component from the config.

So all we need to do is create an xml reader for this file, something like the following.

namespace RoeEngine3.Managers
{
    [XmlType("configuration")]
    public sealed class ConfigurationManager
    {
        public static ConfigurationManager Instance { get; private set; }

        static ConfigurationManager()
        {
            if(Instance == null)
            {
                string file = @"Managers.config";

                if(!File.Exists(file))
                {
                    throw new FileNotFoundException();
                }

                using(StreamReader reader = new StreamReader(file))
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationManager));
                    Instance = (ConfigurationManager)serializer.Deserialize(reader);
                }
            }
        }

        [XmlArray("managers"), XmlArrayItem("manager")]
        public Managers Managers { get; set; }

        [XmlArray("components"), XmlArrayItem("component")]
        public Components Components { get; set; }
    }

    public sealed class Components : KeyedCollection<string, Component>
    {
        protected override string GetKeyForItem(Component item)
        {
            return item.Value;
        }
    }

    public sealed class Managers : KeyedCollection<string, Manager>
    {
        protected override string GetKeyForItem(Manager item)
        {
            return item.Value;
        }
    }

    [XmlType("component")]
    public sealed class Component
    {
        [XmlAttribute("value")]
        public string Value { get; set; }
    }

    [XmlType("manager")]
    public sealed class Manager
    {
        [XmlAttribute("value")]
        public string Value { get; set; }
    }
}

Now we have a list of Managers and Components that we want to activate in the engine.  So in our base engine class we need to write some prep code for our managers and components.

        private void PrepareComponents()
        {
            foreach (Component component in ConfigurationManager.Instance.Components)
            {
                GameComponent baseComponent = ComponentActivator.CreateInstance(Type.GetType(component.Value)) as GameComponent;
                EngineManager.Game.Components.Add(baseComponent);
            }
        }
       
        private void PrepareManagers()
        {    
            foreach (Manager manager in ConfigurationManager.Instance.Managers)
            {
                GameComponent baseManager = ManagerActivator.CreateInstance(Type.GetType(manager.Value)) as GameComponent;              
                Components.Add(baseManager);
            }
        }
        protected override void Initialize()
        {
            PrepareManagers();
            PrepareComponents();

     ... // OTHER INITIALIZE STUFF GOES HERE
 }

Turn on the Power

All that is left for us to do is activate the managers and components.  Just need to create some reflection magic to invoke them.

    public sealed class ManagerActivator
    {
        public static object CreateInstance(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("Manager Type");
            }

            ConstructorInfo info = type.GetConstructor(new Type[] { typeof(Game) });

            if (info == null)
            {
                return null;
            }

            object instance;

            try
            {
                instance = info.Invoke(new object[] { EngineManager.Game });
            }
            catch (Exception ex)
            {
                if (ex is SystemException)
                {
                    throw;
                }
                instance = null;
            }
            return instance;
        }
    }

    public sealed class ComponentActivator
    {
        public static object CreateInstance(Type type)
        {
            return ManagerActivator.CreateInstance(type);
        }
    }

A few side notes here, if you notice the GetConstructor object passes in a Type array of type Game.  This is because all GameComponents have a constructor parameter of the Game.  Your components will obviously need to subscribe to the correct GetConstructor types.

Conclusion

In this article I outlined a simple method of creating a pluggable interface for components and managers.  This interface will allow us to turn on and off components and managers as we need them without writing additional code.

Advertisements

December 17, 2008 Posted by | C#, GameComponent, XBOX360, XNA | 22 Comments