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 游戏开发

Download history 102/week @ 2024-04-28 4/week @ 2024-05-05 9/week @ 2024-05-19

990 每月下载量
2 crates 中使用

MIT 许可证

660KB
17K SLoC

GitHub Workflow Status Crates Docs Codecov Guide

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 标识符。这意味着相同的“类型”可以多次添加到实体中。

现有实现(如 specsplanckhecs)的局限性在于需要创建新类型包装器,以便允许相同内部类型的组件共存。

这导致必须通过例如 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,用于 VelocityPosition,并且有许多 deref 强制转换,以便共存。

不安全

这个库使用不安全进行类型擦除和在存储 ComponentBufferArchetype 中的分配。

许可证:MIT

依赖项

~3–10MB
~97K SLoC