Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Re:creation - a top down action adventure about undeads [hiatus]  (Read 502066 times)

0 Members and 3 Guests are viewing this topic.

dabbertorres

  • Hero Member
  • *****
  • Posts: 505
    • View Profile
    • website/blog
Re:creation - a top down action rpg about undeads
« Reply #75 on: May 20, 2015, 08:59:41 pm »
I see the method Jabberwocky is talking about. The idea is that there isn't one vector for all Components, there are multiple vectors, one for each Component type.

I went with something very similar to what he is describing.

One system per component type, which contains a vector of all Components of that type (well in a container I called an "AssocMap" (I should rename it to something like "AssocVector"), which is essentially a combined vector/hash map so I can access Components via entity ID, but also have sequential memory storage. Prolly not a novel concept or even a best solution, but I liked the result).

My System Implementation

Then "entities" are simply an ID number, for which I use just use an unsigned int.

It's a little out of date, I have some changes I haven't pushed yet, but it should get my underlying concept across. :)


Anyway, as others have said, I'm absolutely loving the art and the art style!

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action rpg about undeads
« Reply #76 on: May 20, 2015, 09:14:07 pm »
Components would no longer be owned by entities; each vector would then store the components of multiple entities in the game.
Yeah, we've had some disscusion about this around the 2nd page of this thread.
I would love to implement this, but I need some ideas to be able to get particular component by its type. Like this:
auto graphicsComponent = componentSystem->get<GraphicsComponent>(entityId);
Any ideas how to do this if I have vectors for each component type in ComponentSystem?

As for creation and destruction of components... I'm thinking about object pools but I wonder how I could make this work, ha-ha.

i don't want to be nitpicking here but comparing ECS and OOP is like comparing mecanic and an internal combustion engine, one is just an organisation based on the concepts brought by the other. OOP is a paradigm and ECS is a pattern, well it's like you've discovered the lost ark but it exists among many patterns brought in the 90's by the "gang of four".  New paradigm brought by functional programing tends to make most of those design patterns a bit outdated but they still worth something in specific situations.
I should have been more exact. When I'm talking about OOP vs ECS, I'm talking about deep hierarchy trees (Entity->Character->NPC->Monster->Dragon etc.) vs ECS approach. Of course, I'm not against OOP. I'm using it a lot in other stuff. But it's not that great for entities and creating new types of stuff. It leads to well known problems and ECS is a great way to get rid of a lot of them!

In other words there's nothing wrong with OOP nor something "normal" or not with OOP, inheritance sucks over composition for sure and it tooks us a bunch of years and smell code to realize it, that's all.
Completely agreed! OOP is great for some things but sucks for others. Composition is not the only way to solve every problem. But ECS stuff is the main core of my engine that's why I talk a lot about it. :)

btw Elias i'd love to be able to do my own pixel art for my own games as you do, they're very convincing. If i may try to be constructive here i would say that the archer guy is a bit too flashy compared to the other characters.
Thanks and good luck! And I appreciate all the feedback so don't worry about providing it. I agree that it archers are more flashy than other characters, but I've yet to draw other characters. There's not many of them, so I'm just experimenting and look what stuff looks good and what stuff does not.
Some variety is good. :)

@dabbertorres, thanks for your idea, I'll see what I can come up with it in mind.
And thanks for your appreciation of my art. That means a lot to me, considering I'm a beginning artist. And I'm very glad that the style I'm doing my art in looks good enough. (It's not the greatest art I can do, but I can't spend a lot of time on art, so I have to throw out some shading, simplify some things, etc.)

« Last Edit: May 20, 2015, 09:15:59 pm by Elias Daler »
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re:creation - a top down action rpg about undeads
« Reply #77 on: May 20, 2015, 09:25:44 pm »
I would love to implement this, but I need some ideas to be able to get particular component by its type. Like this:
auto graphicsComponent = componentSystem->get<GraphicsComponent>(entityId);
Any ideas how to do this if I have vectors for each component type in ComponentSystem?
You have to map types to associative keys that share one type and can be used at runtime. There are multiple approaches: integers returned from templates, std::type_index, ...

Type erasure is the keyword here. You have to transform static type information into dynamic one.


