6
\$\begingroup\$

I am writing a platform game using an entity-component system. Entity-World collisions are robust and work very well, and I'm now working on collisions between entities.

Just to be clear, in my code an Entity is a collection of Attributes (data) and a collection of pointers to Components (logic).

My Entity class has a sendMessage(messageType type, char* messageData) member function, which adds the messageType and messageData to a queue which is then used by the component interested in that message type. So in this example CollisionMessage is used by CollisionComponent and processed at the end of the tick with other messages (after normal collision checks).

My problem is that with every collision, I have two messages sent per collision, from each Entity to the other. This means that either I need to halve the forces I apply since it applies to each entity twice or I need to set up some Entities to only send collision messages and some to only receive collision messages.

So far things work fine when a player collides with an item, as it just gets picked up and removed from the world, but collisions between players and enemies are proving troublesome.

Is there a better way, or is there anything fundamental I've missed?

Thanks.

\$\endgroup\$
1
\$\begingroup\$

I don't think the collision should be handled by the entities at all, or by a component per entity, that need to be called by each entity.

If you only have two generic type of objects, for example actors and walls. Each time you collision detection loop runs, it will check the type of each object (a component provides this information?), for actor-wall one logic is executed, for actor-actor, another logic is executed. Lets assume walls cannot move without being promoted to actors (by changing the value of the relevant component), to maintain the system as simple as possible. No message is sent, anything happens inside the collision detection loop. For actor-actor, for example, each involved actor moves back half the length of the penetration vector. For actor-wall, the actor moves back full the length of the penetration vector, the wall does not move.

That is only an example. Having only two types of objects may not adapt to your game. And having the logic as part of the collision detection loop may not be suitable if you need many types of objects, or if all the types of objects are not known at design time (new types may be provided by loading a library later).

In resume: When two moving objects collide, which one sends the message, and which one receives it?

Answer: Do not use a message system at all. A message system is not suitable for this. The logic that handles the collision for a pair of entities must not by in the entity (or per entity component) and must run only once per pair, not two.

\$\endgroup\$
  • \$\begingroup\$ Sorry if my example was unclear, I'm only holding onto pointers to Components in my Entity class so other parts of the system can tell what Components are acting on it. A Component is called once per frame and iterates over every Entity in its vector. Therefore your suggestion to move objects by 1/2 the penetration amount in my EntityCollisionComponent should just work (as well as other movements based on their physical properties). \$\endgroup\$ – usm Sep 18 '13 at 8:23
  • \$\begingroup\$ I think this answer answers my original question the best - neither Entity sends a message to the other! I'll have a think about whether I still need separate components for detection and resolution (so that I can have interchangeable components for different collision behaviour) and how collision pair data can be passed around. \$\endgroup\$ – usm Sep 18 '13 at 8:43
1
\$\begingroup\$

After discussion with @teodron, and some tinkering on my own, I think I've found something satisfactory and hopefully architecturally sound:

So previously any Entity that needed to collide with other objects and the world would have their position checked and modified in CollisionComponent. I've moved the tile/world collision code into a separate WorldCollisionComponent (which does detection and resolution) and I created a new EntityCollisionDetectionComponent, which will only do detection.

Previously, and here's where I think I was getting confused, I would do collision detection and resolution at the same time, and then I would send an additional message. I had the mental model of a Component acting on a particular Entity so each iteration was from the 'perspective' of the entity.

Instead, the EntityCollisionDetectionComponent will detect pairs of entities colliding and send each Entity a message with the details of the entity it collided with. Essentially, I'm marking an entity as collidable if it contains an EntityCollisionComponent.

I will have an EntitiyCollisionResolutionComponent, which will wait on messages and modify the PositonAttribute and VelocityAttribute of the Entity. Entities that don't need to be moved on collision (e.g. coins and other pickups) won't need this component added to it.

In code this would be something like:

class EntityCollisionDetectionComponent : public Component
{
public:
    void init() { /*...*/ }
    void cleanup() { /*...*/ }
    void update()
    {
        for (size_t i = 0; i < entities_.size(); ++i)
        {
            for (size_t j = i; j < entities_.size(); ++j)
            {
                if (collision(entities[i], entities[j])
                {
                     entities[i]->sendMessage(ENTITY_COLLISION, entities[j]->data);
                     entities[j]->sendMessage(ENTITY_COLLISION, entities[i]->data);
                }
            }
        }
    }
private:
    // Actually a part of the base class.
    std::vector<Entity*> entities_;
}

class EntityCollisionResolutionComponent : public Component
{
public:
    void init() { /*...*/ }
    void cleanup() { /*...*/ }
    void update()
    {
        while (!messageQueue_.empty())
        {
            CollisionResponseMessage* msg = reinterpret_cast<CollisionResponseMessage*>(messageQueue_.front());

            Entity* entity = getEntityById(msg->entityId);
            /*

            ... (compute new position, velocity, etc) ...

            */

            messageQueue.pop();
        }
    }
private:
    // Actually a part of the base class.
    std::vector<Entity*> entities_;
    std::queue<char*> messageQueue_;
}

I'm not going to accept this answer, because although it's a starting point for my solution, there's still a couple of holes (e.g. I realized I should send messages to a message handler which then forwards it to components, instead of sending it to entities; I happened to read an article which talks about converting an existing codebase to Entity-Component design where the compatibility makes sense).

Also, I'm sure someone will have something to say about my use of char* and reinterpret_cast!

Perhaps if/when I refine my system I'll come back and update the answer and accept it, if no-one else has any other ideas.

\$\endgroup\$
  • \$\begingroup\$ Honestly, I'm not that bothered by your use of char* and reinterpret_cast. This highlights the difference between game development and other types of software development. Over on SO it's all about good programming practices and bullet proof software. Here on gamedev it's all about game development which is really about creating good experiences for players. Don't get me wrong, you should keep your eye open for cleaner solutions to your original question, but if, in the end, the game is just as fun (meaning it still works the way you intend it to on targeted platforms) all power to you. \$\endgroup\$ – NauticalMile Sep 17 '13 at 13:58
  • \$\begingroup\$ @NauticalMile Is this true for the AAA industry? I'm looking to get into the games industry in a technical position and I'll take any opportunity to improve my code. I understand that having something that works is best but I'm doing this primarily as a learning exercise. \$\endgroup\$ – usm Sep 17 '13 at 20:36
  • 1
    \$\begingroup\$ Sorry If I mislead you, I do not work in the games industry. I am just expressing what I believe to be the sentiment of the game development community. I get this impression from a couple of years of reading many blogs and forum sites such as this one (see this answer by Nicol Bolas for example), my own experience, and reading books on game development. \$\endgroup\$ – NauticalMile Sep 17 '13 at 23:29
  • \$\begingroup\$ Also, tenpn's answer makes it pretty clear to me that the games industry is more focused on results than loosely-coupled code. \$\endgroup\$ – NauticalMile Sep 17 '13 at 23:30
  • 1
    \$\begingroup\$ I've been in the AAA games industry for a few years now, and my remark made me laugh out loud. That said, avoiding undefined behavior is seen as essential these days, especially if you want to support multiple platforms with their own compilers and toolchains; You're in for a lot of lost time if you rely on a compiler specific quirk. Otoh, "good" software engineering practices tend to hurt performance in general, so don't go overboard with those. \$\endgroup\$ – usm Oct 18 '18 at 21:10
0
\$\begingroup\$

The answer greatly depents on how you store your entities. A very straightforward solution could just check collisions against entities that are stored "after" the one currently being processed e.g. if you store your entities in an array and update in a for-loop do collision checks only on entities with a higher array index than that of the current entitiy. This way each possible collision is only triggered once.

\$\endgroup\$
  • \$\begingroup\$ Yes, my entities are stored in an array in each component. Wouldn't this mean that entities with lower indexes will always be able to 'push' around higher indexed entities since they will always have priority? How can we deal with higher indexed entities walking into a lower index one? The lower index one will never receive a message since it's already sent a message to the higher index entity. \$\endgroup\$ – usm Sep 16 '13 at 10:15
  • 1
    \$\begingroup\$ @usm - by having a different observer dispatch the collision pairs to their appropriate handlers, you will not have to worry that certain objects "handle" the messages. The messages should be handled in a separate component, a collision response should be produced and sent to both colliding objects. That way, your entities only use collision response results. Collision detection should be a separate layer with its own data structures and entity filtering capabilities. \$\endgroup\$ – teodron Sep 16 '13 at 10:31
  • \$\begingroup\$ @teodron So are you saying that my CollisionComponent should determine pairs of collisions, and send the message to a Collision resolution component? That is essentially what I'm doing now, except I send the message to an Entity which then passes it on to any Component that accepts the message. \$\endgroup\$ – usm Sep 16 '13 at 13:52
  • \$\begingroup\$ The terminology seems confusing (I always thought that components are merely essential units of functionality that can be composed into entities).. in this case, you'll need some kind of a collision handler manager that records pairs of colliding objects and find suitable collision resolution responses to send to each of the involved entities. So, you don't delegate an entity with solving collisions, you delegate something that has a separate logical layer. \$\endgroup\$ – teodron Sep 16 '13 at 13:58
  • 1
    \$\begingroup\$ @usm it would seem a bit safer from an architectural POV. The reasoning is this: you have an intelligent broad-mid-narrow phase filtering for your entities. Then your colliding pairs (or n-body tuples!!) are processed and consistent forces and/or impulses are derived. This system that manages the collision flow should be put outside of the entity logic.. so that you won't bother thinking which is the special entity that processes messages and which has the right to send them. \$\endgroup\$ – teodron Sep 16 '13 at 15:42

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.