Replacing ECS with component based architecture, or traditional OOP, or something else?

Started by
15 comments, last by SuperG 8 years, 5 months ago

I am asking this question purely out of interest, and hopefully you guys can point some interesting links my way. I am a relatively new C++ programmer. I feel I have the basics down pact, but it is actually putting them all together in a software project that I find very very very very hard.

So I am making a 2d space ship game and came across ECS via this forum. I was intrigued by the concept and ended up using entitiyX in my game. The game is quite simple now (just the player ship, and some enemy ships that shoot at the player) and I am still learning how to properly use entityX to achieve my goals.

To be honest I am now coming to think of ECS as almost a 'convenience game API' for noobs like me that want to do all the stuff a game should do, but can't program it. For example I made it so my spaceship could shoot a spell, a fireball. To do this I created a SpellSpawner component (the invisible 'thing' I assign to entities that can shoot spells), a Spell component (the actual spell that is created), and the SpellSpawner System (the system which implements the spell logic). The main bit of the logic reads

for (entityx::Entity entity : entities.entities_with_components(position, spell))

which allows me to use those entities that have position and spell components,I then create the fireball entity with

SpellCreator(entity.id(), initVelocity, spellPosition.position, spellPosition.heading, spell->spellType, Colors::Orange).create(entities.create(), nullptr);

which creates my spell entity, and the spell 'moves' because it has components motion and position,and is picked up by the MovementSystem. Collision will also be taken care of because the spell entity has a collidable component. I now plan to add that an enemy ship goes on fire if hit with a fireball, or is frozen and slowed if hit with a ice bolt, and feel I can do this quite easily just with adding or removing components on the fly...

Now I have had a peek at the entityx source code.. it is some serious looking stuff, I have no Idea what is going on. (this probably applies to any ECS framework)....

So finally to my question, roughly how would I achieve the same sort of behavior (as in this spell example), without using ECS. From my readings on the forums, there are still Systems, which process the game logic, but how do they pick up what has to be done? And it seems 'entities' still exist, but not so much as an ECS entity, just a 'game object entity'. Basically what do the systems process, and how do you program this kind of flexibility? How do I create my 'spells' on the fly like this? It does seem like ECS by default is nicely decoupled as game code should be (I can comment out any one of the systems and the game will still run, just without that systems functionality..). Again, do the same concepts hold for non ECS game code?

I plan to program this same 'example scenario' without using ECS, hopefully to make me a better programmer. As amazing as it is for games, I feel it is not actually improving my programming, and makes everything 'too easy'. And yes I do want to program a game, but with the primary objective of learning C++.

Thanks

Advertisement

Well, your thread title does not make much sense. ECS is a buzzword in game development with no concrete definition. It just says that there are entities (also called game objects) which are composed of components. As such an ECS is a component based architecture. Furthermore, composing is the second programming idiom besides inheritance, and both are idioms of traditional OOP. Its just a fact that inheritance was overemphasized in many books and internet sites, so that composition was not recognized to the degree it should.

When you ask whether you should step away from composition and go with inheritance than think again. There is a reason why your trials in ECS world appear to be so smooth. Both composition and inheritance are tools, and you should use the right tool for the right problem.

Another thing to consider is the difference between code and data. It is much more elegant to make differences in game objects by parametrization (i.e. giving them different data) instead of inheriting another class. For example, the look of a spell is a different set of animation data and its damage is just a different value. So there is no need to make a Spell base class and inherit a FireBolt and an IceBolt class from it; the base class is sufficient (in this example; of course, situations may arise where having 1 or 2 inheritance levels would be okay).

The existence of "systems" to handle the components is a step that makes the management of all of the components easier. This is because an entity's components can be registered with the belonging systems when the entity is instantiated and added to the game world. Notice that components, although named commonly so, are ver different in their meaning (e.g. a Placement component and a SpellSpawner component). Due to their inherent differences, it is not logical to manage them the same way. If there are systems to manage them, then each system can be implemented in its own optimal way.

So, for me, going that way has no real alternative if you plan to do something more complex, especially if you don't want to re-program for each single game design decision. Of course, a small and isolated example like your scenario can be implemented in any other way, too.

In the C++ (and Java, etc) community, "traditional C++ OOP code" usually doesn't follow the actual teachings of OOD at all... so it's not really OOP code laugh.png

