#bevy-ecs #bevy #ecs #type-safety

moonshine-kind

Bevy的简单类型安全解决方案

9个版本

0.2.0 2024年7月21日
0.1.7 2024年7月5日
0.1.6 2024年5月4日
0.1.4 2024年3月25日
0.1.0 2023年10月31日

#368 in 游戏开发

Download history 144/week @ 2024-04-28 167/week @ 2024-05-05 2/week @ 2024-05-12 32/week @ 2024-05-19 5/week @ 2024-05-26 8/week @ 2024-06-02 7/week @ 2024-06-09 7/week @ 2024-06-16 2/week @ 2024-06-23 125/week @ 2024-06-30 21/week @ 2024-07-07 15/week @ 2024-07-14 159/week @ 2024-07-21 23/week @ 2024-07-28 5/week @ 2024-08-11

188 每月下载量
4 个crate中使用 (3 直接使用)

MIT 许可证

40KB
571

🍎 Moonshine Kind

crates.io downloads docs.rs license stars

Bevy的简单类型安全解决方案。

概述

一个 Entity 是在Bevy中引用实体的泛型方式

use bevy::prelude::*;

#[derive(Component)]
struct FruitBasket {
    fruits: Vec<Entity>
}

使用实体这种方式存在的问题是缺乏关于实体“类型”的信息。这导致代码容易出错,难以调试和阅读。

这个crate试图通过引入一个新的 Instance<T> 类型来解决这个问题,该类型的行为类似于 Entity,但同时也包含有关实体“类型”的信息

use bevy::prelude::*;
use moonshine_kind::prelude::*;

#[derive(Component)]
struct Fruit;

#[derive(Component)]
struct FruitBasket {
    fruits: Vec<Instance<Fruit>>
}

功能

  • 提高Bevy代码的类型安全和可读性
  • 能够定义自定义实体类型
  • 能够为特定实体类型定义命令
  • 零或最小模板代码

用法

KindInstance

按照定义,如果一个 Entity 符合 Kind T,则它具有 Query<(), <T as Kind>::Filter>,则它具有 Kind T

任何 Component 都自动实现 Kind 特性

impl<T: Component> Kind for T {
    type Filter = With<T>;
}

这意味着您可以使用任何 Component 作为 Instance<T> 的参数

use bevy::prelude::*;
use moonshine_kind::prelude::*;

#[derive(Component)]
struct Apple;

fn count_apples(apples: Query<Instance<Apple>>) {
    println!("Apples: {}", apples.iter().count());
}

或者,您也可以通过实现 Kind 特性来自定义自己的类型

use bevy::prelude::*;
use moonshine_kind::prelude::*;

#[derive(Component)]
struct Apple;

#[derive(Component)]
struct Orange;

struct Fruit;

impl Kind for Fruit {
    type Filter = Or<(With<Apple>, With<Orange>)>;
}

fn count_fruits(fruits: Query<Instance<Fruit>>) {
    println!("Fruits: {}", fruits.iter().count());
}

InstanceRefInstanceMut

如果一个 Kind 同时也是一个 Component,你可以使用 InstanceRef<T>InstanceMut<T> 来通过单个查询项访问 Instance<T> 和相关的组件数据

use bevy::prelude::*;
use moonshine_kind::prelude::*;

#[derive(Component)]
struct Apple {
    freshness: f32
}

impl Apple {
    fn is_fresh(&self) -> bool {
        self.freshness >= 1.0
    }
}

fn fresh_apples(
    apples: Query<InstanceRef<Apple>>
) -> Vec<Instance<Apple>> {
    let mut fresh_apples = Vec::new();
    for apple in apples.iter() {
        if apple.is_fresh() {
            fresh_apples.push(apple.instance());
        }
    }
    fresh_apples
}

InstanceCommands

你还可以扩展 InstanceCommands<T> 来定义针对特定 KindCommands

InstanceCommands<T> 的行为类似于 EntityCommands,并且可以通过 .commands.instance(...) 访问(有关详细信息,请参阅 GetInstanceCommands<T>

use bevy::prelude::*;
use moonshine_kind::prelude::*;

struct Fruit;

impl Kind for Fruit {
    type Filter = (/* ... */);
}

#[derive(Component)]
struct Human;

trait Eat {
    fn eat(&mut self, fruit: Instance<Fruit>);
}

// Humans can eat:
impl Eat for InstanceCommands<'_, Human> {
    fn eat(&mut self, fruit: Instance<Fruit>) {
        // ...
    }
}

fn eat(
    human: Query<Instance<Human>>,
    fruits: Query<Instance<Fruit>>, mut commands: Commands
) {
    let human = human.single();
    if let Some(fruit) = fruits.iter().next() {
        commands.instance(human).eat(fruit);
    }
}

Instance<Any>

在编写泛型代码时,可能需要一个可以是 Any 类型的实例

use moonshine_kind::{prelude::*, Any};

struct Container<T: Kind = Any> {
    items: Vec<Instance<T>>
}

Instance<Any> 在功能上等同于 Entity

类型转换

如果为 T 实现了 CastInto<U>,则 Instance<T> 可以安全地转换为一个 Instance<U>

这通过使用 .cast_into() 方法来完成

你可以使用 kind 宏来实现给定的类型对对应的此特性

use bevy::prelude::*;
use moonshine_kind::prelude::*;

#[derive(Component)]
struct Apple;

struct Fruit;

impl Kind for Fruit {
    type Filter = With<Apple>;
}

// An Apple is a Fruit because we said so:
kind!(Apple is Fruit);

fn init_apple(apple: Instance<Apple>, commands: &mut Commands) {
    init_fruit(apple.cast_into(), commands);
    // ...
}

fn init_fruit(fruit: Instance<Fruit>, commands: &mut Commands) {
    // ...
}

示例

有关完整示例,请参阅 examples/fruits.rs

限制

实例失效

此crate不会监控实例以进行失效。

这意味着如果一个实体被修改到不再匹配某些 Kind T(例如移除 Component T),那么任何引用它的 Instance<T> 都将是无效的。

建议避免对那些可能在运行时被移除而不销毁其关联实体的组件使用类型语义。

但是,如果需要,您可以在使用之前检查实例的有效性。

use bevy::prelude::*;
use moonshine_kind::prelude::*;

struct Fruit;

impl Kind for Fruit {
    type Filter = (/* ... */);
}

fn prune_fruits(
    mut fruits: Vec<Instance<Fruit>>,
    query: &Query<(), <Fruit as Kind>::Filter>
) -> Vec<Instance<Fruit>> {
    fruits.retain(|fruit| {
        // Is the Fruit still a Fruit?
        query.get(fruit.entity()).is_ok()
    });
    fruits
}

支持

对于任何错误、问题或建议,请提交一个问题

您也可以在官方的Bevy Discord服务器上联系我,ID为@Zeenobit

依赖项

~11MB
~193K SLoC