Replies: 3 comments
-
Thank you for the research and sharing it with us here! I totally agree that behavior trees are entities is a great idea. And I understand how the component-based approach definitively can be difficult with the fact that it doesn't update the entities before the end of the stage. I'd really like to see you succeed setting up behavior trees as entities. In bevy-ui-navigation I had a similar issue, and I solved it, somewhat by accident. Responsiveness is more important in UI stuff, so it came as a very positive surprise when suddenly everything felt snappy and smooth. I think ui-navigation is more relevant than expected to your issue, because it relies heavily on a hierarchy of nodes stored in the ECS. Here is a few pointers to help you:
|
Beta Was this translation helpful? Give feedback.
-
I appreciate the encouragement, and the tips! I’m not totally clear on the ramifications of the incoming stageless work, but my naive take is that I shouldn’t plan to depend on stages too much :) I can see how system ordering might increase determinism (and decrease latency) a bit. I can’t completely order all of the systems (since each type of node can exist at multiple arbitrary locations in the tree), but I think a few high level labels (e.g. control, decorator, leaf, etc…) could help! The main reason I went with a single Thanks again! I’m feeling better about trying to push this approach through. |
Beta Was this translation helpful? Give feedback.
-
Progress! I sort of took your advice and consolidated the #[derive(Component, PartialEq, Eq, Debug, Clone)]
pub enum Behavior {
Inactive,
Running,
Succeeded,
Failed,
} I originally wanted to have clear responsibilities between parents and children and to steer clear of borrowing conflicts, but I think the split was a big source of issues. Now I can't accidentally have a node that is both not The main downsides are that I can't simply filter queries on the Also having to juggle parent/child components borrowing is a little tricky. Fortunately this mostly only affects control nodes, of which there likely won't be too many types. Here's the current (still sketchy!) implementation of the sequence node: pub fn child_updated_system(
mut sequences: Query<(Entity, &mut Sequence, &Children)>,
mut behaviors: Query<&mut Behavior>,
) {
for (seq_entity, mut sequence, children) in sequences.iter_mut() {
if behaviors.get(seq_entity).map_or(false, |s| s.is_running()) {
let child_iter = children.iter().enumerate().skip(sequence.index);
let mut parent_behavior = Behavior::Succeeded;
for (index, child_entity) in child_iter {
if let Ok(mut child_behavior) = behaviors.get_mut(*child_entity) {
match *child_behavior {
Behavior::Failed => {
sequence.index = 0;
child_behavior.set(Behavior::Inactive);
parent_behavior = Behavior::Failed;
break;
}
Behavior::Running => {
if index > sequence.index {
sequence.index = index;
}
parent_behavior = Behavior::Running;
break;
}
Behavior::Inactive => {
if index > sequence.index {
sequence.index = index;
}
child_behavior.set(Behavior::Running);
parent_behavior = Behavior::Running;
break;
}
Behavior::Succeeded => {
child_behavior.set(Behavior::Inactive);
}
}
} else {
todo!("child missing a behavior!?");
}
}
// running this outside of the child loop so we can get a &mut Behavior
if let Ok(mut behavior) = behaviors.get_mut(seq_entity) {
behavior.set(parent_behavior);
}
}
}
} The first attempt would often freeze up, especially as I added more agents. This one can handle a bunch racing each other around without getting stuck! (they do pause for a moment when they can't find a path): https://discord.com/channels/691052431525675048/692648638823923732/985366957580771348 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I'm looking up for some advice on approaches to implement a behavior tree in a bevy app. I'll try to summarize my to-date attempts/thought process. Apologies if I say too much or little :)
My first try (https://discord.com/channels/691052431525675048/692648638823923732/983909053778497666) involved using Parent/Children relationships to structure an entity per node. Parent nodes (sequences, etc...) would manage an
Active
component on their children, and children would manage aStatus
component on themselves to communicate status back to their parents.Actually constructing the tree was relatively straightforward (with room for improved ergonomics later):
And leaf nodes were implemented as systems:
This felt nice in theory, but in practice it proved very tricky to get the control flow nodes correct. Even with unit tests I'm still having trouble shaking the bugs out, and race conditions abound.
This partly comes from the fact that nodes communicate via component changes, so it can take as long as a whole frame per node relationship for changes to propagate. I think this might be ok (maybe it'll even end up mimicing more realistic human response times!), but it feels a little loose and makes the flow quite tricky to follow. Not a dead end! But I'm feeling enough pain to wonder if it's a good path.
So my next thought was going from the other direction with an exclusive system. The naive first sketch quickly runs into the fact that I need to borrow the behavior tree from the world AND give nodes relatively arbitrary access:
I'm at a bit of a loss as to how to stitch this stuff together. It intuitively feels like leaf nodes as components/systems would be nice. You can write them in a relatively familiar pattern, they (I guess?) get all of the nice data orientation, can share caches as appropriate, etc... but maybe this is trying to shoehorn things? Is there maybe a better way to give leaf nodes relatively arbitrary query access without writing them as systems or handing them a
&World
reference?Any thoughts? (and thanks for bearing with me!)
Beta Was this translation helpful? Give feedback.
All reactions