From 844d5ae309345f2e44cc104604619a8fc0d08a7b Mon Sep 17 00:00:00 2001 From: Olivia Date: Fri, 13 Dec 2024 15:23:48 +0000 Subject: [PATCH] Fix book text --- books/en_US/src/SUMMARY.md | 4 +- books/en_US/src/c03-01-colours.md | 43 +++++---- books/en_US/src/c03-02-animations.md | 90 ++++++++---------- books/en_US/src/c03-03-sounds-events.md | 1 - books/en_US/src/c03-04-sounds-events.md | 1 - code/.DS_Store | Bin 14340 -> 10244 bytes code/rust-sokoban-c03-01/src/components.rs | 8 ++ code/rust-sokoban-c03-01/src/entities.rs | 2 + code/rust-sokoban-c03-01/src/map.rs | 4 + code/rust-sokoban-c03-02/src/components.rs | 10 ++ code/rust-sokoban-c03-02/src/entities.rs | 4 + code/rust-sokoban-c03-02/src/main.rs | 2 + .../src/systems/rendering.rs | 4 + 13 files changed, 99 insertions(+), 74 deletions(-) delete mode 100644 books/en_US/src/c03-03-sounds-events.md delete mode 100644 books/en_US/src/c03-04-sounds-events.md diff --git a/books/en_US/src/SUMMARY.md b/books/en_US/src/SUMMARY.md index 375791b..3bf0c8f 100644 --- a/books/en_US/src/SUMMARY.md +++ b/books/en_US/src/SUMMARY.md @@ -14,6 +14,6 @@ - [Advanced gameplay](./c03-00-intro.md) - [Coloured boxes](./c03-01-colours.md) - [Animations](./c03-02-animations.md) - - [Events](./c03-03-sounds-events.md) - - [Sounds](./c03-04-sounds-events.md) + - [Events](./c03-03-events.md) + - [Sounds](./c03-04-sounds.md) - [Batch rendering](./c03-05-batch-rendering.md) diff --git a/books/en_US/src/c03-01-colours.md b/books/en_US/src/c03-01-colours.md index f975052..5e06749 100644 --- a/books/en_US/src/c03-01-colours.md +++ b/books/en_US/src/c03-01-colours.md @@ -1,7 +1,9 @@ # Coloured boxes + It's time for a little more flair in our game! The gameplay so far is quite simple, put the box on the spot. Let's make it more exciting by adding different coloured boxes. We'll go with red and blue for now but feel free to adapt this to your preference, and create more colours! To win now you'll have to put the box on the same colour spot to win. ## Assets + First let's add the new assets, right click and download these as images, or create your own! ![Blue box](./images/box_blue.png) @@ -38,78 +40,83 @@ The directory structure should look like this (notice we've removed the old defa ``` ## Component changes + Now let's add an enum for the colour (if you chose to implement more than two colours you'll have to add them here). ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:29:32}} +{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:box_colour}} ``` -Now let's use this enum both for the box and the spot. +Now let's use this enum both for the box and the spot. ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:44:54}} +{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:box}} ``` ## Entity creation -Let's also add the colour as a parameter when we created boxes and spots and make sure we pass the correct asset path based on the colour enum. + +Let's also add the colour as a parameter when we created boxes and spots and make sure we pass the correct asset path based on the colour enum. In order to create the correct string for the asset path we basically want `"/images/box_{}.png"` where `{}` is the colour of the box we are trying to create. The challenge we have now is that we are using an enum for the colour, so the Rust compiler will not know how to convert `BoxColour::Red` into `"red"`. It would be really cool to be able to do `colour.to_string()` and get the right value. Fortunately, Rust has a nice way for us to do this, we need to implement the `Display` trait on the `BoxColour` enum. Here is how that looks like, we simply specify how to map each variant of the enum into a string. ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:34:43}} +{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:box_colour_display}} ``` Now let's include the colour in our entity creation code and use the fancy `colour.to_string()` we just made possible in the previous snippet. ```rust // entities.rs -{{#include ../../../code/rust-sokoban-c03-01/src/entities.rs:27:48}} +{{#include ../../../code/rust-sokoban-c03-01/src/entities.rs:create_box}} ``` ## Map + Now let's change our map code to allow new options for coloured boxes and spots: + * "BB" for blue box * "RB" for red box -* "BS" for blue spot +* "BS" for blue spot * "RS" for red spot ```rust // map.rs -{{#include ../../../code/rust-sokoban-c03-01/src/map.rs}} +{{#include ../../../code/rust-sokoban-c03-01/src/map.rs:map_match}} ``` -And let's update our static map in the main. +And let's update our static map when initialising the level. ```rust -// main.rs -{{#include ../../../code/rust-sokoban-c03-01/src/main.rs:65:80}} +// map.rs +{{#include ../../../code/rust-sokoban-c03-01/src/map.rs:initialize_level}} ``` ## Gameplay -Now we've done the hard work, so we can go ahead and test this code out. You'll notice everything works, but there is a big gameplay bug. You can win by putting the red box on the blue spot and viceversa. Let's fix that. + +Now we've done the hard work, so we can go ahead and test this code out. You'll notice everything works, but there is a big gameplay bug. You can win by putting the red box on the blue spot and viceversa. Let's fix that. We've learnt before that data goes in components and behaviour goes in systems - as per ECS methodology. What we are discussing now is behaviour, so it must be in a system. Remember how we added a system for checking whether you've won or not? Well that is the exact place we are after. Let's modify the run function and check the colour of the spot and the box match. ```rust -// gameplay_state_system.rs -{{#include ../../../code/rust-sokoban-c03-01/src/systems/gameplay_state_system.rs:20:52}} +// gameplay.rs +{{#include ../../../code/rust-sokoban-c03-01/src/systems/gameplay.rs}} ``` Now if you compile the code at this point it should complain about the fact that we are trying to compare two enums with `==`. Rust doesn't know by default how to handle this, so we must tell it. The best way we can do that is add an implementation for the `PartialEq` trait. ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:28:32}} +{{#include ../../../code/rust-sokoban-c03-01/src/components.rs:box_colour_eq}} ``` Now is a good time to discuss these unusual `derive` annotations. We've used them before, but never got too deep into what they do. Derive attributes can be applied to structs or enums and they allow us to add default trait implementations to our types. For example here we are telling Rust to add the `PartialEq` default trait implementations to our `BoxColour` enum. -Here is how the `PartialEq` default implementation looks like, it just checks if something equals itself. If it does, the comparison succeeds and if it doesn't it fails. Don't worry too much about this if it doesn't make sense. +Here is how the `PartialEq` default implementation looks like, it just checks if something equals itself. If it does, the comparison succeeds and if it doesn't it fails. Don't worry too much about this if it doesn't make sense. ```rust pub trait PartialEq { @@ -118,7 +125,7 @@ pub trait PartialEq { } ``` -So by adding the `#[derive(PartialEq)]` on top of the enum we are telling Rust that `BoxColour` now implements the partial eq trait we saw before, which means if we try to do `box_colour_1 == box_colour_2` it will use this implementation which will just check if the colour_1 object is the same as the colour_2 object. This is not the most sophisticated partial equality implementation, but it should do just fine for our usecase. +So by adding the `#[derive(PartialEq)]` on top of the enum we are telling Rust that `BoxColour` now implements the partial eq trait we saw before, which means if we try to do `box_colour_1 == box_colour_2` it will use this implementation which will just check if the colour_1 object is the same as the colour_2 object. This is not the most sophisticated partial equality implementation, but it should do just fine for our usecase. > **_MORE:_** Read more about PartialEq [here](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) and more about derivable traits [here](https://doc.rust-lang.org/book/appendix-03-derivable-traits.html). @@ -126,4 +133,4 @@ Now we can compile the code the reap the rewards of our efforts by seeing the ga ![Sokoban play](./images/colours.gif) -> **_CODELINK:_** You can see the full code in this example [here](https://github.com/iolivia/rust-sokoban/tree/master/code/rust-sokoban-c03-01). \ No newline at end of file +> **_CODELINK:_** You can see the full code in this example [here](https://github.com/iolivia/rust-sokoban/tree/master/code/rust-sokoban-c03-01). diff --git a/books/en_US/src/c03-02-animations.md b/books/en_US/src/c03-02-animations.md index d2ec577..f93627d 100644 --- a/books/en_US/src/c03-02-animations.md +++ b/books/en_US/src/c03-02-animations.md @@ -1,29 +1,36 @@ # Animations -In this section we are going to look at adding animations to our game, we'll start with some basic ones but feel free to add more complex ones given the ideas in this tutorial. We'll add two animations: making the player blink and making the boxes jiggle slightly in place. + +In this section we are going to look at adding animations to our game, we'll start with some basic ones but feel free to add more complex ones given the ideas in this tutorial. We'll add two animations: making the player blink and making the boxes jiggle slightly in place. ## What is an animation? -An animation is simply a set of frames played at a specific time interval that gives the illusion of movement. Think of it like a video (a video is just a set of images played in sequence), but much lower framerate. -For example, to get our player blinking we'll have three animation frames: +An animation is simply a set of frames played at a specific time interval that gives the illusion of movement. Think of it like a video (a video is just a set of images played in sequence), but much lower framerate. + +For example, to get our player blinking we'll have three animation frames: + 1. our current player with the eyes open 1. player with eyes a little bit closed 1. player with eyes completely closed -If we play these three frames in sequence you'll notice it looks like the player is blinking. You can try this out by opening the images and shifting between them quickly on the image preview. +If we play these three frames in sequence you'll notice it looks like the player is blinking. You can try this out by opening the images and shifting between them quickly on the image preview. + +There are a few gotchas on this: -There are a few gotchas on this: * the assets need to be done with a specific framerate in mind - for us we will go with 250 milliseconds, meaning we will play a new animation frame every 250ms, so we will have 4 frames per second * the assets need to be consistent with each other - imagine we had two types of players which had different assets and different looking eyes, we would have to make sure that when we create the three frames mentioned above they would be consistent, otherwise the two players would blink at different rates * designing assets for a lot of frames is a lot of work, so we'll try to keep our animations quite simple and stick to the key frames ## How will it work? + So how is this going to work in our existing Sokoban game? We'll have to: + 1. Change our renderable component to allow multiple frames - we could also create a new renderable component that handles animated renderables and keep the one we have for static renderables, but it feels a bit cleaner to keep them together for now 1. Modify the player entity construction to take multiple frames 1. Keep track of time in our rendering loop - we'll discuss this one in more detail so don't worry if it's not obvious why we need to do this 1. Change the rendering system taking into account the number of frames, the time and the frame that is supposed to be rendered at a given time ## Assets + Let's add the new assets for the player, it should then look like this. Notice we created a convention to name the frames sequentially, this is not strictly necessary, but it will help us keep track of the order easily. ![Player 1](./images/player_1.png) @@ -45,7 +52,8 @@ Let's add the new assets for the player, it should then look like this. Notice w ``` ## Renderable -Now let's update our renderable component to receive multiple frames, instead of having a single path, we'll have a list of paths, this should be pretty straightforward. + +Now let's update our renderable component to receive multiple frames, instead of having a single path, we'll have a list of paths, this should be pretty straightforward. Let's also add two new functions to construct the two types of renderables, either with a single path or with multiple paths. These two functions are associated functions, because they are associated with the struct `Renderable`, but they are the equivalent of static functions in other languages since they don't operate on instances (notice they don't receive `&self` or `&mut self` as the first argument, which means we can call them in the context of the struct not an instance of the struct). They are also similar to factory functions, since they encapsulate the logic and validation required before actually constructing an object. @@ -53,53 +61,56 @@ Let's also add two new functions to construct the two types of renderables, eith ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:19:32}} -{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:48}} +{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:renderable}} ``` Next, we need a way of telling if a renderable is animated or static, which we will use in the rendering system. We could leave the paths member variable public and allow the rendering system to get the length of the paths and infer based on the length, but there is a more idiomatic way. We can add an enum for the kind of renderable, and add a method on the renderable to get that kind, in this way we encapsulate the logic of the kind within the renderable, and we can keep paths private. You can put the kind declaration anywhere in the components.rs, but ideally next to the renderable declaration. ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:14:18}} +{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:renderable_kind}} ``` Now let's add a function to tell us the kind of a renderable based on the internal paths. ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:25:40}} -{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:48}} +{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:renderable_kind_fn}} ``` -And finally, because we made paths private, we need to allow users of renderable to get a specific path from our list. For static renderables, this will be the 0th path (the only one) and for animated paths we'll let the rendering system decide which path should be rendered based on the time. The only tricky bit here is if we get asked for a frame bigger than what we have, we will wrap that around by modding with the length. +And finally, because we made paths private, we need to allow users of renderable to get a specific path from our list. For static renderables, this will be the 0th path (the only one) and for animated paths we'll let the rendering system decide which path should be rendered based on the time. The only tricky bit here is if we get asked for a frame bigger than what we have, we will wrap that around by modding with the length. ```rust // components.rs -{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:25}} +{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:renderable_path_fn}} +``` - //... +Finally, we add a way to construct renderables based on one or more paths. -{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:42:48}} +```rust +// components.rs +{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:renderable_new_fn}} ``` ## Entity creation + Next up, let's update our player entity creation to account for multiple paths. Notice now we are using the `new_animated` function to construct the renderable. ```rust // entities.rs -{{#include ../../../code/rust-sokoban-c03-02/src/entities.rs:48:60}} +{{#include ../../../code/rust-sokoban-c03-02/src/entities.rs:create_box}} ``` And let's update everything else to use the `new_static` function - here is how we are doing it for the wall entity creation, feel free to go ahead and apply this to the other static entities. ```rust // entities.rs -{{#include ../../../code/rust-sokoban-c03-02/src/entities.rs:5:14}} +{{#include ../../../code/rust-sokoban-c03-02/src/entities.rs}} ``` ## Time -Another component we will need for this is keeping track of time. What does time have to do with this and how does this connect with frame rate? The basic idea is this: ggez controls how often the rendering system gets called, and this depends on the frame rate which in turn depends on how much work we are doing on every iteration of the game loop. Because we don't control this, in the span of a second we could get called 60 times or 57 times or maybe even 30 times. This means we cannot base our animation system on the framerate, and instead we need to keep it based on time. + +Another component we will need for this is keeping track of time. What does time have to do with this and how does this connect with frame rate? The basic idea is this: ggez controls how often the rendering system gets called, and this depends on the frame rate which in turn depends on how much work we are doing on every iteration of the game loop. Because we don't control this, in the span of a second we could get called 60 times or 57 times or maybe even 30 times. This means we cannot base our animation system on the framerate, and instead we need to keep it based on time. Because of this we need to keep track of the delta time - or how much time passes between the previous loop and the current loop. And because the delta time is much smaller than our animation frame interval (which we have decided on 250ms), we need to keep the cumulative delta - or how much time has passed since the beginning of the game being launched. @@ -108,55 +119,37 @@ Because of this we need to keep track of the delta time - or how much time passe Let's now add a resource for time, this doesn't fit into our component model since time is just some global state that needs to be kept. ```rust -// resources.rs -{{#include ../../../code/rust-sokoban-c03-02/src/resources.rs:45:48}} -``` - -And don't forget to register the new resource. - -```rust -// resources.rs -{{#include ../../../code/rust-sokoban-c03-02/src/resources.rs:12:16}} +// components.rs +{{#include ../../../code/rust-sokoban-c03-02/src/components.rs:create_time}} ``` And now let's update this time in our main loop. Luckily ggez provides a function to get the delta, so all we have to do is accumulate it. ```rust // main.rs -{{#include ../../../code/rust-sokoban-c03-02/src/main.rs:24:45}} +{{#include ../../../code/rust-sokoban-c03-02/src/main.rs:update}} ``` - ## Rendering system + Now let's update our rendering system. We will get the kind from the renderable, if it's static we simply use the first frame, otherwise we figure out which frame to get based on the delta time. Let's first add a function to enapsulate this logic of getting the correct image. ```rust // rendering_system.rs -{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering_system.rs:17}} - //... -{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering_system.rs:34:54}} +{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering.rs:get_image}} ``` And finally, let's use the new `get_image` function inside the run function (we will also have to add time to the `SystemData` definition and a couple of imports, but that should be pretty much it). ```rust -// rendering_system.rs -{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering_system.rs:57:81}} - - //... - -{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering_system.rs:88}} - - //... - -{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering_system.rs:97}} -{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering_system.rs:98}} - +// rendering.rs +{{#include ../../../code/rust-sokoban-c03-02/src/systems/rendering.rs:run_rendering}} ``` ## Box animations + Now that we've learned how to do this, let's extend this to make the boxes animate as well. All we have to do is add new assets and fix the entity creation and everything should just work. Here are the assets I used, feel free to re-use them or create new ones! ![Box red 1](./images/box_red_1.png) @@ -165,16 +158,9 @@ Now that we've learned how to do this, let's extend this to make the boxes anima ![Box blue 2](./images/box_blue_2.png) ## Wrap up + That was a long section, but I hope you enjoyed it! Here is how the game should look now. ![Sokoban animations](./images/animations.gif) > **_CODELINK:_** You can see the full code in this example [here](https://github.com/iolivia/rust-sokoban/tree/master/code/rust-sokoban-c03-02). - - - - - - - - diff --git a/books/en_US/src/c03-03-sounds-events.md b/books/en_US/src/c03-03-sounds-events.md deleted file mode 100644 index 1f187df..0000000 --- a/books/en_US/src/c03-03-sounds-events.md +++ /dev/null @@ -1 +0,0 @@ -# Events diff --git a/books/en_US/src/c03-04-sounds-events.md b/books/en_US/src/c03-04-sounds-events.md deleted file mode 100644 index d5f373a..0000000 --- a/books/en_US/src/c03-04-sounds-events.md +++ /dev/null @@ -1 +0,0 @@ -# Sounds diff --git a/code/.DS_Store b/code/.DS_Store index c09eaa927cc83957442b9fcc898e20620c838f77..8147e4ab7de6159cd1af51da0c0c424804c4c828 100644 GIT binary patch delta 884 zcmZoEXbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~3gIoZI3MH0wo5CCFEFr~{-%#hEJ z4a7+sFPgDWe2}}Dor8sgQDAbTy7DAtw#lXnK?Wz23UV@wOAHLIGcqx=u(GjpaBy*O z@p8omXXKX$mn4>y7CR*tMT2+&i6t3HlN(gM{W&-}IO7E*s;iBSO?4EEjSXsb6spb5 z4RjPtjLm9mIXT2t4Q)LWax1HDILDL})Iyg8SGWmiE`{X4mNt4%$vs)*X7Z)Vuf|BUQi{F_i^PA{0 iGE6q%QJ$P&!p^AyQUgp829tA4ChAd=25I7xr;GpvM9KF6 literal 14340 zcmeHOTWl0n82~s z#uy(!G(Kt6fYB#J!;2v5iwQm$qc3vtnix%t@e+AZqP(F0nR6EC6me%`jA7>_bN(~u zod29T-+cf1&zU_70N9e#76H@)fJ7IQicHay01+my39Ou5W5PJfLAsbSr4;}NvM`@w z8+MPWJvJjmSxmo-uE~&r+$en;Qji3T+DlUpF_IK(PTEY_wmG)3ASXZ|Kp;RMKp;RM zK%gK5nD3<^Rd5-^0RjO60RmSLVDm$SE+*qbj&SKt9Tc%8JS%5QcsEd*_5p5QM98?1 zBV4+wq~ofRVTqW6gw1jdndh!TOBaYWe^7l1PF{p zfbI1Q+Q)Km@r3OsH$=26@fuV6_<0q6$;ae)?iRzk;wGYXi31!ltp%1Ma z$QrigY}7Lj$%Inr?6Qa2)3()_(OG$K+BS3jmXlDb(n&L&Gm}}p*)+S1eLCe;I_ZpU znw_>|ux2*(W%a#TRyL-nZ?j`(vaN1o2}Q{{`UaiaG6wZFYSHO4s7t1(VEEXr(`U`D zn;%`Wym{5xN~LPjWO=H*+0b+=YwR%e%%-H1)-BDjcJ>SzT6S0Opy3!PQ}4Et{d!or z5I$QuW9DtQSF0*7zgj51ma2TJ! z19%V*;nVmW9>JIK6?_$6#}jxG-^O?FG=73-@Eo4Uuki=`5r4v;@giOhm51ceME;l| z;Y46Pzt6{^X_PA58U}XCb4i&eolAyMOh4-yi+sndymef)7j8d2#A$u6#E7Ys^V-$5vnAr9B8l6& zxpaF%3Awj&_51~4G7Dv#XgV5`$N-e_yi{JYG)yLSQ~6GZJ4YS@n! z+PDWDd=&TNV>p736SKPHlYiC=k0 zy*5v0F7dm+W1*h`qWNIUMm4q^-)wZ%p!_qUZ0?qDJWos`{ z%^qs8bC%|~x?Q|L3QOUHGGll+y10H`Bo=MjKQA)e6s2@SZ1Mj6VQFgZ!etv0yUl*f zKH!cAFh6~W>4fJ}e6aA*Ui%@tK$rvn8~9&Nwc0%6O4t83kTnGUH}Jn=ad~t2--e?6 zZ{ukH8)DwuzyED4DgWE(K{`L7mYig+g9|&Oj01sMZFx)1__Q9WjPHc^>P~gTaI7v8NJ@L zhHMY#$8tm~CGv8_XnmRi*NS@PDE6JeKhn{XLONO$(jIT%t_b}jT`Jyyvolr{(w?b3 zbgn2)m&(-6kuGkY_Uv+?nUMCg%i(>O$}UHY)+eX=AzSZ4|F}=2MJpvTPm9qOg|ycr zvQ@huPV>G?#UqN*7KOCOBf6_R|482~9+7Cq!gwy~+4;Zw|Ni_xuNA}r0s#UyB?4I1 z*45TRL(BiJ=^Z6IYZswwJ6){KdvvitX;+0J_c$I&_c$Kiu;X}3-AW>&vN%hU9O05u y6#n~zfcd`^@imfXwG-_BZ}>MXftgPq%(c9$onZfeqd&X}%!)$Vv)Z}t{{Jt!AaP*; diff --git a/code/rust-sokoban-c03-01/src/components.rs b/code/rust-sokoban-c03-01/src/components.rs index 1cc84e0..da565c8 100644 --- a/code/rust-sokoban-c03-01/src/components.rs +++ b/code/rust-sokoban-c03-01/src/components.rs @@ -16,12 +16,17 @@ pub struct Wall {} pub struct Player {} +// ANCHOR: box_colour_eq #[derive(PartialEq)] +// ANCHOR: box_colour pub enum BoxColour { Red, Blue, } +// ANCHOR_END: box_colour +// ANCHOR_END: box_colour_eq +// ANCHOR: box_colour_display impl Display for BoxColour { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(match self { @@ -31,7 +36,9 @@ impl Display for BoxColour { Ok(()) } } +// ANCHOR_END: box_colour_display +// ANCHOR: box pub struct Box { pub colour: BoxColour, } @@ -39,6 +46,7 @@ pub struct Box { pub struct BoxSpot { pub colour: BoxColour, } +// ANCHOR_END: box pub struct Movable; diff --git a/code/rust-sokoban-c03-01/src/entities.rs b/code/rust-sokoban-c03-01/src/entities.rs index 2b7e014..3cf6108 100644 --- a/code/rust-sokoban-c03-01/src/entities.rs +++ b/code/rust-sokoban-c03-01/src/entities.rs @@ -21,6 +21,7 @@ pub fn create_floor(world: &mut World, position: Position) -> Entity { )) } +// ANCHOR: create_box pub fn create_box(world: &mut World, position: Position, colour: BoxColour) -> Entity { world.spawn(( Position { z: 10, ..position }, @@ -41,6 +42,7 @@ pub fn create_box_spot(world: &mut World, position: Position, colour: BoxColour) BoxSpot { colour }, )) } +// ANCHOR_END: create_box pub fn create_player(world: &mut World, position: Position) -> Entity { world.spawn(( diff --git a/code/rust-sokoban-c03-01/src/map.rs b/code/rust-sokoban-c03-01/src/map.rs index 48e8aa0..34adf76 100644 --- a/code/rust-sokoban-c03-01/src/map.rs +++ b/code/rust-sokoban-c03-01/src/map.rs @@ -2,6 +2,7 @@ use crate::components::{BoxColour, Position}; use crate::entities::*; use hecs::World; +// ANCHOR: initialize_level pub fn initialize_level(world: &mut World) { const MAP: &str = " N N W W W W W W @@ -17,6 +18,7 @@ pub fn initialize_level(world: &mut World) { load_map(world, MAP.to_string()); } +// ANCHOR_END: initialize_level pub fn load_map(world: &mut World, map_string: String) { // read all lines @@ -34,6 +36,7 @@ pub fn load_map(world: &mut World, map_string: String) { }; // Figure out what object we should create + // ANCHOR: map_match match *column { "." => { create_floor(world, position); @@ -65,6 +68,7 @@ pub fn load_map(world: &mut World, map_string: String) { "N" => (), c => panic!("unrecognized map item {}", c), } + // ANCHOR_END: map_match } } } diff --git a/code/rust-sokoban-c03-02/src/components.rs b/code/rust-sokoban-c03-02/src/components.rs index f7d5882..4091d46 100644 --- a/code/rust-sokoban-c03-02/src/components.rs +++ b/code/rust-sokoban-c03-02/src/components.rs @@ -9,16 +9,21 @@ pub struct Position { pub z: u8, } +// ANCHOR: renderable pub struct Renderable { paths: Vec, } +// ANCHOR_END: renderable +// ANCHOR: renderable_kind pub enum RenderableKind { Static, Animated, } +// ANCHOR_END: renderable_kind impl Renderable { + // ANCHOR: renderable_new_fn pub fn new_static(path: &str) -> Self { Self { paths: vec![path.to_string()], @@ -30,7 +35,9 @@ impl Renderable { paths: paths.iter().map(|p| p.to_string()).collect(), } } + // ANCHOR_END: renderable_new_fn + // ANCHOR: renderable_kind_fn pub fn kind(&self) -> RenderableKind { match self.paths.len() { 0 => panic!("invalid renderable"), @@ -38,13 +45,16 @@ impl Renderable { _ => RenderableKind::Animated, } } + // ANCHOR_END: renderable_kind_fn + // ANCHOR: renderable_path_fn pub fn path(&self, path_index: usize) -> String { // If we get asked for a path that is larger than the // number of paths we actually have, we simply mod the index // with the length to get an index that is in range. self.paths[path_index % self.paths.len()].clone() } + // ANCHOR_END: renderable_path_fn } pub struct Wall {} diff --git a/code/rust-sokoban-c03-02/src/entities.rs b/code/rust-sokoban-c03-02/src/entities.rs index aa3133e..f64aae5 100644 --- a/code/rust-sokoban-c03-02/src/entities.rs +++ b/code/rust-sokoban-c03-02/src/entities.rs @@ -17,6 +17,7 @@ pub fn create_floor(world: &mut World, position: Position) -> Entity { )) } +// ANCHOR: create_box pub fn create_box(world: &mut World, position: Position, colour: BoxColour) -> Entity { world.spawn(( Position { z: 10, ..position }, @@ -36,6 +37,7 @@ pub fn create_box_spot(world: &mut World, position: Position, colour: BoxColour) BoxSpot { colour }, )) } +// ANCHOR_END: create_box pub fn create_player(world: &mut World, position: Position) -> Entity { world.spawn(( @@ -54,6 +56,8 @@ pub fn create_gameplay(world: &mut World) -> Entity { world.spawn((Gameplay::default(),)) } +// ANCHOR: create_time pub fn create_time(world: &mut World) -> Entity { world.spawn((Time::default(),)) } +// ANCHOR_END: create_time diff --git a/code/rust-sokoban-c03-02/src/main.rs b/code/rust-sokoban-c03-02/src/main.rs index 9e69694..0482c48 100644 --- a/code/rust-sokoban-c03-02/src/main.rs +++ b/code/rust-sokoban-c03-02/src/main.rs @@ -31,6 +31,7 @@ struct Game { // ANCHOR: handler impl event::EventHandler for Game { + // ANCHOR: update fn update(&mut self, context: &mut Context) -> GameResult { // Run input system { @@ -51,6 +52,7 @@ impl event::EventHandler for Game { Ok(()) } + // ANCHOR_END: update fn draw(&mut self, context: &mut Context) -> GameResult { // Render game entities diff --git a/code/rust-sokoban-c03-02/src/systems/rendering.rs b/code/rust-sokoban-c03-02/src/systems/rendering.rs index 383ccec..fbb6c40 100644 --- a/code/rust-sokoban-c03-02/src/systems/rendering.rs +++ b/code/rust-sokoban-c03-02/src/systems/rendering.rs @@ -14,6 +14,7 @@ use std::time::Duration; use crate::components::*; use crate::constants::*; +// ANCHOR: run_rendering pub fn run_rendering(world: &World, context: &mut Context) { // Clearing the screen (this gives us the background colour) let mut canvas = @@ -52,6 +53,7 @@ pub fn run_rendering(world: &World, context: &mut Context) { // on the screen. canvas.finish(context).expect("expected to present"); } +// ANCHOR_END: run_rendering pub fn draw_text(canvas: &mut Canvas, text_string: &str, x: f32, y: f32) { let mut text = Text::new(TextFragment { @@ -64,6 +66,7 @@ pub fn draw_text(canvas: &mut Canvas, text_string: &str, x: f32, y: f32) { canvas.draw(&text, Vec2::new(x, y)); } +// ANCHOR: get_image pub fn get_image(context: &mut Context, renderable: &Renderable, delta: Duration) -> Image { let path_index = match renderable.kind() { RenderableKind::Static => { @@ -84,3 +87,4 @@ pub fn get_image(context: &mut Context, renderable: &Renderable, delta: Duration Image::from_path(context, image_path).unwrap() } +// ANCHOR_END: get_image