Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add tile-based game example w/ a tutorial in the book, replacing getting-started guide #269

Merged
merged 49 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
434b616
docs: remove getting-started guide and sokoban placeholder with an in…
Trouv Nov 19, 2023
f9f1291
docs: add SunnyLand-player.png tileset
Trouv Nov 19, 2023
b294d2a
docs: split tile-based-game tutorial into a few chapters
Trouv Nov 19, 2023
7b02943
docs: define sections in create-your-ldtk-project chapter
Trouv Nov 19, 2023
8981f40
docs: write autotile setup section of tile-based game example
Trouv Nov 19, 2023
effdbfb
docs: write Add Entity layer section to tutorial
Trouv Nov 19, 2023
5edaea4
docs: add instruction for making the world layout horizontal
Trouv Nov 19, 2023
b9c8112
docs: write Design some levels section of tutorial
Trouv Nov 19, 2023
89f7ee5
docs: add tile-based-game LDtk project to assets
Trouv Nov 19, 2023
d568c12
docs: move images for tile-based-game tutorial into their own directory
Trouv Nov 21, 2023
eedb950
docs: add empty spawn-your-ldtk-project-in-bevy page to tutorial
Trouv Nov 21, 2023
81d8cba
docs: write intro to LDtk project creation chapter
Trouv Nov 21, 2023
b5fc6aa
docs: write introduction to spawn in bevy chapter
Trouv Nov 21, 2023
8f7681d
docs: set up tile_based_game.rs example somewhat
Trouv Nov 21, 2023
4dbdcd0
docs: write set up minimal Bevy App section of tutorial and define th…
Trouv Nov 21, 2023
ef642f9
docs: order App builders for tile_based_game example in expected orde…
Trouv Nov 22, 2023
1487844
docs: write spawn camera and world bundle section of tutorial
Trouv Nov 22, 2023
2cc23cd
docs: simplify code snippets in spawn-your-ldtk-project-in-bevy slightly
Trouv Nov 22, 2023
fa64b51
docs: write the spawn sprites section of the tutorial
Trouv Nov 23, 2023
d836f14
docs: remove reference to intgrid value in spawn-in-bevy intro
Trouv Nov 23, 2023
d8f30a5
docs: simplify the spawn sprites section to have fewer code snippets
Trouv Nov 23, 2023
328b7cf
docs: begin chapter on adding gameplay
Trouv Nov 23, 2023
88206b3
docs: rename system to translate_grid_coords_entities in tile_based_g…
Trouv Nov 23, 2023
7bcb668
docs: add more data and methods to LevelWalls (previously WallLocatio…
Trouv Nov 23, 2023
4597a5c
docs: finish rename of resource to LevelWalls
Trouv Nov 23, 2023
7a5b0f7
docs: write initial move_player_from_input and translate_grid_coords_…
Trouv Nov 24, 2023
1627554
docs: write section adding more components to the player in tutorial
Trouv Nov 24, 2023
94f68a6
docs: add LevelWalls logic to movement system so tutorial can include…
Trouv Nov 24, 2023
67c2dab
docs: write 'Implement tile-based movement' section of tile-based gam…
Trouv Nov 24, 2023
98e75c6
docs: write 'Update translation from GridCoords value' section of til…
Trouv Nov 24, 2023
b473273
docs: register WallBundle in tile_based_game example and update code …
Trouv Nov 26, 2023
37b7cc0
docs: write cache_wall_locations system in tile_based_game example
Trouv Nov 26, 2023
9091281
docs: use a GRID_SIZE const in tile_based_game example
Trouv Nov 26, 2023
baac1f2
docs: write 'prevent tile-based movement into walls' section of tile-…
Trouv Nov 26, 2023
86e6182
docs: write 'Trigger level transitions on victory' section of tutorial
Trouv Nov 26, 2023
048cc8c
docs: add final 'Game Over' level to tile based game
Trouv Nov 26, 2023
436cebc
docs: add embedded video for complete game.
Trouv Nov 26, 2023
58fb7a3
docs: link to Tile-based Game tutorial instead of getting-started in …
Trouv Nov 26, 2023
e16e257
docs: add link to tutorial in tile_based_game example
Trouv Nov 26, 2023
33e7c97
chore: fix clippy issues in tile-based-game example
Trouv Nov 26, 2023
f795d03
docs: fix line numbers after adding link to example
Trouv Nov 26, 2023
9d6452a
docs: mention tile_based_game example in tutorial
Trouv Nov 26, 2023
eadf241
docs: reduce redundancy tile-based-game tutorial introduction
Trouv Nov 28, 2023
51bfd17
docs: improve flow of introductory paragraph to 'Create your own LDtk…
Trouv Nov 28, 2023
a30e2b8
docs: separate setting the world layout into its own section
Trouv Nov 28, 2023
565b3ce
docs: make formatting more consistent in create-your-ldtk-project
Trouv Nov 28, 2023
8c4b9b0
docs: use a video embed instead of an image at the end of the autotil…
Trouv Nov 28, 2023
a0b6df3
docs: emphasize the importance of naming and editor visual of the ent…
Trouv Nov 28, 2023
ec22c00
docs: standardize some language and styling in the remaining chapters…
Trouv Dec 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/atlas/SunnyLand-player.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,673 changes: 1,673 additions & 0 deletions assets/tile-based-game.ldtk

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion book/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Deployment of this book to github pages is also performed by `bevy_ecs_ldtk`'s C
Splitting the documentation up this way means that this book is not necessarily meant to be read in order.
Some chapters are intended to be read while working on your own project, while others are meant to be more like studying material.
The following chapters are good jumping-off points for beginners:
- [*Getting Started* tutorial](getting-started.md)
- [*Tile-based Game* tutorial](tutorials/tile-based-game/index.html)
- [*Game Logic Integration* explanation]()

## Other resources
Expand Down
6 changes: 4 additions & 2 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

[Introduction](README.md)
# Tutorials
- [Getting Started](tutorials/getting-started.md)
- [Sokoban]()
- [Tile-based Game](tutorials/tile-based-game/README.md)
- [Create your LDtk project](tutorials/tile-based-game/create-your-ldtk-project.md)
- [Spawn your LDtk project in Bevy](tutorials/tile-based-game/spawn-your-ldtk-project-in-bevy.md)
- [Add gameplay to your project](tutorials/tile-based-game/add-gameplay-to-your-project.md)
- [Platformer]()
# Explanation
- [Game Logic Integration]()
Expand Down
28 changes: 0 additions & 28 deletions book/src/tutorials/getting-started.md

This file was deleted.

21 changes: 21 additions & 0 deletions book/src/tutorials/tile-based-game/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Tile-based Game
In this tutorial you will make a tile-based game with LDtk levels.
Game entities will be locked to a grid of tiles like sokoban, or snake.
You will go through the process of creating an LDtk project, loading the project into bevy, and adding gameplay.

This tutorial does have an example associated with it in the [`bevy_ecs_ldtk` repository](https://github.com/trouv/bevy_ecs_ldtk):
```bash
$ cargo run --example tile_based_game --release
```

## Prerequisites
You will need to perform the following setup/installations:
- [Bevy project setup](https://bevyengine.org/learn/book/getting-started/setup/) for the version specified in the [compatibility chart](https://github.com/Trouv/bevy_ecs_ldtk#compatibility).
- [LDtk installation](https://ldtk.io/versions/), for the version specified in the [compatibility chart](https://github.com/Trouv/bevy_ecs_ldtk#compatibility).

You will also need some simple assets:
- A tileset for the environment with at least a background tile, a wall tile, and a "goal"-ish tile.
- A tileset for the the player.

For these purposes this tutorial will use the `environment/tileset.png` and `spritesheets/player.png` assets respectively from [SunnyLand by Ansimuz](https://ansimuz.itch.io/sunny-land-pixel-game-art), licensed under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/).
However, you will be able to follow this tutorial using any tilesets, so long as they have tiles appropriate for the above purposes.
221 changes: 221 additions & 0 deletions book/src/tutorials/tile-based-game/add-gameplay-to-your-project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# Add gameplay to your project
In this section, you will integrate gameplay to the Bevy/LDtk project created in the previous sections.
This includes tile-based movement, collision, and level transitions.
You are welcome to bring your own tile-based LDtk project to this tutorial, but some of the values specified in here are specific to the LDtk project created in this tutorial, such as...
- the IntGrid value of walls (1)

For details about the tutorial in general, including prerequisites, please see the parent page.

## Add marker component and `GridCoords` to the player
In order to implement tile-based movement and tile-based mechanics, you'll need to deal with an entity's position in tile-space rather than just Bevy world translation.
`bevy_ecs_ldtk` provides a component that is suitable for this, and it has integration with the `LdtkEntity` derive.
Add the `GridCoords` component to the `PlayerBundle`, and give it the `#[grid_coords]` attribute.
The player entity will then be spawned with a `GridCoords` component whose value matches the entity's position in grid-space.

Also give it a `Player` marker component so that you can query for it more easily in future systems.
Derive `Default` for this component.
`bevy_ecs_ldtk` will use this default implementation when spawning the component unless otherwise specified.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
{{#include ../../../../examples/tile_based_game.rs:42:52}}
```

## Implement tile-based movement
The player now has the components you will need to implement tile-based movement.
Write a system that checks for just-pressed WASD input and converts it to a `GridCoords` direction.
I.e., `(0,1)` for W, `(-1,0)` for A, `(0,-1)` for S, and `(1,0)` for D.
Then, add the new direction to the player entity's `GridCoords` component.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
# #[derive(Component)]
# struct Player;
fn main() {
App::new()
// other App builders
.add_systems(Update, move_player_from_input)
.run();
}

{{#include ../../../../examples/tile_based_game.rs:91:93}}
{{#include ../../../../examples/tile_based_game.rs:95:109}}
*player_grid_coords = destination;
}
}
```

## Update translation from `GridCoords` value
If you play the game at this point, you'll notice that the player entity doesn't appear to be moving at all.
The `GridCoords` component may be updating correctly, but the entity's `Transform` is what determines where it is rendered.
`bevy_ecs_ldtk` does not maintain the `Transform` of `GridCoords` entities automatically.
This is left up to the user, which allows you to implement custom tweening or animation of the transform as you please.

Write a system that updates the `Transform` of `GridCoords` entities when their `GridCoords` value changes.
`bevy_ecs_ldtk` does provide a utility function to help calculate the resulting translation - provided you know the size of the cells of the grid.
For the LDtk project set up in this tutorial using the `SunnyLand` tilesets, this grid size is 16.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
# fn move_player_from_input() {}
fn main() {
App::new()
// other App builders
{{#include ../../../../examples/tile_based_game.rs:15:19}}
),
)
.run();
}

{{#include ../../../../examples/tile_based_game.rs:116:126}}
```

## Prevent tile-based movement into walls
Movement works logically *and* visually now.
However, you might notice that you can move *into* the walls of the level.
To implement tile-based collision, you will need to add components to the walls to identify their locations, and check against these locations when trying to move the player.

Create a new bundle for the wall entities, and give them a marker component.
Derive `LdtkIntCell` for this bundle, and register it to the app with `register_ldtk_int_cell` and the wall's intgrid value.
This bundle actually only needs this one marker component - IntGrid entities spawn with a `GridCoords` without requesting it.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
fn main() {
App::new()
// other App builders
{{#include ../../../../examples/tile_based_game.rs:24}}
.run();
}

{{#include ../../../../examples/tile_based_game.rs:66:72}}
```

There are a lot of ways to go about implementing the collision systems.
Naively, you could query for all of the `Wall` entities every time the player tries to move and check their `GridCoords` values.
In this tutorial, you will implement something a little more optimized: caching the wall locations into a resource when levels spawn.

Create a `LevelWalls` resource for storing the current wall locations that can be looked up by-value.
Give it a `HashSet<GridCoords>` field for the wall locations.
Give it fields for the level's width and height as well so you can prevent the player from moving out-of-bounds.
Then, implement a method `fn in_wall(&self, grid_coords: &GridCoords) -> bool` that returns true if the provided `grid_coords` is outside the level bounds or contained in the `HashSet`.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
use std::collections::HashSet;

fn main() {
App::new()
// other App builders
{{#include ../../../../examples/tile_based_game.rs:25}}
.run();
}

{{#include ../../../../examples/tile_based_game.rs:74:89}}
```

Now, add a system that listens for `LevelEvent::Spawned` and populates this resource.
It will need access to all of the wall locations to populate the `HashSet` (`Query<&GridCoords, With<Wall>>`).
It will also need access to the `LdtkProject` data to find the current level's width/height (`Query<&Handle<LdtkProject>>` and `Res<Assets<LdtkProject>>`).
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
# use std::collections::HashSet;
# const GRID_SIZE: i32 = 16;
# #[derive(Default, Resource)]
# struct LevelWalls {
# wall_locations: HashSet<GridCoords>,
# level_width: i32,
# level_height: i32,
# }
# impl LevelWalls {
# fn in_wall(&self, grid_coords: &GridCoords) -> bool {
# grid_coords.x < 0
# || grid_coords.y < 0
# || grid_coords.x >= self.level_width
# || grid_coords.y >= self.level_height
# || self.wall_locations.contains(grid_coords)
# }
# }
# #[derive(Component)]
# struct Wall;
# fn move_player_from_input() {}
# fn translate_grid_coords_entities() {}
fn main() {
App::new()
// other App builders
{{#include ../../../../examples/tile_based_game.rs:15:20}}
)
)
.run();
}

{{#include ../../../../examples/tile_based_game.rs:128:155}}
```

Finally, update the `move_player_from_input` system to access the `LevelWalls` resource and check whether or not the player's destination is in a wall.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
# use std::collections::HashSet;
# #[derive(Component)]
# struct Player;
# #[derive(Default, Resource)]
# struct LevelWalls {
# wall_locations: HashSet<GridCoords>,
# level_width: i32,
# level_height: i32,
# }
# impl LevelWalls {
# fn in_wall(&self, grid_coords: &GridCoords) -> bool {
# grid_coords.x < 0
# || grid_coords.y < 0
# || grid_coords.x >= self.level_width
# || grid_coords.y >= self.level_height
# || self.wall_locations.contains(grid_coords)
# }
# }
{{#include ../../../../examples/tile_based_game.rs:91:114}}
```

With this check in place, the player should now be unable to move into walls!

## Trigger level transitions on victory
The final step is to implement the goal functionality.
When the player reaches the goal, the next level should spawn until there are no levels remaining.

Similar to the `PlayerBundle`, give the `GoalBundle` its own marker component and `GridCoords`.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
{{#include ../../../../examples/tile_based_game.rs:54:64}}
```

Then, write a system that checks if the player's `GridCoords` and the goal's `GridCoords` match.
For a small optimization, filter the player query for `Changed<GridCoords>` so it's only populated if the player moves.
If they do match, update the `LevelSelection` resource, increasing its level index by 1.
`bevy_ecs_ldtk` will automatically despawn the current level and spawn the next one when this resource is updated.
```rust,no_run
# use bevy::prelude::*;
# use bevy_ecs_ldtk::prelude::*;
# #[derive(Component)]
# struct Player;
# #[derive(Component)]
# struct Goal;
# fn move_player_from_input() {}
# fn translate_grid_coords_entities() {}
# fn cache_wall_locations() {}
fn main() {
App::new()
// other App builders
{{#include ../../../../examples/tile_based_game.rs:15:23}}
.run();
}

{{#include ../../../../examples/tile_based_game.rs:157::}}
```

With this, the simple tile-based game is complete.
When you navigate the player to the goal, the next level will begin until there are no levels remaining.

<div style="width:100%;height:0px;position:relative;padding-bottom:56.250%;"><iframe src="https://streamable.com/e/i342f8" frameborder="0" width="100%" height="100%" allowfullscreen style="width:100%;height:100%;position:absolute;left:0px;top:0px;overflow:hidden;"></iframe></div>
Loading