By the way, I completely agree with dwarfman78 regarding OOP vs ECS, unfortunately the term "OOP" is often used wrongly because people think of all the things that can be done wrong with it. Some time ago I wrote something related:
Quote from: Nexus
I think we should not fall in the typical "OOP vs. components" discussion, in my opinion the view that these concepts are contradictory is a misunderstanding. Often, OOP is associated with overused inheritance and unflexible class hierarchies, and people forget that a entity component system is built on top of most OOP features, too (purists could even consider ECS as a sort of design pattern, but probably there's already another name for it).

Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action rpg about undeads
« Reply #78 on: May 20, 2015, 09:31:46 pm »
Type erasure is the keyword here. You have to transform static type information into dynamic one.
std::type_index is pretty cool, but how can I map it to particular std::vector?
std::map<std::type_index, std::vector<T>> componentVectors
would be cool to have but I can't do that. What are possible ways to do this?

So, this is what I hope to get:
T* getComponent<T>(int entityId) {
    auto componentVector = ...; // get vector!
    return &componentVector[entityId]; // and some error checking there, of course
}

By the way, I completely agree with dwarfman78 regarding OOP vs ECS, unfortunately the term "OOP" is often used wrongly because people think of all the things that can be done wrong with it. Some time ago I wrote something related:
Quote from: Nexus
I think we should not fall in the typical "OOP vs. components" discussion, in my opinion the view that these concepts are contradictory is a misunderstanding. Often, OOP is associated with overused inheritance and unflexible class hierarchies, and people forget that a entity component system is built on top of most OOP features, too (purists could even consider ECS as a sort of design pattern, but probably there's already another name for it).
Yeah, I'll be talking about "deep hierarchy/inheritance trees" in the future because it seems to be a lot more accurate term.
« Last Edit: May 20, 2015, 09:38:54 pm by Elias Daler »
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re:creation - a top down action rpg about undeads
« Reply #79 on: May 20, 2015, 09:46:05 pm »
How do I store different components in one vector?

Ahh, right!  I somehow missed that you stored your components in the Entity, rather than in the system.  As dabbertorres pointed out (heh), this only works if you store your components in a system, not in the entity.

So there's a system for each component type, e.g. PhysicsSystem which stores a vector of Physics components, e.g.

class PhysicsSystem
{
public:
   // called per-frame.
   // iterates over and updates every Physics component in the game as required
   void Update();

   // retrieve a Physics component
   // simple implementation, in real life you'd want bounds checking
   Physics* GetPhysics(unsigned int index) { return m_physicsComponents[index]; }

   Physics*  CreatePhysics(...);
   void DestroyPhysics(unsigned int index);

private:
   std::vector<Physics> m_physicsComponents;
}
 

You can use c++ templates to make this work for any component in the game, although again this approach is more complex than you need for many types of games.

An Entity is then just a collection of components like this:

enum ComponentType
{
   Physics,
   Graphics,
   // etc...
};

class Entity
{
private:
   // Maps a ComponentType to the index of the component, which must be looked up
   // via the corresponding ComponentSystem.
   // (This particular implementation does not support multiple components of the same type.)
   std::map<ComponentType, unsigned int> m_components;
};


1.  It invalidates any Component pointers or references which may currently be in use by your gamesystem code, since the vector moves in memory.  This can lead to some nasty dangling pointer/reference bugs if you're not very careful about when you add components to the vector, in relation to when you operate on components within the vector.
As a solution to this problem, typical ECS implementations use a sort of handles (e.g. integers containing information to index) instead of pointers and references.

For sure, handles are the way to go.  But even then you can run into trouble.  At some point, you need to turn a handle into a pointer or a reference to call functions on it. 

Here's an example of where you might get into trouble.

void SummonPet(Entity* pEntity)
{
   // ...

   // check my Mana component (or Vitals component, or whatever) to see if I have
   // enough Mana to summon the pet.
   int nManaToSummon = 10;

   Mana* pMana = ManaSystem::GetMana(pEntity);
   if (pMana->GetCurrent() < nManaToSummon)
   {
      return false;
   }

   // ... code to create pet entity here ...

   // decrement mana
   pMana->DecrementCurrent(nManaToSummon);
   // *** CRASH ***
   // The ManaSystem's vector of Mana component may have shifted,
   // because we created a new Entity (the pet) which may itself have required a Mana
   // component, and called ManaSystem::CreateMana.  This invalidates our pMana
   // pointer (dangling pointer bugs, ew!).

   return true;
};
 

____________

re:  move constructors and move assignment operators:

Yep, this is totally one way to solve the problem.

Another way is to just rely on the default destructors and constructors (bitwise copy), for your components, and control your component's lifetimes with a Component::Init() and Component::Destroy() function, which is where you would handle any dynamic mem allocation, for instance.
« Last Edit: May 20, 2015, 09:49:43 pm by Jabberwocky »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re:creation - a top down action rpg about undeads
« Reply #80 on: May 20, 2015, 09:46:14 pm »
std::map<std::type_index, std::vector<T>> componentVectors
would be cool to have but I can't do that. What are possible ways to do this?
Type erasure, as I said.
struct ComponentVectorBase
{
    virtual ~ComponentVectorBase();
};

template <typename T>
struct ComponentVector : ComponentVectorBase
{
    std::vector<T> components;
};
« Last Edit: May 20, 2015, 09:56:02 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action rpg about undeads
« Reply #81 on: May 20, 2015, 09:51:51 pm »
@Jabberwocky Thanks for your ideas!
But I'm afraid I need more generic approach to do this, because not every component has it's own system and I currently have 18 types of components. Who knows how many I'll add in the future?

@Nexus Oh yeah, that'll do it. Thanks!
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Jesper Juhl

  • Hero Member
  • *****
  • Posts: 1405
    • View Profile
    • Email
Re:creation - a top down action rpg about undeads
« Reply #82 on: May 20, 2015, 09:55:08 pm »
re:  move constructors and move assignment operators:

Yep, this is totally one way to solve the problem.

Another way is to just rely on the default destructors and constructors (bitwise copy) ...
Where do you get the "bitwise copy" notion from?
The default copy ctor does not do a bitwise copy - it does a member by member copy.
And if you are memcpy()'ing (a bitwise copy) an object, then it better be std::is_pod / is_trivially_copyable.

What exactly do you mean?
« Last Edit: May 20, 2015, 10:09:48 pm by Jesper Juhl »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re:creation - a top down action rpg about undeads
« Reply #83 on: May 20, 2015, 10:05:34 pm »
Here's an example of where you might get into trouble.
True, but as a user of such an ECS you have to be aware that any change to the structure of the components (their addition or removal) invalidates all component pointers and references. That's why you would re-acquire a pointer from the handle.

A component handle can also be encapsulated, so that you don't have to call getComponent() again and again:
ComponentHandle<C> h(components, entity);
C& component = h.get(); // or simply *h

By the way, before everyone reinvents the wheel here: EntityX is a nice ECS library for C++. I don't like too much how it mixes its event system in, and the fact that they provide multiple syntaxes for everything obscures the API a bit, but design-wise, it's definitely worth having a look at. Keep in mind that one tends to overengineer quite a bit when delving too deep into possible optimizations; you should stay realistic about your game's scope before incorporating all kinds of features and neat tricks that you can never exploit.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action rpg about undeads
« Reply #84 on: May 20, 2015, 10:12:08 pm »
Keep in mind that one tends to overengineer quite a bit when delving too deep into possible optimizations; you should stay realistic about your game's scope before incorporating all kinds of features and neat tricks that you can never exploit.
Totally. That's why I won't rebuild my ECS in near future (maybe when I have some spare time to experiment...). Making stuff like this is fun but I have no problems with perfomance now. My main focus is getting game to very playable state so I can release some demo for people to play around with and give me their feedback. I hope to finish it by the end of this summer. I'm feeling pretty anxious about it, so the demo won't we public for some time, I guess. :)

And I'll take a lot at EntityX some time.
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re:creation - a top down action rpg about undeads
« Reply #85 on: May 20, 2015, 10:26:27 pm »
Yep, honestly for a smallish-type game, I think your more simple ECS approach is probably best, Elias.  Like I said earlier, it gives all the flexibility of an ECS, a clean simple solution, but just not the performance benefits (which you likely don't need).


Where do you get the "bitwise copy" notion from?
The default copy ctor does not do a bitwise copy - it does a member by member copy.
And if you are memcpy()'ing (a bitwise copy) an object, then it better be std::is_pod.

What exactly do you mean?

Oh, yeah sorry.  That was confusing on my part.

What I mean is that normally the constructor, copy constructor, assignment operator and destructor is where you would (de)allocate new memory, if needed.  For example, let's say I have an ActiveEffects component, which stores what effects (e.g. spells in an RPG) an entity is currently affected by.

// This is a component which stores effects on an entity
class ActiveEffects
{
private:
   // These are the individual effects which this entity has.
   // e.g. Maybe I cast a spell called "wind runner" on myself that makes me run fast.
   // I need Effect* pointers because there are different kinds of Effect subclasses,
   // e.g.  MoveSpeedEffect, AttributeBoostEffect, etc.
   std::vector<Effect*> m_effects;  
};

Normally, you'd see those pointers and think, I need to write a whole lot of constructor/copy constructor/assignment operator/destructor logic, or I might end up with memory leaks or duplicate pointers. 

For example, your copy constructor might look like this:

ActiveEffects::ActiveEffects(const ActiveEffects& i_rhs)
{
   std::vector<Effect*>::const_iterator it = i_rhs.m_effects.begin();
   std::vector<Effect*>::const_iterator end = i_rhs.m_effects.end();
   for(; it != end; ++it)
   {
      const Effect* pOriginal = *it;
      // This next line doesn't even actually work, because Effect* is pointing at
      // a subclass, but you get the idea...
      Effect* pCopy = new Effect(*pOriginal);
      m_effects.push_back(pCopy);
   }
}
 

So, now every time your vector of ActiveEffect components moves in memory, the copy constructor and destructor is called on every each one.  This is a whole bunch of newing in the copy constructor, and a whole lot of deleting in the destructor.  That's really stupid, because we aren't changing any effects.

Move constructors can solve this.  But now every component in the game, and every data member they contain needs to have:
  • assignment operator
  • move assignment operator
  • copy constructor
  • move constructor

That's fine, and that's what they're designed for.But my approach is to skip all of those.

The default copy constructor does exactly what I want - it copies the pointers without newing or deleting the Effects.  But that means I can't delete any memory in the destructor (if I did, I would delete all my Effects every time the ActiveEffects vector grew).  Instead, I put my destructor logic in a function called Component::Destroy(), which is called by the ComponentSystem whenever we actually want to destroy a component, rather than just moving it.

Haha, wow this thread is going crazy.  2 new posts while I was typing this up!  Interesting game and tech discussion, I guess.  :)

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re:creation - a top down action rpg about undeads
« Reply #86 on: May 20, 2015, 10:39:33 pm »
True, but as a user of such an ECS you have to be aware that any change to the structure of the components (their addition or removal) invalidates all component pointers and references. That's why you would re-acquire a pointer from the handle.

Totally agreed.  That's all I was saying, too. 

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re:creation - a top down action rpg about undeads
« Reply #87 on: May 20, 2015, 10:52:38 pm »
Move constructors can solve this.  But now every component in the game, and every data member they contain needs to have:
  • assignment operator
  • move assignment operator
  • copy constructor
  • move constructor
Yes, but that doesn't mean the user has to implement them all. For move constructor and assignment operator, he can benefit from the = default mechanism.

The problem is that you're using raw pointers to store memory, which is generally a bad idea. If you used std::unique_ptr instead, then destructor and move ctor/op= would come for free.

If you additionally need copy semantics, you can use aurora::CopiedPtr, and things will just work fine. This smart pointer deep-copies through polymorphic hierarchies.
class MyClass
{
   std::vector<aurora::CopiedPtr<Effect>> effects;
};

MyClass origin = ...;
MyClass copy = origin; // BAM, deep-copy.

This is a really powerful idiom, I still wonder why not even Boost has something alike. Anyway, Aurora does :D


The default copy constructor does exactly what I want - it copies the pointers without newing or deleting the Effects.  But that means I can't delete any memory in the destructor (if I did, I would delete all my Effects every time the ActiveEffects vector grew).  Instead, I put my destructor logic in a function called Component::Destroy(), which is called by the ComponentSystem whenever we actually want to destroy a component, rather than just moving it.
Yes, that's also a possibility. The problem here is that you have to be extra-careful to call the right functions, while constructors and destructors impose a class invariant that guarantees this. For example, with exceptions, the manual calls can easily be forgotten.

@Elias: Tell us if we're going too much off-topic here, then we can continue in another thread ;)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

dwarfman78

  • Full Member
  • ***
  • Posts: 228
  • I'm bietzsche, Nietzsche !
    • MSN Messenger - cd4c@hotmail.com
    • View Profile
    • Email
Re:creation - a top down action rpg about undeads
« Reply #88 on: May 20, 2015, 10:53:17 pm »
Make games not ECS. 8)

(click to show/hide)
@dwarfman78
github.com/dwarfman78

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action rpg about undeads
« Reply #89 on: May 21, 2015, 08:47:02 am »
Some great discussion happening here. ;)

I've posted new dev log on my blog: https://eliasdaler.wordpress.com/2015/05/21/recreation-dev-log-april-may-2015/
It covers what I did in last two months. I'm pretty proud about the progress!
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler