2
\$\begingroup\$

I'm currently creating a game engine for my personal project.
And I'm currently implementing Entity and Component System.

Classes and Interfaces Definitions

Here the classes and interfaces that involved to this problem:

  • Entity: abstract class for any entity in the game, it does not implement any interface or class. It has Children mechanism, so an Entity can add another Entity as it child and able to remove it any time, it can also contains multiple Component.
  • Component: abstract class for any component in the game. The Component is used to add, remove and / or modify the behavior or ability of an Entity. I use this to aid C# limitation that a class cannot inherit multiple classes at a time. Entity can contains multiple Component, however, it cannot contains multiple Component with same type at the same time.
  • Scene: a class that represent a "Screen" or "State" in the game. Scene can contains multiple Entity objects and responsible for render the Entity that implement IRenderable as well as updating Entity that implement IUpdatable and IInputable
  • IRenderable: Represent an object that can be rendered and displayed on the screen.
  • IUpdatable: Represent an object that can be updated each frame in the game.
  • IInputable: Represent an object that able to receive player input.

The Codes

So here the simplified codes:

Entity:

public abstract class Entity
{
    private List<Component> _components = new List<Component>();
    private List<Entity> _children = new List<Entity>();

    public void AddChild(Entity child)
    {
        _children.Add(child);
    }

    public void RemoveChild(Entity child)
    {
        _children.Remove(child);
    }

    public Entity[] GetChildren()
    {
        return _children.ToArray();
    }

    public void AddComponent<T>()
        where T : Component
    {
        if (_components.Find((c) => c is T) != null)
            return; // each entity can only contains one type of component at the same time

        var component = (T)Activator.CreateInstance(typeof(T));
        component.Owner = this;
        _components.Add(component);
    }

    public void RemoveComponent<T>()
        where T : Component
    {
        var component = _components.Find((c) => c is T);
        if (component == null)
            return;

        component.Owner = null;
        _components.Remove(component);
    }

    public Component[] GetComponents()
    {
        return _components.ToArray();
    }
}

Component:

public abstract class Component
{
    public Entity Owner { get; protected internal set; }
}

Scene:

public class Scene : IRenderable, IUpdatable, IInputable
{
    private List<Entity> _entities = new List<Entity>();

    public bool Enabled { get; set; }
    public bool Visible { get; set; }

    public void Add(Entity entity)
    {
        _entities.Add(entity);
    }

    public void Remove(Entity entity)
    {
        _entities.Remove(entity);
    }

    // Render, Update and Input function in the Scene will automatically called at the game loop by game window
    public void Render(RenderTarget target, RenderStates states)
    {
        if (Visible)
        {
            foreach (var entity in _entities)
            {
                if (entity is IRenderable)
                    ((IRenderable)entity).Render(target, states);
            }
        }
    }

    public void Update(double delta)
    {
        if (Enabled)
        {
            foreach (var entity in _entities)
            {
                if (entity is IUpdatable)
                    ((IUpdatable)entity).Update(delta);
            }
        }
    }

    public void Input(InputEventArgs e)
    {
        if (Enabled)
        {
            foreach (var entity in _entities)
            {
                if (entity is IInputable)
                    ((IInputable)entity).Input(e);
            }
        }
    }

}

The Interfaces:

public interface IRenderable
{
    bool Visible { get; set; }
    void Render(RenderTarget target, RenderStates states);
}

public interface IUpdatable
{
    bool Enabled { get; set; }
    void Update(double delta);
}

public interface IInputable
{
    bool Enabled { get; set; }
    void Input(InputEventArgs e);
}

Questions

  1. Does any class / interface violates or lacking it's capability and / or functionality from it's name? for example, IRenderable enforce the class that implement it to implement Visible property, which mean all "Renderable" object should have "Visible" state, is such design correct?

  2. At the shown code above, the Scene is not responsible to check the Entity children and it's components, and it also possible to add another Entity as a child while implementing interface that not implemented by it's parent. Consider following example:

    public class CustomEntity : Entity
    {
        // Some codes here...
    }
    
    public class Sprite : Entity, IRenderable
    {
        public bool Visible { get; set; }
    
        // ...
    
        public void Render(RenderTarget target, RenderStates states)
        {
            // If the sprite is not visible, it should not render itself and ignore the children
            if (!Visible)
                return;
    
            // Render the sprite here...
    
            // Since scene does not check the Entity children, 
            // We need to render the child and components in case they implement IRenderable
            foreach (var child in GetChildren())
            {
                if (child is IRenderable) 
                {
                    if (((IRenderable)child).Visible)
                        ((IRenderable)child).Render(target, states);
                }
            }
    
            foreach (var component in GetComponents())
            {
                if (component is IRenderable) 
                {
                    if (((IRenderable)component).Visible)
                        ((IRenderable)component).Render(target, states);
                }
            }
        }
    }
    

    the CustomEntity does not implement IRenderable interface, however, it is possible to do something like this:

    var myEntity = new CustomEntity();
    var mySprite = new Sprite();
    
    myEntity.AddChild(mySprite);
    

    and of course mySprite won't be rendered when myEntity is added to the Scene, the same thing applies to the Component. Is this correct behavior? or should Scene handle those stuffs?

