7个不稳定版本 (3个重大更新)
0.4.0 | 2024年3月3日 |
---|---|
0.3.0 | 2024年2月24日 |
0.2.3 | 2023年10月25日 |
0.1.2 | 2023年10月23日 |
在机器人技术中排名第162
56KB
960 行
bhv
bhv是一个用于处理行为树库。
但什么是行为树呢?
行为树基本上是一种树,每个节点定义了节点类型独有的某些动作/函数。每个节点在执行后返回一个“状态”,这决定了节点是否已经完全执行完成(以及它是否执行成功)或者它仍然需要稍后执行。父节点提供组合(想象一下运行其所有子节点直到其中一个返回“已执行但未成功”的节点),而叶节点充当“原子动作”,即无法进一步分割的动作(想象一下简单的条件检查或向终端打印消息)。为了在节点之间共享状态,将一个“上下文”类型传递给每个正在运行的节点。父节点通常通过其子节点来调整上下文类型,而叶节点通常是定义具体上下文类型的节点。
有一组常用的节点,因此库提供了这些节点,特别是
适配器节点 - 将函数适配成树节点的叶节点,例如在条件成功时执行成功的cond
,或者简单地执行函数并返回成功的action
。
装饰器节点 - 具有一个子节点的节点,可以操纵子节点的状态或根据某些条件多次运行它,例如将执行状态从成功变为失败并反之的inv
,或者运行节点直到它完成n次,例如repeat(n)
。
复合节点 - 至少有一个子节点根据某些条件运行它们的节点,例如 sequence
只要子节点成功执行就运行其子节点,或者 selector
直到其中一个子节点成功执行。
如你所见,基于节点状态的组合提供了类似于编程语言中控制流的逻辑。此外,状态之间的转换变得很简单,只需为节点提供一个条件以及条件成功时需要执行的状态节点(即条件的一个 sequence
然后是其他节点)。这使得行为树成为状态机的替代品,在状态机中,状态之间彼此不了解,因为状态管理逻辑发生在父节点内部而不是子节点内部。
要深入了解和更多示例,请参阅这个GameAIPro章节,这是一个很好的资源。
安装
简单到只需
cargo add bhv
# or for the `events` feature
cargo add bhv --features events
在你想使用库的项目目录下运行。
功能
该库提供了一些在行为树中常见的节点。具体来说,提供的节点分为以下类别
- 适配器节点,将函数适配为行为树节点。
- 装饰器节点,改变节点的行为和结果。
- 复合节点,同时运行多个节点。
有关特定节点的帮助,请参阅crate的文档。
展示
// Guess the number game implemented using behavior trees.
use std::{
io::{self, Write},
time::{SystemTime, UNIX_EPOCH},
};
use bhv::*;
fn main() {
game()
}
// The data that will be used by the nodes
#[derive(Default)]
struct Context {
guess: u32,
answer: u32,
}
// the nodes
struct RandomizeAnswer(u32, u32);
struct ReadInput;
macro_rules! print_msg {
($($arg:tt)*) => {
action(|_ctx| print!($($arg)*))
};
}
fn game() {
let tree = seq! {
RandomizeAnswer(0, 101),
seq! {
print_msg!("Enter a number from 0 to 100\n"),
ReadInput,
sel! {
seq! {
cond(|ctx: &Context| ctx.guess < ctx.answer),
print_msg!("Your guess is smaller than the actual number\n").fail(), // The game is not over yet
},
seq! {
cond(|ctx: &Context| ctx.guess == ctx.answer),
print_msg!("Your guess is correct!\n"),
},
seq! {
cond(|ctx: &Context| ctx.guess > ctx.answer),
print_msg!("Your guess is greater than the actual number\n").fail(), // The game is not over yet
}
},
}.repeat_until_pass(),
};
tree.execute(&mut Context::default());
}
impl Bhv for RandomizeAnswer {
type Context = Context;
fn update(&mut self, ctx: &mut Self::Context) -> Status {
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
ctx.answer = (time % ((self.1 - self.0) as u64)) as u32 + self.0;
Status::Success
}
}
impl Bhv for ReadInput {
type Context = Context;
fn update(&mut self, ctx: &mut Self::Context) -> Status {
io::stdout().flush().unwrap_or_default();
let mut buff = String::new();
io::stdin()
.read_line(&mut buff)
.map(|_| match buff.trim().parse() {
Ok(v) => {
ctx.guess = v;
Status::Success
}
Err(e) => {
println!("Error reading from stdin :{}\t buffer: '{}'", e, buff);
Status::Failure
}
})
.unwrap_or_else(|_| Status::Running)
}
}
功能
事件
版本0.3引入了行为树的新模型,在events
功能标志下可用。这个实验性功能允许节点只在相关事件发生时运行。当这个功能被认为完整时,它将替换实际默认实现,因此建议将现有代码库迁移到使用此功能。
与“默认”实现相比,events
上的节点引入了一个名为should_react_to(&self, kind: EventKind) -> bool
的新函数,该函数指示类型为kind
的事件是否与此节点相关。默认情况下,所有事件都被视为相关,除非行为被重写。此外,现在的update
函数现在是react(&mut self, event: &dyn Event, ctx: &mut Self::Context)
,其中使用Event
trait确保传递的值是要用作事件数据的。这个模型允许节点适应更异步的事件驱动执行模型。
为了反映这些变化,现在 execute
函数接受一个新的参数,该参数可以转换成一个迭代器,该迭代器产生 &dyn Event
,并用作节点的事件流。这个函数是 BhvExt
的部分,而不是 Bhv
,这意味着它只能有默认实现,而且节点现在可以 ?Sized
。
至于提供的节点差异,当启用此功能时,.repeat_until(cond)
将不会出现。为了实现与 bhv.repeat_until(cond)
相似的行为,请使用 seq! { bhv.pass(), cond }.repeat_until_pass()
。或者,您可以使用新的 .wait_for::<EventType>()
装饰器,该装饰器仅在触发特定事件时运行节点。为了使用此装饰器,事件应实现 EventType
。将 EventType
实现者视为简单的 Event
类型,它们不是 enum
类型。
要为您的类型实现 Event
,请按照以下步骤进行
- 对于非
enum
类型,只需将EventType
实现为
use bhv::*;
struct MyType; // my special event type
impl EventType for MyType {}
// Event is automatically implemented for EventType
- 对于
enum
类型,实现Event
如下
use bhv::*;
enum MyEvents {
A(i32),
B(char),
C,
}
impl Event for MyEvents {
fn event_name(&self) -> &str {
match self {
Self::A(_) => "Event::A",
Self::B(_) => "Event::B",
Self::C => "Event::C",
}
}
}
许可证
此模块采用 MIT 许可证。