jojoleprowebsite

[Done] The Sauce of https://jojolepro.com
git clone https://git.jojolepro.com/jojoleprowebsite.git
Log | Files | Refs | README | LICENSE

commit e26beccf8200f8184201a1492edc62e1cd36779e
parent bcb7eee17d92caba00e7392eafad4b7be0a4c6c0
Author: jojolepro <jojolepro@jojolepro.com>
Date:   Tue,  1 Jun 2021 16:55:38 -0400

create blog post about planck ecs tutorial.

Diffstat:
Asrc/blog/2021-06-01_getting_started_with_ecs/index.txt | 581+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/blog/index.gmi | 1+
Msrc/blog/index.html | 1+
3 files changed, 583 insertions(+), 0 deletions(-)

diff --git a/src/blog/2021-06-01_getting_started_with_ecs/index.txt b/src/blog/2021-06-01_getting_started_with_ecs/index.txt @@ -0,0 +1,581 @@ +Getting Started with ECS using Planck ECS +================================================================================ +Hello hello! This tutorial is aimed having between zero and moderate experience +with ECS. + +Let's jump right in! + +Creating our test project +-------------------------------------------------------------------------------- +Let's start a project so you can follow along. + +``` +cargo new --bin start_with_ecs +cd start_with_ecs +``` + +Great! Let's add our dependencies in Cargo.toml: + +``` +[dependencies] +planck_ecs = "1.2.0" +``` + +Now, we are ready to start talking about the ECS! + +ECS? +-------------------------------------------------------------------------------- +Entity-Component-System. + +It is a way of organising data that is well suited for game projects and +simulations. + +In this tutorial, we are not going to make parallel with how Object Oriented +programming works. We will just talk about the concepts one by one. + +Entity +-------------------------------------------------------------------------------- +An entity is a "thing" to which you can attach "properties". + +For example, what we call an "Apple" is an entity. You are an entity. +Every object is an entity. Simply put: Entity is the fancy name we give to +"something". + +Component +-------------------------------------------------------------------------------- +Components are the properties that make entities what they are. +For example, an "Apple" have these properties: +- Red Color +- Sweet Taste +- Round Shape + +Components are used to describe what the entity is by attaching data (meaning) +to it. + +System +-------------------------------------------------------------------------------- +Systems are simply functions. They run code. That's it! + +Is this all there is? +-------------------------------------------------------------------------------- +No, there are a couple extra concepts introduced by Planck ECS. +Don't be afraid, they are simple! + +World +-------------------------------------------------------------------------------- +World is a structure which contains everything that the Systems should have +access to. + +Resource +-------------------------------------------------------------------------------- +Anything contained inside of the world is called a "Resource". +It is just a convenient name. + +Dispatcher +-------------------------------------------------------------------------------- +Dispatchers are used to execute systems (which are just functions!) using data +that is inside of the World. + +They are optional, but very convenient! + +Ready? Set? +-------------------------------------------------------------------------------- +Go! + +Open up src/main.rs + +We will first import Planck ECS' features into our game. +Add the following line at the start of the file: + +``` +use planck_ecs::*; +``` + +Next, we will need something to contain the data we create. Let's create +a new World inside of the main function: + +``` +fn main() { + let mut world = World::default(); +} +``` + +Our First Resource +-------------------------------------------------------------------------------- +Let's insert our first Resource into the world. +We will use the Entities resource, which is a structure that holds a list of +entities that exist in the World. + +``` +fn main() { + let mut world = World::default(); + world.initialize::<Entities>(); +} +``` + +The initialize function will insert Entities inside of the World. + +Initialize works for all structures that have a default value +(implements the Default trait). +In case you ever need to insert a structure which does not implement Default, +you can always use Option<YourResource>, since Option's default is always +Option::None. + +Our First Entity +-------------------------------------------------------------------------------- +Let's now use our Entities resource to create our first entity: + +``` +fn main() { + let mut world = World::default(); + world.initialize::<Entities>(); + let entity1 = world.get_mut::<Entities>().unwrap().create(); +} +``` + +Here, get_mut gives us access to the Resource in World. +Since World doesn't contain every single possible Resource, it returns an Option +which we unwrap. + +Finally, we call the create() function on Entities, which creates a new Entity +and gives it to us. + +Since it happens often that we initialize something and then access it right +away, there is a convenient function that does both steps at the same time: +world.get_mut_or_default. + +Let's rewrite our code using it: +``` +fn main() { + let mut world = World::default(); + let entity1 = world.get_mut_or_default::<Entities>().create(); +} +``` + +Our first Component +-------------------------------------------------------------------------------- +Now, let's add some meaning to this Entity. We will attach a color to it. + +First, we will create the Color component. This can be anything: +a struct, an enum, you choose. + +Here, let's use an enum. Add the following before or after the main function: +``` +#[derive(Debug, PartialEq)] +enum Color { + Red, + Blue, + Green, +} + +fn main() { +[...] +``` + +And now, we can tell the World that we want to use this enum as a Component by +initializing a Components resource and specifying that we want the Color enum. +``` +fn main() { + let mut world = World::default(); + world.initialize::<Components<Color>>(); + let entity1 = world.get_mut_or_default::<Entities>().create(); +} +``` + +Finally, let's attach our Component to the Entity! + +``` +fn main() { + let mut world = World::default(); + world.initialize::<Components<Color>>(); + let entity1 = world.get_mut_or_default::<Entities>().create(); + world.get_mut::<Components<Color>>().unwrap().insert(entity1, Color::Red); +} +``` + +Here we use the insert(entity, component) method available on the Components +resource. + +Here, we have two simplifications that we can do to our code. +First is to tell rust to guess that we want the Color component by using _. + +``` +fn main() { + let mut world = World::default(); + world.initialize::<Components<Color>>(); + let entity1 = world.get_mut_or_default::<Entities>().create(); + world.get_mut::<Components<_>>().unwrap().insert(entity1, Color::Red); +} +``` + +Here, rust is smart enough to understand from our usage of Color::Red on the +right side that what we want is Components<Color>. + +Now, this code looks strangely like the one we saw when creating the Entity +previously. Let's apply the same trick: + +``` +fn main() { + let mut world = World::default(); + let entity1 = world.get_mut_or_default::<Entities>().create(); + world.get_mut_or_default::<Components<_>>().insert(entity1, Color::Red); +} +``` + +Yep! We can use get_mut_or_default here too! + +Let summarize. Now, we have an Entity stored in the Entities resource. +We also have a Color::Red component that is stored in the Components<Color> +resource and that is "assigned" to our Entity. + +Using Systems +-------------------------------------------------------------------------------- +Remember the core concepts? Entity, Component and System. +Well, now we are at the System part. + +As we saw previously, Systems are just functions that use data from the World. + +Let's write a System that changes the color of the apple to blue. + +``` +fn change_color_system(colors: &mut Components<Color>) -> SystemResult { + Ok(()) +} + +fn main() { + [...] +``` + +We have some things to explain here. +The parameters of Systems use references (that's the & and &mut symbols). +A special rule for Systems is that all parameters using &mut must be placed +after all parameters using &. + +For example, the following is invalid: +``` +fn system(a: &mut A, b: &B, c: &mut C) -> SystemResult {...} +``` + +It should instead be written as: +``` +fn system(b: &B, a: &mut A, c: &mut C) -> SystemResult {...} +``` + +Now, you might wonder what that SystemResult thing is. +This is a feature that is unique to Planck ECS: Error handling. + +In most implementation of Entity Component Systems, it is assumed that Systems +cannot fail, which is quite naive. +Here, returning SystemResult allows the user to return either Ok(()), which +means no error occured, or Err(SomeErrorType), which indicates that the System +failed to execute. + +For this tutorial, we will always use Ok. + +Now, let's change the color of that apple! + +``` +fn change_color_system(colors: &mut Components<Color>) -> SystemResult { + for color in join!(&mut colors) { + *color = Color::Blue; + } + Ok(()) +} +``` + +The join!() macro returns an iterator over the Components we reference. +Here, we tell the join!() macro to give us all Color Components mutable, so +that we can modify them. + +Then, we change the color. + +Running the System +-------------------------------------------------------------------------------- +To run a System, we use: + +``` +fn main () { + [...] + change_color_system.system().run(&mut world).unwrap(); +} +``` + +This will automatically get the required Components from World and call +the function change_color_system. + +Now, what happens if the System uses a Resource which is not in World? +It will crash! + +Let's fix this up: +``` +fn main() { + [...] + let mut system = change_color_system.system(); + system.initialize(&mut world); + system.run(&mut world).unwrap(); + +} +``` + +When calling the initialize function on a System, it will use world.initialize() +for every Resource that is in the System's parameters. + +Convenient, right? + +Dispatcher +-------------------------------------------------------------------------------- +Remember Dispatchers? We saw that they are used to execute multiple Systems. +Well they do more than that, they also call system.initialize for you! + +They are also very convenient, because without them you would need three +lines for each System! + +Here's how we use a Dispatcher: +``` +fn main() { + let mut world = World::default(); + let mut dispatcher = DispatcherBuilder::default() + .add(change_color_system) + .build(&mut world); + + let entity1 = world.get_mut_or_default::<Entities>().create(); + world.get_mut_or_default::<Components<_>>().insert(entity1, Color::Red); + + dispatcher.run_seq(&mut world).unwrap(); +} +``` + +The Dispatcher is pretty much self explanatory. Simply chain +.add(system).add(system2).add(system3) to add Systems, then complete the +Dispatcher using the build(&mut world) function. + +It is when calling build(&mut world) that the initialize function will be called +on each System. + +Finally, we run the Systems using dispatcher.run_seq(&mut world). + +run_seq means "Run Sequentially", which will execute the Systems one after the +other. + +Planck ECS also has a feature where you can execute multiple Systems at the +same time. We will not use it in this tutorial. + +Multiple Components +-------------------------------------------------------------------------------- +Now, let's come back to our game! +Let's imagine that we had another Entity that also had a Color Component, but +that we don't want to change its Color. + +``` +fn main() { + let mut world = World::default(); + let apple = world.get_mut_or_default::<Entities>().create(); + let orange = world.get_mut_or_default::<Entities>().create(); + world.get_mut_or_default::<Components<_>>().insert(apple, Color::Red); + world.get_mut_or_default::<Components<_>>().insert(orange, Color::Red); +} +``` + +With our current System, both entities will have their colors changed to blue. + +We need a way to add some more meaning (data) to distinguish between the two +entities. + +There are a couple ways to do this. +- We can add a "Apple" Component. +- We can add a "ColorChanging" Component. + +Generally speaking, the second approach is to prefer, because it encourages +re-using Systems for more than one type of Entity. + +Let's create this Component: + +``` +struct ColorChanging; +``` + +and add it to our apple Entity: + +``` +let apple = world.get_mut_or_default::<Entities>().create(); +let orange = world.get_mut_or_default::<Entities>().create(); +world.get_mut_or_default::<Components<_>>().insert(apple, Color::Red); +world.get_mut_or_default::<Components<_>>().insert(apple, ColorChanging); //here +world.get_mut_or_default::<Components<_>>().insert(orange, Color::Red); +``` + +Finally, let's make it so our System only runs on Entities that have a +ColorChanging Component: + +``` +fn change_color_system( + changings: &Components<ColorChanging>, + colors: &mut Components<Color>) -> SystemResult { + + for (_changing, color) in join!(&changings && &mut colors) { + *color.unwrap() = Color::Blue; + } + + Ok(()) +} +``` + +First, we add the Components<ColorChanging> parameter. + +Then, we modify the parameters of join!() macro. +Here, we tell it to give us the Component pairs only for Entities having +ColorChanging AND Color. We also specify that we want ColorChanging immutably +(we are not going to modify it) and Color mutably (we do change the color). + +At the left side of the for loop, we ignore the ColorChanging Component by +adding a _ in front of the variable name. We do this because we used the +ColorChanging Component as a filter for join, but we don't actually plan on +using it. + +Finally, we added .unwrap() after the color variable. +We need this when joining over multiple Components at the same time. + +This is because the join macro also supports || OR operations. For example, if +we used: +``` +join!(&mut colors || &changings) +``` +we would have pairs of Components that look like this: +``` +(&mut Option<Color>, &Option<ColorChanging>). + +Because we use &&, we can assume that both Options have a value (Some). +But if we use ||, then we cannot assume this, as one of the Entities has a +Color but no ColorChanging Component. + +Making sure everything works +-------------------------------------------------------------------------------- +It is always a good practice to verify that our Systems are working fine. +Let's do this quickly in the main function: +``` + [...] + dispatcher.run_seq(&mut world).unwrap(); + + assert_eq!(*world.get::<Components<Color>>().unwrap().get(apple).unwrap(), Color::Blue); + assert_eq!(*world.get::<Components<Color>>().unwrap().get(orange).unwrap(), Color::Red); +} +``` + +Removing a Component +-------------------------------------------------------------------------------- +Simple! +Use: + +``` + world.get_mut::<Components<Color>>().unwrap().remove(apple); +``` + +Of course, this works inside of Systems too, since you have Components<Color> +as a parameter! + +Removing an Entity +-------------------------------------------------------------------------------- +Also simple! +Use: + +``` + world.get_mut::<Entities>().unwrap().kill(apple); +``` + +However, it is important to know that removing an Entity using kill(entity) will +NOT delete its Components that are in the Components<T> Resources. + +This can be done automatically by using the world.maintain() function: + +``` + world.maintain(); +``` + +It is recommended to run world.maintain() after using +dispatcher.run_seq(&mut world), as this ensures everything is cleaned up! + +Conclusion +-------------------------------------------------------------------------------- +I hope this was instructive! + +It might seem like there are lots of part, but also consider that you now know +almost every single function of Planck ECS! + +If you liked this tutorial or the work I do (I wrote Planck ECS, by the way!), +consider donating on my Patreon. +https://patreon.com/jojolepro + +It is your donations that enable me to continue creating Rust libraries and +learning material like this one! + +Full Code +-------------------------------------------------------------------------------- +Here is the full code created in this tutorial: + +``` +use planck_ecs::*; + +#[derive(Debug, PartialEq)] +enum Color { + Red, + Blue, + Green, +} + +struct ColorChanging; + +fn change_color_system( + changings: &Components<ColorChanging>, + colors: &mut Components<Color>, +) -> SystemResult { + for (_changing, color) in join!(&changings && &mut colors) { + *color.unwrap() = Color::Blue; + } + Ok(()) +} + +fn main() { + let mut world = World::default(); + let mut dispatcher = DispatcherBuilder::default() + .add(change_color_system) + .build(&mut world); + + let apple = world.get_mut_or_default::<Entities>().create(); + let orange = world.get_mut_or_default::<Entities>().create(); + world + .get_mut_or_default::<Components<_>>() + .insert(apple, Color::Red); + world + .get_mut_or_default::<Components<_>>() + .insert(apple, ColorChanging); + world + .get_mut_or_default::<Components<_>>() + .insert(orange, Color::Red); + + dispatcher.run_seq(&mut world).unwrap(); + + assert_eq!( + *world + .get::<Components<Color>>() + .unwrap() + .get(apple) + .unwrap(), + Color::Blue + ); + assert_eq!( + *world + .get::<Components<Color>>() + .unwrap() + .get(orange) + .unwrap(), + Color::Red + ); + + world.get_mut::<Components<Color>>().unwrap().remove(apple); + world.get_mut::<Entities>().unwrap().kill(apple); + + world.maintain(); +} +``` + += https://patreon.com/jojolepro = diff --git a/src/blog/index.gmi b/src/blog/index.gmi @@ -1,3 +1,4 @@ +=> 2021-06-01_getting_started_with_ecs Getting Started with ECS using Planck ECS => 2021-05-31_minigene_and_the_future Minigene And The Future => 2021-01-13_removing_the_us Removing the US => 2021-01-13_planck_ecs Planck: A Minimalistic Yet Performant ECS Library diff --git a/src/blog/index.html b/src/blog/index.html @@ -1,3 +1,4 @@ +<a href="2021-06-01_getting_started_with_ecs">Getting Started with ECS using Planck ECS</a> <a href="2021-05-31_minigene_and_the_future">Minigene And The Future</a> <a href="2021-01-13_removing_the_us">Removing the US</a> <a href="2021-01-13_planck_ecs">Planck: A Minimalistic Yet Performant ECS Library</a>