That's all for now, in case you find something weird with the design, I'm open for suggestion. I'll update the question if I've got something more to ask.

\$\endgroup\$
  • 2
    \$\begingroup\$ I have rolled back the last edit. Please see what you may and may not do after receiving answers. Basically, don't update your code in the question based on advice, as it invalidates people's answers. If your code doesn't represent your actual code, we'd normally see that as off-topic, because people will give advice on the code your present, and if it doesn't MATCH the code you actually have, you might get useless advice. Something to remember. \$\endgroup\$ – Pimgd Mar 18 '16 at 13:56
3
\$\begingroup\$

Some issues:

Your type casting is not the most readable. Also, if you use the casted object more than once, it may be less inefficient.

Instead of:

if(object is Type)
     ((Type)object).DoSomething();

Use:

var casted = object as Type;
if (casted != null) {
    casted.DoSomething();
}

This way, you do not need to cast the object every time it is used, so it may be faster, and it is more readable.

Also, for your components, it may be faster to use a Dictionary<Type, Component>, as it does not need to iterate through every key, so instead of:

if (_components.Find((c) => c is T) != null)
     return;

You should use:

if (_components.ContainsKey(typeof(T))) {
    return;
}

It is also a bad practice to exclude braces from if statements, as it may confuse some if there are multiple statements, all indented.

For example, you may write:

if (condition)
    DoSomething();
    DoSomethingElse();

At first, it may look perfectly normal, especially if you are doing a quick read-through. (Of course, using an auto-indenter solves the problem, but the code may still be hard to read for some.)

Furthermore, in this method:

public Entity[] GetChildren()
{
    return _children.ToArray();
}

you are converting a List to an Array. Since List uses an Array internally, it is not expensive, but unless you have a good reason to keep it, it's better to leave it in list form. The same applies to GetComponents.

To answer your questions:
2. This should be good object-oriented code. The object-specific methods are defined on each object, and the Scene, which has a role of rendering the items, should be the one calling the Render method.

If it is intended for the children of CustomEEntity to be visible, it will be best to implement the Render method and Visible property in the CustomEntity.

On the other hand, if CustomEntity is not related to rendering, it should not have an IRenderable child at all. In this case, you may want to check the interfaces implemented by each child and component before adding, and make sure the object implements the interfaces as well.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your reply, I changed type casting code and use Dictionary to store the components. About the braces, I stripped the braces from original codes for simplicity sake, I'll edit the question. Anyway, so the Scene is the one that should calling the Render method of an Entity and it's children? \$\endgroup\$ – SirusDoma Mar 18 '16 at 13:50
  • \$\begingroup\$ The method in the Entity should render its children, especially especially since it may be nested. The Render method should do everything the Entity needs to render fully, but it should always be called by the renderer. \$\endgroup\$ – somebody Mar 18 '16 at 13:52
  • \$\begingroup\$ Thanks for your clarification, and I saw your edited answer everything seems to be clear now! \$\endgroup\$ – SirusDoma Mar 18 '16 at 14:12
  • 1
    \$\begingroup\$ If you want to prevent the IRenderable from being added, the easiest way is probably to have a method that takes an Entity parent, and Entity child, then in the function do something like: if (!parent is <interface> && child is <interface>) {throw new <yourexception>("<interface>")}, where <yourexception> prints something like "Parent does not support {0}", interface \$\endgroup\$ – somebody Mar 18 '16 at 14:18
  • \$\begingroup\$ Thanks for your help, I need one more thing to clarify: how about the Components? the main purpose of Component is to add, remove as well as modify an Entity behavior. for example: I need to add IUpdatable and IRenderable Component to an Entity while the Entity itself doesn't implement those interfaces. In such case, the Component need to be updated (and rendered) on each frame while the Entity itself is not (It's sounds make sense for me). What should I do in such case? is Component should apply same rule as the Child? or let the Scene handle the Component stuffs? \$\endgroup\$ – SirusDoma Mar 18 '16 at 21:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.