If a GameplayTagCondition is the question, a GameplayTagOperation is the action. It describes a change to apply to a collection: which tag to affect, how to change its value, and optionally what conditions must be true before the change is allowed to happen.
Operations are the mutation primitive of the system. Every time something needs to modify tag state, whether that is applying an effect, consuming a resource, granting an ability, or clearing a status, an operation is the object that carries that intent. Because operations are data, they can be authored in an editor, stored in assets, passed between systems, and reused freely without any system having to know the specifics of what it is executing.
Anatomy of an Operation #
An operation has three parts.
The tag names what is being changed. It identifies the entry in the collection that will be affected.
The arithmetic instruction describes how the change is applied. Set replaces the current value entirely. Add increases it. Subtract decreases it, and will not go below zero since zero means absent. Multiply and Divide scale the current value. Min and Max enforce a boundary, keeping the value at or below a ceiling or at or above a floor without requiring calling code to check first.
The value is the operand the instruction works with. For Set, it is the new value. For Add and Subtract, it is the amount to change by. For Min and Max, it is the boundary being enforced.
Together these three parts express the full range of state changes a tag-driven system typically needs. Applying a poison stack, draining stamina, granting a charge, capping a resource, clearing a buff outright, all of these are operations differing only in their tag, instruction, and value.
Live Operands from the Collection #
The operand value does not have to be a fixed constant. An operation can nominate a second tag as its value source, and when it does the operand is resolved from the collection at the moment the operation is applied rather than being baked in at authoring time.
This makes it possible to express relationships between two pieces of live state as a single operation. Clamping Player.Health to never exceed Player.MaxHealth is a Min operation on Player.Health with Player.MaxHealth as the value source. Whatever Player.MaxHealth holds at the time of application is the ceiling used, so the operation stays correct even if maximum health changes during play. No condition checks, no custom code, no intermediate variable.
When a fixed constant is set alongside a live value source, the constant is ignored. If no value source is set, the constant is used. The two can coexist in the authored data without conflict.
Conditional Application #
What distinguishes an operation from a bare arithmetic call is the ability to carry its own conditions. An operation can include a list of conditions that must all evaluate to true against the target collection before the change is applied. If the conditions are not met, the operation does nothing and reports that it did not apply.
This transforms an operation from a simple instruction into a self-contained rule. The operation carries its own guard logic. It does not need the calling code to check preconditions first, nor does it need a surrounding system to make decisions on its behalf. The operation itself knows when it should and should not fire.
A burning effect that only stacks when the target is already oiled, a heal that only triggers when health is below half, a silence that cannot be applied to a target already immune, all of these are operations with condition lists. The conditions and the mutation travel together as a single authored unit.
When an operation has no conditions attached, it always applies. An unconditional operation is simply one where the condition list is empty, and an empty condition list evaluates to true by design. Adding conditions later is purely additive and does not require changing anything else.
Checking Without Applying #
An operation can be asked whether it would apply, without actually making any change. This is useful when a system needs to preview or validate a set of operations before committing them, or when UI needs to show whether an action is currently available without executing it.
The check and the application use exactly the same condition evaluation, so a check that returns true and a subsequent application will agree on the result as long as the collection has not changed between them.
Operations as Data #
The real value of packaging tag, instruction, value, and conditions together into a single object is that operations become first-class data in your project.
A designer can author an operation that grants a status effect, specifying exactly which tag changes in what way under which circumstances, without writing any code. That operation can be stored in a shared asset, referenced from multiple abilities, triggers, or item definitions, and reused anywhere the same change needs to happen. It can be part of a list that is executed in sequence, or passed to a system that applies whichever operations are currently relevant.
This is how game logic shifts from being written into systems to being expressed in content. The system applies operations. The designer decides which operations exist and when they fire. Neither needs to change when the other does.
Examples #
Coming Soon
