Jump to content
  • Advertisement
Sign in to follow this  
Galaban

Game event messaging, subtyping, and atomicity

This topic is 2527 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm sketching out the design of my game messaging system and find myself unsure of how the implementation details of the Message class and the distinctly different member variables needed for different types of events.

In general, the is a very clear seperation between the model of the game and its view. Commands are sent from the controller to the model, all internal calculations regarding the effects of these commands are calculated within the model, and a sequence of messages is sent to all registered subscribers. The view recieves these messages and uses them to calculate what it should display to represent an action. These events can also be piped to, say, the displayed combat log or a module for saving replays.

When considering the design of a Message class, it's fairly clear the different times of events require different parameters. A castSpell event requires a caster, the spellID, intended targets, and so forth. A recieveDamage event requires the recipiant, amount of damage, damage type, and possibly other flags indicating whether it was a critical hit and so forth.

As I understand it, older methods of using a switch statement on a messageType parameter so that the message handler knows how to handle each class of message (or using RTTI to similar effect) are considered bad form, in favor of more proper use of the message's inherant polymorphism. Of course, one cannot put the handling routines in the message themselves, since the same message will be handled differently by different subscribers (eg: the game view vs. combat log vs. replay recorder). From what I read, this is where double dispatch tends to be used, routing each specific subclass of message to its own handling routine (eg: doCastSpell, doRecieveDamage, etc.)

A problem with double dispatch in this case is that events are not strictly atomic. Well, perhaps that's not strictly true; each event instance passed does represent a discrete event, but in many cases these events are part of a larger event.

For example, say that a unit casts a fireball spell and damages a number of units with it. This single action (casting fireball) results in a sequence of events, eg:

castSpell(caster, fireball, targets)
recieveDamage(target1, 15, fire)
recieveDamage(target2, 12, fire)
recieveDamage(target3, 21, fire)

In order for the view to properly display the results of this action, it needs to read multiple message in a row (in this case, so that it knows what damage numbers to display over each unit when the fireball animation explodes, although there are more complex instances of this). If each message was routed to a seperate handling function (such as via double dispatch), it seems like this would be significantly trickier to do. The visualization of a recieveDamage event on its own is a meaningless concept; it can only be properly scheduled in the context of the larger sequence of events.

Does this mean that, despite the code smell associated with switch-on-class-type, I might indeed be better off using such an approach, if the handling of one message requires knowing subtypes of subsequent messages? Is there a more elegant approach that I am overlooking here? Any feedback or suggestions would be welcome.

Share this post


Link to post
Share on other sites
Advertisement
Personally i think you are making this overly complicated. I would do something like the following


void WizardUnit::CastSpell( fireball )
{
if ( EnoughMana() )
TriggerCastSpellEvent( this, fireball, spellRange );
}


void SpellController::OnCastSpellEvent( unit, fireball, spellRange )
{
vector enemiesInRange = GetUnitsInRange( unit->Position, spellRange );
foreach ( enemy in enemiesInRange )
{
enemy->TakeDamage( FIRE, 10 );
}
}


void GraphicsController::OnCastSpellEvent( unit, fireball, spellRange )
{
RenderSpellAnimation();
}


void Enemy::TakeDamage( FIRE, amount )
{
if ( !ShieldUp() )
{
TriggerDamageTakenEvent( this, FIRE, amount * currentFireResistance );
}
}


void GameLog::OnDamageTakenEvent( enemy, FIRE, amount )
{
LOG << enemy->Name << " takes " << amount << " damage!";
}


void GraphicsController::OnDamageTakenEvent( enemy, FIRE, amount )
{
RenderFloatingNumber( enemy->Position, amount );
}




I think you get the point. I don't think events should know about subsequent events it may or may not trigger, the beauty of a good events system is that it knows about other events and objects as little as possible.

Check out boost::signals if you don't know about it already

Share this post


Link to post
Share on other sites
Thanks for the code sketch and I will admit that me overthinking things is totally possibly, but I think I may still have a few issues.


I don't think events should know about subsequent events it may or may not trigger, the beauty of a good events system is that it knows about other events and objects as little as possible.


