2

I want to create a small Entity-Component-System example and created some components like

internal struct Position : IComponent
{
    public int X { get; set; }
    public int Y { get; set; }
}

and

internal struct MovementSpeed : IComponent
{
    public int Value { get; set; }
}

Every component implements a currently empty interface IComponent. When the system is looping through the entities I would like to find the relevant components fast.

I thought about creating a Dictionary that holds the component type as the key and the component of the current entity as the value.

I started with public Dictionary<Type, IComponent> Components { get; }

I can add components using myEntity.Components.Add(typeof(Movement), new Movement() as IComponent);

but how can I return a component? I created a movement-system example

internal class Movement : ISystem
{
    public void Update()
    {
        foreach (Entity entity in EntityPool.activeEntities.Values) // Loop through all entities
        {
            Dictionary<Type, IComponent> components = entity.Components;

            if (components.TryGetValue(typeof(Position), out Position positionComponent))
            {
                if (components.TryGetValue(typeof(MovementSpeed), out MovementSpeed movementSpeedComponent))
                {
                    // TEST: move (1 * movementspeed) units
                    positionComponent.X += movementSpeedComponent.Value;
                    positionComponent.Y += movementSpeedComponent.Value;
                }
            }
        }
    }
}

if (components.TryGetValue(typeof(Position), out Position positionComponent)) crashes because the value of the dictionary does not return the component of desired Type itself, it returns the interface.

How can I make it work?

(Yeah I know I could use a ECS framework but I would like to do it on my own for learning purposes)

Question3r
  • 2,166
  • 19
  • 100
  • 200
  • You'd need to use reflection to find and get the value of the specified member. A much better approach would be to put the members in your interface. I'm not sure I get what you're doing but that is kind of the purpose of using interfaces. – Jonathan Wood Dec 09 '18 at 20:36
  • Why do you have nested `if` s ? – Fabjan Dec 09 '18 at 20:40
  • @JonathanWood would you mind explaining it a little bit more? – Question3r Dec 09 '18 at 20:42
  • @Fabjan because this system should only update entities that have a position and a movementspeed component attached – Question3r Dec 09 '18 at 20:42
  • Probably not. Especially since you didn't even clarify what *it* is. – Jonathan Wood Dec 09 '18 at 20:46
  • 1
    Retrieve it from the dictionary as an `IComponent` and then cast it to a `Position`. Since you are only pulling `Positions`, the cast should always succeed (though I'd use `is` or `as` to make sure and prevent an `InvalidCastException`) – Flydog57 Dec 09 '18 at 20:50

2 Answers2

5

The short answer: You can't. If dictionary is of type Dictionary<Type, IComponent> then it will return only IComponent.

However you could create an extension method for this:

public static class DictionaryExtensions
{
    public static TComponent GetValueOrNull<TComponent>(this Dictionary<Type, IComponent> dict) where TComponent : IComponent
    {
        dict.TryGetValue(typeof(TComponent), out IComponent component);
        return component as TComponent;
    } 
}

And use it:

internal class Movement : ISystem
{
    public void Update()
    {
        foreach (Entity entity in EntityPool.activeEntities.Values) // Loop through all entities
        {
            var posComponent = entity.Components.GetValueOrNull<Position>();

            if (posComponent != null)
            {
                // some code
            }
        }
    }
}
Fabjan
  • 13,506
  • 4
  • 25
  • 52
  • 2
    This is not a bad answer, but just be aware that there is a more general convention with TryGet.... methods. These tend to return a bool (true if it got it and false if not there), and returns the actual value as an out parameter. (See Dictionary for an example, but even the various things like int.TryParse() is basically the same convention). – Adam G Dec 09 '18 at 21:14
  • Yeah, I've changed the name to GetValueOrNull – Fabjan Dec 09 '18 at 21:26
  • I know there is hard to reach the result but does he is really out of chance? What about this approach? https://stackoverflow.com/questions/972636/casting-a-variable-using-a-type-variable especially dynamic keyword. Bad side of that solution will be slow performace in runtime and there is very big limitation of manipulation with objects. – VitezslavSimon Dec 09 '18 at 21:33
1

If you insert an item of type IComponent, you can only retrievee the item as IComponent. If you ask the Dictionary for a specific type, you can cast to the type directly.

        foreach (Entity entity in EntityPool) // Loop through all entities
        {
            Dictionary<Type, IComponent> components = entity.Components;
            if (components.TryGetValue(typeof(Position), out IComponent positionComponent))
            {
                Position position = (Position)positionComponent;
                if (components.TryGetValue(typeof(MovementSpeed), out IComponent movementSpeedComponent))
                {
                    MovementSpeed speed = (MovementSpeed)movementSpeedComponent;
                    // TEST: move (1 * movementspeed) units
                    position.X += speed.Value;
                    position.Y += speed.Value;
                }
            }
        }

With linq you have a very efficient way, to operate on a list. Perhaps is this a better way for you. Here is an example:

    public void Update2()
    {
        List<IComponent> list = new List<IComponent>();
        list.OfType<Position>().ToList().ForEach(p =>
        {
            var speed = list.OfType<MovementSpeed?>().FirstOrDefault();
            if (speed.HasValue)
            {
                p.X = speed.Value.Value;
                p.Y = speed.Value.Value;
            }
        });
    }
Kostarsus
  • 37
  • 5