13 个不稳定版本 (6 个破坏性更新)
0.7.1 | 2024年5月1日 |
---|---|
0.6.2 | 2024年2月15日 |
0.6.0 | 2023年10月29日 |
0.4.0 | 2023年4月11日 |
0.1.0 | 2022年5月30日 |
#61 in 游戏开发
990 每月下载量
在 2 crates 中使用
660KB
17K SLoC
Flax
Flax 是一个高效且易于使用的实体组件系统。
世界通过简单的标识符组织,称为 Entity
,它可以附加任意数量的组件。
系统在世界的实体上操作,并提供应用程序逻辑。
请阅读 用户指南
特性
实时演示
在这里查看使用 wasm 的实时演示:[链接](https://ten3roberts.github.io/flax/asteroids)
示例用法
// Declare static components
use flax::*;
component! {
health: f32,
regen: f32,
pos: (f32, f32),
player: (),
items: Vec<String>,
}
let mut world = World::new();
// Spawn an entity
let p = EntityBuilder::new()
.set(health(), 50.0)
.tag(player())
.set(pos(), (0.0, 0.0))
.set(regen(), 1.0)
.set_default(items())
.spawn(&mut world);
let mut query = Query::new((health().as_mut(), regen()));
// Apply health regeneration for all matched entites
for (health, regen) in &mut query.borrow(&world) {
*health = (*health + regen).min(100.0);
}
系统
具有逻辑的查询可以抽象为系统,多个系统可以收集到计划中。
let regen_system = System::builder()
.with_query(Query::new((health().as_mut(), regen())))
.for_each(|(health, regen)| {
*health = (*health + regen).min(100.0);
})
.boxed();
let despawn_system = System::builder()
.with_query(Query::new(entity_ids()).filter(health().le(0.0)))
.with_cmd_mut()
.build(|mut q: QueryBorrow<EntityIds, _>, cmd: &mut CommandBuffer| {
for id in &mut q {
cmd.despawn(id);
}
})
.boxed();
let mut schedule = Schedule::from([regen_system, despawn_system]);
schedule.execute_par(&mut world)?;
关系
Flax 提供了实体之间的一等多对多关系,这对于树形场景层次结构、图和实体之间的物理连接非常有用。
关系可以是无状态的,也可以有相关数据,如弹簧或连接强度。
关系对缓存友好,查询子项不需要随机访问。此外,关系在销毁时会被清理,在序列化过程中保持稳定,即使实体 ID 由于冲突而迁移。
有关更多详细信息,请参阅 指南。
component! {
child_of(parent): () => [ Debuggable ],
}
let mut world = World::new();
let parent = Entity::builder()
.set(name(), "Parent".into())
.spawn(&mut world);
let child1 = Entity::builder()
.set(name(), "Child1".into())
.set_default(child_of(parent))
.spawn(&mut world);
与其他 ECS 的比较
与其他 ECS 实现相比,组件只是附加数据的另一个 Entity
标识符。这意味着相同的“类型”可以多次添加到实体中。
现有实现(如 specs、planck 或 hecs)的局限性在于需要创建新类型包装器,以便允许相同内部类型的组件共存。
这导致必须通过例如 derive-more
或在使用过程中取消引用 newtypes 来转发所有特质实现。
通过将组件与类型分离,组件可以一起工作,而无需使用 deref
或 newtype 构造。
component! {
velocity: Vec3,
position: Vec3,
}
let vel = world.get(entity, velocity())?;
let mut pos = world.get_mut(entity, position())?;
let dt = 0.1;
*pos += *vel * dt;
另外,由于组件必须先声明,这限制了可以作为组件插入的类型数量。这修复了由类型指定组件引起的细微错误,例如使用 Arc<Type>
而不是仅仅 Type
,这导致后续系统在实体上找不到 Type
。
使用静态声明的组件使 Rust 类型系统禁止这些情况并提前捕获这些错误。
动机
在学校的游戏开发中,我使用了 hecs
ECS。这是一个很棒的库,作者 Ralith 在接受贡献和询问方面做得非常出色。
尽管如此,我经常因为 类似 类型而出现细微的错误。游戏引擎中充满了巨大的 newtype,用于 Velocity
、Position
,并且有许多 deref 强制转换,以便共存。
不安全
这个库使用不安全进行类型擦除和在存储 ComponentBuffer
和 Archetype
中的分配。
许可证:MIT
依赖项
~3–10MB
~97K SLoC