I agree in general, though I still wonder how to properly schedule animations involving a series of events. The code above seems to suggest that the animation for the spell cast is performed via GraphicsController::OnCastSpellEvent and then damage numbers are displayed after this is finished via GraphicsController::OnDamageTakenEvent. However, there is a specific point in the fireball animation where damage should be displayed and this point is not after the entire animation is finished. Moreover, the proper point might be in different places for different spell animations. This is especially true for things which score multiple hits, or hit multiple targets in sequence. For example, a piercing projectile attack should display the damage numbers over each target's head as the visualization of the projectile passes through them. The DamageTaken events themselves would have no idea how to properly stagger the time of their appearance; this information would only be possible to calculate when the spell that caused this damage is known.

Share this post


Link to post
Share on other sites

Thanks for the code sketch and I will admit that me overthinking things is totally possibly, but I think I may still have a few issues.

[quote name='widowfactory' timestamp='1315949070' post='4861262']
I don't think events should know about subsequent events it may or may not trigger, the beauty of a good events system is that it knows about other events and objects as little as possible.


I agree in general, though I still wonder how to properly schedule animations involving a series of events. The code above seems to suggest that the animation for the spell cast is performed via GraphicsController::OnCastSpellEvent and then damage numbers are displayed after this is finished via GraphicsController::OnDamageTakenEvent. However, there is a specific point in the fireball animation where damage should be displayed and this point is not after the entire animation is finished. Moreover, the proper point might be in different places for different spell animations. This is especially true for things which score multiple hits, or hit multiple targets in sequence. For example, a piercing projectile attack should display the damage numbers over each target's head as the visualization of the projectile passes through them. The DamageTaken events themselves would have no idea how to properly stagger the time of their appearance; this information would only be possible to calculate when the spell that caused this damage is known.
[/quote]

From the point of the event system, the example you give should not actually be involved. The first thing I would suggest is rethinking the method of communicating certain bits of data. Let's take the "cast fireball" example and break it down:

The game object state:
Waits for the "time" to cast to hit and watches for input.
If time to "cast" occurs, post "cast fireball" event.
If movement/interrupt occurs, post nothing and just cancel the wait to cast.

The animation controller:
When game object "prep-cast" state is entered: blend into the start cast animation.
When game object state exits, it tells us to play "cast" or "cancel" for the fireball.

The events you actually "send":
Pre-cast: no events at all.
Cancelled: no events at all.
Cast: "cast fireball".

Let's "assume" that the fireball has a delay time till hit and that it has an area of effect component so applies most damage to the target but applies some to those around the target when it eventually hits. This is rather complicated but the "event" system has nothing to do with it directly and there should be no "context" moving with the events except in reference to a static data descriptor. Assuming the fireball, you can describe the event chain with the following:

event( CastSpell, "Fireball" ) {
Execute={DelayedEvent( "PerformCast", 10 ), PlayAnimation( "Warmup" )};
AnyMovement=Cancel;
}

event( PerformCast ) {
Execute=PlayAnimation( "Cast" );
}

The pseudo code is basically just data structures that the code would look at. When "CastSpell/Fireball" is executed it simply passes an event with reference to the "Fireball" description. These events "chain" based on the content described. So, if "Execute" is a "std::vector" of events, you simply walk through them as normal and post them, or set a timer to post them later. As such, you should probably reconsider how you are thinking about your events. Events should be light, should not contain "state" information beyond source/target information, and should refer to "static" data if complex components are involved.

If you need more description of separating the description from events let me know. I don't want to delve into the details as it would make this a massive post..

Share this post


Link to post
Share on other sites

though I still wonder how to properly schedule animations involving a series of events.


Ok, well that's a problem of maintaining state within your game objects, and something you should keep seperate to your events system.

In my engine, i have 'Controllers' and 'Renderers'. Controllers handle all logic and state control, renderers purely handle graphics. Renderers react to events that occur within the controller object. (Ignore the fact i had a 'GraphicsController' in my last post, that probably should have been a 'SpellRenderer' or somesuch)

I have a component class called 'StateMachine', which i have as a member variable for the 'Controller' classes that require state control. This is maintains a list of states, and the type of state for each one. Different types of state include one that may only be active for one frame (oneshot), another that lasts for a set period of time (timed), and another that should remain active until told otherwise via a member function call (dowhile).

So in your example, we could have a 'FireballSpellAnimationController', which maintains states:

INACTIVE - do nothing, spell not invoked yet (dowhile)
WARMUP - set any variables, load animation graphics (oneshot)
CAST - play spell cast animation (timed / dowhile)
PROJECTILE - play animation of fire ball flying through the air till it meets it's target (dowhile)
EXPLODE - play animation of fire ball exploding, expanding outwards (timed)
FINISH - all finished, clean up (oneshot), set state back to INACTIVE

As each state is entered / left you can launch the appropriate events, which will then be reacted to by your FireballAnimationRenderer. You can have an EnemyHit event too, which will be fired whenever an enemy is caught in the spell (which will be in either at the end of the 'PROJECTILE' state or within the 'EXPLODE' state. This is the event other objects can subscribe to to handle the damage taken / floating number rendering.

Share this post


Link to post
Share on other sites

Events should be light, should not contain "state" information beyond source/target information, and should refer to "static" data if complex components are involved.


Perhaps I am misunderstanding, but are you saying you consider it inappropriate for there to even BE events like RecieveDamage, given that they contain information other than source/target (ie: damage type and amount)? If so, why? They are distinct events that can have individual consequences (such as triggering counterattacks or other reaction abilities) regardless of their source.



[quote name='Galaban' timestamp='1316124554' post='4862235']
though I still wonder how to properly schedule animations involving a series of events.


Ok, well that's a problem of maintaining state within your game objects, and something you should keep seperate to your events system.
[/quote]

In this case, perhaps not. I probably should have made it clear from the beginning, but this game is turn-based. Thus, as far as the game logic is concerned, there IS no delay between the initiating of an attack and its results. Proper scheduling of casting and damage thus cannot be handled by inserting real-time delays between when events are fired; CastSpell and RecieveDamage events will always be received effectively simultaneously by the view.

Share this post


Link to post
Share on other sites
It sounds to me as though you simply need a less granular message, i.e. one that is atomic. It should contain enough information for the view to know what to display and how, such as spell type, source(s), target(s), damage amounts, any buffs or modifiers that would affect the visuals, etc.

Share this post


Link to post
Share on other sites

this game is turn-based. Thus, as far as the game logic is concerned, there IS no delay between the initiating of an attack and its results. Proper scheduling of casting and damage thus cannot be handled by inserting real-time delays between when events are fired; CastSpell and RecieveDamage events will always be received effectively simultaneously by the view.


Well my system should still work for you with a bit of tweaking, you'll just need to have different events for the game logic events and for the animation events.


Perhaps I am misunderstanding, but are you saying you consider it inappropriate for there to even BE events like RecieveDamage, given that they contain information other than source/target (ie: damage type and amount)? If so, why? They are distinct events that can have individual consequences (such as triggering counterattacks or other reaction abilities) regardless of their source.


Personally I disagree with AllEightUp, i think an event should provide any relevent information a subscriber may need; which for a 'takedamage' event would include source unit, target unit, amount, damage type and any buffs/modifiers. If you base your events system around boost::signals, your subscribing objects will be able to also bind additional arguments which it may need. I don't know how much you know about boost signals, but i find it invaluable for any application i write now http://www.boost.org/doc/libs/1_41_0/doc/html/signals/tutorial.html

Share this post


Link to post
Share on other sites

It sounds to me as though you simply need a less granular message, i.e. one that is atomic. It should contain enough information for the view to know what to display and how, such as spell type, source(s), target(s), damage amounts, any buffs or modifiers that would affect the visuals, etc.


That does make a fair bit of sense. The issue is that these events are discrete as far as the internal game logic is concerned. There are events that can trigger off CastSpell (regardless of whether it does damage, or even accomplishes anything useful, such as counterspells or magical feedback), and events that can trigger off RecieveDamage (regardless of what sort of attack triggered this, such as damage reflection or self-buffs when injured).

Even if the view only receives events organized into a format more useful to it (as you suggest above), the model needs to deal with them at a more discrete level, so SOME part of the code still has to sift through messages of different types and combine them, which seems to leave me with the original problem.

Moreover, certain reaction abilities need to interpose themselves in the middle of the animation of the triggering spell. For example, consider a magic repulsion field that can redirect spell projectiles to other locations. The visualization of this should show the fireball being cast and moving towards its intended destination, then when it reaches the edge of the field, the activation of this field should be displayed, and the fireball projectile should continue onto its new location before exploding. Not only does the visualization of one effect (the repulsion field) need to be scheduled at a specific time, dependent on the visualization of the spell triggering it, but it needs to modify the actual visualization of a different spell.

I hope it doesn't sound like I'm just constantly moving the goal posts here; I appreciate the comments, and I've found them to each contain things worth thinking on.

Share this post


Link to post
Share on other sites

In this case, perhaps not. I probably should have made it clear from the beginning, but this game is turn-based. Thus, as far as the game logic is concerned, there IS no delay between the initiating of an attack and its results. Proper scheduling of casting and damage thus cannot be handled by inserting real-time delays between when events are fired; CastSpell and RecieveDamage events will always be received effectively simultaneously by the view.


[font="Verdana"]My current project seems to be very similiar and so i'm really happy about this thread. I totally agree: for a turn-based system, the handling of timing related information should not be a part of the game logic. So where to put it?

[/font][font="Verdana"]Basically, the concept you're advocating (and this is the same line of thinking i followed with my project so far) is the following:

[/font]
  • [font="Verdana"]The game system
    [/font][font="Verdana"]- first handles all the game rules
    [/font][font="Verdana"]- then sends out messages about what is happening/happened to everyone listening (engine, ai, etc ...).[/font][font="Verdana"]
    • The game engine
      - then reads these events
      - and interprets them visually.
      The main problem lies in this process of interpretation:

      The events in the game system happen instantly (though in an ordered fashion) in a single moment
      - but -
      the engine has stretch out this moment so that it looks like it happened in real time. And so the question is:

      Where to put this stretching? In the game system? No. In the engine? Maybe; but it would actually be better to have the engine just be bothered with rendering.

      So: We need something in between these two. Let's build up an analogy:

      [/font]
      [font="Verdana"]messages sent - a theater piece. in written form
      interpreting them - all the actors on stage, performing the play
      rendering engine - the lighting system in the theater, emitting photons for the audience to percieve

      [/font][font="Verdana"]In this model, the stretching of the moment would occur in the interpretation phase. I think it would be best, not to make the messages too small: Let us consider again the example of the fireball. Here the problem was to determine when to display the damage. For turn based battle this should not be part of the game logic! If you were sending[/font]

      [font="Verdana"]- spellEvent(fireball, targetList),[/font]
      [font="Verdana"]- damageEvent(target1, 20, fire)[/font]
      [font="Verdana"]- damageEvent(target2, 30, fire)[/font]
      [font="Verdana"]- damageEvent(target3, 13, fire)[/font]

      [font="Verdana"]you'd have to recompile these 4 seperate messages into a single event for the interpretation-machine to interpret. So instead, i would go with something like this[/font]


      [font="Verdana"]---- inside the game system ----[/font]

      [font="Verdana"]
      class spellEvent implements GameEvent{
      private BattleParticipant caster;
      private Spell spellCast;
      private Map<BattleParticipant, Damage> damageTaken;
      }


      class Damage{
      private float damage;
      private List<String> damageTypes;
      }
      [/font]

      [font="Verdana"]So inside the battle code it would go something like[/font]

      [font="Verdana"]
      ...
      Spell currentSpell = currentActor.getAction();
      Map <BattleParticipant, Damage> damageTaken = new Map<...>();
      foreach(BattleParticipant target : currentSpell.getTargetsHit() ){
      damageTaken.add( target, currentSpell.calcDamage(target) );
      }
      interpretationListener.send(new SpellEvent(currentActor, currentSpell, damageTaken));
      [/font]

      [font="Verdana"]---- inside the interpretation system ----[/font]

      [font="Verdana"]
      class SpellAnimation{
      private SpellEvent eventToInterpret;
      // Here comes the method that tells you when to display the damage taken
      private float calcDamageTime(BattleParticipant target){
      // use maybe
      // - eventToInterpret.caster.position
      // - eventToInterpret.spell.projectileSpeed
      // - target.position
      }
      }

      [/font]

      [font="Verdana"]Well... I don't think the way i have written it down is actually a good way but i hope you get the idea. The point i want to make is that you need three parts; the flow of information would be like this:[/font]

      [font="Verdana"][Input]>[Game System]>[Interpretation System]>[Engine][/font]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

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

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!