bevy_gauge
A attribute system for Bevy.
Built for games where attributes depend on other attributes — RPGs with derived attributes, ARPGs with PoE-style damage pipelines, or anything where changing one value should automatically ripple through a chain of dependencies, even across entities.
Quick Start
use *;
use *;
Change Vitality later and MaxHealth updates automatically.
Features
Flat Attributes
Simple numeric values. Modifiers are summed:
attributes.flat_attribute;
attributes.add_modifier; // now 75
Expression Modifiers
Modifiers can be expressions that reference other attributes. Dependencies are tracked automatically:
attributes.add_expr_modifier?;
attributes.add_expr_modifier?;
// Changing Vitality propagates → MaxHealth → HealthRegen
attributes.add_modifier;
Expressions support arithmetic, parentheses, min/max/abs/clamp, cross-entity references (Strength@Wielder), and tag queries (Damage{FIRE|MELEE}).
Complex Attributes
Named parts combined by an expression — each part receives modifiers independently:
// PoE-style: base * (1 + increased) * more
attributes.complex_attribute?;
attributes.add_modifier;
attributes.add_modifier; // +50%
attributes.add_modifier; // 20% more → 1.2x
// Damage = 100 * 1.5 * 1.2 = 180
Sum adds modifiers together. Product multiplies them as (1+v) factors.
Tags and Filtered Evaluation
Attach tags to modifiers, then query with a filter. Modifiers are inserted generally — a modifier tagged FIRE applies to any query that includes fire. Queries are specific — you query a leaf combination like FIRE | SWORD to get the total for that exact damage instance.
This lets you naturally express things like "+10 fire damage" (applies to fire swords, fire bows, etc.), "+5 sword damage" (applies to physical swords, fire swords, etc.), and "+3 damage" (applies to everything).
Define your tag hierarchy with the define_tags! macro:
define_tags!
// Register at startup
This generates a struct with TagMask constants: DamageTags::FIRE, DamageTags::SWORD, DamageTags::ELEMENTAL, etc. Group tags like ELEMENTAL are the OR of their children (FIRE | COLD | LIGHTNING).
// Inserting — tag as broadly as the modifier applies
attributes.add_modifier_tagged; // all physical
attributes.add_modifier_tagged; // all fire
attributes.add_modifier_tagged; // all sword
attributes.add_modifier; // all damage (global)
// Querying
let fire_sword = attributes.evaluate_tagged;
// = fire(10) + sword(5) + global(3) = 18
let phys_sword = attributes.evaluate_tagged;
// = physical(100) + sword(5) + global(3) = 108
let fire_bow = attributes.evaluate_tagged;
// = fire(10) + global(3) = 13
A modifier matches a query when all of its tag bits are present in the query. Untagged modifiers are global — they match everything.
Cross-Entity Dependencies
Attributes on one entity can reference attributes on another through source aliases:
// Sword damage scales with wielder's Strength
attributes.add_expr_modifier?;
attributes.register_source;
// Hand the sword to someone else — one call, everything updates
attributes.register_source;
Instant (One-Shot) Mutations
Apply attribute changes once without leaving persistent modifiers. Used for damage application, ability effects, etc.
Expressions can reference attributes on role entities via @role syntax. Roles are temporary source aliases that only exist for the duration of the evaluation:
// An arrow hits a target. Damage depends on the arrow, the bow, and the attacker.
let on_hit = instant! ;
let roles: & = &;
attributes.apply_instant;
Batch Operations
// Spawn-time initialization
commands.spawn;
// Runtime buffs
let enchant = mod_set! ;
enchant.apply;
Attribute Requirements
Boolean gates over attributes for equipment prerequisites, ability conditions, state machine transitions:
commands.spawn;
Derived Components
Sync struct fields with attribute values using a derive macro:
Fields with #[read] update from attributes automatically. Fields with #[write] push values back into the attribute system.
Deferred Commands
Set up attributes at spawn time when only Commands is available:
commands.entity.attrs;
The closure runs during command flush with full AttributesMut access — expression modifiers, complex attributes, cross-entity sources, and reads all work.
Reading vs Writing
Reading only needs &Attributes:
Writing goes through AttributesMut, which maintains dependency edges and propagates changes:
License
MIT OR Apache-2.0