0

As a beginner I'll try to explain my problem as good as I can:

I'm currently trying to program a "simple" ECS. My basic idea is that I have a base "Entity class" which includes all sorts of functions for adding and removing components. Now I want to create some "templates" for entities, e.g.:

Entity Sprite:
Contains Texture
Contains Position
Contains Alpha

Let's suppose I want to create an NPC:

Entity NPC:
Contains Texture
Contains Position
Contains Alpha
Contains Text

I want to take the short route and say:

Entity NPC:
Contains "Components of Sprite"
Contains Text

In order to have access to all of the components of my derived class "Sprite" without actually needing an instance of it I wanted to store them in a static list which is also loaded when its constructor is called. Seems easy enough.

My problem now is that I want to have a function within the "Entity" class that takes a derived class, looks up its components and adds them to the current instance. This however fails because I'm not able to access the Entity's static fields. I've tried it with generics, but that attempt failed as well:

public void AddComponents<T> (T pEntity) where T : Entity {
    List<Component> componentsToAdd = pEntity.StaticComponents;
    Member 'Entity.BaseComponents' cannot be accessed with an instance reference;
    qualify it with a type name instead

I think I'm completely off track. Do I either need to completely forget about the static idea and just initialize a temporary Entity to use its Components? Is there a more elegant way to tackle this problem?

  • 9
    @Ewan: Stop spreading misinformation. If you could never use static, they wouldn't have put it into the language in the first place. – Robert Harvey Mar 19 '18 at 21:53
  • 9
    @Ewan: static in C# doesn't fit that category. It sounds like you either don't know how to use it properly, or you drank the TDD Koolaid that makes people allergic to static. – Robert Harvey Mar 19 '18 at 22:05
  • 1
    @RobertHarvey Well you seem to have a ton of experience, so I'm just going to bite the bullet and create instances. Thanks for your effort :) – Maxracer Mar 19 '18 at 22:18
  • 1
    You can either do that, or give up the idea of inheritance in a static context and specify types explicitly. – Robert Harvey Mar 19 '18 at 22:19
  • 2
    @RobertHarvey, I agree that Ewan lives in the past with his views on static, but for the record, it was "drinking the TDD koolaid" that got me back into using statics in a big way :) – David Arno Mar 20 '18 at 8:14
6

The XY problem is a commonly occurring problem, where trying to fix one thing causes a different issue.

I think you're a few levels deep here, and are essentially faced with an XYZ... problem. The complexity of what you're trying to do is massively out of scope with what you're actually trying to achieve.

So I'm going to take a few steps back here and start from the beginning. Or at least, I'm going to start from what I think your initial X problem was. If I'm wrong, please do correct me.


If all you have is a hammer, everything looks like a nail.

You've learned about inheritance, and are trying to solve everything by using inheritance.

You've labeled yourself as a beginner. I'm not sure how much of a beginner you are, but you should look up a few other approaches:

I picked these two topics for a specific reason: their main focus is on problems that people often first (wrongly) try to solve by using inheritance (and then end up regretting that decision).

If you don't know interfaces or composition, I suggest you read up on the links (other resources can be found on Google too of course). At least understand how they're used, as I will be referring to them in the rest of the answer.


Entity Sprite:
Contains Texture
Contains Position
Contains Alpha

Entity NPC:
Contains "Components of Sprite"
Contains Text

You should be using composition here:

Entity NPC:
Contains a Sprite object
Contains Text

Or, the C# equivalent:

public class Npc
{
    public String Text { get; set; }
    public Sprite Sprite { get; set; }
}

I read up on ECS following the link you provided, and nothing in the documentation states that components aren't allowed to implement each other. Following that, using one component as the property of another solves all your requirements.

This also means that if Sprite is updated at a later stage (e.g. a new property is added), that all components with a Sprite property are updated accordingly.


I think you're taking the explanation of ECS (as per your link) too literally.

Design of an entity
An entity only consists of an ID and a container of components. The idea is to have no game methods embedded in the entity. The container doesn't have to be located physically together with the entity, but should be easy to find and access

You're looking to implement an entity like this:

public class Entity
{
    public Guid Id { get; set; }
    public List<IComponent> Components { get; set; }
}

And it makes sense, up to a point. Based on your question, and how you've approach your example NPC and Sprite classes, I get the feeling that you think that every property should be a component in and of itself. It shouldn't be.

