Friday, November 18, 2011

Let's Make a Game Engine for XNA 4.0r, Part 3, BaseEntity and BaseComponent

In Part 2 we discussed the MainGame and SceneManager classes. If you remember, SceneManager holds the list of all entities in the scene, and we learned a bit about how entities are created and how components are attached to them. Now we'll learn about Entities and Components, specifically their base classes for each.

First you'll need to create two folders within the main project, named "Components", and "Entity". Within components create a file called "BaseComponent.cs", and within Entity create a file called "BaseEntity.cs". You're file structure should now look like this:

BaseEntity is not an abstract class, in fact, most entities will get their properties from their components, and the entity is just what ties those components together. BaseComponent however is abstract, all components must derived directly or indirectly from BaseComponent.

BaseEntity
Ok, let's start with BaseEntity. Since Entities belong to the SceneManager we allow BaseEntity to keep a reference to the SceneManager, which also gives it access to the MainGame through the SceneManager. Also, just like Managers, Entities has names. The difference with Entities is that since the base class is not abstract, the name can be stored directly on the Entity rather than requiring a GetName() function.

private SceneManager manager;
public SceneManager Manager
{
    get { return manager; }
}

private String name;
public String Name
{
    get { return name; }
}

Entities hold a Dictionary of components based on their names, just like MainGame holds a list of Managers based on names, and SceneManager holds a list of Entities based on their names. And like the SceneManager, Entity also has Add/Remove/Get methods to add and get components by their names.
private Dictionary<String, BaseComponent> components;

public void AddComponent(BaseComponent component)
{
    BaseComponent checkComponent = null;
    if (components.TryGetValue(component.Name, out checkComponent))
    {
         throw new Exception("Component type " + component.Name + " already exists on this object: " + name);
    }

    components.Add(component.Name, component);
}

public void RemoveComponent(BaseComponent component)
{
    BaseComponent checkComponent = null;
    if (!components.TryGetValue(component.Name, out checkComponent))
    {
        throw new Exception("Component type " + component.Name + " doesn't exists on this object: " + name);
    }

    components.Remove(component.Name);
}

public BaseComponent GetComponent(String componentName)
{
    BaseComponent component = null;
    if (!components.TryGetValue(componentName, out component))
    {
        // Component wasn't found
    }

    return component;
}

And the remaining functions serve only to pass Update and Draw calls to each component:
public void Update(GameTime gameTime)
{
    foreach (KeyValuePair<String, BaseComponent> pair in components)
    {
        (pair.Value).Update(gameTime);
    }
}

public void Draw(GameTime gameTime, List<RenderDescription> renderDescriptions)
{
    foreach ( KeyValuePair<String, BaseComponent> pair in components )
    {
        (pair.Value).Draw(gameTime, renderDescriptions);
    }
}

Here's the full BaseEntity file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using SimpleGameEngine.Scene;
using SimpleGameEngine.Components;
using SimpleGameEngine.Render;

namespace SimpleGameEngine.Entity
{
    public class BaseEntity
    {
        private SceneManager manager;
        public SceneManager Manager
        {
            get { return manager; }
        }

        private String name;
        public String Name
        {
            get { return name; }
        }

        private Dictionary<string, basecomponent> components;

        // Public for performance reasons, accessors cause copies
        public Vector3 position = Vector3.Zero;        
        public Matrix rotation = Matrix.Identity;

        public BaseEntity(SceneManager sceneManager, String EntityName)
        {
            this.components = new Dictionary<string, basecomponent>();

            this.manager = sceneManager;
            this.name = EntityName;
        }

        public void AddComponent(BaseComponent component)
        {
            BaseComponent checkComponent = null;
            if (components.TryGetValue(component.Name, out checkComponent))
            {
                throw new Exception("Component type " + component.Name + " already exists on this object: " + name);
            }

            components.Add(component.Name, component);
        }

        public void RemoveComponent(BaseComponent component)
        {
            BaseComponent checkComponent = null;
            if (!components.TryGetValue(component.Name, out checkComponent))
            {
                throw new Exception("Component type " + component.Name + " doesn't exists on this object: " + name);
            }

            components.Remove(component.Name);
        }

        public BaseComponent GetComponent(String componentName)
        {
            BaseComponent component = null;
            if (!components.TryGetValue(componentName, out component))
            {
                // Component wasn't found
            }

            return component;
        }

        public void Update(GameTime gameTime)
        {
            foreach (KeyValuePair<string, basecomponent> pair in components)
            {
                (pair.Value).Update(gameTime);
            }
        }

        public void Draw(GameTime gameTime, List renderDescriptions)
        {
            foreach ( KeyValuePair<string, basecomponent> pair in components )
            {
                (pair.Value).Draw(gameTime, renderDescriptions);
            }
        }
    }
}

BaseComponent
BaseComponent is a very small and simple class, for now at least. It just holds common properties that all components must derived from. BaseComponent holds the reference to the Entity that it is connected to, this lets all components communicate up to the Entity. The reference to the Entity is stored from within the constructor:
private BaseEntity parent;
public BaseEntity Parent
{
    get { return parent; }
}

public BaseComponent(BaseEntity parentEntity)
{
    this.parent = parentEntity;
}

By now you should be getting a feel for some common things in the engine, such as accessing systems by Strings, and Initialization methods registering systems with their owners. In the case of components they handle a GetName() function just like Managers and Entities, and they also have an Initialization function that registers the component with the Entity:
public String Name
{
    get { return this.GetName(); }
}

// Name of the component
protected abstract String GetName();
        
protected virtual void Initialize()
{
    this.parent.AddComponent(this);
}

And all we're left with is a declaration of a virtual Update and virtual Draw function, which allows components to receive either of these from the SceneManager:
public virtual void Update(GameTime gameTime) {}
public virtual void Draw(GameTime gameTime, List renderDescriptions) {}

Here's the entire file for 'BaseComponent.cs':
using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using SimpleGameEngine.Entity;
using SimpleGameEngine.Render;

namespace SimpleGameEngine.Components
{
    abstract public class BaseComponent
    {
        private BaseEntity parent;
        public BaseEntity Parent
        {
            get { return parent; }
        }
        
        public String Name
        {
            get { return this.GetName(); }
        }

        public BaseComponent(BaseEntity parentEntity)
        {
            this.parent = parentEntity;
        }

        // Name of the component
        protected abstract String GetName();
        
        protected virtual void Initialize()
        {
            this.parent.AddComponent(this);
        }

        public virtual void Update(GameTime gameTime) {}
        public virtual void Draw(GameTime gameTime, List<renderdescription> renderDescriptions) {}
    }
}

Up next in Part 4 is the RenderComponent and CameraComponent.

No comments:

Post a Comment