Skip to content

Commit

Permalink
Merge pull request #113 from iolivia/olivia/hecs
Browse files Browse the repository at this point in the history
Update to hecs
  • Loading branch information
iolivia authored Dec 13, 2024
2 parents 5bb3398 + 844d5ae commit c6d8117
Show file tree
Hide file tree
Showing 159 changed files with 3,928 additions and 5,448 deletions.
28 changes: 14 additions & 14 deletions books/en_US/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Summary

- [Base game](./c01-00-intro.md)
- [Project setup](./c01-01-setup.md)
- [Entity Component System](./c01-02-ecs.md)
- [Components and entities](./c01-03-entities-components.md)
- [Rendering system](./c01-04-rendering.md)
- [Project setup](./c01-01-setup.md)
- [Entity Component System](./c01-02-ecs.md)
- [Components and entities](./c01-03-entities-components.md)
- [Rendering system](./c01-04-rendering.md)
- [Gameplay](./c02-00-intro.md)
- [Map loading](./c02-01-map-loading.md)
- [Moving the player](./c02-02-move-player.md)
- [Pushing boxes](./c02-03-push-box.md)
- [Modules](./c02-04-modules.md)
- [Gameplay](./c02-05-gameplay.md)
- [Map loading](./c02-01-map-loading.md)
- [Moving the player](./c02-02-move-player.md)
- [Pushing boxes](./c02-03-push-box.md)
- [Modules](./c02-04-modules.md)
- [Gameplay](./c02-05-gameplay.md)
- [Advanced gameplay](./c03-00-intro.md)
- [Coloured boxes](./c03-01-colours.md)
- [Animations](./c03-02-animations.md)
- [Sounds and events](./c03-03-sounds-events.md)
- [Batch rendering](./c03-04-batch-rendering.md)

- [Coloured boxes](./c03-01-colours.md)
- [Animations](./c03-02-animations.md)
- [Events](./c03-03-events.md)
- [Sounds](./c03-04-sounds.md)
- [Batch rendering](./c03-05-batch-rendering.md)
13 changes: 9 additions & 4 deletions books/en_US/src/c01-02-ecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,29 @@
In this section we will discuss Sokoban in more detail and how we will architect our game.

## Sokoban

Here is how a Sokoban game looks like, if you are not familiar with the gameplay already. We have walls and boxes, and the goal is for the player to move the boxes onto their spots by pushing them around.

![Sokoban play](./images/sokoban.gif)

## ECS

ECS (Entity Component System) is an architectural pattern for writing games which follows the composition over inheritance principle. We will be using ECS heavily in this project, much like most Rust games, so let's spend a bit of time familiarizing ourselves with the key concepts:

* **Components** - data-only structs which hold different characteristics of entities: some examples of components: Position, Renderable, Movement, etc. The key part here is that this is pure data, no behaviour.
* **Entities** - entities are made up of multiple components, for example a player might be made up by Position, Renderable & Movement, while the floor might just be Position & Renderable since it doesn't move. Entities are pretty much just dummy containers of one or more components with a unique identifier.
* **Systems** - systems use entities and components and contain behaviour and logic based on that data. For example, you could have a rendering system which just iterates through all entities which contain renderable components and draws all of them. The key here is that the components themselves don't contain any behaviour, instead they use a system to interpret the data and act.

If that doesn't make sense yet don't worry, we will discuss some practical examples in the next section applied to our Sokoban game.


## Sokoban architecture

Based on what we know now about how a Sokoban game should work, we will need a few different types of "things": walls, a player, floors, boxes and box spots. These will be our *entities*.

Now we have to identify what these entities will be made of, or what *components* we need. Well first of all we will need to keep track of where everything is on the map, so we need some sort of position component. Secondly, some (but not all) entities can move. The player can move around, but boxes can also move if they get pushed by the player. Finally, we need a way to render each entity, so we need some renderable component.

Here is how our first idea of entities and components looks like:

1. **Player entity**: Position, Renderable, Movable
1. **Wall entity**: Position, Renderable
1. **Floor entity**: Position, Renderable
Expand All @@ -30,13 +34,14 @@ Here is how our first idea of entities and components looks like:

At first thinking in ECS might be difficult, so don't worry if you don't understand everything yet or if this doesn't seem familiar with the way you've done things in other languages.

## Specs
Finally, let's bring in an ECS crate. There are a ton of them out there, but we will use [specs](https://specs.amethyst.rs/docs/tutorials/) for this book.
## Hecs

Finally, let's bring in an ECS crate. There are a ton of them out there, but we will use [hecs](https://crates.io/crates/hecs) for this book.

```
{{#include ../../../code/rust-sokoban-c01-03/Cargo.toml:9:11}}
```

Next up, we'll start implementing entities and components!

> **_CODELINK:_** You can see the full code in this example [here](https://github.com/iolivia/rust-sokoban/tree/master/code/rust-sokoban-c01-03).
> ***CODELINK:*** You can see the full code in this example [here](https://github.com/iolivia/rust-sokoban/tree/master/code/rust-sokoban-c01-03).
18 changes: 7 additions & 11 deletions books/en_US/src/c01-03-entities-components.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
# Components and entities

In this section we will create our components, we'll see how to create entities and register everything to keep specs happy.

## Defining components

Let's start by defining components. We previously discussed Position, Renderable and Movement - we'll skip movement for now. We will also need some components to identify each entity - for example we will need a Wall component so we can identify an entity as a wall by the fact that it has a wall component.

This should hopefully be straight-forward, the position components stores the x, y and z coordinates which will tell us where something is on the map, and the renderable component will receive a string path pointing to an image which we can render. All other components are [marker components](https://specs.amethyst.rs/docs/tutorials/11_advanced_component.html?highlight=marker#marker-components), with no data (yet).


```rust
{{#include ../../../code/rust-sokoban-c01-03/src/main.rs:13:42}}
{{#include ../../../code/rust-sokoban-c01-03/src/main.rs:components}}
```

Among the familiar Rust code we've got some new syntax, we're using a powerful Rust feature called `Procedural Macros` which is used in `#[storage(VecStorage)]`. These type of macros are essentially functions that at compile time consume some syntax and produce some new syntax.

> **_MORE:_** Read more about procedural macros [here](https://doc.rust-lang.org/book/ch19-06-macros.html).
## Registering components
In order for specs to be happy we have to tell it ahead of time what components we will be using. Let's create a function to register components into specs.

```rust
{{#include ../../../code/rust-sokoban-c01-03/src/main.rs:61:69}}
```

## Creating entities

An entity is simply a numeric identifier tied to a set of components. So the way we'll create entities is by simply specifying which components they contain.

This is how entity creation looks now.

```rust
{{#include ../../../code/rust-sokoban-c01-03/src/main.rs:71:124}}
{{#include ../../../code/rust-sokoban-c01-03/src/main.rs:entities}}
```

## Assets
Expand Down Expand Up @@ -57,10 +52,11 @@ Let's add the images to our project. We'll add a `resources` folder which will h
```

## World creation

Finally, let's tie everything together. We'll need to create a specs::World object, we'll add that to our Game struct and we will initialize it first thing in our main. Here is the full code, running now should render the same blank window, but we've made tremendous progress in actually setting up our game components and entities! Next up, we'll get to rendering so we'll finally see something on screen!

```rust
{{#include ../../../code/rust-sokoban-c01-03/src/main.rs}}
{{#include ../../../code/rust-sokoban-c01-03/src/main.rs:main}}
```

Note that running now will report some warnings in the console about unused import(s) and/or fields, don't worry about these just yet as we'll fix them in the coming chapters.
Expand Down
25 changes: 8 additions & 17 deletions books/en_US/src/c01-04-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,19 @@
It's time for our first system, the rendering system. This system will be responsible for drawing all our entities on the screen.

## Rendering system setup
First we'll define the `RenderingSystem` struct, it will need access to the ggez context in order to actually render.

```rust
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:47:49}}
```

We've got some new syntax here; `'a` is called a lifetime annotation. It's needed because the compiler can't see how long the reference in `RenderingSystem` is valid, meaning that we have to specify the lifetime annotation.

> **_MORE:_** Read more about lifetimes [here](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html).
Now let's implement the System trait for our Rendering system. This doesn't do anything yet, we're just setting up the scaffolding. The definition of SystemData means that we will have access to the storage of position and renderable components, and the fact that it's read storage means we only get immutable access, which is exactly what we need.
First we'll start with a blank implementation, something like this:

```rust
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:51:57}}
// implementation here
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:83:84}}
pub fn run_rendering(world: &World, context: &mut Context) {
// TODO add implementation
}
```

Finally let's run the rendering system in our draw loop. This means that every time the game updates we will render the latest state of all our entities.

```rust
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:97:111}}
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:handler}}
```

Running the game now should compile, but it will probably not do anything yet, since we haven't filled in any of the implementation of the rendering system and also we haven't created any entities.
Expand All @@ -38,21 +29,22 @@ Running the game now should compile, but it will probably not do anything yet, s
```

Here is the implementation of the rendering system. It does a few things:

* clear the screen (ensuring we don't keep any of the state rendered on the previous frame)
* get all entities with a renderable component and sort them by z (we do this in order to ensure we can render things on top of each other, for example the player should be above the floor, otherwise we wouldn't be able to see them)
* iterate through sorted entities and render each of them as an image
* finally, present to the screen

```rust
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:56:83}}
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:rendering_system}}
```

## Add some test entities

Let's create some test entities to make sure things are working correctly.

```rust
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:179:204}}
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs:init}}
```

Finally, let's put everything together and run. You should see something like this! This is super exciting, now we have a proper rendering system and we can actually see something on the screen for the first time. Next up, we're going to work on the gameplay so it can actually feel like a game!
Expand All @@ -63,7 +55,6 @@ Final code below.

> **_NOTE:_** Note that this is a very basic implementation of rendering and as the number of entities grow the performance will not be good enough. A more advanced implementation of rendering which uses batch rendering can be found in [Chapter 3 - Batch Rendering](/c03-04-batch-rendering.html).

```rust
{{#include ../../../code/rust-sokoban-c01-04/src/main.rs}}
```
Expand Down
11 changes: 4 additions & 7 deletions books/en_US/src/c02-01-map-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
Last chapter we left off at creating some entities to test our rendering system, but now it's time to render a proper map. In this section we will create a text based map configuration which we will load.

## Map config

First step, let's try to load a level based on a 2d map that looks like this.

```
{{#include ../../../code/rust-sokoban-c02-01/src/main.rs:181:189}}
{{#include ../../../code/rust-sokoban-c02-01/src/main.rs:map}}
where:
. is an empty spot
Expand All @@ -17,16 +18,12 @@ S is a box spot
N is nothing: used for the outer edges of the map
```

Let's make a string for this, eventually we can load from a file but for simplicity let's go with a constant in the code for now.

```rust
{{#include ../../../code/rust-sokoban-c02-01/src/main.rs:179:193}}
```
Eventually we can load from a file but for simplicity let's go with a constant in the code for now.

And here is the implementation of load map.

```rust
{{#include ../../../code/rust-sokoban-c02-01/src/main.rs:195:234}}
{{#include ../../../code/rust-sokoban-c02-01/src/main.rs:init}}
```

The most interesting Rust concept here is probably the `match`. We are using the basic feature of pattern matching here, we are simply matching on the values of each token found in the map config, but we could do a lot of more advanced conditions or types of patterns.
Expand Down
Loading

0 comments on commit c6d8117

Please sign in to comment.