Most of the blogs where people try to set up this dichotomy between OOP and ECS as two radically different schools of thought, are written by people who only know that "traditional C++ OOP" (i.e. they don't really know OOP at all)... and their conclusions aren't necessarily correct. These people have been burnt by bad code that people incorrectly claimed was OOP, and have decided based on these claims that OOP is bad and wrong and doesn't utilize composition very well.

Real OOP puts emphasis on making larger, more complex objects, by composing them out of simpler components. So a good OOP design will actually be similar to one of these "ECS"/component designs.

If you want to learn C++ game programming, you don't need a big framework to get started. Just write the code that you need, solve the specific problems that you need, in plain C++.

Sorry I don't have anything helpful to add, but...

As such an ECS is a component based architecture.


The use of inheritance here made me chuckle.

Beginner in Game Development?  Read here. And read here.

 

Honestly... ECS is pretty much a flavor of OOP. Just a noob friendly version. OOP is intended to make as much of your code reusable as possible, or provide common low level interphases. It's really hard to explain... as I'm one of the many people who was taught it in it's "Traditional" way >.<


So finally to my question, roughly how would I achieve the same sort of behavior (as in this spell example), without using ECS. From my readings on the forums, there are still Systems, which process the game logic, but how do they pick up what has to be done? And it seems 'entities' still exist, but not so much as an ECS entity, just a 'game object entity'. Basically what do the systems process, and how do you program this kind of flexibility? How do I create my 'spells' on the fly like this? It does seem like ECS by default is nicely decoupled as game code should be (I can comment out any one of the systems and the game will still run, just without that systems functionality..). Again, do the same concepts hold for non ECS game code?

This is something that comes with maturity and experience.

When people start learning about programming they tend to think in very small units.

First it is struggling to understand a few lines of code. Two or three lines, then five and ten lines. Eventually forty or fifty lines of code is no longer intimidating. Soon they explode and write a 200+ line program all inside a single main function.

Then it expands to thinking about items at a function-level abstraction. It takes some mental rework, but soon they get comfortable with the idea of breaking out small logical units that can be reused.

Some time later they get comfortable with objects and classes and data packets. This slowly evolves over time, sometimes taking many years. Many programmers fail to advance beyond this point. The fact that you can have little bundles of both data and behavior and that they can interact in amazing ways is enough for most software.

After even more maturity, developers begin to envision larger collections as systems. Thinking in terms of broad systems tends to be a difficult jump. Thinking in terms of systems is not the same as thinking in terms of abstractions. It is understanding the inner pieces well enough that you can envision many different moving parts as they relate to each other in motion, and understand the full collection.

Any time you start building comprehensive systems, like a well-designed ECS or game engine, you need to have a good understanding of all the parts inside it and how they flow together into a whole.

When you build up well-designed systems with well-crafted modules and data flows, with experience you can figure out how all the different pieces are attached and how you can add or remove modules to the system so they fit neatly together.

You can gain knowledge by reading and studying what others have done. It is important, but is not experience.

You build well-working systems by applying your experience. You gain experience easiest by building and working with systems, especially with badly-working systems.

Whilst not 100% accurate, I generally say a game architecture uses an inheritance based system if a game object inherits from more than one ancestor. I.e Object <- MovableObject <- Enemy <- BadMan

If minimal inheritance is used, i.e the architecture prefers composition to inheritance such as Component <- Position and then a position component is added to multiple objects, then I would suggest this one uses the component entity system.

That is in C++. In C, I find the best way for OOP is the component entity system since large chains of inheritance can get complex.

http://tinyurl.com/shewonyay - Thanks so much for those who voted on my GF's Competition Cosplay Entry for Cosplayzine. She won! I owe you all beers :)

Mutiny - Open-source C++ Unity re-implementation.
Defile of Eden 2 - FreeBSD and OpenBSD binaries of our latest game.
OP: I think where some of your confusion comes from is the boilerplate code that frameworks such as EntityX use under the hood in order to implement their behavior. Part of the complexity involves structuring an object based on some bit of data that describes the structure of that object. For example, it is easy enough to 'compose' an object using concrete members to create an aggregate class:


struct Thing
{
  Position pos;
  Orientation orient;
  Velocity vel;
  OtherData d1;
  SomeMoreData d2;
};
Such structure is enforced at compile-time, making ad-hoc object description cumbersome. Such a composition structure can be every bit as rigid as a deep hierarchy tree, which also is enforced at compile-time.

Composition frameworks build a system where an aggregate object can be described at run-time, instanced at run-time from a data description. Objects in these frameworks are ad-hoc in nature, and quite often instanced through a system that reads a data file in some format, and populates a generic container with a list of components. The structure of the object is not determined at compile time. Due to the flexibility of such a system, quite a bit of seemingly-complex boilerplate code is necessary. That is what EntityX and others offer: they write the boilerplate so you don't have to.

Essentially, such a framework needs to implement a few standard behaviors. 1) Construct an aggregate object from a descriptor of components. 2) Facilitate communication between objects and components (ie, via event passing or some other scheme) 3) Handle update/render/etc... main loop functionality 4) Provide tools for object lifetime management, instancing, serializing, etc...

While the concepts of a composition-based framework are relatively simple, the concrete execution of them in C++ quite often involves some fairly complex code. Additionally, such a system is STILL probably going to make extensive use of inheritance. For example, an object that implements the basic container for a list of components works well if all components inherit from a base Component class, to enable storing them in a single vector. Objects that can receive events work well if they inherit from some sort of base EventReceiver class, so that the core systems can hand off an event to an object without caring about its ultimate type.


a game object inherits from more than one ancestor. I.e Object <- MovableObject <- Enemy <- BadMan

Please no. Don't do that.

Inheritance means it should be exactly substitutable as a replacement. That is NOT what you are doing here.

In that specific example, what happens if you need an enemy that is not moveable?

There are many other similar examples, making something that jumps, something that flies, something that swims, but then nightmares occur when you need to make JumpingAndFlying, JumpingAndSwimming, FlyingAndSwimming, JumpingAndFlyingAndSimming ... Don't go there.

Game objects tend to be COMPOSED OF other behaviors and properties, so composition is the right tool.

Inheritance is an exact "IS A" relationship that requires perfect substitution. Inheritance is also one of the strongest relationships you can grant inside code. (In C++ specifically, the only stronger relationship is friend.)

Inheritance can be useful for interfaces, where a leaf object can inherit from multiple interfaces and abstract base classes. But otherwise, inheritance should be thoughtfully considered and normally rejected in favor of composition.

There are two different object-oriented programming (OOP) techniques in your toolbox ; Inheritance and Composition. For whatever reason, OOP education tends to ignore composition, and as a result OOP is synonymous with inheritance. ECS's happen to use composition, so they seem revolutionary and new at first glance.

However, like all toolboxes, sometimes you need a screwdriver, sometimes you need a hammer...

[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler

This topic is closed to new replies.

Advertisement