use bevy::prelude::*; /// This is a guided introduction to Bevy's "Entity Component System" (ECS) /// All Bevy app logic is built using the ECS pattern, so definitely pay attention! /// /// Why ECS? /// * Data oriented: Functionality is driven by data /// * Clean Architecture: Loose coupling of functionality / prevents deeply nested inheritance /// * High Performance: Massively parallel and cache friendly /// /// ECS Definitions: /// /// Component: just a normal Rust data type. generally scoped to a single piece of functionality /// Examples: position, velocity, health, color, name /// /// Entity: a collection of components with a unique id /// Examples: Entity1 { Name("Alice"), Position(0, 0) }, Entity2 { Name("Bill"), Position(10, 5) } /// Resource: a shared global piece of data /// Examples: asset_storage, events, system state /// /// System: runs logic on entities, components, and resources /// Examples: move_system, damage_system /// /// Now that you know a little bit about ECS, lets look at some Bevy code! // Our Bevy app's entry point fn main() { // Bevy apps are created using the builder pattern. Here add our App::build() // This plugin runs our app's "system schedule" exactly once. Most apps will run on a loop, // but we don't want to spam your console with a bunch of example text :) .add_plugin(ScheduleRunnerPlugin::run_once()) // Resources can be added to our app like this .add_resource(A { value: 1 }) // Resources that implement the Default or FromResources trait can be added like this: .init_resource::() .init_resource::() // Systems can be added to our app like this // the system() call converts normal rust functions into ECS systems .add_system(empty_system.system()) // Startup systems run exactly once BEFORE all other systems. These are generally used for // app initialization code (adding entities and resources) .add_startup_system(startup_system) // Systems that need resources to be constructed can be added like this .init_system(complex_system) // Here we just the rest of the example systems .add_system(resource_system.system()) .add_system(for_each_entity_system.system()) .add_system(resources_and_components_system.system()) .add_system(command_buffer_system.system()) .add_system(thread_local_system) .add_system(closure_system()) .add_system(stateful_system.system()) .run(); } // RESOURCES: "global" state accessible by systems struct A { value: usize, } #[derive(Default)] struct B { value: usize, } struct C; // COMPONENTS: pieces of functionality we add to entities struct X { value: usize, } struct Y { value: usize, } // SYSTEMS: logic that runs on entities, components, and resources // This is the simplest system. It will run once each time our app updates: fn empty_system() { println!("hello!"); } // Systems can also read and modify resources: fn resource_system(a: Resource, mut b: ResourceMut) { b.value += 1; println!("resource_system: {} {}", a.value, b.value); } // This system runs once for each entity with the X and Y component // NOTE: x is a read-only reference (Ref) whereas y can be modified (RefMut) fn for_each_entity_system(x: Ref, mut y: RefMut) { y.value += 1; println!("for_each_entity_system: {} {}", x.value, y.value); } // This system is the same as the above example, but it also accesses resource A // NOTE: resources must always come before components in system functions fn resources_and_components_system(a: Resource, x: Ref, mut y: RefMut) { y.value += 1; println!("resources_and_components:"); println!(" components: {} {}", x.value, y.value); println!(" resource: {} ", a.value); } // This is a "startup" system that runs once when the app starts up. The only thing that distinguishes a // startup" system from a "normal" system is how it is registered: // app.add_startup_system(startup_system) // app.add_system(normal_system) // With startup systems we can create resources and add entities to our world, which can then be used by // our other systems: fn startup_system(world: &mut World, resources: &mut Resources) { // We already added A and B when we built our App above, so we don't re-add them here resources.insert(C); // Add some entities to our world world.insert( (), vec![ (X { value: 0 }, Y { value: 1 }), (X { value: 2 }, Y { value: 3 }), ], ); // Add some entities to our world world.insert( (), vec![ (X { value: 0 }, Y { value: 1 }), (X { value: 2 }, Y { value: 3 }), ], ); } // This system uses a command buffer to create a new entity on each iteration // Normal systems cannot safely access the World instance because they run in parallel // Command buffers give us the ability to queue up changes to our World without directly accessing it // NOTE: Command buffers must always come before resources and components in system functions fn command_buffer_system(command_buffer: &mut CommandBuffer, a: Resource) { // Creates a new entity with a value read from resource A command_buffer.insert((), vec![(X { value: a.value },)]); } // If you really need full/immediate read/write access to the world or resources, you can use a "thread local system". // These run on the main app thread (hence the name "thread local") // WARNING: These will block all parallel execution of other systems until they finish, so they should generally be avoided // NOTE: You may notice that this looks exactly like the "setup" system above. Thats because they are both thread local! fn thread_local_system(world: &mut World, _resources: &mut Resources) { world.insert((), vec![(X { value: 1 },)]); } // These are like normal systems, but they also "capture" variables, which they can use as local state. // This system captures the "counter" variable and uses it to maintain a count across executions // NOTE: This function returns a Box type. If you are new to rust don't worry! All you // need to know for now is that the Box contains our system AND the state it captured. // You may recognize the .system() call from when we added our system functions to our App in the main() // function above. Now you know that we are actually converting our functions into the Box type! fn closure_system() -> Box { let mut counter = 0; (move |x: Ref, mut y: RefMut| { y.value += 1; println!("closure_system: {} {}", x.value, y.value); println!(" ran {} times: ", counter); counter += 1; }) .system() } // Closure systems should be avoided in general because they hide state from the ECS. This makes scenarios // like "saving", "networking/multiplayer", and "replays" much harder. // Instead you should use the "state" pattern whenever possible: #[derive(Default)] struct State { counter: usize, } fn stateful_system(mut state: RefMut, x: Ref, mut y: RefMut) { y.value += 1; println!("stateful_system: {} {}", x.value, y.value); println!(" ran {} times: ", state.counter); state.counter += 1; } // If you need more flexibility, you can define complex systems using "system builders". // SystemBuilder enables scenarios like "multiple queries" and "query filters" fn complex_system(_resources: &mut Resources) -> Box { let mut counter = 0; SystemBuilder::new("complex_system") .read_resource::() .write_resource::() // this query is equivalent to the system we saw above: system(x: Ref, y: RefMut) .with_query(<(Read, Write)>::query()) // this query only runs on entities with an X component that has changed since the last update .with_query(>::query().filter(changed::())) .build( move |_command_buffer, world, (a, ref mut b), (x_y_query, x_changed_query)| { println!("complex_system:"); println!(" resources: {} {}", a.value, b.value); for (x, mut y) in x_y_query.iter_mut(world) { y.value += 1; println!( " processed entity {} times: {} {}", counter, x.value, y.value ); counter += 1; } for x in x_changed_query.iter(world) { println!(" x changed: {}", x.value); } }, ) }