Minimalistic implementation of entity kinds for Bevy ECS.

Overview

Bevy ๐Ÿ’– Kindly

This crate is a minimalistic implementation of Kinded Entities for Bevy game engine.

In summary, it allows the user to define, construct, and query entities of different kinds. Each kind of entity is defined with an expected set of components, and a specialized command queue which may be extended with commands for specific kinds of entities.

This means that instead of writing this... ๐Ÿ˜ตโ€๐Ÿ’ซ

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

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

You can write this... ๐Ÿ˜Œ

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

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

Where Person, Item, and Bucket can be defined as unique entity kinds.

The end result is increased readability because it's much easier to distinguish references to different kinds of entities. It also allows the user to make safer assumptions about the existence of some expected components on a specific kind of entity.

Integration

Add to Cargo.toml (replace * with your desired version):

[dependencies]
bevy_kindly = "*"

Usage

To define an entity kind, you can derive 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);

You may also use default_bundle and bundle to define a bundle yourself:

#[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,
};

Note that you may either define bundle or components, not both. The same rule applies to default_bundle and default_components.

Alternatively, you could also just implement EntityKind trait manually:

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
  }
}

Entities can be spawned with a kind in 3 separate ways, all of which are identical in underlying implementation. They can either be spawned using spawn_with_kind<T>:

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

Or using insert_kind<T> if the entity is already spawned, or if the entity may have multiple kinds:

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

Or by just inserting a KindBundle<T> directly:

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

Notice how you must provide the required components in order to mark the entity as the given kind.

Any system can filter queries using WithKind<T> and EntityWithKind<T> world queries. EntityWithKind<T> is designed to function like an Entity, but with a kind. WithKind<T> can be used as a query filter when the actual entity is not needed.

For example:

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();
    ...
  }
}

Additionally, any entity kind can have special commands that may only be invoked on entities of that kind. This is done by extending 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);
        });
    }
}

These commands can then be invoked on any entity with kind:

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

Or:

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);

Any EntityRef may also be "casted" safely into a kind using try_with_kind:

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

Cost

This implementation works by adding a private component with some PhantomData<T> to every entity with kind T. This component is then checked or used as filter by systems as needed in order to guarantee kind correctness. Beyond that, there is no other runtime cost associated with this. There is no need to register any additional systems or types.

Examples

In examples directory, you can find some examples which outline some use cases:

Note: It is recommended that you look at person.rs and navigation.rs before going through other examples.

Limitations

  • There is no safety against direct removal of entity kind components.
  • If an entity has multiple kinds, any intersection of the expected components can cause unwanted overrides.
You might also like...
A generated entity component system ๐ŸฆŽ

gecs ๐ŸฆŽ A generated entity component system. The gecs crate provides a compile-time generated, zero-overhead ECS for simulations on a budget. Unlike o

Specs - Parallel ECS

Specs Specs Parallel ECS Specs is an Entity-Component System written in Rust. Unlike most other ECS libraries out there, it provides easy parallelism

High performance Rust ECS library
High performance Rust ECS library

Legion aims to be a feature rich high performance Entity component system (ECS) library for Rust game projects with minimal boilerplate. Getting Start

๐Ÿคนโ€ 2D sprite rendering extension for the specs ECS system

specs-blit 2D sprite rendering extension for the Specs ECS system. All sprites are loaded onto a big array on the heap. Example // Setup the specs wor

bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy
bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy

Bevy Voxel Cone Tracing bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy. Bevy Version

Minecraft-esque voxel engine prototype made with the bevy game engine. Pending bevy 0.6 release to undergo a full rewrite.
Minecraft-esque voxel engine prototype made with the bevy game engine. Pending bevy 0.6 release to undergo a full rewrite.

vx_bevy A voxel engine prototype made using the Bevy game engine. Goals and features Very basic worldgen Animated chunk loading (ala cube world) Optim

A simple extension for `bevy-editor-pls` to support tilemap editing right inside the bevy app.

What is this This is a simple tilemap editor plugin, that hooks right into bevy_editor_pls to work with bevy_ecs_tilemap. It works completely within i

Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.
Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.

Portals for Bevy Bevy Simple Portals is a Bevy game engine plugin aimed to create portals. Those portals are (for now) purely visual and can be used t

Minecraft using Bevy and Bevy-Meshem

minecraft_bevy Minecraft_bevy was built to showcase bevy_meshem. After a week of developing it has: Chunk loading / unloading each chunk's mesh is bei

Owner
null
wecs (wckd-ecs) is a simple ECS library suitable for general use.

wecs wecs (wckd-ecs) is a simple ECS library heavily based on bevy_ecs. Motivation As part of my "Road to Rust GameDev" journey, I wanted to learn how

Joรฃo Victor 9 Jul 2, 2023
A tilemap rendering crate for bevy which is more ECS friendly.

bevy_ecs_tilemap A tilemap rendering plugin for bevy which is more ECS friendly by having an entity per tile. Features A tile per entity Fast renderin

John 414 Dec 30, 2022
๐Ÿท๏ธ Markers for Bevy ECS Entities

Bevy ECS Markers Adds the support for marking entites and fetching them in queries Example View the whole example here #[derive(EntityMarker)] enum Pl

Chopped Studio 2 Dec 23, 2022
Bring Bevy's ECS to Godot4

bevy_godot4 Bring the design power of Bevy's ECS to the mature engine capabilities of Godot 4. WARNING: This crate is very early in development, and i

JR 4 May 8, 2023
A simple type safety solution for Bevy ECS.

?? Moonshine Kind A simple type safety solution for Bevy ECS. Overview An Entity is a generic way to reference entities within Bevy ECS: #[derive(Comp

null 8 Nov 3, 2023
Minimalistic state machine for Bevy Game Engine.

??๏ธ Moonshine Behavior Minimalistic state machine for Bevy game engine. Overview This crates is designed to provide a simple, stack-based, lightweight

null 3 May 4, 2023
A direct ecs to low-level server implementation for Godot 4.1

godot_ecs What if Godot 4.1 and Bevy got married? Well, you'd get one interesting duo of data driven goodness. In Development This crate is not produc

null 5 Oct 6, 2023
Entity Component System focused on usability and speed.

Shipyard โš“ Shipyard is an Entity Component System focused on usability and speed. If you have any question or want to follow the development more clos

Dylan Ancel 524 Jan 1, 2023
A performant, small and versatile entity component system written in Rust

A performant, zero-dependency ECS library with a nice API written in Rust. Usage # Cargo.toml [dependecies] kiwi-ecs = "1.3" // lib.rs use kiwi_ecs::

Jonas Everaert 9 Nov 18, 2022
Rust bindings for entity-gym.

EntityGym for Rust EntityGym is a Python library that defines a novel entity-based abstraction for reinforcement learning environments which enables h

null 18 Apr 15, 2023