Bevyhub
docs > beet > quick-start

Quick Start

In this tutorial we will create a simple action that prints the name of a behavior when it starts running.

Show Final Code
src/main.rs
use beet::prelude::*;
use bevy::prelude::*;
#[derive(Component, Action)]
#[observers(log_name_on_run)]
struct LogNameOnRun;
fn log_name_on_run(trigger: Trigger<OnRun>, query: Query<&Name>) {
if let Ok(name) = query.get(trigger.entity()) {
println!("Running: {}", name);
}
}
fn main() {
let mut app = App::new();
app.add_plugins((
BeetPlugins,
ActionPlugin::<LogNameOnRun>::default()
));
app.world_mut()
.spawn((
Name::new("My Behavior"),
SequenceFlow,
LogNameOnRun
))
.with_children(|parent| {
parent.spawn((
Name::new("Child 1"),
LogNameOnRun,
EndOnRun::success()
));
parent.spawn((
Name::new("Child 2"),
LogNameOnRun,
EndOnRun::success()
));
})
.flush_trigger(OnRun);
}

Lets start by creating a new project and running it.

Installation
cargo init hello_beet
cd hello_beet
cargo add bevy beet
cargo run
# Hello, world!

📣 Our First Action

Ok, now we’re ready to create our first action. Actions are a component with associated behavior, in this case an observer that watches for the OnRun trigger.

src/main.rs
use beet::prelude::*;
use bevy::prelude::*;
#[derive(Component)]
struct LogNameOnRun;
fn log_name_on_run(trigger: Trigger<OnRun>, query: Query<&Name>) {
if let Ok(name) = query.get(trigger.entity()) {
println!("Running: {}", name);
}
}

Now we can update the main function to create a new App and run our first behavior.

src/main.rs
fn main(){
let mut app = App::new();
app.add_plugins(BeetPlugins)
.observe(log_name_on_run);
app.world_mut()
.spawn(Name::new("My Behavior"))
.flush_trigger(OnRun);
}

Now lets give that a go.

Terminal window
cargo run
# Running: My Behavior

But wait, we didn’t add the LogNameOnRun component, so why did it print something out? When we call app.observe, that creates a global listener. Sometimes that is what we want, but action observers are usually designed for a single behavior.

📋 The Action Macro

Beet provides the Action macro which ensures that action observers are only triggered on entities that have the associated component. Lets update our code to use the macro.

  1. Add the Action macro
    src/main.rs
    #[derive(Component)]
    #[derive(Component, Action)]
    #[observers(log_name_on_run)]
    struct LogNameOnRun;
  2. Register the action’s observers with the ActionPlugin
    src/main.rs
    app.add_plugins(BeetPlugins)
    .observe(log_name_on_run);
    app.add_plugins((
    BeetPlugins,
    ActionPlugin::<LogNameOnRun>::default(),
    ));
  3. Add the LogNameOnRun component to the entity
    src/main.rs
    app.world_mut()
    .spawn(Name::new("My Behavior"))
    .spawn((
    Name::new("My Behavior"),
    LogNameOnRun
    ))
    .flush_trigger(OnRun);
  4. Run the updated code
    Terminal window
    cargo run
    # Running: My Behavior

Yaay! Now our log_name_on_run observer will only run when it is triggered on entities with the LogNameOnRun component.

🌲 Trees

Now we have the basic workflow down we’re ready to create our first tree.

For this we will use two actions built into Beet:

  • SequenceFlow A sequence runs its children in the order they were added.
  • EndOnRun This action will immediately trigger an OnRunResult which bubbles up to its parent.

Lets add the SequenceFlow and two children.

src/main.rs
app.world_mut()
.spawn((
Name::new("My Behavior"),
LogNameOnRun,
SequenceFlow::default()
))
.with_children(|parent| {
parent.spawn((
Name::new("Child 1"),
LogNameOnRun,
EndOnRun::success()
));
parent.spawn((
Name::new("Child 2"),
LogNameOnRun,
EndOnRun::success()
))
});
.flush_trigger(OnRun);
Can you guess what the output will be?
Terminal window
cargo run
# Running: My Behavior
# Running: Child 1
# Running: Child 2

Congratulations, you just created your first behavior!

🎸 Just for fun

An OnRun trigger usually returns an OnRunResult. Lets change a few lines to see that in action.

src/main.rs
fn log_name_on_run(trigger: Trigger<OnRun>, query: Query<&Name>) {
fn log_name_on_run(trigger: Trigger<OnRunResult>, query: Query<&Name>) {
if let Ok(name) = query.get(trigger.entity()) {
println!("Running: {}", name);
println!("Result: {} - {:?}", name, trigger.event());
}
}
Terminal window
cargo run
# Result: Child 1 - OnRunResult(Success)
# Result: Child 2 - OnRunResult(Success)
# Result: My Behavior - OnRunResult(Success)

Note how the order previously reflected the order in which each behavior starts, but now reflects the order in which they end.