7个版本

0.3.3 2023年7月9日
0.3.2 2023年6月4日
0.3.1 2022年10月11日
0.2.1 2022年10月6日
0.1.0 2022年10月5日

#1462 in 游戏开发

44 每月下载量

自定义许可证

32KB
168

Bevy 💖 Kindly

这个包是对Bevy游戏引擎的分类实体的极简实现。

总结来说,它允许用户定义、构建和查询不同种类的实体。每种实体种类都使用一组预期的组件定义,并带有可扩展的特定实体种类的命令队列。

这意味着您不需要写这样的... 😵‍💫

#[derive(Component)]
struct Friends(Vec<Entity>);

#[derive(Component)]
struct Inventory {
  items: Vec<Entity>,
  buckets: HashMap<Entity, Vec<Entity>>,
}

而是可以写这样的... 😌

#[derive(Component)]
struct Friends(Vec<Person>);

#[derive(Component)]
struct Inventory {
  items: Vec<Item>,
  buckets: HashMap<Bucket, Vec<Item>>,
}

其中 PersonItemBucket 可以定义为唯一的实体种类。

最终结果是可读性提高,因为区分不同种类实体的引用要容易得多。它还允许用户对特定种类实体上存在某些预期组件进行更安全的假设。

集成

将以下内容添加到 Cargo.toml(将 * 替换为您希望使用的版本)

[dependencies]
bevy_kindly = "*"

使用方法

要定义实体种类,您可以派生 EntityKind

#[derive(EntityKind)]
#[default_components(Friends)] // Optional: Components inserted into every `Person` by default
#[components(Name, Age)]       // Optional: Components that must be provided to spawn a `Person`
struct Person(Entity);

您也可以使用 default_bundlebundle 来定义自己的捆绑包

#[derive(EntityKind)]
#[default_bundle(DefaultPersonBundle)] // Optional: Bundle inserted into every `Person` by default
#[bundle(PersonBundle)]                // Optional: Bundle that must be provided to spawn a `Person`
struct Person(Entity);

#[derive(Bundle, Default)]
struct DefaultPersonBundle {
  friends: Friends,
};

#[derive(Bundle)]
struct PersonBundle {
  name: Name,
  age: Age,
};

请注意,您必须定义 bundlecomponents 之一,而不是两者都定义。同样,这条规则也适用于 default_bundledefault_components

或者,您也可以手动实现 EntityKind 特性

struct Person(Entity);

impl EntityKind for Person {
  type DefaultBundle = (Friends,);
  type Bundle = (Name, Age);
  
  // This function is called by the library to create new instances of this kind, but only when it's actually safe to do so
  // User should not be calling this function directly, unless in special cases.
  unsafe fn from_entity_unchecked(entity: Entity) -> Self {
    Self(entity)
  }
  
  fn entity(&self) -> Entity {
    self.0
  }
}

实体可以通过三种方式以种类进行生成,这三种方式在底层实现上都是相同的。它们可以使用 spawn_with_kind<T>

commands.spawn_with_kind::<Person>(("Alice".into(), Age(25)));

生成,如果实体已经生成,或者实体可能有多个种类,则使用 insert_kind<T>

commands.entity(entity).insert_kind::<Person>(("Alice".into(), Age(25)));

或者直接插入一个 KindBundle<T>

commands.entity(entity).insert(KindBundle::<Owner>::new(("Alice".into(), Age(25))));

注意,您必须提供所需组件以将实体标记为给定种类。

任何系统都可以使用 WithKind<T>EntityWithKind<T> 全局查询来过滤查询。 EntityWithKind<T> 设计成像 Entity 一样工作,但有一个种类。 WithKind<T> 可以在不需要实际实体时用作查询过滤器。

例如

fn do_something_with_people_and_their_friends(query: Query<(EntityWithKind<Person>, &Friends)>) {
  for (person, friends) in &query {
    let person: Person = person.get();
    ...
    let entity: Entity = person.entity();
    ...
  }
}

此外,任何实体种类都可以有特殊的命令,这些命令可能只能在同一种类的实体上调用。这是通过扩展 EntityKindCommands<T> 来实现的

trait PersonCommands {
    // Only people can be friends with each other
    fn add_friend(self, friend: Person);
}

impl PersonCommands for &mut EntityKindCommands<'_, '_, '_, Person> {
    fn add_friend(self, friend: Person) {
        let person = self.get();
        self.commands().add(move |world: &mut World| {
            // These unwraps are safe(er), because every `Person` entity has a `Friends` component
            world.get_mut::<Friends>(person.entity()).unwrap().0.push(friend);
            world.get_mut::<Friends>(friend.entity()).unwrap().0.push(person);
        });
    }
}

然后可以在具有该种类的任何实体上调用这些命令

let alice = commands.spawn_with_kind::<Person>(("Alice".into(), Age(25))).get();
commands.spawn_with_kind::<Person>(("Bob".into(), Age(30))).add_friend(alice);

或者

let alice = commands.spawn_with_kind::<Person>(("Alice".into(), Age(25))).get();
let bob = commands.spawn_with_kind::<Person>(("Bob".into(), Age(30))).get();
commands.with_kind(&alice).add_friend(bob);

任何 EntityRef 都可以使用 try_with_kind 安全地转换为种类

let person: Option<Person> = world.entity(entity).try_with_kind::<Person>();

成本

此实现通过为具有种类 T 的每个实体添加一个包含一些 PhantomData<T> 的私有组件来工作。然后,该组件被系统检查或使用,以便在需要时保证种类正确性。除此之外,与此相关的运行时成本为零。不需要注册任何额外的系统或类型。

示例

examples 目录中,您可以找到一些示例,这些示例概述了一些用例

注意:建议在查看其他示例之前先查看 person.rsnavigation.rs

限制

  • 无法防止直接删除实体种类组件。
  • 如果实体具有多个种类,任何预期组件的交集都可能引起不希望的覆盖。

依赖

~9–17MB
~203K SLoC