{"id":27012672,"url":"https://github.com/RJ/bevy_behave","last_synced_at":"2025-04-04T12:04:10.303Z","repository":{"id":275777265,"uuid":"926588864","full_name":"RJ/bevy_behave","owner":"RJ","description":"Behaviour trees for bevy, with on-demand entity spawning for task nodes.","archived":false,"fork":false,"pushed_at":"2025-03-27T08:32:17.000Z","size":1215,"stargazers_count":42,"open_issues_count":5,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-04T01:44:43.681Z","etag":null,"topics":["gamedev"],"latest_commit_sha":null,"homepage":"https://docs.rs/bevy_behave","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RJ.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-03T14:24:24.000Z","updated_at":"2025-04-01T03:20:23.000Z","dependencies_parsed_at":"2025-02-04T15:04:58.983Z","dependency_job_id":"3ad925cc-f850-44b7-b4dd-7bbb06993ddb","html_url":"https://github.com/RJ/bevy_behave","commit_stats":null,"previous_names":["rj/bevy_behave"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RJ%2Fbevy_behave","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RJ%2Fbevy_behave/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RJ%2Fbevy_behave/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RJ%2Fbevy_behave/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RJ","download_url":"https://codeload.github.com/RJ/bevy_behave/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247107831,"owners_count":20884797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gamedev"],"created_at":"2025-04-04T12:02:06.002Z","updated_at":"2025-04-04T12:04:10.297Z","avatar_url":"https://github.com/RJ.png","language":"Rust","readme":"# bevy_behave\n\n\u003cdiv align=\"left\"\u003e\n\u003cp\u003e\n    \u003cstrong\u003eA behaviour tree plugin for bevy with dynamic spawning.\u003c/strong\u003e\n\u003c/p\u003e\n\u003cp\u003e\n    \u003ca href=\"https://crates.io/crates/bevy_behave\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/bevy_behave.svg\" alt=\"crates.io\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/bevy_behave\"\u003e\u003cimg src=\"https://img.shields.io/badge/docs-latest-blue.svg\" alt=\"docs.rs\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://discord.com/channels/691052431525675048/1347180005104422942\"\u003e\u003cimg src=\"https://img.shields.io/badge/discord-bevy_behave-blue\" alt=\"discord channel\"/\u003e\u003c/a\u003e\n\n\u003c/p\u003e\n\u003c/div\u003e\n\n`bevy_behave` is a behaviour tree plugin for bevy (0.15) with a sensible API and minimal overheads.\nNo magic is required for the task components, they are are regular bevy components using triggers to report status.\n\nWhen an action node (aka leaf node, task node) in the behaviour tree runs, it will spawn an entity with\nthe components you specified in the tree definition. The tree then waits for this entity to\ntrigger a status report, at which point the entity will be despawned.\n\nYou can also take actions without spawning an entity by triggering an observed `Event`, which can also be used as a conditional in a control node.\n\n\n\nThis tree definition is from the [chase example](https://github.com/RJ/bevy_behave/blob/main/examples/chase.rs):\n\n```rust\nlet npc_entity = get_enemy_entity();\nlet player = get_player_entity();\n// The tree definition (which is cloneable).\n// and in theory, able to be loaded from an asset file using reflection (PRs welcome).\n// When added to the BehaveTree component, this gets transformed internally to hold state etc.\n//\n// These trees are `ego_tree::Tree\u003cBehave\u003e` if you want to construct them manually.\n// Conventient macro usage shown below.\nlet tree = behave! {\n    Behave::Forever =\u003e {\n        Behave::Sequence =\u003e {\n            Behave::spawn((\n                Name::new(\"Wait until player is near\"),\n                WaitUntilPlayerIsNear{player}\n            )),\n            Behave::Sequence =\u003e {\n                Behave::spawn((\n                    Name::new(\"Move towards player while in range\"),\n                    MoveTowardsPlayer{player, speed: 100.0}\n                )),\n                // MoveTowardsPlayer suceeds if we catch them, in which randomize our colour.\n                // This uses a trigger to take an action without spawning an entity.\n                Behave::trigger(RandomizeColour),\n                // then have a nap (pause execution of the tree)\n                // NB: this only runs if the trigger_req was successful, since it's in a Sequence.\n                Behave::Wait(5.0),\n            }\n        }\n    }\n};\n```\n\n\n\u003cdetails\u003e\n\n\u003csummary\u003e\u003csmall\u003eYou can also compose trees from subtrees\u003c/small\u003e\u003c/summary\u003e\n\n```rust\n\nlet npc_entity = get_enemy_entity();\nlet player = get_player_entity();\n// Breaking a tree into two trees and composing, just to show how it's done.\nlet chase_subtree = behave! {\n    Behave::Sequence =\u003e {\n        Behave::spawn((\n            Name::new(\"Move towards player while in range\"),\n            MoveTowardsPlayer{player, speed: 100.0}\n        )),\n        // MoveTowardsPlayer suceeds if we catch them, in which randomize our colour.\n        // This uses a trigger to take an action without spawning an entity.\n        Behave::trigger(RandomizeColour),\n        // then have a nap (pause execution of the tree)\n        // NB: this only runs if the trigger_req was successful, since it's in a Sequence.\n        Behave::Wait(5.0),\n    }\n};\n\nlet tree = behave! {\n    Behave::Forever =\u003e {\n        // Run children in sequence until one fails\n        Behave::Sequence =\u003e {\n            // WAIT FOR THE PLAYER TO GET CLOSE\n            // Spawn with any normal components that will control the target entity:\n            Behave::spawn((\n                Name::new(\"Wait until player is near\"),\n                WaitUntilPlayerIsNear{player}\n            )),\n            // CHASE THE PLAYER\n            @ chase_subtree\n        }\n    }\n};\n```\n\n\u003c/details\u003e\n\n\u003cbr\u003e\n\nOnce you have your tree definition, you spawn an entity to run the behaviour tree by adding a `BehaveTree` component:\n\n```rust\n// Spawn an entity to run the behaviour tree.\n// Make it a child of the npc entity for convenience.\n// The default is to assume the Parent of the tree entity is the Target Entity you're controlling.\ncommands.spawn((\n    Name::new(\"Behave tree for NPC\"),\n    BehaveTree::new(tree)\n)).set_parent(npc_entity);\n```\n\nIf your behaviour tree is not a child of the target entity you want to control, you can specify the target entity explicitly:\n\n```rust\nlet target = get_entity_to_control();\ncommands.spawn((\n    Name::new(\"Behave tree for NPC\"),\n    BehaveTree::new(tree),\n    BehaveTargetEntity::Entity(target),\n));\n```\n\nOr in case of a deeper hierarchy, you can use `BehaveTargetEntity::RootAncestor` to find the topmost entity.\n\n\n\n### Control Flow Nodes\n\nThe following control flow nodes are supported. Control flow logic is part of the `BehaveTree` and doesn't spawn extra entities.\n\n| Node                    | Description                                                                                                                       |\n| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| `Behave::Sequence`      | Runs children in sequence, failing if any child fails, succeeding if all children succeed.                                        |\n| `Behave::Fallback`      | Runs children in sequence until one succeeds. If all fail, this fails. Sometimes called a Selector node.                          |\n| `Behave::Invert`        | Inverts success/failure of child. Must only have one child.                                                                       |\n| `Behave::AlwaysSucceed` | Succeeds instantly.                                                                                                               |\n| `Behave::AlwaysFail`    | Fails instantly.                                                                                                                  |\n| `Behave::While`         | Runs the second child repeatedly, provided the first child returns success. If only one child, runs it repeatedly until it fails. |\n| `Behave::IfThen`        | If the first child succeeds, run the second child. (otherwise, run the optional third child)                                      |\n\n\n#### Control Flow Node Examples\n\n\n##### Sequence\n\nUse `Behave::Sequence` to run children in sequence, failing if any child fails, succeeding if all children succeed.\n\nThis example runs a trigger (and assuming it reports success..), waits 5 secs, then spawns an entity with an imagined `BTaskComponent` to do something.\n\n```rust\nlet tree = behave! {\n    Behave::Sequence =\u003e {\n        Behave::trigger(DoA),\n        Behave::Wait(5.0),\n        Behave::spawn_named(\"B-Doer\", BTaskComponent::default()),\n    }\n};\n```\n\n\n##### Fallback\n\nUse `Behave::Fallback` to run children in sequence until one succeeds. If they all fail, the Fallback node also fails.\n\n```rust\nlet tree = behave! {\n    Behave::Fallback =\u003e {\n        Behave::trigger(TryA),\n        Behave::trigger(TryB),\n        Behave::trigger(TryC),\n    }\n};\n```\n\n##### While (single child usage)\n\nYou can wrap a single node in a `Behave::While` node to repeat it until it fails.\n\n```rust\nlet tree = behave! {\n    Behave::While =\u003e {\n        Behave::trigger(DoSlowThingUntilFailure),\n    }\n};\n```\n\n##### While (two child usage)\n\nWith two children, the first child is the conditional check. If it succeeds, the second child is run. And then the node repeats.\n\n```rust\nlet tree = behave! {\n    Behave::While =\u003e {\n        Behave::trigger(AirbourneCheck),\n        Behave::spawn_named(\"Fly!\", (FlapWings::default(), PointToes::default())),\n    }\n};\n```\n\n##### IfThen (two child usage)\n\nThe first child is the conditional check, the second is only run if the condition succeeds.\n\n```rust\nlet tree = behave! {\n    Behave::IfThen =\u003e {\n        Behave::trigger(HungryCheck),\n        Behave::Sequence =\u003e {\n            // move to food, but only allow 10 seconds to do so. Then eat, if we got there.\n            Behave::spawn_named(\"Go to food\", (MoveToFood::default(), BehaveTimeout::from_secs(10.0, false))),\n            Behave::trigger(EatFood),\n        },\n    }\n};\n```\n\n##### IfThen (three child usage)\n\nAn optional third child acts as the \"else\" clause, and is run if the conditional fails.\n\n```rust\nlet tree = behave! {\n    Behave::IfThen =\u003e {\n        Behave::trigger(HungryCheck),\n        Behave::Sequence =\u003e {\n            Behave::spawn_named(\"Go to food\", (MoveToFood::default(), BehaveTimeout::from_secs(10.0, false))),\n            Behave::trigger(EatFood),\n        },\n        Behave::trigger(TidyKitchen),\n    }\n};\n```\n\n\n### Task Nodes\n\nTask nodes are leaves of the tree which take some action, typically doing something to control your target entity, such as making it move.\n\n##### Behave::Wait\n\nWaits a given duration before Succeeding. The timer is ticked by the tree itself, so no entities are spawned.\n\n```rust\nlet tree = behave! {\n    Behave::Wait(5.0),\n};\n```\n\n##### Behave::spawn(...) and Behave::spawn_named(...)\n\nWhen a `Behave::spawn_named` node runs, a new entity is spawned with the bundle of components you provided along with a\n`BehaveCtx` component, used to get the target entity the tree is controlling, and the mechanism to generate status reports.\n\nOnce a result is reported, the entity is despawned.\n\n```rust\n// Flap our wings, and succeed (end the task) after 60 seconds.\nlet tree = behave! {\n    Behave::spawn_named(\"Flying Task\",\n        (WingFlapper::default(), BehaveTimeout::from_secs(60.0, true))\n    )\n};\n```\n\nPrefer the `Behave::spawn_named` variant, because in addition to adding a `Name` component to the spawned entity, it exposes this name in debug logging.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eAn example implementation (click to reveal)\u003c/summary\u003e\n\n```rust\n// An example plugin to provide a `WingFlapper` task component.\n\nfn wing_flapper_task_plugin(app: \u0026mut App) {\n    app.add_systems(FixedUpdate, wing_flap_system);\n}\n\n#[derive(Component, Clone, Default)]\nstruct WingFlapper {\n    speed: f32,\n}\n\nfn wing_flap_system(\n    mut q_target: Query\u003c\u0026mut Wings, With\u003cBirdMarker\u003e\u003e,\n    flapper_tasks: Query\u003c(\u0026WingFlapper, \u0026BehaveCtx)\u003e,\n    mut commands: Commands\n) {\n    // for each entity with a WingFlapper component and a BehaveCtx, flap the wings for its target entity\n    for (flapper, ctx) in flapper_tasks.iter() {\n        // the target entity is the one being controlled by the behaviour tree that spawned this task entity\n        let target = ctx.target_entity();\n        let Ok(mut target_wings) = q_target.get_mut(target) else {\n            // Maybe the wings fell off? report task failure.\n            commands.trigger(ctx.failure());\n            continue;\n        };\n        target_wings.flap(flapper.speed);\n    }\n}\n```\n\u003c/details\u003e\n\n\n##### Behave::trigger(...)\n\nWhen a `Behave::trigger` node runs, it will trigger an event, which the user observes and can either respond to with a success or failure immediately, or respond later from another system. You must specify an arbitrary `Clone` type which is passed along as\nthe payload of the trigger event, along with the `BehaveCtx`.\n\nHere's how you might use a trigger conditional check to execute a specific task if a height condition is met:\n\n```rust\nlet tree = behave! {\n    Behave::IfThen =\u003e {\n        Behave::trigger(HeightCheck { min_height: 10.0 }),\n        Behave::spawn_named(\"High Thing\", TakeActionWhenHigh::default()),\n    }\n};\n```\n\u003cdetails\u003e\n\n\u003csummary\u003eAnd the implementation (click to reveal)\u003c/summary\u003e\n\n\n```rust\n// An example plugin to provide a `HeightCheck` trigger task\n\nfn height_check_task_plugin(app: \u0026mut App) {\n    // add a global observer to answer conditional queries for HeightCheck:\n    app.add_observer(on_height_check);\n}\n\n// Trigger payloads just need to be Clone.\n// They are wrapped in a BehaveTrigger, which is a bevy Event.\n#[derive(Clone)]\nstruct HeightCheck {\n    min_height: f32,\n}\n\n// you respond by triggering a success or failure event created by the ctx:\nfn on_height_check(trigger: Trigger\u003cBehaveTrigger\u003cHeightCheck\u003e\u003e, q: Query\u003c\u0026Position\u003e, mut commands: Commands) {\n    let ev = trigger.event();\n    let ctx: \u0026BehaveCtx = ev.ctx();\n    let height_check: \u0026HeightCheck = ev.inner();\n    // lookup the position of the target entity (ie the entity this behaviour tree is controlling)\n    let character_pos = q.get(ctx.target_entity()).expect(\"Character entity missing?\");\n    if character_pos.y \u003e= height_check.min_height {\n        commands.trigger(ctx.success());\n    } else {\n        commands.trigger(ctx.failure());\n    }\n}\n\n```\n\u003c/details\u003e\n\n\u003cbr\u003e\n\nIf you respond with a success or failure from the observer you can treat the event as a conditional test as part of a control flow node. Alternatively, you can use it to trigger a side effect and respond later from another system. Just make sure to copy the `BehaveCtx` so you can generate a success or failure event at your leisure.\n\n\n\n### Cargo Example\n\nHave a look at the [chase example](https://github.com/RJ/bevy_behave/blob/main/examples/chase.rs) to see how these are used.\nRun in release mode to support 100k+ enemies at once:\n```bash\ncargo run --release --example chase\n```\n\n\n### Utility components\n\nFor your convenience:\n\n##### Triggering completion after a timeout\n\nTo trigger a status report on a dynamic spawn task after a timeout, use the `BehaveTimeout` helper component:\n\n```rust\nlet tree = behave! {\n    Behave::spawn_named(\"Long running task that succeeds after 5 seconds\", (\n        LongRunningTaskComp::default(),\n        BehaveTimeout::from_secs(5.0, true)\n    ))\n};\n```\n\nThis will get the `BehaveCtx` from the entity, and trigger a success or failure report for you after the timeout.\n\n\n\n### `behave!` macro\n\nThe `behave!` macro is more powerful version of the `ego_tree::tree!` macro.\nYou can use ego_tree's `tree!` macro to build the tree, but this macro has some additional features\nto make composing behaviours easier:\n\n##### Merging in subtrees:\n\nUse `@` to insert a subtree into the current tree:\n```rust\n#[derive(Clone)]\nstruct A;\n#[derive(Clone)]\nstruct B;\nfn get_tree() -\u003e Tree\u003cBehave\u003e {\n    let subtree = behave! {\n        Behave::Sequence =\u003e {\n            Behave::trigger(A),\n            Behave::Wait(1.0),\n            Behave::trigger(B),\n        }\n    };\n\n    behave! {\n        Behave::Sequence =\u003e {\n            Behave::Wait(5.0),\n            @ subtree\n        }\n    }\n}\n```\n\nUse `...` to insert multiple subtrees from an iterator of trees:\n```rust\n#[derive(Clone)]\nstruct A;\n#[derive(Clone)]\nstruct B;\nfn get_tree() -\u003e Tree\u003cBehave\u003e {\n    let subtrees = [\n        behave! { Behave::Wait(1.0) },\n        behave! { Behave::Wait(2.0) },\n        behave! { Behave::Wait(3.0) },\n    ];\n\n    behave! {\n        Behave::Sequence =\u003e {\n            Behave::Wait(5.0),\n            ... subtrees\n        }\n    }\n}\n```\n\n\n##### Inserting nodes from an iterator:\n\nUse `@[ ]` to insert leaf nodes (`Behave` enum type, not a tree) from an iterator:\n```rust\n#[derive(Clone)]\nstruct A;\n#[derive(Clone)]\nstruct B;\nfn get_tree() -\u003e Tree\u003cBehave\u003e {\n    let children = vec![\n        Behave::trigger(A),\n        Behave::Wait(1.0),\n        Behave::trigger(B),\n    ];\n    behave! {\n        Behave::Sequence =\u003e {\n            @[ children ]\n        }\n    }\n}\n```\n\n### Debug Logging\n\nCall `BehaveTree::with_logging(true)` to enable debug verbose logging:\n\n```rust\n\nlet tree = behave! { Behave::Wait(5.0) }; // etc\n\ncommands.spawn((\n    Name::new(\"Behave tree for NPC\"),\n    BehaveTree::new(tree).with_logging(true),\n));\n```\n\n\u003cimg src=\"https://github.com/RJ/bevy_behave/blob/main/examples/console_logging.png\"\u003e\n\n### Performance\n\nis good.\n\n* There's just one global observer for receiving task status reports from entities or triggers.\n* Most of the time, the work is being done in a spawned entity using one of your action components,\nand in this state, there is a marker on the tree entity so it doesn't tick or do anything until\na result is ready.\n* Avoided mut World systems – the tree ticking should be able to run in parallel with other things.\n* So a fairly minimal wrapper around basic bevy systems.\n\nIn release mode, i can happily toss 100k enemies in the chase demo and zoom around at max framerate.\nIt gets slow rendering a zillion gizmo circles before any bevy_behave stuff gets in the way.\n\n**Chase example**\n\nThis is the chase example from this repo, running in release mode on an M1 mac with 100k enemies.\nEach enemy has a behaviour tree child and an active task component entity. So 1 enemy is 3 entities.\n\nhttps://github.com/user-attachments/assets/e12bc4dd-d7fb-4eca-8810-90d65300776d\n\n**Video from my space game**\n\nHere I have more complex behaviour trees managing orbits, landing, etc. Lots of PID controllers at work.\nNo attempts at optimising the logic yet, but I can add 5k ships running behaviours. Each is a dynamic avian physics object exerting forces via a thruster.\n\n\n\n\nhttps://github.com/user-attachments/assets/ef4f0539-0b4d-4d57-9516-a39783de140f\n\n\n\n\n### Chat / Questions?\n\nSay hi in the [bevy_behave discord channel](https://discord.com/channels/691052431525675048/1347180005104422942).\n\n\n\n### License\n\nSame as bevy: MIT or Apache-2.0.\n\n\u003chr\u003e\n\n\n##### Paths not taken\n\n\u003cdetails\u003e\n\n\u003csummary\u003eAlternative approach taking `IntoSystem` (not taken)\u003c/summary\u003e\n\n#### Alternative approach for conditionals\n\nI considered doing control flow by taking an `IntoSystem` with a defined In and Out type,\nsomething like this:\n```rust\n\npub type BoxedConditionSystem = Box\u003cdyn System\u003cIn = In\u003cBehaveCtx\u003e, Out = bool\u003e\u003e;\n\n#[derive(Debug)]\npub enum Behave {\n    // ...\n    /// If, then\n    Conditional(BoxedConditionSystem),\n}\n\nimpl Behave {\n    pub fn conditional\u003cMarker\u003e(system: impl IntoSystem\u003cIn\u003cBehaveCtx\u003e, bool, Marker\u003e) -\u003e Behave {\n        Behave::Conditional(Box::new(IntoSystem::into_system(system)))\n    }\n}\n```\n\nThen you could defined a cond system like, which is quite convenient:\n\n```rust\nfn check_distance(In(ctx): In\u003cBehaveCtx\u003e, q: Query\u003c\u0026Position, With\u003cPlayer\u003e\u003e) -\u003e bool {\n    let Ok(player_pos) = q.get(ctx.target_entity).unwrap();\n    player_pos.x \u003c 100.0\n}\n```\n\n\nHowever I don't think the resulting data struct would be cloneable, nor could you really read\nit from an asset file for manipulation (or can you?)\n\nI would also need mutable World in the \"tick trees\" system, which would stop it running in parallel maybe.\nAnyway observers seem to work pretty well.\n\u003c/details\u003e\n\n","funding_links":[],"categories":["Code Organization"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRJ%2Fbevy_behave","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRJ%2Fbevy_behave","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRJ%2Fbevy_behave/lists"}