That is a much too granular approach. What you want to do is create classes (the normal C# way) which have properties and fullfill a goal (implemented through an interface) and which act as a component.
The properties of such a class are not necessarily components in and of themselves! They can be (like the Sprite in NPC, see example above), but they don't have to be components (you can have normal strings, ints, ...)

This feels like a theoretical design, more than a practical one. This is seemingly confirmed by the criticism listed on the page you linked:

Overall, ECS is a mixed personal reflection of orthogonal well-established ideas in general Computer science and Programming language theory.

I'm not going to tell you that ECS doesn't work, or isn't a good solution. But what I am certain of is that a pure ECS approach is much too complex for someone who labels themselves a beginner.

There are other approaches that maintain the intention of ECS, without taking such a hardline stance on the format. Observing the inheritance/interface/composition trichotomy that I've listed, an ECS-like system would heavily favor composition as its main architecture.

Let me show you a simple working example of an ECS-like approach which relies on composition. A short reminder of the important parts of the design:

The ECS architecture uses composition, not complex inheritance trees. An entity will be typically made up of an ID and a list of components that are attached to it. Any type of game object can be created by adding the correct components to an entity. This can also allow the developer to easily add features of one type of object to another, without any dependency issues.

For example, a player entity could have a "bullet" component added to it, and then it would meet the requirements to be manipulated by some "bulletHandler" system, which could result in that player doing damage to things by running into them.

I will use the bullet example later on.


Note

There are things here which can be approached in several ways. Some people prefer an abstract base class. Others prefer using an interface. I'm going to mix and match these based on what I think will be the clearest explanation. That doesn't mean the other option isn't viable.

This example is only an example. There is room for improvement, which I have omitted for the sake of both clarity and brevity.


Entity

Since the main goal of the entity class is to provide a singular data type on which all game objects are based; I'm not going to use inheritance or interfaces here.
Inheritance and interfaces only make functional sense when there are multiple classes that have shared logic/exposed properties and methods.

public class Entity
{
    public Guid Id { get; set; }
    public List<IComponent> Components { get; set; } = new List<IComponent>();
}

This maintains the goal of an entity: provide a singular type, where every object of that type will be a composition of components.

This class can be expanded with some logic to help you in the future, e.g. some utility methods that help with finding the components you're looking for:

public IComponent GetComponentByUniqueName(string name)
{
      return this.Components.SingleOrDefault(c => c.Name == name);
}

public bool HasComponent(string name)
{
      return this.Components.Any(c => c.Name == name);
}

public IEnumerable<IComponent> GetComponentsByName(string name)
{
      return this.Components.Where(c => c.Name == name);
}

Which methods to add to the entity class is left as an exercise to you.


Component

The entity code already reveals that we'll be using an interface here:

public interface IComponent
{
    public string Name { get; set; }
    public string Type { get; }
}

For the sake of example, I'm going to assume that a component only lists a name "MyBullet" and a type (e.g. "BulletComponent"). Any other properties are assumed to be specific to a given component type, and can therefore only be accessed by casting the IComponent object back to its main type.

Secondly, I would suggest relying on a base class here, because the logic for calculating the value of Type can be abstracted:

public class BaseComponent : IComponent
{
    public virtual string Name { get; set; }

    public string Type => this.GetType().Name;
}

An example of the bullet component can then be:

public class BulletComponent : BaseComponent
{    
     //Bullet-specific properties
     public bool IsArmorPiercing { get; set; }
     public double Damage { get; set; }
}

Seeing it in action

  1. Making a player entity who has a bullet component.
var myPlayer = new BaseEntity() { Id = new Guid() };

var bulletComp = new BulletComponent()
                     {
                         Name = "Player Bullet",
                         IsArmorPiercing = false,
                         Damage = 150
                     };

var myPlayer.Components.Add(bulletComp);
  1. Making a bullet handler.
public class BulletHandler
{
      public bool CanHandle(BaseEntity entity)
      {
            return entity.HasComponentType("BulletComponent");
      }

      public void Handle(BaseEntity entity)
      {
          if(!CanHandle(entity)) return;

          var bulletComp = entity.GetComponentByType("BulletComponent") as BulletComponent;

          //Further handling, e.g.:
          int damageDone = bulletComp.Damage;
      }
}

Note: There is a lot of room for improvement here. You can use a generic Handler<T> (where in this case T is BulletComponent), you can avoid the magic string typing, ... This is just a basic example to show you how to check if an entity has a particular component, and how to access its properties.


Update

I seem to have skipped over your mention of using static here (or I read it and then forgot about it).

You shouldn't be using statics here. It's not what you want. Statics should be used for globally accessible values. As a simple example, the version number ("0.1") of your current application is the same for your entire application, you never want to use two different version numbers at the same time; therefore the version number can be contained in a static scope so everyone uses the same value at all times.

But your components should still retain their (object-specific) individuality. As a simple example, player A's name shouldn't be the same as a player B's name, so their Name property(/component) shouldn't be static.

I can't think of a place where statics would meaningfully improve the code in this answer.

Note: There may be ulterior reasons for using statics not related to the code in this answer, but that's of course out of scope for my statement here.

  • Well damn, thanks for your input. I really have to work on my phrasing, sorry about that. Many thanks for your in-depth explanation - I currently struggle a lot with my approaches, your comments really help to understand your line of reasoning. I'm going to tinker around some more with ECS, but I completely agree that this project is probably way too complex for myself at the moment, but it's a good way to understand the up- and downsides of such a system. And yeah, I already ran into the hammer-nail-problem, kind of a shame that I fulfill every stereotype of a beginner :P – Maxracer Mar 26 '18 at 20:01
0

I think you're getting way too fancy here.

I want to take the short route and say:

Entity NPC:
Contains "Components of Sprite"
Contains Text

You can use simple utility functions here to achieve this same effect, like so:

void MakeSprite(Entity ent)
{
      ent.Add<Texture>();
      ent.Add<Position>();
      ent.Add<Alpha>();
}

void MakeNpc(Entity ent)
{
      MakeSprite(ent);
      ent.Add<Text>();
}

Something to this effect, and done. You don't have to jump through all these hurdles trying to generalize this solution to the heart of your ECS's "entity type registry" maintaining like a static list of components per type and having to have different entity subtypes.

I'm currently trying to program a "simple" ECS. My basic idea is that I have a base "Entity class" which includes all sorts of functions for adding and removing components.

Now this is admittedly a bit opinionated but I really think you're kind of counteracting some of the flexibility of the ECS if you use inheritance for the entities. In practice it might not make too much difference if the sole purpose of it is to simplify construction of common component combinations, but I'm tempted to question the mindset there. Because a lot of the power of the ECS comes from the idea that your entities are like a "blank slate", composing components to create new things on the fly (including at runtime, through script or editor), that you didn't even anticipate before.

Like I might have an entity, and what is this? It might start out having no components associated, so it's like a nada, a nil, a null thing that is just sitting in the scene. But then I might attach a camera component to it and cool, now I can look through it, though it can't be moved or oriented since it has no motion component. I might then attach a directional light to it and now it's like a camera that shines light in the direction it's looking, which might be useful to navigate in the dark. Now I might attach a motion component to it and now it can move around and be oriented in space. Then I might attach a weapon component to it, specifically a minigun, and it might now be capable of firing at things. I might then attach a script component to it which the interpreter runs repeatedly where, upon encountering a living creature in its line of sight, it will begin firing upon it, making it a "sentry" of sorts. Then I might attach a model and physics component to give it form and volume in space which also allows the physics system to pick it up and allow it to get swept away by forces like tornadoes. I might then link its camera to another camera in the scene at a security station which renders its output to a textured plane where I can look at what the sentry is seeing on a monitor inside the security station, only to discover that it's in a totally different location than where I put it and damaged since it got swept away by a tornado.

And this whole light-flashing camera sentry "thingamajig" which is influenced by physics was not something I anticipated in advance whatsoever. It's just something I whipped up on the fly using a generic entity and attaching an interesting combo of components to it to get what I wanted while playing with the engine at runtime, and it's through toying with such engines at runtime that got me so interested in the ECS as a way to handle constantly changing user-end requirements without intrusive design changes as I was accustomed to seeing where a lot of alternative architectural designs would require anticipating this "sentry light/camera/weapon thingamajig" possibility more upfront or else potentially face very costly design changes.

Yet thinking of different component combinations as yielding a different entity data type is somewhat moving away from this sort of mindset, and you might even inadvertently work your way towards making your engine incapable of dealing with particular component combinations in a predictable way if you go too far in the direction of thinking of like a camera entity as being a wholly different type of entity from a light entity instead of just the same generic entity type with a different combo of components to make it behave like a camera, or a light, or even both at the same time.

Of course, practically speaking, you might have a lot of common component combos you use that yield something of a "preset/template" for common entity/component combos you frequently create, and would otherwise call for a lot of code duplication to create if you listed every individual component type to add to each common combination, but you can eliminate such duplication with a simple and straightforward procedural approach without being tempted to subtype your entities, and I think those simpler approaches encourage a more generalized way to think about the ECS with the "has-a" composition mindset, not the "is-a" inheritance sort of mindset.

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.