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 游戏开发
188 每月下载量
在 4 个crate中使用 (3 直接使用)
40KB
571 行
🍎 Moonshine Kind
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代码的类型安全和可读性
- 能够定义自定义实体类型
- 能够为特定实体类型定义命令
- 零或最小模板代码
用法
Kind
和 Instance
按照定义,如果一个 Entity
符合 Kind
T
,则它具有 Query<(), <T as Kind>::Filter>
,则它具有 Kind
T
。
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());
}
InstanceRef
和 InstanceMut
如果一个 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>
来定义针对特定 Kind
的 Commands
。
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