Jump to content
  • Advertisement


  • Content Count

  • Joined

  • Last visited

Community Reputation

1 Neutral

About mujina

  • Rank

Personal Information

  • Interests

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. What could be a way of avoiding using inheritance and virtual methods when designing components for an entity-component-system? I'll be more specific about my design issue: I currently have different classes for different kinds of colliders (let's say, CircleCollider and LineCollider). My system that checks for collisions and updates the positions and/or velocities of my entities should be something like: for entity_i in alive_entities { collider_i = get_collider_of_entity(entity_i) // components of same kind are stored contiguously in separate arrays transform_i = get_transform_of_entity(entity_i) for entity_j in alive_entities { collider_j = get_collider_of_entity(entity_j) transform_j = get_transform_of_entity(entity_j) if check_collision(collider_i, collider_j) { update(transform_i) update(transform_j) } } } my problem is that I don't have a generic `get_collider_of_entity` function, but rather a function `get_circle_collider_of_entity` and a separate one `get_line_collider_of_entity`, and so on. (This happens because under the hood I am keeping a mapping (entity_id -> [transform_id, sprite_id, circle_collider_id, line_collider_id, ...]) that tells me whether an entity is using certain kinds of components and which are the indices of those components in the arrays containing the actual components instances. As you can see, each component class is corresponding to a unique index, namely the index position of the array of the mapping described above. For example, transforms are 0, sprites are 1, circle colliders are 2, line colliders are 3, and so on.) I am in need to write a system as the one in the snippet above. I can write several overloaded `check_collision` functions that implement the logic for collision detection between different kinds of geometric primitives, but my problem is that I am not sure how to obtain a generic `get_collider_of_entity` function. I would need something that would get me the collider of an entity, regardless of whether the entity has a circle collider, a line collider, a square collider, etc. One solution could be to write a function that checks whether in my internal entity_id -> [components_ids] mapping a certain entity has a collider at any of the indices that correspond to colliders. For example, say that the indices related to the collider classes are indices 10 to 20, then my function would do get_collider_of_entity (entity_id) { for comp_type_id in 10..20{ if mapping[entity_id][comp_type_id] not null { return components_arrays[comp_type_id][entity_id] } } return null } This could turn out to be pretty slow, since I have to do a small search for every collider of every entity. Also, it may not be straightforward to handle returned types here. (I'm working with C++, and the first solution - that is not involving inheritance in any way - would be returning a std::variant<CircleCollider, LineCollider, ... all kinds of components>, since I would need to return something that could be of different types). Another solution could be having some inheritance among components, e.g. all specific component classes inherit from a base Collider, and overrride some virtual `collide_with(const Collider& other)` method. Then I would redesign my mapping to probably reserve just one index for colliders, and then I would actual colliders in a polymorphic array of pointers to colliders, instead of having a separate array for CircleColliders, another for LineColliders, and so on. But this would destroy any attempt to be cache-friendly in my design, wouldn't it? That's why I am looking for alternatives. A third alternative would be to just have a single, only, Collider class. That would internally store the "actual type" ( aka what kind of collider it is ) with dynamic information (like an enum ColliderType). Then I would have all colliders have all members needed by any kind of colliders, and specific collision detection functions which I can dispatch dynamically that only use some of that data. (Practical example: a "Collider" would have a radius, and the coordinate for 2 points, and in case its type was "circle" it would only make use of the radius and of one of the 2 points - used as the center -, while if it was a "segment" it would only make use of the 2 points). My gut feeling is that this would bloat all colliders, and, even if the bloat could be reduced - using unions in some smart way for storing members? I wouldn't know how -, then still the design would be pretty brittle. I'm clueless and open for ideas and advice! How do you handle in general situations in which you have components that can be naturally modeled as subclasses of a more generic component class? Inheritance? Smart hacks with variants, templates, macros, custom indexing? Dynamic "internal" type?
  2. Thank you everyone for your answers! @Zakwayda What do you mean "what I posted won't necessarily reside on the stack"? Aren't arrays local and therefore created on the stack? (Unless one creates a dynamic one with ptr = new Component[N]) Regarding the instantiation of components, you would suggest for something like struct { vector<TransformComp> transforms; vector<ColliderComp> colliders; vector<SpriteComp> sprites; ... } components where all components are created (as defaults) just once at the beginning (and new ones are created whenever the vectors expand), and I then reference to them for "populating" an Entity? (Meaning, an entity is something like a list of indexes that refer to which components belong to that entity, and instantiating an entity just means instantiating this list, without any creation of new components, and maybe setting some members of its components, since the default ones may not work). For the third point, yes, I was pondering about what to go for: simple arrays? pointers? vectors? (smart) pointers to vectors? other collections, like std::array?) @DixiE Thank you for the comprehensive snippet! I am not very familiar with Rust. I had a look at the library and at the conf transcript, but I haven't been fully able to grok the contents. What seemed interesting in the conference transcript was the Generational Indexing. To me, it seemed a way to solve the "need for instantiation of new components" problem, since it's a way of marking unused components so that they are reused later for new entities (correct me if I'm wrong). I'll have to read everything again, because I am not sure I got any idea on how to solve the "not all entities use all components" problem (which is about addressing the inefficiencies due to allocating N components of all types at the beginning). Any reference for C++ on these topics? @Shaarigan Nice! What data structure do you use for the first point? (To keep all components of one kind, and expanding the collection in a thread-safe way. By the way, do you create N new components when you have the need for expanding one of those collections? Or do you just allocate additional memory and copy all old components to the new buffer, and then create components when you need them?) What do you mean with " This way we keep the performance impact on adding new component instances of a type to an entity instead of where the systems aquire them" ? I'm not sure I understand. Your technique (with entities that are just masks for which components they use) is similar to the ECS implementation described here: is it? Or is it much different? What do you mean exactly by "collecting entities before they are processed"? That you do something like: // running update for system S relevant_entities = vector for entity in entities { if entity.mask matched S.mask relevant_entities.push(entity) } for relevant_entity in relevant_entities { // do stuff on relevant_entity's components that are important for S } instead of just looping once over all entities and updating their components if their mask matches the system's mask? (I.e. everything in one loop instead of 2 loops, one for "filtering entities" and one for updating components). Interesting addition the one about locks and systems updating components in parallel! Any reference for that? (For ECS with parallel systems). @Dawoodoz I am sorry I did not understand a single word of you answer. Is it supposed to be an answer to this thread? Please don't take that as an offense, it is probably me not understanding what you said because it was too complicated for me! (Just starting out)
  3. Hi! I am trying to build a small 2D engine, and I was trying to model my entities using an (sort of) ECS (entity component system). Every entity is just an index, and all entities are represented by components of various kinds. All my components live in a struct of arrays, something like: struct { TransformComponent transforms[N]; SpriteComponent sprites[N]; ColliderComponent colliders[N]; ... } entities; Now, my question: Should I go for arrays on the stack, as in the example, or for arrays on the heap (e.g. just having `TransformComponent * transforms` and then creating new components with `new TransformComponent{...}`), or for a preallocated buffer of raw memory -either on stack or on heap- (e.g. `unsigned char transforms_buffer[N * sizeof(TransformComponent)]`) and then creating new instances using placement new (`new(&transforms_buffer) TransformComponent{...}`), or for using smart pointers/arrays? I feel there are so many different ways one could go, and I don't know which one I should prefer really. Thanks! MP
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!