120
\$\begingroup\$

I have two user cases:

  1. How would entity_A send a take-damage message to entity_B?
  2. How would entity_A query entity_B's HP?

Here's what I've encountered so far:

  • Message queue
    1. entity_A creates a take-damage message and posts it to entity_B's message queue.
    2. entity_A creates a query-hp message and posts it to entity_B. entity_B in return creates an response-hp message and posts it to entity_A.
  • Publish/Subscribe
    1. entity_B subscribes to take-damage messages (possibly with some preemptive filtering so only relevant message are delivered). entity_A produces take-damage message that references entity_B.
    2. entity_A subscribes to update-hp messages (possibly filtered). Every frame entity_B broadcasts update-hp messages.
  • Signal/Slots
    1. ???
    2. entity_A connects an update-hp slot to entity_B's update-hp signal.

Is there something better? Do I have a correct understanding of how these communication schemes would tie into a game engine's entity system?

\$\endgroup\$

10 Answers 10

70
\$\begingroup\$

Good question! Before I get to the specific questions you asked, I'll say: don't underestimate the power of simplicity. Tenpn is right. Keep in mind that all you're trying to do with these approaches is find an elegant way to defer a function call or decouple the caller from the callee. I can recommend coroutines as a surprisingly intuitive way to alleviate some of those problems, but that's a little off-topic. Sometimes, you're better off just calling the function and living with the fact that entity A is coupled directly to entity B. See YAGNI.

That said, I've used and been happy with the signal/slot model combined with simple message passing. I used it in C++ and Lua for a fairly successful iPhone title that had a very tight schedule.

For the signal/slot case, if I want entity A to do something in response to something entity B did (e.g. unlock a door when something dies) I might have entity A subscribe directly to entity B's death event. Or possibly entity A would subscribe to each of a group of entities, increment a counter on each event fired, and unlock the door after N of them have died. Also, "group of entities" and "N of them" would typically be designer defined in the level data. (As an aside, this is one area where coroutines can really shine, e.g., WaitForMultiple( "Dying", entA, entB, entC ); door.Unlock();)

But that can get cumbersome when it comes to reactions that are tightly coupled to C++ code, or inherently ephemeral game events: dealing damage, reloading weapons, debugging, player-driven location-based AI feedback. This is where message passing can fill in the gaps. It essentially boils down to something like, "tell all the entities in this area to take damage in 3 seconds," or "whenever you complete the physics to figure out who I shot, tell them to run this script function." It's difficult to figure out how to do that nicely using publish/subscribe or signal/slot.

This can easily be overkill (versus tenpn's example). It can also be inefficient bloat if you have a lot of action. But despite its drawbacks, this "messages and events" approach meshes very well with scripted game code (e.g. in Lua). The script code can define and react to its own messages and events without the C++ code caring at all. And the script code can easily send messages that trigger C++ code, like changing levels, playing sounds, or even just letting a weapon set how much damage that TakeDamage message delivers. It saved me a ton of time because I wasn't having to constantly fool around with luabind. And it let me keep all of my luabind code in one place, because there wasn't much of it. When properly coupled, you can use embedded languages like Lua to easily add new features/monsters/weapons/levels/etc to the game without ever recompiling the C++ code.

Also, my experience with use case #2 is that you're better off handling it as an event in the other direction. Instead of asking what the entity's health is, fire an event/send a message whenever the health makes a significant change.

In terms of interfaces, btw, I ended up with three classes to implement all of this: EventHost, EventClient, and MessageClient. EventHosts create slots, EventClients subscribe/connect to them, and MessageClients associate a delegate with a message. Note that a MessageClient's delegate target doesn't necessarily need to be the same object that owns the association. In other words, MessageClients can exist solely to forward messages to other objects. FWIW, the host/client metaphor is kind of inappropriate. Source/Sink might be better concepts.

Sorry, I kinda rambled there. It's my first answer :) I hope it made sense.

\$\endgroup\$
8
  • \$\begingroup\$ Thanks for the answer. Great insights. The reason I'm over designing the message passing is because of Lua. I'd like to be able to create new weapons without new C++ code. So your thoughts answered some of my unasked questions. \$\endgroup\$
    – deft_code
    Commented Jul 25, 2010 at 5:54
  • \$\begingroup\$ As for the coroutines I too am a great believer in coroutines, but I never get to play with them in C++. I had a vague hope of using coroutines in the lua code to handle blocking calls (eg, wait-for-death). Was it worth the effort? I'm afraid that I may be blinded by my intense desire for coroutines in c++. \$\endgroup\$
    – deft_code
    Commented Jul 25, 2010 at 5:57
  • \$\begingroup\$ Lastly, What was the iphone game? Can I get more information on the entity system you used? \$\endgroup\$
    – deft_code
    Commented Jul 25, 2010 at 5:58
  • 2
    \$\begingroup\$ The entity system was mostly in C++. So there was, for example, an Imp class that handled the Imp's behavior. Lua could change Imp's parameters at spawn or via message. The goal with Lua was to fit in a tight schedule, and debugging Lua code is very time-consuming. We used Lua to script levels (which entities go where, events that happen when you hit triggers). So in Lua, we'd say things like, SpawnEnt( "Imp" ) where Imp is a manually registered factory association. It would always spawn into one global pool of entities. Nice and simple. We used a lot of smart_ptr and weak_ptr. \$\endgroup\$
    – BRaffle
    Commented Jul 27, 2010 at 13:03
  • 1
    \$\begingroup\$ So BananaRaffle: Would you say this is an accurate summary of your answer: "All 3 of the solutions you posted have their uses, as do others. Don't look for the one perfect solution, just use what you need where it makes sense." \$\endgroup\$
    – Ipsquiggle
    Commented Sep 8, 2010 at 16:19
81
\$\begingroup\$
// in entity_a's code:
entity_b->takeDamage();

You asked how comercial games do it. ;)

