0
\$\begingroup\$

I am writing a game in C#/XNA. Often, it's useful to have a function like this in a game engine:

   Entity CreateEntity(string id, Vector3 location)
   {
       ...
   }

So that, for instance, you can do something like this:

   List<string> entitiesToSpawn = { "Tree", "Bird", "Flower", ... }

   foreach (string entity in entitiesToSpawn)
   {
       CreateEntitiy(entity, location);
   }

Or else you can easily store the ids in data files, or populate generic GUI elements with them, for instance. What, in your opinion, is the best way to implement CreateEntity in such a way that is scalable and preserves this kind of interface? Right now, I have a gigantic static switch statement, which is obviously not sustainable. Perhaps I can create a big static map of strings to functions? Or is there an easier way to get this kind of functionality out of my engine?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

It's a nice approach and really useful especially if you are planning to add level files. Surely a gigantic switch will not work for too many Entities. I think you should use a map. But instead of making it static you can create a class EntityFactory which will include it. This EntityFactory may have two methods. One to create new Entities and one to Add new Entities to the map.

public class EntityFactory
{
    private Dictionary<string, Func<Entity>> factoryMethods = new Dictionary<string, Func<Entity>>();

    public void RegisterClass<T>(string name) where T : Entity , new()
    {
        factoryMethods[name] = () => new T();
    }

    public Entity CreateEntity(string name)
    {
        return factoryMethods[name]();
    }
}

The RegisterClass method creates a simple function that only calls the constructor of the T type.

You can use it like this:

factory.RegisterClass<TreeEntity>("tree");

Entity tree = factory.CreateEntity("tree")

Of course you will need at some point to register all your classes. This can be done at the start of your game.

Edit: This is how you could use this piece of code if you wanted to automatically register all Entity types using reflection:

        var assembly = Assembly.GetAssembly(typeof(Entity));
        var entityTypes = assembly.GetTypes().Where(t => t.BaseType == typeof(Entity));//get everything that derives from Entity
        foreach(var type in entityTypes)
        {
            string name = type.Name;

            //call the generic method of the factory using the type of the entity
            MethodInfo method = typeof(EntityFactory).GetMethod("RegisterClass").MakeGenericMethod(new Type[] { type });
            method.Invoke(factory, new object[] { name });
        }

This will register all classes that derive from Entity with name their class name. For instance class TreeEntity : Entity will be registered as "TreeEntity"

Although it works, personally I would not chose this approach. Also one side note, this will only work for all classes that directly derive from Entity if you need to, you can change the criterion of type selection in Where(t => t.BaseType == typeof(Entity)). Again this piece of code should be called at the start of your game.

\$\endgroup\$
4
  • \$\begingroup\$ Good, of course! I will still need to have a big list of strings to functions though. I suppose that can happen inside the constructor of each entity class to prevent the giant list and keep things looking maintainable. \$\endgroup\$
    – mklingen
    Commented Dec 21, 2014 at 3:41
  • \$\begingroup\$ I am not sure I got this but calling factory.RegisterClass<TreeEntity>("tree"); inside the constructor of TreeEntity will not work. Don't forget that this constructor will be called only by the factory itself. You can make these registrations in the constructor of the EntityFactory though. \$\endgroup\$
    – Exaila
    Commented Dec 21, 2014 at 10:08
  • \$\begingroup\$ @Exalia, seems like there should be some way of automatically registering the classes without maintaining a big list, that's what I'm trying to avoid. \$\endgroup\$
    – mklingen
    Commented Dec 21, 2014 at 15:28
  • \$\begingroup\$ There is a way but in my personal opinion is nasty. You can have a big list which is not that bad or use reflection to fetch all entities. \$\endgroup\$
    – Exaila
    Commented Dec 21, 2014 at 21:44
0
\$\begingroup\$

I'm a bit late to the party, but here is my solution from my games EntityManager. It avoids static maps and switch statements.

I've commented the code to help explain what it does.

/// <summary>
/// Creates an entity from a given XML block.
/// </summary>
/// <param name="data">The XML string containing the entities data.</param>
/// <returns>The created entity, or null if the entity could not be created.</returns>
public AEntity CreateNewEntity(string data)
{
    // convert the XML string to a Linq XML element
    XElement element = XElement.Parse(data);

    // find the entity type
    string entityType = element.Element("entityType").Value;

    // get the local namespace and attach the entity type to it
    string t = string.Format("{0}.{1}", this.GetType().Namespace, entityType);
    // try and find the class type of our entity
    Type T = Type.GetType(t);
    // no type - return
    if (T == null)
        return null;

    // otherwise lets create an instance of our entity, assign it an ID, and then load it
    AEntity entity = (AEntity)Activator.CreateInstance(T, GetRandomID());
    entity.Load(element);

    // return our new entity
    return entity;
}

The basic entity:

abstract class AEntity
{
    public int ID
    {
        get;
        protected set;
    }

    public AEntity(int id)
    {
        ID = id;
    }

    // Other stuff here
}

And a sample entity in XML format is as follows (from a much large XML list):

<entity>
    <entityType>EntityPlayer</entityType>
    <position x="0" y="0" z="0"/>
</entity>

So far this has worked for me, but it has the following limitations:

  • Entity classes need to be in the same namespace as my manager.
  • Entities need to have the class name specified in the XML, so if I rename a class, I need to update all the XML's, or just find/replace (not that it happens often).
  • Doesn't report an error when an unknown entity type is encountered, only returns null to indicate it couldn't create.

1) Isn't much of a problem for me - everything entity related lives in its own entity namespace. 3) Isn't much of a problem either, since a null result can be considered an error, its just that I don't get any more additional data out of it. 2) is the biggest problem, but I think defining codes to differentiate entities is a problem that every sort of entity system faces (and is also the point of the original question as well) so its kind of a moot point.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .