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.
Entity
), and it looks like a reimplementation of polymorphism. \$\endgroup\$