The Moonstone Damage Pipeline
A Reposting of an old post I made some time ago! This covers at a high level the philosophy that guides how I handle damage in Moonstone (and games in general).
I'm currently moving over my blog posts from a bunch of different sites, this just so happened to be first! I will be continuing to edit these as I go as there is some missing context.
Whenever I talk about "Damage Pipelines" people look at me like I've got three heads. For most games it's good enough to handle damage immediately, but for Moonstone I wanted a lot more control over how damage got applied, and I wanted the freedom to add and modify mechanics quickly without rebuilding the system every time I wanted to tweak something.
This solution has been the best of both worlds for me.
The Moonstone
Damage Pipeline





Damage Message
AActor* Culprit;
UAbility* Ability;
FVector ImpactPoint;
TSubclassOf<UDamageType> DamageType;
TSubclassOf<UHitReaction> HitReactClass;
float OutgoingDamageValue;
FGameplayTagContainer Tags;
Damage Messages
Any time we want to deal damage or perform a knockback, we send a struct, specifically a Damage Message and/or Knockback Message thru the Damage Pipeline. These messages can be created anywhere, but usually they are generated as the result of an action taken by a Gameplay Ability.
On the left you'll see the fundamental types I use in my own damage plumbing. There's some cosmetic members I've left out for simplicity like FXInteraction Data, because it's not germane to the central topic and is best covered in a future blog.
Damage and Knockback info is split between two different messages because it lets me invalidate one message and not the other. IE: When I want a Unit to be immune to knockback, but take Damage... or visa-versa, when I want a Unit to be immune to a damage type, but still take knockback.
Note for those following along at home: I use a pretty heavily modified version of Epic's Gameplay Ability System, but all of this can be done completely without it!
bool WasInvalidated;
Damage Message
Knockback Message
AActor* Culprit;
AActor* Culprit;
UAbility* Ability;
FVector ImpactPoint;
FVector ImpactDirection;
TSubclassOf<UKnockbackType> KnockbackClass;
float KnockbackDistance;
float KnockbackHeight;
float KnockbackDuration;
bool WasInvalidated;


✉️
✉️
Either one or both of these messages are sent to a Damage Receiver Component that lives on the target we want to deal damage and/or knockback to. At this point, the journey through the Damage Pipeline begins!

Damage Receiver
Damage Message
✉️


The Damage Receiver lives on every damageable object in the game. It's responsibility is to act as the entry point for the Damage Pipeline, marshalling messages for consumption during damage processing. Messages are enqueued, and (usually) processed in the order they are received.
Inside of the Damage Receiver is a set of Pipeline Operators, these are configurable individually on each receiver and can be turned off and on at will. They are able to modify any value in the message and pass it along, or they can invalidate a message, which removes it from being used by the system.
Operators are stages added to the pipeline to process messages. Each of them handles a different aspect of gameplay, modifying the message as it passes through the damage pipeline. They can also "Hold" and "Defer" messages.
This is where they trigger some external process and wait for the result. As an example: When a weapon swing might hit a player controlled pawn, the "Coyote Damage" system will proc, and give the player a few frames to react. If they do, it will mark the message invalid. If they do not: It will release the message for further processing.
This Pipeline Operator system is where the flexibility lives! Each Damage Receiver Component can configure the pipeline as they need. This is the secret sauce that let me quickly iterate and add features without blowing up previously existing work.
✉️
✉️


I've never hated a term more than "Coyote Damage", it does psychic damage to me every time I say it, but it is a term people immediately grok.


✉️








✉️
✉️
✉️
Think of Invalidation like the message getting yoinked off the conveyor belt as it passes through.
Any Pipeline Operator can declare a message "Invalid", which will halt the processing of the message, and effectively discard it. The example I give (Coyote Damage) took me only an hour or two to implement, it's been a real lifesaver.

Damage Execution
& Reaction
Damage Message
✉️


We construct a Gameplay Effect using an effect class passed to it via the Damage Type. On that Gameplay Effect we have set a Damage Execution that handles Attribute Changes.
If we were not using the Gameplay Ability System, this is where we would modify the Attributes directly.
With regards to Hit Reactions: There's a lot to be said here that is really more of an Animation Post. To oversimplify, the important bit here is having both Full Body animations that block movement and actions, for big heavy interrupting reactions, and also upper- body, additive hit reactions that you layer over the locomotion animations of the character that don't interrupt other animations or block input.
A Topic for another post perhaps.
✉️
✉️


FX Presentation


Apply Knockback
We also broadcast messages out to other systems that might be listening, such as FX, or to the Movement Component so we can handle Knockback.
These are subjects I want to cover in more detail at a later date.