\$\endgroup\$
7
  • 8
    \$\begingroup\$ A down vote? Seriously, that's how it's normally done! Entity systems are great but they don't help hit the early milestones. \$\endgroup\$
    – tenpn
    Commented Jul 22, 2010 at 8:18
  • \$\begingroup\$ I make Flash games professionally, and this is how I do it. You call enemy.damage(10) and then you look up any info you need from public getters. \$\endgroup\$
    – Iain
    Commented Jul 22, 2010 at 12:29
  • 7
    \$\begingroup\$ This is seriously how commercial game engines do it. He's not joking. Target.NotifyTakeDamage( DamageType, DamageAmount, DamageDealer, etc. ) is usually how it goes. \$\endgroup\$ Commented Jul 22, 2010 at 15:16
  • 3
    \$\begingroup\$ Do commercial games misspell "damage" too? :-P \$\endgroup\$
    – Ricket
    Commented Aug 1, 2010 at 17:12
  • 16
    \$\begingroup\$ Yes, they do misspel damage, among other things. :) \$\endgroup\$
    – CodeSmile
    Commented Aug 7, 2010 at 14:18
17
\$\begingroup\$

A more serious answer:

I've seen blackboards used a lot. Simple versions are nothing more than struts that are updated with things like an entity's HP, which entities can then query.

Your blackboards can either be the world's view of this entity (ask B's blackboard what its HP is), or an entity's view of the world (A queries its blackboard to see what the HP of A's target is).

If you only update the blackboards at a sync point in the frame, you can then read from them at a later point from any thread, making multithreading pretty simple to implement.

More advanced blackboards may be more like hashtables, mapping strings to values. This is more maintainable but obviously has a run-time cost.

A blackboard is traditionally only one-way communication - it wouldn't handle the dishing out of damage.

\$\endgroup\$
3
  • \$\begingroup\$ I had never heard of the blackboard model before now. \$\endgroup\$
    – deft_code
    Commented Jul 22, 2010 at 8:24
  • \$\begingroup\$ They are also good for reducing dependencies, the same way an event queue or publish/subscribe model does. \$\endgroup\$
    – tenpn
    Commented Jul 22, 2010 at 10:55
  • 2
    \$\begingroup\$ This is also the canonical “definition” of how an “ideal” E/C/S system “should work.” The Components make up the blackboard; the Systems are the code acting upon it. (Entities, of course, are just long long ints or similar, in a pure ECS system.) \$\endgroup\$
    – BRPocock
    Commented Dec 22, 2011 at 21:43
9
\$\begingroup\$

I have studied this issue a bit and I have seen a nice solution.

Basically it's all about subsystems. It is similar to the blackboard idea mentioned by tenpn.

Entities are made of components, but they are only property bags. No behavior is implemented in entities themselves.

Let's say, entities have a Health component and a Damage component.

Then you have some MessageManager and three subsystems: ActionSystem, DamageSystem, HealthSystem. At one point ActionSystem does its calculations upon the game world and generates an event:

HIT, source=entity_A target=entity_B power=5

