0
\$\begingroup\$

I'm developing a top-down shooter in C++ using an entity-component system, and have run into a problem when attempting to refactor powerups. Currently I have a PowerupSystem that is essentially a load of if...else statements that directly modifies player entity attributes (for example speed).

I'd like to move towards using different powerup classes that inherit from a base PowerupComponent class that encapsulate powerup behaviour. An example of this would be SpeedPowerup. These derived classes would have a constructor that changes a value in the player entity, and a destructor that sets it back to its original value.

The problem I'm having is that the powerup constructors and destructors need access to the player entity data to make these changes (stored in my World class), giving me a circular dependency issue.

The dependency is:

world -> powerup system -> powerup -> world

Any ideas on how I could overcome this issue are appreciated.

Thanks in advance, Rich.

\$\endgroup\$
  • \$\begingroup\$ If the player attributes are attributes of the player entity, why are they in the world class? ;) \$\endgroup\$ – DMGregory Nov 26 '17 at 21:01
  • \$\begingroup\$ I'm not sure. I just put entities in the world because logically they will exist within the world created. Is there a better place to put them? \$\endgroup\$ – Rich. T. Nov 27 '17 at 12:15
  • \$\begingroup\$ Why don't you just make the powerup depend only on what it needs (player entity data)? Then you would have both world and powerup depend on something else, rather then a circular dependency. \$\endgroup\$ – Filip Milovanović Nov 27 '17 at 16:38
  • \$\begingroup\$ The problem I'm having is that powerups could potentially affect any other component. For example, player speed and weapon damage. \$\endgroup\$ – Rich. T. Dec 9 '17 at 10:50
1
\$\begingroup\$

The way I would design this would be based on physics collisions.

I would register the PowerUpSystem with the PhysicsSystem so that it can be notified about times when game objects collide so that it can handle those collisions.

void PowerUpSystem::OnCollisionEvent(const CollisionEvent& event)
{
  Entity* sender = event->GetSender();
  Entity* receiver = event->GetReceiver();

  //! do something here with the two entities.
}

I would expect the power-up entity to have some component type that your PowerUpSystem would look for and might verify that the player entity is actually the player and not some other entity. You wouldn't want some wandering NPC to be able to greedily acquire a power-up.

At this point, you have a component that knows what attribute to modify in what way and your entity instance. You could simply use delegation here like this:

void SpeedUpComponent::ApplyToEntity(Entity* entity)
{
   //! Apply the speed modification to the provided entity.
}

What I would do is separate the concept of a power-up and an aura/buff that has been applied so that you can easily track them separately and react accordingly.

So SpeedUpComponent applies a SpeedAuraComponent that could either then be tracked by the PowerUpSystem or by a separate aura/buff system.

\$\endgroup\$
  • \$\begingroup\$ Hi Naros, thanks for the explanation. I like the way you've organised things, but I'm not sure that it helps me with my problem. I think I'd just end up with a longer dependency chain, but one that would still be circular. world -> physics system -> powerup system -> entity -> powerup -> world From what I can tell in your code above the CollisionEvent class would need access to &world so that it can affect entity data. I think this would give me the same problem, but maybe I have misunderstood? \$\endgroup\$ – Rich. T. Dec 12 '17 at 18:37
  • \$\begingroup\$ Maybe my problem is the way I've structured the entities to begin with. In my world class I simply have vectors of components, with an entity being defined as the components at a particular index. For example the first entity might be a player, which is index 0. Index 0 of each component vector is the data for the player entity. I then just loop through all of the game systems passing reference to world, which includes each of the component vectors, e.g. graphicsResult = graphics.update(&world);. \$\endgroup\$ – Rich. T. Dec 12 '17 at 18:38
  • \$\begingroup\$ @Rich.T. Then basically your CollectionEvent would include the two entity indices and your various systems should have a way to query the World for those components based on those indices. I used an Entity object that effectively represents your indices but otherwise the same logic and approach hold true. \$\endgroup\$ – Naros Dec 12 '17 at 19:29

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.