Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your first attributes

This chapter gives the mage life and mana, and shows them changing.

Declaring attributes

An attribute is a marker type with #[derive(Attribute)]. Its rules go on #[attribute(..)]:

use bevy_mettle::prelude::*;
/// What's left of the mage's life: never below 0, nor above its maximum.
#[derive(Attribute)]
#[attribute(base = 100.0, min = 0.0, max = MaxLife)]
struct Life;

#[derive(Attribute)]
#[attribute(base = 100.0)]
struct MaxLife;

#[derive(Attribute)]
#[attribute(base = 100.0, min = 0.0)]
struct Mana;

base is what a new actor starts with. min and max are limits: a number, or another attribute. Life is capped by the mage’s own maximum life. Attributes lists every setting.

Adding them

Add each attribute to the app with add_attribute, after MettlePlugins:

use bevy::prelude::*;
use bevy_mettle::prelude::*;
#[derive(Attribute)] #[attribute(base = 100.0, min = 0.0, max = MaxLife)] struct Life;
#[derive(Attribute)] #[attribute(base = 100.0)] struct MaxLife;
#[derive(Attribute)] #[attribute(base = 100.0, min = 0.0)] struct Mana;
fn spawn_mage() {}
fn show_life() {}
fn main() {
    App::new()
        .add_plugins((DefaultPlugins, MettlePlugins))
        .add_attribute::<Life>()
        .add_attribute::<MaxLife>()
        .add_attribute::<Mana>()
        .add_systems(Startup, spawn_mage)
        .add_systems(Update, show_life)
        .run();
}

The mage

An actor has an attribute when it has its Base. Spawn the mage with one of each:

use bevy::prelude::*;
use bevy_mettle::prelude::*;
#[derive(Attribute)] #[attribute(base = 100.0, min = 0.0, max = MaxLife)] struct Life;
#[derive(Attribute)] #[attribute(base = 100.0)] struct MaxLife;
#[derive(Attribute)] #[attribute(base = 100.0, min = 0.0)] struct Mana;
#[derive(Component)]
struct Mage;

fn spawn_mage(mut commands: Commands) {
    commands.spawn((
        Mage,
        Base::<Life>::default(),
        Base::<MaxLife>::default(),
        Base::<Mana>::default(),
    ));
}

Base::default() starts at the attribute’s base. Base::new(80.0) starts elsewhere.

Each Base<A> brings a Current<A>: the value with every modifier counted, kept within its limits. Your systems read it like any component:

use bevy::prelude::*;
use bevy_mettle::prelude::*;
#[derive(Attribute)] #[attribute(base = 100.0, min = 0.0, max = MaxLife)] struct Life;
#[derive(Attribute)] #[attribute(base = 100.0)] struct MaxLife;
fn show_life(lives: Query<&Current<Life>, Changed<Current<Life>>>) {
    for life in &lives {
        info!("life: {}", life.get());
    }
}

Current is written only when its value changes, so Changed<Current<Life>> is life changing.

Changing a base

The base is yours to set: what’s left of life after a hit, a level gained. Change it through Base::set or Base::add. The next time attributes are kept, Current follows, within its limits:

use std::time::Duration;
use bevy::prelude::*;
use bevy::time::TimeUpdateStrategy;
use bevy_mettle::prelude::*;
#[derive(Attribute)] #[attribute(base = 100.0, min = 0.0, max = MaxLife)] struct Life;
#[derive(Attribute)] #[attribute(base = 100.0)] struct MaxLife;
let mut app = App::new();
app.add_plugins((MinimalPlugins, MettlePlugins))
    .add_attribute::<Life>()
    .add_attribute::<MaxLife>()
    // Each update moves time on by one fixed step.
    .insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f64(1.0 / 64.0)));
app.finish();
app.cleanup();
let mage = app
    .world_mut()
    .spawn((Base::<Life>::default(), Base::<MaxLife>::default()))
    .id();
app.update();
app.update();

// A hit for 30.
app.world_mut().get_mut::<Base<Life>>(mage).unwrap().add(-30.0);
app.update();
assert_eq!(app.world().get::<Current<Life>>(mage).unwrap().get(), 70.0);

// Its maximum drops below what's left: life is capped.
app.world_mut().get_mut::<Base<MaxLife>>(mage).unwrap().set(50.0);
app.update();
assert_eq!(app.world().get::<Current<Life>>(mage).unwrap().get(), 50.0);

The base keeps 70: the limit applies to Current. If maximum life comes back up, so does life.

Being told

As an actor’s Current<A> changes, AttributeChanged<A> is triggered on it, with what it was and what it is. Observe it for a health bar, or for death:

use bevy::prelude::*;
use bevy_mettle::prelude::*;
#[derive(Attribute)] #[attribute(base = 100.0, min = 0.0)] struct Life;
fn died(changed: On<AttributeChanged<Life>>, mut commands: Commands) {
    if changed.new <= 0.0 {
        commands.entity(changed.entity).despawn();
    }
}

Add it with app.add_observer(died), or on the mage alone with commands.entity(mage).observe(died).

Next, How it fits together shows where effects and abilities come in.