This event is published to the MessageManager. Now at one point in time the MessageManager goes through the pending messages and finds that the DamageSystem has subscribed to HIT messages. Now the MessageManager delivers the HIT message to the DamageSystem. The DamageSystem goes through its list of entities which have Damage component, calculates the damage points depending on the hit power or some other state of both entities etc. and publishes event

DAMAGE, source=entity_A target=entity_B amount=7

The HealthSystem has subscribed to the DAMAGE messages and now when the MessageManager publishes the DAMAGE message to the HealthSystem, the HealthSystem has access to both entities entity_A and entity_B with their Health components, so again the HealthSystem can do its calculations (and maybe publish corresponding event to the MessageManager).

In such a game engine, the format of messages is the only coupling between all the components and subsystems. The subsystems and entities are completely independent and unaware of each other.

I don't know whether some real game engine has implemented this idea or not, but it seems pretty solid and clean and I hope someday to implement it myself for my hobbyist level game engine.

\$\endgroup\$
1
  • \$\begingroup\$ This is a much better answer than the accepted answer IMO. Decoupled, maintainable and extendible (and also not a coupling disaster like the joke answer of entity_b->takeDamage();) \$\endgroup\$ Commented May 26, 2017 at 16:39
5
\$\begingroup\$

Why not have a global message queue, something like:

messageQueue.push_back(shared_ptr<Event>(new DamageEvent(entityB, 10, entityA)));

With:

DamageEvent(Entity* toDamage, uint amount, Entity* damageDealer);

And at the end of the game loop/event handling:

while(!messageQueue.empty())
{
    Event e = messageQueue.front();
    messageQueue.pop_front();
    e.Execute();
}

I think this is the Command pattern. And Execute() is a pure virtual in Event, which derivatives define and do stuff. So here:

DamageEvent::Execute() 
{
    toDamage->takeDamage(amount); // Or of course, you could now have entityA get points, or a recognition of damage, or anything.
}
\$\endgroup\$
4
\$\begingroup\$

If your game is single player, just use the target objects method (as tenpn suggested).

If you are (or want to support) multiplayer (multiclient to be exact), use a command queue.

  • When A does damage to B on client 1 just queue the damage event.
  • Synchronize the command queues via the network
  • Handle the queued commands on both sides.
\$\endgroup\$
2
  • 2
    \$\begingroup\$ If you're serious about avoiding cheating, A doesn't daamge B on the client at all. The client owning A sends an "attack B" command to the server, which does exactly what tenpn said; the server then syncs that state with all relevant clients. \$\endgroup\$
    – user744
    Commented Aug 1, 2010 at 11:46
  • \$\begingroup\$ @Joe: Yes, if there is a server that is a valid point to consider, but sometimes it's ok to trust the client (e.g. on a console) to avoid heavy server load. \$\endgroup\$
    – Andreas
    Commented Aug 6, 2010 at 6:39
2
\$\begingroup\$

I would say: Use neither, as long as you don't explicitly need instant-time feedback from the damage.

The damage-taking entity/component/whatever should push the events to either a local event-queue or a system on an equal level that holds damage-events.

There should then be an overlaying system with access to both entities that requests the events from entity a and passes it to entity b. By not creating a general event-system that anything can use from anywhere to pass an event to anything at any time, you create explicit data-flow which always makes code easier to debug, easier to measure performance, easier to understand and read and often leads to a more well-designed system in general.

\$\endgroup\$
2
\$\begingroup\$

So what happens if we have player A and B trying to hit eachother in the same update() cycle? Suppose the Update() for player A happens to occur before the Update() for player B in Cycle 1 (or tick, or whatever you call it). There's two scenario's I can think of:

  1. Immediate processing through a message:

    • player A.Update() sees the player wants to hit B, player B gets a message notifying the damage.
    • player B.HandleMessage() updates the hitpoints for player B (he dies)
    • player B.Update() sees player B is dead.. he can't attack player A

This is unfair, player A and B should hit eachother, player B died before hitting A just because that entity/gameobject got update() later.

  1. Queueing the message

    • Player A.Update() sees the player wants to hit B, player B gets a message notifying the damage and stores it in a queue
    • Player A.Update() checks its queue, it's empty
    • player B.Update() first checks for moves so player B send a message to player A with damage aswell
    • player B.Update() also handles messages in the queue, processes the damage from player A
    • New cycle (2): Player A wants to drink a health potion so Player A.Update() is called and the move is processed
    • Player A.Update() checks the message queue and processes damage from player B

Again this is unfair.. player A is supposed to take the hitpoints in the same turn/cycle/tick!

