227 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 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!
 | |
| 
 | |
| 
 | |
| //
 | |
| // COMPONENTS: Pieces of functionality we add to entities. These are just normal Rust data types
 | |
| //
 | |
| 
 | |
| struct X {
 | |
|     value: usize,
 | |
| }
 | |
| struct Y {
 | |
|     value: usize,
 | |
| }
 | |
| 
 | |
| //
 | |
| // RESOURCES: "Global" state accessible by systems. These are also just normal Rust data types!
 | |
| //
 | |
| 
 | |
| struct A {
 | |
|     value: usize,
 | |
| }
 | |
| 
 | |
| #[derive(Default)]
 | |
| struct B {
 | |
|     value: usize,
 | |
| }
 | |
| 
 | |
| struct C {
 | |
|     value: usize,
 | |
| }
 | |
| 
 | |
| //
 | |
| // SYSTEMS: Logic that runs on entities, components, and resources. These run once each time the app updates.
 | |
| //
 | |
| 
 | |
| // This is the simplest system:
 | |
| fn empty_system() {
 | |
|     println!("hello!");
 | |
| }
 | |
| 
 | |
| // Systems can also read and modify resources:
 | |
| fn resource_system(a: Resource<A>, mut b: ResourceMut<B>) {
 | |
|     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<X>, mut y: RefMut<Y>) {
 | |
|     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<A>, x: Ref<X>, mut y: RefMut<Y>) {
 | |
|     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:
 | |
| //      Startup: app.add_startup_system(startup_system)
 | |
| //      Normal:  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) {
 | |
|     resources.insert(A { value: 1 });
 | |
| 
 | |
|     // 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<A>) {
 | |
|     // 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<dyn Schedulable> 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<dyn Schedulable> type!
 | |
| fn closure_system() -> Box<dyn Schedulable> {
 | |
|     let mut counter = 0;
 | |
|     (move |x: Ref<X>, mut y: RefMut<Y>| {
 | |
|         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<State>, x: Ref<X>, mut y: RefMut<Y>) {
 | |
|     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<dyn Schedulable> {
 | |
|     let mut counter = 0;
 | |
|     SystemBuilder::new("complex_system")
 | |
|         .read_resource::<A>()
 | |
|         .write_resource::<B>()
 | |
|         .read_resource::<C>()
 | |
|         // this query is equivalent to the system we saw above: system(x: Ref<X>, y: RefMut<Y>)
 | |
|         .with_query(<(Read<X>, Write<Y>)>::query())
 | |
|         // this query only runs on entities with an X component that has changed since the last update
 | |
|         .with_query(<Read<X>>::query().filter(changed::<X>()))
 | |
|         .build(
 | |
|             move |_command_buffer, world, (a, ref mut b, c), (x_y_query, x_changed_query)| {
 | |
|                 println!("complex_system:");
 | |
|                 println!("    resources: {} {} {}", a.value, b.value, c.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);
 | |
|                 }
 | |
|             },
 | |
|         )
 | |
| }
 | |
| 
 | |
| // Our Bevy app's entry point
 | |
| fn main() {
 | |
|     // Bevy apps are created using the builder pattern. We use the builder to add systems and resources to our app 
 | |
|     App::build()
 | |
|         // Plugins are just a grouped set of app builder calls (just like we're doing here).
 | |
|         // The plugin below 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(C { value: 1 })
 | |
|         // Resources that implement the Default or FromResources trait can be added like this:
 | |
|         .init_resource::<B>()
 | |
|         .init_resource::<State>()
 | |
|         // 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 add 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();
 | |
| } | 
