2 个版本

0.0.2 2021年5月21日
0.0.1 2020年1月5日

#839游戏开发

Apache-2.0

68KB
1.5K SLoC

狂喜 - 静态类型 ECS


lib.rs:

用于实现实体-组件-系统(ECS)模式的库。

API 基本基于 specs,但更侧重于在编译时静态验证库的使用(而不是像 specs 那样动态验证)。这牺牲了一些灵活性,但几乎所有的逻辑错误都能在编译时被发现。

它(目前)也没有像 specs 那样优化,因为它是为 rogue-like 设计的。

使用方法

实现 ECS 需要以下步骤

  1. 使用 define_world! 宏定义你需要存储的组件和资源。这将生成一个名为 World 的结构体,以及库与它交互所需的特质实现
  2. 实现一个或多个 System
  3. 使用 run_systemtraits/trait.WorldInterface.html#method.run_system 方法在 World 上运行你的 System

特性

这个库以某种高级方式使用了 Rust 的类型系统。在 traits 模块中,你可以找到 NestFlatten 特质,它们允许将平面元组(如 (A, B, C))转换为嵌套表示 (A, (B, (C, ()))) 并再次转换回来。这些特质为长度最多为 32 的元组实现了,这对于大多数用例应该足够了。

在API边界将扁平元组转换为嵌套元组,使我们能够递归地实现某些特性,而不是需要为每个特性编写宏以在扁平元组类型上实现它们。因此,您会在代码库的各个地方看到具有 Nest/Flatten 特性边界的类型参数。由于无法告诉编译器 NestFlatten 是相反的操作,偶尔您会看到指定嵌套表示也是可展平的边界。

此外,我们还有一些 类型级元编程 特性,提供了一定程度的编译时不变性检查。

通常,客户端代码不需要太担心这些,但它确实有使编译器错误信息不那么有帮助的不幸副作用。

示例

#[derive(Debug, PartialEq)]
pub struct Data {
    x: u32,
}

// `Default` impl that isn't the additive identity.
impl Default for Data {
    fn default() -> Data {
        Data { x: 128 }
    }
}

#[derive(Debug, Default, PartialEq)]
pub struct MoreData {
    y: u32,
}

define_world!(
    #[derive(Default)]
    pub world {
        components {
            test1: BasicVecStorage<Data>,
            test2: BasicVecStorage<MoreData>,
        }
        resources {}
    }
);

let mut w = World::default();
w.new_entity().with(Data { x: 1 }).build();
w.new_entity().with(Data { x: 1 }).build();
let md = w
    .new_entity()
    .with(Data { x: 2 })
    .with(MoreData { y: 42 })
    .build();
w.new_entity().with(Data { x: 3 }).build();
w.new_entity().with(Data { x: 5 }).build();
w.new_entity().with(Data { x: 8 }).build();

/// `TestSystem` adds up the values in every `Data` component (storing the result in `total`),
/// and multiplies every `MoreData` by the `Data` in the same component.
#[derive(Default)]
struct TestSystem {
    total: u32,
}

impl<'a> System<'a> for TestSystem {
    type Dependencies = (
        ReadComponent<'a, Data>,
        WriteComponent<'a, MoreData>,
    );
    fn run(&'a mut self, (data, mut more_data): Self::Dependencies) {
        self.total = 0;

        (&data,).for_each(|_, (d,)| {
            self.total += d.x;
        });

        (&data, &mut more_data).for_each(|_, (d, md)| {
            md.y *= d.x;
        });
    }
}

let mut system = TestSystem::default();
w.run_system(&mut system);

assert_eq!(system.total, 20);
assert_eq!(
    <World as GetComponent<'_, MoreData>>::get(&w).get(md),
    Some(&MoreData { y: 84 })
);

通过 ReadComponent 访问的组件不能可变迭代

#[derive(Debug, PartialEq)]
pub struct Data {
    x: u32,
}

define_world!(
    pub world {
        components {
            test1: BasicVecStorage<Data>,
        }
        resources {}
    }
);

#[derive(Default)]
struct TestSystem {}

impl<'a> System<'a> for TestSystem {
    type Dependencies = (
        ReadComponent<'a, Data>,
    );
    fn run(&'a mut self, (data,): Self::Dependencies) {
        (&mut data,).for_each(|(d,)| {
            // do something
        });
    }
}

无运行时依赖