\$\endgroup\$
6
  • 4
    \$\begingroup\$ You're not really answering the question but I think your answer would make an excellent question itself. Why not go ahead and ask how to resolve such an "unfair" prioritization? \$\endgroup\$
    – bummzack
    Commented Mar 22, 2011 at 7:15
  • \$\begingroup\$ I doubt that most games care about this unfairness because they update so frequently that it is rarely an issue. One simple workaround is to alternate between iterating forwards and backwards through the entity list when updating. \$\endgroup\$
    – Kylotan
    Commented Mar 22, 2011 at 20:21
  • \$\begingroup\$ I use 2 calls so I call Update() to all entities, then after the loop I iterate again and call something like pEntity->Flush( pMessages ); . When entity_A generates a new event, it is not read by entity_B in that frame (it has the chance to take the potion too) then both receive damage and after that they process the message of the potion healing which would be the last in the queue. Player B still dies anyway as the potion message is the last in the queue :P but it may be useful for other kind of messages such as clearing pointers to dead entities. \$\endgroup\$ Commented Aug 31, 2011 at 22:58
  • \$\begingroup\$ I think at the frame levels, most game implementations are simply unfair. like Kylotan said. \$\endgroup\$
    – v.oddou
    Commented Dec 17, 2014 at 1:56
  • \$\begingroup\$ This problem is insanely easy to solve. Just apply the damage to each other in the message handlers or whatever. You definitely shouldn't flag the player as dead inside of the message handler. In "Update()" you simply do "if(hp <= 0) die();" (at the beginning of "Update()" for example). That way both can kill each other at the same time. Also: Often you don't damage player directly, but through some intermediate object like a bullet. \$\endgroup\$
    – Tara
    Commented Aug 20, 2015 at 13:35
1
\$\begingroup\$

Just make the call. Do not do request-hp folllowed by query-hp -- if you follow that model you will be in for a world of hurt.

You might want to have a look at Mono Continuations as well. I think it'd be ideal for NPCs.

\$\endgroup\$
0
\$\begingroup\$

You have tagged the question as : "A programming paradigm in which gameobjects (entities) are composed out of components, and are operated upon by systems. Each entity is an ID that points to specific components." (note: the tags are a mess and this tag should really be called , ECS for short. Perhaps the prevailing terminology was different, back in 2010 when the question was asked.)

Yet, the question is about entities sending messages to each other. This is not something that exists in ECS. In an ECS architecture, components are where the data is, systems are the code that operates on the data, and entities just tie components together into coherent game objects. Entities don't do things except as defined by their components. Components don't really do things either, just tell systems what to do. When you are playing the game and you see a monster attack a player, the attack is done by (for example) the MonsterAttackSystem, not the MonsterEntity - in fact there is no MonsterEntity, just an Entity with a HealthBarComponent and a ChasePlayersComponent.

Of course, don't take this as specific advice - it might be that in your game you have something called AttackSystem or CombatSystem or ScriptingSystem, and CombatantComponent or ScriptableObjectComponent. The point is that the attack is done by a system and not by an entity, even if it makes the entity move on your computer screen and the strength of the attack is based on the entity's stats.

So with that in mind, both your questions need re-framing. Because the questions are vague, I will fill in specific examples:

How would [a spike trap] send a take-damage message to [a player]?

The TrapSystem may send a take-damage message to the HealthSystem.

One way to implement this is to directly call upon the HealthSystem: healthSystem.takeDamage(player, 20);.

Another way is to queue this message until it's the HealthSystem's turn to run: healthSystemQueue.append(TakeDamage(player, 20));

One advantage of the latter is that if the player gets hit multiple times in the same game tick, the order isn't perceptible to players - even if one trap is enough to kill the player, they can still be hit by multiple traps and a monster "simultaneously" without revealing which trap is processed first. Sometimes this is desirable. It also means the player can't become dead at any time outside of HealthSystem processing, which can prevent bugs.

The rendering system could read the same event and use it to display an icon showing the amount of damage, like in Runescape.

How would [a monster] query [a player]'s HP [in order to target the weakest player in range]?

The MonsterAttackSystem may query the HealthSystem to find out the player's health. This should be a direct function call, because there is no reason to defer it. If take-damage is deferred, then deferring query-hp as well makes no difference, but adds needless complexity.

At least, that works in the framework I am using, where health is private data of the HealthSystem. Depending on how your particular ECS framework does things, you might instead look up and query the HealthComponent for that entity. Details, details.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .