├── .github └── workflows │ ├── lint.yml │ ├── publish-book.yml │ └── release-please.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── Typical_2D_platformer_example.ldtk ├── WorldMap_Free_layout.ldtk ├── atlas │ ├── MV Icons Complete Sheet Free - ALL.png │ ├── NuclearBlaze_by_deepnight.png │ ├── SunnyLand-player.png │ └── SunnyLand_by_Ansimuz-extended.png ├── collectathon.ldtk ├── field_instances.ldtk ├── my_project.ldtk ├── player.png └── tile-based-game.ldtk ├── book ├── .gitignore ├── book.toml └── src │ ├── README.md │ ├── SUMMARY.md │ ├── api-reference.md │ ├── blurb.md │ ├── explanation │ ├── anatomy-of-the-world.md │ ├── game-logic-integration.md │ ├── level-selection.md │ └── limitations.md │ ├── how-to-guides │ ├── create-bevy-relations-from-ldtk-entity-references.md │ ├── make-level-selection-follow-player.md │ ├── migration-guides │ │ ├── README.md │ │ ├── migrate-from-0.10-to-0.11.md │ │ ├── migrate-from-0.8-to-0.9.md │ │ └── migrate-from-0.9-to-0.10.md │ └── respawn-levels-and-worlds.md │ └── tutorials │ └── tile-based-game │ ├── README.md │ ├── add-gameplay-to-your-project.md │ ├── create-your-ldtk-project.md │ ├── images │ ├── all-walls-rule.png │ ├── auto-tile-walls.png │ ├── background-rule.png │ ├── bevy-setup.png │ ├── bevy-sprites.png │ ├── entities-layer.png │ ├── goal-entity.png │ ├── horizontal-wall-edge-rule.png │ ├── levels.png │ ├── player-entity.png │ ├── tilesets.png │ ├── vertical-wall-edge-rule.png │ ├── wall-inner-corner-rule.png │ ├── wall-layer.png │ ├── wall-outer-corner-rule.png │ └── world-layout.png │ └── spawn-your-ldtk-project-in-bevy.md ├── docs ├── LICENSE-APACHE └── LICENSE-MIT ├── examples ├── basic.rs ├── collectathon │ ├── README.md │ ├── coin.rs │ ├── main.rs │ ├── player.rs │ └── respawn.rs ├── field_instances │ ├── enemy.rs │ ├── equipment.rs │ ├── health.rs │ ├── level_title.rs │ ├── main.rs │ └── mother.rs ├── level_set.rs ├── platformer │ ├── camera.rs │ ├── climbing.rs │ ├── colliders.rs │ ├── enemy.rs │ ├── game_flow.rs │ ├── ground_detection.rs │ ├── inventory.rs │ ├── main.rs │ ├── misc_objects.rs │ ├── player.rs │ └── walls.rs ├── tile_based_game.rs └── traitless.rs ├── macros ├── Cargo.toml ├── LICENSE ├── README.md ├── docs │ ├── LICENSE-APACHE │ └── LICENSE-MIT └── src │ ├── ldtk_entity.rs │ ├── ldtk_int_cell.rs │ └── lib.rs ├── repo └── platformer-example.gif └── src ├── app ├── entity_app_ext.rs ├── int_cell_app_ext.rs ├── ldtk_entity.rs ├── ldtk_int_cell.rs └── mod.rs ├── assets ├── ldtk_asset_plugin.rs ├── ldtk_external_level.rs ├── ldtk_json_with_metadata.rs ├── ldtk_project.rs ├── ldtk_project_data.rs ├── level_indices.rs ├── level_locale.rs ├── level_metadata.rs ├── level_metadata_accessor.rs └── mod.rs ├── components ├── entity_iid.rs ├── level_iid.rs ├── level_set.rs └── mod.rs ├── ldtk ├── all_some_iter.rs ├── color.rs ├── fake.rs ├── field_instance.rs ├── impl_definitions.rs ├── ldtk_fields.rs ├── loaded_level.rs ├── mod.rs └── raw_level_accessor.rs ├── level.rs ├── lib.rs ├── plugin.rs ├── resources ├── level_event.rs ├── level_selection.rs └── mod.rs ├── systems.rs ├── tile_makers.rs └── utils.rs /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | # Run cargo check (with various feature permutations) 14 | check: 15 | name: Check Package 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout sources 19 | uses: actions/checkout@v3 20 | - name: Cache 21 | uses: actions/cache@v3 22 | with: 23 | path: | 24 | ~/.cargo/bin/ 25 | ~/.cargo/registry/index/ 26 | ~/.cargo/registry/cache/ 27 | ~/.cargo/git/db/ 28 | target/ 29 | key: ${{ runner.os }}-cargo-check-${{ hashFiles('**/Cargo.toml') }} 30 | - name: Install stable toolchain 31 | uses: dtolnay/rust-toolchain@stable 32 | - name: Install Dependencies 33 | run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev 34 | - name: Run cargo check (minimal features, internal levels, exclude examples) 35 | run: cargo check --no-default-features --features internal_levels 36 | - name: Run cargo check (minimal features, external levels, exclude examples) 37 | run: cargo check --no-default-features --features external_levels 38 | - name: Run cargo check (default features) 39 | run: cargo check --all-targets 40 | - name: Run cargo check (all features) 41 | run: cargo check --all-targets --all-features 42 | 43 | # Run cargo test --all-features 44 | test: 45 | name: Test Suite 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout sources 49 | uses: actions/checkout@v3 50 | - name: Cache 51 | uses: actions/cache@v3 52 | with: 53 | path: | 54 | ~/.cargo/bin/ 55 | ~/.cargo/registry/index/ 56 | ~/.cargo/registry/cache/ 57 | ~/.cargo/git/db/ 58 | target/ 59 | key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.toml') }} 60 | - name: Install stable toolchain 61 | uses: dtolnay/rust-toolchain@stable 62 | - name: Install Dependencies 63 | run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev 64 | - name: Run cargo test 65 | run: cargo test --all-features 66 | 67 | # Run cargo clippy --all-targets --all-features -- -D warnings 68 | clippy: 69 | name: Clippy 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Checkout sources 73 | uses: actions/checkout@v3 74 | - name: Cache 75 | uses: actions/cache@v3 76 | with: 77 | path: | 78 | ~/.cargo/bin/ 79 | ~/.cargo/registry/index/ 80 | ~/.cargo/registry/cache/ 81 | ~/.cargo/git/db/ 82 | target/ 83 | key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.toml') }} 84 | - name: Install stable toolchain 85 | uses: dtolnay/rust-toolchain@stable 86 | with: 87 | components: clippy 88 | - name: Install Dependencies 89 | run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev 90 | - name: Run clippy 91 | run: cargo clippy --all-targets --all-features -- -D warnings 92 | 93 | # Run cargo fmt --all -- --check 94 | format: 95 | name: Format 96 | runs-on: ubuntu-latest 97 | steps: 98 | - name: Checkout sources 99 | uses: actions/checkout@v3 100 | - name: Install stable toolchain 101 | uses: dtolnay/rust-toolchain@stable 102 | with: 103 | components: rustfmt 104 | - name: Run cargo fmt 105 | run: cargo fmt --all -- --check 106 | 107 | # Run mdbook build (tests all code snippets) 108 | build-test-book: 109 | name: Build and test book 110 | runs-on: ubuntu-latest 111 | steps: 112 | - name: Checkout sources 113 | uses: actions/checkout@v3 114 | - name: Cache 115 | uses: actions/cache@v3 116 | with: 117 | path: | 118 | ~/.cargo/bin/ 119 | ~/.cargo/registry/index/ 120 | ~/.cargo/registry/cache/ 121 | ~/.cargo/git/db/ 122 | target/ 123 | key: ${{ runner.os }}-mdbook-build-${{ hashFiles('**/Cargo.toml') }} 124 | - name: Install stable toolchain 125 | uses: dtolnay/rust-toolchain@stable 126 | - name: Install mdbook 127 | uses: peaceiris/actions-mdbook@v1 128 | with: 129 | mdbook-version: '0.4.35' 130 | - name: Install mdbook-keeper 131 | run: cargo install mdbook-keeper --git https://github.com/tfpk/mdbook-keeper/ --rev 12f116d0840c69a6786dba3865768af3fde634f3 --force 132 | - name: Run mdbook build (tests all code snippets) 133 | run: CARGO_MANIFEST_DIR=. mdbook build book 134 | -------------------------------------------------------------------------------- /.github/workflows/publish-book.yml: -------------------------------------------------------------------------------- 1 | name: publish book 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*.*.*' 9 | 10 | jobs: 11 | publish-book: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout sources 15 | uses: actions/checkout@v3 16 | - name: Cache 17 | uses: actions/cache@v3 18 | with: 19 | path: | 20 | ~/.cargo/bin/ 21 | ~/.cargo/registry/index/ 22 | ~/.cargo/registry/cache/ 23 | ~/.cargo/git/db/ 24 | target/ 25 | key: ${{ runner.os }}-mdbook-publish-${{ hashFiles('**/Cargo.toml') }} 26 | - name: Install stable toolchain 27 | uses: dtolnay/rust-toolchain@stable 28 | - name: Install mdbook 29 | uses: peaceiris/actions-mdbook@v1 30 | with: 31 | mdbook-version: '0.4.35' 32 | - name: Install mdbook-keeper 33 | run: cargo install mdbook-keeper --git https://github.com/tfpk/mdbook-keeper/ --rev 12f116d0840c69a6786dba3865768af3fde634f3 --force 34 | - name: build book 35 | run: CARGO_MANIFEST_DIR=. mdbook build book 36 | - name: Publish 37 | uses: peaceiris/actions-gh-pages@v3 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | publish_dir: ./book/book 41 | destination_dir: ${{ github.ref_name }} 42 | - name: Publish latest 43 | uses: peaceiris/actions-gh-pages@v3 44 | if: github.ref_type == 'tag' 45 | with: 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | publish_dir: ./book/book 48 | destination_dir: latest 49 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: release-please 7 | jobs: 8 | release-please: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: google-github-actions/release-please-action@v3 12 | with: 13 | token: ${{ secrets.PAT }} 14 | release-type: rust 15 | package-name: bevy_ecs_ldtk 16 | bump-minor-pre-major: true 17 | changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"docs","section":"Documentation Changes","hidden":false},{"type":"example","section":"Example Changes","hidden":false},{"type":"refactor","section":"Code Refactors","hidden":true},{"type":"ci","section":"CI Changes","hidden":true}]' 18 | extra-files: | 19 | README.md 20 | book/src/README.md 21 | book/src/explanation/game-logic-integration.md 22 | book/src/explanation/level-selection.md 23 | book/src/explanation/anatomy-of-the-world.md 24 | book/src/how-to-guides/respawn-levels-and-worlds.md 25 | src/lib.rs 26 | src/components/mod.rs 27 | src/components/level_set.rs 28 | src/resources/level_selection.rs 29 | src/app/ldtk_entity.rs 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | */target 3 | Session.vim 4 | */Session.vim 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_ecs_ldtk" 3 | description = "An ECS-friendly ldtk plugin for bevy." 4 | version = "0.12.0" 5 | edition = "2021" 6 | authors = ["Trevor Lovell "] 7 | repository = "https://github.com/Trouv/bevy_ecs_ldtk" 8 | license = "MIT OR Apache-2.0" 9 | keywords = ["bevy", "ldtk", "game", "gamedev", "map-editor"] 10 | categories = ["game-development"] 11 | exclude = ["assets/*", "repo/*", "scripts/*"] 12 | 13 | [workspace] 14 | members = ["macros"] 15 | 16 | [dependencies] 17 | bevy_ecs_ldtk_macros = { version = "0.12.0", optional = true, path = "macros" } 18 | bevy_ecs_tilemap = { version = "0.16", default-features = false } 19 | bevy = { version = "0.16.0", default-features = false, features = [ 20 | "bevy_sprite", 21 | ] } 22 | derive-getters = "0.3.0" 23 | serde = { version = "1.0", features = ["derive"] } 24 | serde_json = "1.0" 25 | regex = "1" 26 | thiserror = "1.0" 27 | paste = "1.0" 28 | derive_more = "0.99.17" 29 | path-clean = "1.0.1" 30 | 31 | [dev-dependencies] 32 | bevy = "0.16" 33 | bevy_rapier2d = "0.30" 34 | fake = { version = "2.8.0", features = ["uuid"] } 35 | rand = "0.8" 36 | bevy-inspector-egui = "0.31" 37 | 38 | [features] 39 | default = ["derive", "render", "internal_levels"] 40 | derive = ["bevy_ecs_ldtk_macros"] 41 | atlas = ["bevy_ecs_tilemap/atlas"] 42 | render = ["bevy_ecs_tilemap/render"] 43 | internal_levels = [] 44 | external_levels = [] 45 | 46 | [package.metadata.docs.rs] 47 | all-features = true 48 | 49 | [[example]] 50 | name = "platformer" 51 | path = "examples/platformer/main.rs" 52 | 53 | [[example]] 54 | name = "field_instances" 55 | path = "examples/field_instances/main.rs" 56 | 57 | [[example]] 58 | name = "collectathon" 59 | path = "examples/collectathon/main.rs" 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | bevy_ecs_ldtk is dual-licensed under either 2 | 3 | * MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT) 4 | * Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 5 | 6 | at your option. 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `bevy_ecs_ldtk` 2 | [![crates.io](https://img.shields.io/crates/v/bevy_ecs_ldtk)](https://crates.io/crates/bevy_ecs_ldtk) 3 | [![docs.rs](https://docs.rs/bevy_ecs_ldtk/badge.svg)](https://docs.rs/bevy_ecs_ldtk) 4 | [![crates.io](https://img.shields.io/crates/d/bevy_ecs_ldtk)](https://crates.io/crates/bevy_ecs_ldtk) 5 | [![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](./LICENSE) 6 | [![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking) 7 | [![CI](https://github.com/Trouv/bevy_ecs_ldtk/actions/workflows/ci.yml/badge.svg)](https://github.com/Trouv/bevy_ecs_ldtk/actions/workflows/ci.yml) 8 | 9 | [`bevy_ecs_ldtk`](https://crates.io/crates/bevy_ecs_ldtk) is an ECS-friendly [LDtk](https://ldtk.io/) plugin for [Bevy](https://bevyengine.org/). 10 | It allows you to use LDtk projects as an asset, spawn levels, and insert bevy components/bundles on LDtk entities/tiles. 11 | This plugin is ECS-friendly, partly for its internal usage of ECS that provides extra functionality to users, and partly for its usage of [`bevy_ecs_tilemap`](https://crates.io/crates/bevy_ecs_tilemap) for rendering tilemaps. 12 | This is all behind an ergonomic API, providing low-boilerplate solutions to common use cases. 13 | For less common use cases, strategies that leverage this plugin's ECS constructs are also available. 14 | 15 | ![platformer-example](repo/platformer-example.gif) 16 | 17 | `cargo run --example platformer --release` 18 | 19 | ## Features 20 | - Support for all layer types 21 | - Support for loading external levels 22 | - Hot reloading 23 | - Solutions for easily loading/unloading levels, changing levels, loading level neighbors... 24 | - Low-boilerplate solutions for spawning bundles for LDtk Entities and IntGrid 25 | tiles using derive macros (other options available) 26 | - `serde` types for LDtk based off LDtk's [QuickType 27 | loader](https://ldtk.io/files/quicktype/LdtkJson.rs), but with several QoL 28 | improvements 29 | - Support for Wasm (and tile spacing) through "atlas" feature 30 | 31 | ## Documentation 32 | Documentation for this plugin is available in two main places. 33 | - API reference on [docs.rs](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/) 34 | - Tutorials, Explanation, and Guides in the [`bevy_ecs_ldtk` book](https://trouv.github.io/bevy_ecs_ldtk/v0.12.0/index.html) 35 | 36 | In the book, the following chapters are good jumping-off points for beginners: 37 | - [*Tile-based Game* tutorial](https://trouv.github.io/bevy_ecs_ldtk/v0.12.0/tutorials/tile-based-game/index.html) 38 | - [*Level Selection* explanation](https://trouv.github.io/bevy_ecs_ldtk/v0.12.0/explanation/level-selection.html) 39 | - [*Game Logic Integration* explanation](https://trouv.github.io/bevy_ecs_ldtk/v0.12.0/explanation/game-logic-integration.html) 40 | 41 | Cargo examples are also available in this repository: 42 | ```sh 43 | $ cargo run --example example-name 44 | ``` 45 | 46 | ## Compatibility 47 | | bevy | bevy_ecs_tilemap | LDtk | bevy_ecs_ldtk | 48 | | --- | --- | --- | --- | 49 | | 0.16 | 0.16 | 1.5.3 | 0.12 | 50 | | 0.15 | 0.15 | 1.5.3 | 0.11 | 51 | | 0.14 | 0.14 | 1.5.3 | 0.10 | 52 | | 0.12 | 0.12 | 1.5.3 | 0.9 | 53 | | 0.11 | 0.11 | 1.3.3 | 0.8 | 54 | | 0.10 | 0.10 | 1.1 | 0.7 | 55 | | 0.10 | 0.10 | 1.1 | 0.6 | 56 | | 0.9 | 0.9 | 1.1 | 0.5 | 57 | | 0.8 | 0.7 | 1.1 | 0.4 | 58 | | 0.7 | 0.6 | 1.1 | 0.3 | 59 | | 0.6 | 0.5 | 0.9 | 0.2 | 60 | | 0.6 | 0.5 | 0.9 | 0.1 | 61 | 62 | ## Asset Credits 63 | - [SunnyLand](https://ansimuz.itch.io/sunny-land-pixel-game-art), a texture pack by Ansimuz, licensed under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) 64 | - [PIXEL FANTASY RPG ICONS](https://cazwolf.itch.io/caz-pixel-free), an icon pack by Caz, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 65 | - [Nuclear Blaze](https://github.com/deepnight/ldtk/blob/master/app/extraFiles/samples/atlas/NuclearBlaze_by_deepnight.aseprite), a tileset by Deepnight, licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). Tileset was exported from aseprite to png, but no other modifications were made. 66 | -------------------------------------------------------------------------------- /assets/atlas/MV Icons Complete Sheet Free - ALL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/assets/atlas/MV Icons Complete Sheet Free - ALL.png -------------------------------------------------------------------------------- /assets/atlas/NuclearBlaze_by_deepnight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/assets/atlas/NuclearBlaze_by_deepnight.png -------------------------------------------------------------------------------- /assets/atlas/SunnyLand-player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/assets/atlas/SunnyLand-player.png -------------------------------------------------------------------------------- /assets/atlas/SunnyLand_by_Ansimuz-extended.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/assets/atlas/SunnyLand_by_Ansimuz-extended.png -------------------------------------------------------------------------------- /assets/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/assets/player.png -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | doctest_cache 3 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Trevor Lovell"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "bevy_ecs_ldtk Book" 7 | 8 | [preprocessor.keeper] 9 | command = "mdbook-keeper" 10 | after = ["links"] 11 | manifest_dir = "." 12 | externs = ["bevy", "bevy_ecs_ldtk"] 13 | -------------------------------------------------------------------------------- /book/src/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ## `bevy_ecs_ldtk` 4 | 5 | {{ #include blurb.md }} 6 | 7 | ## This book 8 | This book is a work in progress, but aims to provide the following pieces of documentation: 9 | - tutorials: lessons detailing the creation of simple games from start to finish 10 | - explanation: clarification of concepts and strategies employed by `bevy_ecs_ldtk`, including details about how it works and why 11 | - how-to guides: recommended solutions to common problems, as well as migration guides 12 | 13 | This book is not an API reference. 14 | For that, please refer to `bevy_ecs_ldtk`'s documentation on [docs.rs](https://docs.rs/bevy_ecs_ldtk/). 15 | 16 | While this book aims to be comprehensive, it should also be easy to maintain and up-to-date. 17 | This is why, in consort with the API reference, documentation for `bevy_ecs_ldtk` aims to satisfy [The Grand Unified Theory of Documentation](https://documentation.divio.com/). 18 | Furthermore, code snippets in this book are automatically tested by `bevy_ecs_ldtk`'s CI wherever possible with the help of [mdBook-Keeper](https://github.com/tfpk/mdbook-keeper/). 19 | This should help inform maintainers when changes to the plugin have made documentation out-of-date. 20 | Deployment of this book to github pages is also performed by `bevy_ecs_ldtk`'s CI automatically on new releases. 21 | 22 | Splitting the documentation up this way means that this book is not necessarily meant to be read in order. 23 | Some chapters are intended to be read while working on your own project, while others are meant to be more like studying material. 24 | The following chapters are good jumping-off points for beginners: 25 | - [*Tile-based Game* tutorial](tutorials/tile-based-game/index.html) 26 | - [*Level Selection* explanation](explanation/level-selection.md) 27 | - [*Game Logic Integration* explanation](explanation/game-logic-integration.md) 28 | 29 | ## Other resources 30 | This book is not suitable documentation for bevy or LDtk. 31 | Some resources for learning Bevy include those listed on the [Bevy website](https://bevyengine.org/learn), as well as the unofficial [Bevy Cheat Book](https://bevy-cheatbook.github.io/). 32 | LDtk also provides documentation on [its website](https://ldtk.io/docs/). 33 | 34 | `bevy_ecs_ldtk`'s [source code](https://github.com/Trouv/bevy_ecs_ldtk) is available on github. 35 | This repository also contains [cargo examples](https://github.com/Trouv/bevy_ecs_ldtk/tree/v0.12.0/examples), which can be run after cloning the repository using `$ cargo run --example example-name`. 36 | These examples may be difficult to follow on their own, and many of their strategies are described in this book. 37 | When viewing these examples, be careful to checkout the correct git tag for the version of the plugin you are using. 38 | Some changes may have been made to the plugin or to the examples on the `main` branch that are not released yet, and trying to apply these to the version of the plugin you are using can lead to errors. 39 | 40 | ## License 41 | The pages of this book fall under the same license as the rest of the `bevy_ecs_ldtk` repository. 42 | I.e., this book is dual-licensed under [MIT](http://opensource.org/licenses/MIT) and [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) at your option. 43 | The plain text of this license is available in the `bevy_ecs_ldtk` repository's [LICENSE file](https://github.com/Trouv/bevy_ecs_ldtk/blob/main/LICENSE). 44 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](README.md) 4 | # Tutorials 5 | - [Tile-based Game](tutorials/tile-based-game/README.md) 6 | - [Create your LDtk project](tutorials/tile-based-game/create-your-ldtk-project.md) 7 | - [Spawn your LDtk project in Bevy](tutorials/tile-based-game/spawn-your-ldtk-project-in-bevy.md) 8 | - [Add gameplay to your project](tutorials/tile-based-game/add-gameplay-to-your-project.md) 9 | - [Platformer]() 10 | # Explanation 11 | - [Level Selection](explanation/level-selection.md) 12 | - [Game Logic Integration](explanation/game-logic-integration.md) 13 | - [Anatomy of the World](explanation/anatomy-of-the-world.md) 14 | - [Plugin Schedule]() 15 | - [Asset Model]() 16 | - [Limitations](explanation/limitations.md) 17 | # How-To Guides 18 | - [Register Bundles for Intgrid Tiles and LDtk Entities]() 19 | - [Process Entities Further with Blueprints]() 20 | - [Combine Tiles into Larger Entities]() 21 | - [Create Bevy Relations from LDtk Entity References](how-to-guides/create-bevy-relations-from-ldtk-entity-references.md) 22 | - [Respawn Levels and Worlds](how-to-guides/respawn-levels-and-worlds.md) 23 | - [Make LevelSelection Follow Player](how-to-guides/make-level-selection-follow-player.md) 24 | - [Animate Tiles]() 25 | - [Camera Logic]() 26 | - [Implement Fit-Inside Camera]() 27 | - [Implement Fit-Around Camera]() 28 | - [Implement Parallax]() 29 | - [Retrieve Field Instance Data]() 30 | - [Retrieve Loaded Level Data]() 31 | - [Compile to WASM]() 32 | - [Compile Headless]() 33 | - [Migration Guides](how-to-guides/migration-guides/README.md) 34 | - [Migrate from 0.8 to 0.9](how-to-guides/migration-guides/migrate-from-0.8-to-0.9.md) 35 | - [Migrate from 0.9 to 0.10](how-to-guides/migration-guides/migrate-from-0.9-to-0.10.md) 36 | - [Migrate from 0.10 to 0.11](how-to-guides/migration-guides/migrate-from-0.10-to-0.11.md) 37 | --- 38 | [API Reference](api-reference.md). 39 | -------------------------------------------------------------------------------- /book/src/api-reference.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /book/src/blurb.md: -------------------------------------------------------------------------------- 1 | [`bevy_ecs_ldtk`](https://crates.io/crates/bevy_ecs_ldtk) is an ECS-friendly [LDtk](https://ldtk.io/) plugin for [Bevy](https://bevyengine.org/). 2 | It allows you to use LDtk projects as an asset, spawn levels, and insert bevy components/bundles on LDtk entities/tiles. 3 | This plugin is ECS-friendly, partly for its internal usage of ECS that provides extra functionality to users, and partly for its usage of [`bevy_ecs_tilemap`](https://crates.io/crates/bevy_ecs_tilemap) for rendering tilemaps. 4 | This is all behind an ergonomic API, providing low-boilerplate solutions to common use cases. 5 | For less common use cases, strategies that leverage this plugin's ECS constructs are also available. 6 | -------------------------------------------------------------------------------- /book/src/explanation/anatomy-of-the-world.md: -------------------------------------------------------------------------------- 1 | # Anatomy of the World 2 | Once an [`LdtkWorldBundle`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LdtkWorldBundle.html) is spawned, [levels are selected](level-selection.md), and the associated assets finish loading, the level spawning process begins. 3 | The result is a deeply nested hierarchy of entities which can be difficult to navigate, but predictable. 4 | It can be useful to write code that makes assumptions about the relationships between `bevy_ecs_ldtk` entities. 5 | To assist with this, this chapter will explain the anatomy of a `bevy_ecs_ldtk` world. 6 | 7 | ## Hierarchy 8 | The basic hierarchy of spawned entities and their identifying components/bundles are as follows. 9 | This does exclude some special cases which are explained in more detail below. 10 | Each bullet indent indicates a parent/child relationship. 11 | - The world entity, with an [`LdtkWorldBundle`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LdtkWorldBundle.html) bundle. 12 | - The level entities, with a [`LevelIid`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LevelIid.html) component. 13 | - For Entity layers - a layer entity with just a [`LayerMetadata`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LayerMetadata.html) component. 14 | - LDtk Entity entities, with an [`EntityInstance`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/ldtk/struct.EntityInstance.html) component, or possibly others if you're using [`LdtkEntity` registration](game-logic-integration.html#ldtkentity-and-ldtkintcell-registration). 15 | - For Tile/AutoTile/IntGrid layers: `bevy_ecs_tilemap` tilemap entities, with a [`TilemapBundle`](https://docs.rs/bevy_ecs_tilemap/latest/bevy_ecs_tilemap/type.TilemapBundle.html) **and** a [`LayerMetadata`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LayerMetadata.html) component. 16 | - For IntGrid layers - tile entities with an [`IntGridCell`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.IntGridCell.html) component, or possibly others if you're using [`LdtkIntCell` registration](game-logic-integration.html#ldtkentity-and-ldtkintcell-registration). 17 | - For Tile/AutoTile layers (or IntGrid layers with AutoTile functionality) - `bevy_ecs_tilemap` tile entities, with a [`TileBundle`](https://docs.rs/bevy_ecs_tilemap/latest/bevy_ecs_tilemap/tiles/struct.TileBundle.html) bundle. 18 | 19 | ## Worldly Entities 20 | The [`LdtkEntity` derive macro](game-logic-integration.html#ldtkentity-and-ldtkintcell-registration) allows you to define entities as ["worldly"](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/app/trait.LdtkEntity.html#worldly). 21 | The intention of this feature is to support entities that are allowed to persist and traverse between levels, like a player in a GridVania layout. 22 | 23 | One consequence of an entity being worldly is a change in its placement in the above hierarchy. 24 | Instead of being spawned as a child of the Entity layer entity, worldly entities will be children of the world entity (after one update). 25 | This makes the worldly entity independent of their origin level, so that if the origin level is unloaded, the worldly entity can still persist. 26 | 27 | Furthermore, a worldly entity will *not* be spawned if it already exists. 28 | This prevents two of the same worldly entity existing if the origin level is despawned and respawned. 29 | For example, if the worldly player entity traverses far enough away that their origin level is unloaded, then returns to it, there won't suddenly be two players. 30 | 31 | ## Tile metadata components 32 | LDtk allows you to associate metadata with particular tiles in a tileset. 33 | `bevy_ecs_ldtk` responds to this by adding additional components to tiles that have metadata *in addition to* those described in the [hierarchy](#hierarchy): 34 | 35 | - [`TileMetadata`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.TileMetadata.html) 36 | - [`TileEnumTags`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.TileEnumTags.html) 37 | 38 | Naturally, this can only occur in Tile/AutoTile layers (or IntGrid layers with AutoTile functionality), since the metadata is defined on tilesets. 39 | 40 | ## Level backgrounds 41 | LDtk allows you to supply a background color and a background image for individual levels. 42 | `bevy_ecs_ldtk` renders these by default. 43 | The background color is spawned as a normal bevy [`Sprite`](https://docs.rs/bevy/latest/bevy/prelude/struct.Sprite.html), as a child of the level entity. 44 | The background image, if it exists, is also spawned as a `Sprite`. 45 | 46 | These background sprites can be disabled (not spawned) using the settings resource [`LdtkSettings`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LdtkSettings.html): 47 | ```rust,no_run 48 | use bevy::prelude::*; 49 | use bevy_ecs_ldtk::prelude::*; 50 | 51 | fn main() { 52 | App::new() 53 | // other App builders 54 | .insert_resource(LdtkSettings { 55 | level_background: LevelBackground::Nonexistent, 56 | ..default() 57 | }) 58 | .run(); 59 | } 60 | ``` 61 | 62 | ## Layers with colliding tiles 63 | It is possible for LDtk Tile/AutoTile layers to have colliding tiles. 64 | In other words, a single layer can have more than one tile in the same location. 65 | 66 | `bevy_ecs_tilemap` tilemaps only allow one tile per position. 67 | So, `bevy_ecs_ldtk` supports layers with colliding tiles by spawning multiple tilemaps. 68 | Each of them will have the same [`LayerMetadata`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LayerMetadata.html) component. 69 | This means that users cannot assume that there will be only one `LayerMetadata` entity per layer. 70 | 71 | 72 | ## Z order 73 | To correctly define the render order of the tiles and entities in a level, `bevy_ecs_ldtk` uses the `z` value of their `Transform` components. 74 | Z order is only applied to [level backgrounds](#level-backgrounds), [layer entities](#layers-with-colliding-tiles), and [worldly entities](#worldly-entities). 75 | Tiles and non-worldly entities will simply inherit the z-ordering in their `GlobalTransform`. 76 | 77 | `bevy_ecs_ldtk` begins with a `z` value of 0 for the background-most entities, and increments this by 1 for each layer above that. 78 | This sounds simple, but can actually be pretty difficult to predict thanks to some special cases mentioned above. 79 | 80 | [Background colors and background images](#level-backgrounds) will usually get the `z` values of 0 and 1 respectively. 81 | However, if the background image does not exist, the `z` value of 1 will be freed for the next layer instead. 82 | If level backgrounds are disabled entirely, both 0 and 1 will be freed for the next layer. 83 | 84 | From here, each layer generally increments the `z` value by 1. 85 | However, note that [there can be multiple layer entities for a single LDtk layer](#layers-with-colliding-tiles). 86 | Each of these additional layer entities will also increment the `z` value by 1. 87 | 88 | Since this can be difficult to predict, it is generally recommended to avoid making assumptions about the `z` value of a layer. 89 | -------------------------------------------------------------------------------- /book/src/explanation/game-logic-integration.md: -------------------------------------------------------------------------------- 1 | # Game Logic Integration 2 | Loading LDtk levels into Bevy doesn't get you very far if you cannot play them. 3 | 4 | Aside from rendering tilemaps, LDtk has features for placing gameplay objects on Entity layers. 5 | Even within tilemaps, IntGrid layers imply a categorization of tiles, and perhaps a game designerly meaning. 6 | It is fundamental to associate the LDtk entities and IntGrid tiles with Bevy entities/components. 7 | `bevy_ecs_ldtk` is designed around a couple core strategies for doing so, which will be discussed here. 8 | 9 | ## `LdtkEntity` and `LdtkIntCell` registration 10 | The `LdtkEntity`/`LdtkIntCell` registration API allows you to hook custom bevy `Bundle`s into the level spawning process. 11 | You define what components you want on the entity with a bundle, define how they should be constructed with the `LdtkEntity` or `LdtkIntCell` derive, and register the bundle to the `App` for a given LDtk entity identifier, or IntGrid value. 12 | 13 | ```rust,no_run 14 | use bevy::prelude::*; 15 | use bevy_ecs_ldtk::prelude::*; 16 | 17 | fn main() { 18 | App::new() 19 | // other App builders 20 | .register_ldtk_entity::("Player") 21 | .run(); 22 | } 23 | 24 | #[derive(Default, Component)] 25 | struct Player; 26 | 27 | #[derive(Default, Bundle, LdtkEntity)] 28 | struct PlayerBundle { 29 | player: Player, 30 | #[sprite] 31 | sprite: Sprite, 32 | } 33 | ``` 34 | 35 | How does `LdtkEntity`/`LdtkIntCell` construct the bundle when derived? 36 | Without any intervention, the bundle's fields are constructed using the bundle's `Default` implementation. 37 | However, various attributes are available to override this behavior, like `#[sprite]` in the above example. 38 | This attribute gives the entity a sprite using the tileset in its LDtk editor visual. 39 | For documentation about all the available attributes, check out the API reference for these traits: 40 | - [`LdtkEntity`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/app/trait.LdtkEntity.html) 41 | - [`LdtkIntCell`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/app/trait.LdtkIntCell.html) 42 | 43 | This approach is suitable for many common, simple use cases. 44 | There's also room for more granular, component-level customization within some of the attributes, like `#[with(...)]` or `#[from_entity_instance]`. 45 | Of course, the traits can also be manually implemented for the even-more-custom cases. 46 | 47 | ## Post-processing plugin-spawned entities 48 | There are still many cases where `LdtkEntity`/`LdtkIntCell` registration is insufficient. 49 | Perhaps you need to spawn children of the entity, or need access to more resources in the `World`. 50 | For these more demanding cases, post-processing plugin-spawned entities in a custom system is always an option. 51 | 52 | If an LDtk entity does not have a matching `LdtkEntity` registration, it will be spawned with an `EntityInstance` component by default. 53 | This component contains the raw LDtk data for that entity. 54 | Querying for newly-spawned `EntityInstance` entities can be a good starting point for implementing your own custom spawning logic. 55 | Intgrid tiles have similar behavior, except their default component is `IntGridCell`, which simply contains the IntGrid value for that tile. 56 | 57 | ```rust,no_run 58 | # use bevy::prelude::*; 59 | # use bevy_ecs_ldtk::prelude::*; 60 | #[derive(Default, Component)] 61 | struct PlayerChild; 62 | 63 | #[derive(Default, Component)] 64 | struct Player; 65 | 66 | fn process_player( 67 | mut commands: Commands, 68 | new_entity_instances: Query<(Entity, &EntityInstance, &Transform), Added>, 69 | assets: Res, 70 | ) 71 | { 72 | for (entity, entity_instance, transform) in new_entity_instances.iter() { 73 | if entity_instance.identifier == "Player".to_string() { 74 | commands 75 | .entity(entity) 76 | .insert(Player) 77 | .insert(( 78 | Sprite::from_image(assets.load("player.png")), 79 | *transform, 80 | )) 81 | .with_children(|commands| { 82 | commands.spawn(PlayerChild); 83 | }); 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | This approach makes spawning entities from LDtk just as powerful and customizable as a Bevy system, because that's all it is. 90 | `LdtkEntity` and `LdtkIntCell` ultimately make some assumptions about what data from the LDtk asset and the Bevy world you will need to spawn your entity, which post-processing avoids. 91 | However, there are some pretty obvious ergonomics issues to this strategy compared to using registration: 92 | - You need to manually filter `EntityInstance`s for the desired LDtk entity identifier. 93 | - You need to manually perform the iteration of the query. 94 | - You may need to manually find the associated layer data, or tileset image, or tileset definition (if necessary). 95 | - You need to be careful not to overwrite the plugin-provided `Transform` component. 96 | 97 | ## A combined approach - the blueprint pattern 98 | At least one of these ergonomics issues can be alleviated with a combined approach. 99 | If you register an `LdtkEntity`/`LdtkIntCell` with a marker component, querying for it later won't require filtering for a particular entity instance identifier. 100 | The plugin does that for you when giving the entity your bundle, then you can write queries that filter for the marker component instead of `EntityInstance` or `IntGridCell`. 101 | Furthermore, if you can add the transform-overwriting bundles within the `LdtkEntity` bundle, you won't need to tiptoe around the `Transform` in your post-processing system. 102 | 103 | ```rust,no_run 104 | # use bevy::prelude::*; 105 | # use bevy_ecs_ldtk::prelude::*; 106 | fn main() { 107 | App::new() 108 | // other App builders 109 | .register_ldtk_entity::("Player") 110 | .add_systems(Update, process_player) 111 | .run(); 112 | } 113 | 114 | #[derive(Default, Component)] 115 | struct PlayerChild; 116 | 117 | #[derive(Default, Component)] 118 | struct Player; 119 | 120 | #[derive(Default, Bundle, LdtkEntity)] 121 | struct PlayerBundle { 122 | player: Player, 123 | #[sprite] 124 | sprite: Sprite, 125 | } 126 | 127 | fn process_player( 128 | mut commands: Commands, 129 | new_players: Query>, 130 | ) 131 | { 132 | for player_entity in new_players.iter() { 133 | commands 134 | .spawn(PlayerChild) 135 | .insert(ChildOf(player_entity)); 136 | } 137 | } 138 | ``` 139 | 140 | Using a simple component or a marker component for the initial spawn of an entity and processing it further in another system is called the "blueprint pattern". 141 | You may find it desirable to use the `LdtkEntity`/`LdtkIntCell` derives to construct most of the components, but need post-processing for the more demanding ones. 142 | This approach is recommended over filtering for `Added` or `Added`. 143 | -------------------------------------------------------------------------------- /book/src/explanation/level-selection.md: -------------------------------------------------------------------------------- 1 | # Level Selection 2 | Once you have spawned an [`LdtkWorldBundle`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LdtkWorldBundle.html) with a handle pointing to your LDtk project file, the levels you have selected will spawn as children of the world bundle. 3 | You have a couple options for selecting levels, which will be discussed in this chapter. 4 | 5 | ## `LevelSelection` resource 6 | The highest-level option for selecting a level to spawn is using the [`LevelSelection`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/enum.LevelSelection.html) resource. 7 | This resource allows you to specify a particular level either by its indices in the project/world, its identifier, its iid, or its uid. 8 | Once this resource is added or changed, levels will be spawned/despawned in order to match your selection. 9 | 10 | One additional feature worth pointing out is loading level neighbors. 11 | You can enable this with the settings resource [`LdtkSettings`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LdtkSettings.html): 12 | 13 | ```rust,no_run 14 | use bevy::prelude::*; 15 | use bevy_ecs_ldtk::prelude::*; 16 | 17 | fn main() { 18 | App::new() 19 | // other App builders 20 | .insert_resource(LevelSelection::index(0)) 21 | .insert_resource(LdtkSettings { 22 | level_spawn_behavior: LevelSpawnBehavior::UseWorldTranslation { 23 | load_level_neighbors: true 24 | }, 25 | ..default() 26 | }) 27 | .run(); 28 | } 29 | ``` 30 | 31 | With this set, the plugin will spawn the currently-selected level's neighbors in addition to the currently-selected level. 32 | This can be especially useful for GridVania/Free-style worlds where it's important to have a level spawned before the player traverses to it. 33 | Note: this *only* works if you are using the `LevelSelection` resource. 34 | 35 | ## `LevelSet` component 36 | One component in the `LdtkWorldBundle` is [`LevelSet`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.LevelSet.html). 37 | This component can be used for lower-level level selection. 38 | Instead of selecting one level globally with a `LevelSelection` resource, you can select a specific set of levels by their iids. 39 | From the `level_set` cargo example: 40 | ```rust,no_run 41 | # use bevy::prelude::*; 42 | # use bevy_ecs_ldtk::prelude::*; 43 | {{#include ../../../examples/level_set.rs:28:50}} 44 | # fn main() {} 45 | ``` 46 | 47 | This component is actually used by `LevelSelection` under the hood. 48 | So, in order for this workflow to work properly, no `LevelSelection` resource can exist in the world. 49 | This also implies, as mentioned in the previous section, that `load_level_neighbors` cannot be used with the `LevelSet` workflow. 50 | However, the `LevelSpawnBehavior::UseWorldTranslation` option in general *does* work, and should be used if you plan to spawn multiple levels anyway. 51 | 52 | `LevelSet` is ideal for more complex level-spawning needs. 53 | It is an option if you need any level-spawning behavior that `LevelSelection`/`load_level_neighbors` are not capable of. 54 | Furthermore, if you have more than one `LdtkWorldBundle` spawned, it can be used to select different levels per-world, which is impossible with global level selection. 55 | 56 | When the set of levels in the `LevelSet` is updated, an extra layer of change-detection is employed to make these changes idempotent/declarative. 57 | In other words, the plugin will observe what levels are already spawned before trying to respond to the changes in `LevelSet`. 58 | Only levels *in* the level set that *aren't* currently spawned will be spawned - and only levels *not in* the level set that *are* currently spawned will be despawned. 59 | Everything else will be left alone, remaining spawned or despawned appropriately. 60 | -------------------------------------------------------------------------------- /book/src/explanation/limitations.md: -------------------------------------------------------------------------------- 1 | # Limitations 2 | 3 | ## Spacing and Padding 4 | 5 | Due to a difference in the handling of spacing and padding between `bevy_ecs_tilemap` and LDtk spacing is not perfectly supported. This can be resolved by having the value of padding and spacing be equal, for example both 0, or both 1 and so on. Previous versions of `bevy_ecs_tilemap` require the `atlas` feature flag enabled for WASM support and also for tile spacing to work with Tile and AutoTile layers. 6 | -------------------------------------------------------------------------------- /book/src/how-to-guides/create-bevy-relations-from-ldtk-entity-references.md: -------------------------------------------------------------------------------- 1 | # Create Bevy Relations from LDtk Entity References 2 | LDtk allows entities to point to other entities using a field. 3 | This is analogous to a bevy "relation" - a component on one entity that stores the `Entity` identifier of another entity. 4 | 5 | This chapter goes through one possible method for resolving LDtk entity references as such. 6 | This code is used in the `field_instances` cargo example, and facilitates "enemy" entities pointing to another "enemy" entity as their "mother". 7 | 8 | ## Register unresolved reference 9 | First, create a component representing an "unresolved" entity reference, storing the target entity's LDtk iid rather than a bevy `Entity`: 10 | ```rust,no_run 11 | # use bevy::prelude::*; 12 | # use bevy_ecs_ldtk::prelude::*; 13 | {{ #include ../../../examples/field_instances/mother.rs:10:11 }} 14 | ``` 15 | 16 | Create a method for constructing this component from an `&EntityInstance`. 17 | This should retrieve the value of the entity reference field instance on the LDtk entity. 18 | Most likely, you'll use a hard-coded field identifier ("mother" in this example) to find it: 19 | ```rust,no_run 20 | # use bevy::prelude::*; 21 | # use bevy_ecs_ldtk::prelude::*; 22 | # {{ #include ../../../examples/field_instances/mother.rs:11 }} 23 | {{ #include ../../../examples/field_instances/mother.rs:13:23 }} 24 | ``` 25 | 26 | Add this component to the `LdtkEntity` and configure it to be constructed using this method. 27 | This guide assumes that you've already registered this bundle to the app. 28 | ```rust,no_run 29 | # use bevy::prelude::*; 30 | # use bevy_ecs_ldtk::prelude::*; 31 | # {{ #include ../../../examples/field_instances/mother.rs:10 }} 32 | # {{ #include ../../../examples/field_instances/mother.rs:11 }} 33 | # impl UnresolvedMotherRef { fn from_mother_field(_: &EntityInstance) -> UnresolvedMotherRef { todo!() } } 34 | {{ #include ../../../examples/field_instances/enemy.rs:7:8}} 35 | {{ #include ../../../examples/field_instances/enemy.rs:15:19}} 36 | ``` 37 | 38 | ## Resolve reference in post-processing 39 | Create a second relational component that stores the actual bevy `Entity` that this `Unresolved` reference should "resolve" to. 40 | ```rust,no_run 41 | # use bevy::prelude::*; 42 | # use bevy_ecs_ldtk::prelude::*; 43 | {{ #include ../../../examples/field_instances/mother.rs:26:27 }} 44 | ``` 45 | 46 | Finally, create a ["post-processing"](../explanation/game-logic-integration.html#post-processing-plugin-spawned-entities) system that takes entities with the `Unresolved` component, finds the entity with the matching `EntityIid`, and replaces the `Unresolved` component with the relational component. 47 | ```rust,no_run 48 | # use bevy::prelude::*; 49 | # use bevy_ecs_ldtk::prelude::*; 50 | # {{ #include ../../../examples/field_instances/mother.rs:10 }} 51 | # {{ #include ../../../examples/field_instances/mother.rs:11 }} 52 | # {{ #include ../../../examples/field_instances/mother.rs:26 }} 53 | # {{ #include ../../../examples/field_instances/mother.rs:27 }} 54 | {{ #include ../../../examples/field_instances/mother.rs:29:51 }} 55 | ``` 56 | -------------------------------------------------------------------------------- /book/src/how-to-guides/make-level-selection-follow-player.md: -------------------------------------------------------------------------------- 1 | # Make LevelSelection Follow Player 2 | In games with GridVania/Free world layouts, it is common to make the player ["worldly"](../explanation/anatomy-of-the-world.html#worldly-entities) and have them traverse levels freely. 3 | This level traversal requires levels to be spawned as/before the Player traverses to them, and for levels to be despawned as the player traverses away from them. 4 | 5 | This guide demonstrates one strategy for managing levels like this: having the `LevelSelection` follow the player entity. 6 | This code comes from the `collectathon` cargo example. 7 | 8 | ## Use world translation for levels and load level neighbors 9 | Rather than spawning a level the moment the player travels to them, this guide instead loads levels *before* they reach them. 10 | Use the ["load level neighbors"](../explanation/level-selection.html#levelselection-resource) feature, so the plugin spawns not just the currently selected level, but its neighbors too. 11 | ```rust,no_run 12 | # use bevy::prelude::*; 13 | # use bevy_ecs_ldtk::prelude::*; 14 | fn main() { 15 | App::new() 16 | // Other App builders 17 | {{ #include ../../../examples/collectathon/main.rs:13:18 }} 18 | .run(); 19 | } 20 | ``` 21 | 22 | ## Determine bounds of spawned levels and update level selection 23 | With `load_level_neighbors` enabled, any level that the player can traverse to will already be spawned, barring teleportation. 24 | Use the transforms of the spawned levels and width/height info from the level's asset data to create a `Rect` of the level's bounds. 25 | 26 | 27 | To access the level asset data, you first need to access the project asset data. 28 | Assuming you only have one project, query for the only `LdtkProjectHandle` entity and look up its asset data in the `LdtkProject` asset store. 29 | Then, get the raw level data for every spawned level using the level entity's `LevelIid` component (there is a provided method for this). 30 | 31 | ```rust,no_run 32 | # use bevy::prelude::*; 33 | # use bevy_ecs_ldtk::prelude::*; 34 | # #[derive(Component)] 35 | # struct Player; 36 | {{ #include ../../../examples/collectathon/player.rs:59:74 }} 37 | } 38 | } 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | The level's `GlobalTransform`'s x/y value should be used as the lower-left bound of the `Rect`. 44 | Add the raw level's `px_wid` and `pix_hei` values to the lower-left bound to calculate the upper-right bound. 45 | 46 | ```rust,no_run 47 | # use bevy::prelude::*; 48 | # use bevy_ecs_ldtk::ldtk::Level; 49 | # fn foo(level_transform: &GlobalTransform, level: &Level) { 50 | {{ #include ../../../examples/collectathon/player.rs:76:85 }} 51 | # } 52 | ``` 53 | 54 | After creating a `Rect` of the level bounds, check if the player is inside those bounds and update the `LevelSelection` resource accordingly. 55 | The full system should look something like this: 56 | ```rust,no_run 57 | # use bevy::prelude::*; 58 | # use bevy_ecs_ldtk::prelude::*; 59 | # #[derive(Component)] 60 | # struct Player; 61 | {{ #include ../../../examples/collectathon/player.rs:59:93 }} 62 | ``` 63 | -------------------------------------------------------------------------------- /book/src/how-to-guides/migration-guides/README.md: -------------------------------------------------------------------------------- 1 | # Migration Guides 2 | Most releases of `bevy_ecs_ldtk` introduce breaking changes. 3 | For these, migration guides are provided to help users migrate their existing games to the new version. 4 | If you're contributing a breaking change to `bevy_ecs_ldtk`, you may be asked to describe it in the appropriate migration guide. 5 | -------------------------------------------------------------------------------- /book/src/how-to-guides/migration-guides/migrate-from-0.10-to-0.11.md: -------------------------------------------------------------------------------- 1 | # Migrate from 0.10 to 0.11 2 | 3 | ## Bevy upgrade 4 | `bevy_ecs_ldtk` has upgraded to Bevy and `bevy_ecs_tilemap` version `0.15`. 5 | A Bevy `0.15` migration guide is available on [Bevy's website](https://bevyengine.org/learn/migration-guides/0-14-to-0-15/). 6 | 7 | ## `LdtkSpriteSheetBundle` replaced with `Sprite` 8 | Since the `Sprite` struct in Bevy `0.15` can now store `TextureAtlas` information on its own, the use of `LdtkSpriteSheetBundle` has been replaced by a simple use of `Sprite`. The macro has changed as well, and is now named `#[sprite_sheet]`. 9 | ```rust,ignore 10 | // 0.10 11 | # use bevy_ecs_ldtk::prelude::*; 12 | # use bevy::prelude::*; 13 | #[derive(Default, Bundle, LdtkEntity)] 14 | struct PlayerBundle { 15 | #[sprite_sheet_bundle] 16 | sprite_bundle: LdtkSpriteSheetBundle, 17 | #[grid_coords] 18 | grid_coords: GridCoords, 19 | } 20 | ``` 21 | ```rust,ignore 22 | // 0.11 23 | # use bevy_ecs_ldtk::prelude::*; 24 | # use bevy::prelude::*; 25 | #[derive(Default, Bundle, LdtkEntity)] 26 | struct PlayerBundle { 27 | #[sprite_sheet] 28 | sprite_sheet: Sprite, 29 | #[grid_coords] 30 | grid_coords: GridCoords, 31 | } 32 | ``` 33 | 34 | ## `SpriteBundle` also replaced with `Sprite` 35 | When using a `SpriteBundle` with the `#[sprite_bundle]` macro, use a `Sprite` instead. The macro is now named `#[sprite]`. 36 | ```rust,ignore 37 | // 0.10 38 | # use bevy_ecs_ldtk::prelude::*; 39 | # use bevy::prelude::*; 40 | #[derive(Bundle, LdtkEntity, Default)] 41 | pub struct Player { 42 | player: PlayerComponent, 43 | health: Health, 44 | #[sprite_bundle] 45 | sprite_bundle: SpriteBundle, 46 | } 47 | ``` 48 | ```rust,ignore 49 | // 0.11 50 | # use bevy_ecs_ldtk::prelude::*; 51 | # use bevy::prelude::*; 52 | #[derive(Bundle, LdtkEntity, Default)] 53 | pub struct Player { 54 | player: PlayerComponent, 55 | health: Health, 56 | #[sprite] 57 | sprite: Sprite, 58 | } 59 | ``` 60 | 61 | ## `Handle` replaced with `LdtkProjectHandle` 62 | Handles cannot be used as components in Bevy `0.15` onward. This has two changes. 63 | ### Call `.into()` when loading a project 64 | First, you must call `.into()` when loading the world. 65 | ```rust,ignore 66 | // 0.10 67 | # use bevy_ecs_ldtk::prelude::*; 68 | # use bevy::prelude::*; 69 | fn setup(mut commands: Commands, asset_server: Res) { 70 | commands.spawn(LdtkWorldBundle { 71 | ldtk_handle: asset_server.load("my_project.ldtk"), 72 | ..Default::default() 73 | }); 74 | } 75 | ``` 76 | ```rust,ignore 77 | // 0.11 78 | # use bevy_ecs_ldtk::prelude::*; 79 | # use bevy::prelude::*; 80 | fn setup(mut commands: Commands, asset_server: Res) { 81 | commands.spawn(LdtkWorldBundle { 82 | ldtk_handle: asset_server.load("my_project.ldtk").into(), 83 | ..Default::default() 84 | }); 85 | } 86 | ``` 87 | ### Replace usages of `Handle` 88 | Second, uses of `Handle` in queries must be replaced with `LdtkProjectHandle`. It is enough to replace the type in the signature, as the `LdtkProjectHandle` type is a drop-in replacement for the handle. 89 | 90 | ```rust,ignore 91 | // 0.10 92 | # use bevy_ecs_ldtk::prelude::*; 93 | # use bevy::prelude::*; 94 | fn respawn_world( 95 | mut commands: Commands, 96 | ldtk_projects: Query>>, 97 | input: Res>, 98 | ) { 99 | if input.just_pressed(KeyCode::KeyR) { 100 | commands.entity(ldtk_projects.single()).insert(Respawn); 101 | } 102 | } 103 | ``` 104 | ```rust,ignore 105 | // 0.11 106 | # use bevy_ecs_ldtk::prelude::*; 107 | # use bevy::prelude::*; 108 | fn respawn_world( 109 | mut commands: Commands, 110 | ldtk_projects: Query>, 111 | input: Res>, 112 | ) { 113 | if input.just_pressed(KeyCode::KeyR) { 114 | commands.entity(ldtk_projects.single()).insert(Respawn); 115 | } 116 | } 117 | ``` 118 | 119 | -------------------------------------------------------------------------------- /book/src/how-to-guides/migration-guides/migrate-from-0.9-to-0.10.md: -------------------------------------------------------------------------------- 1 | # Migrate from 0.9 to 0.10 2 | 3 | ## Bevy upgrade 4 | `bevy_ecs_ldtk` has upgraded to Bevy and `bevy_ecs_tilemap` version `0.14`. 5 | A Bevy `0.14` migration guide is available on [Bevy's website](https://bevyengine.org/learn/migration-guides/0-13-to-0-14/). 6 | 7 | ## `SpriteSheetBundle` replaced with `LdtkSpriteSheetBundle` 8 | In `0.14`, Bevy depricated `SpriteSheetBundle` to clear up confusion for new users. To maintain existing functionality with the `#[sprite_sheet_bundle]` macro, `SpriteSheetBundle` has been re-implemented as `LdtkSpriteSheetBundle` 9 | ```rust,ignore 10 | // 0.9 11 | #[derive(Default, Bundle, LdtkEntity)] 12 | struct PlayerBundle { 13 | player: Player, 14 | #[sprite_sheet_bundle] 15 | sprite_bundle: SpriteSheetBundle, 16 | #[grid_coords] 17 | grid_coords: GridCoords, 18 | } 19 | ``` 20 | ```rust,ignore 21 | // 0.10 22 | # use bevy_ecs_ldtk::prelude::*; 23 | # use bevy::prelude::*; 24 | #[derive(Default, Bundle, LdtkEntity)] 25 | struct PlayerBundle { 26 | #[sprite_sheet_bundle] 27 | sprite_bundle: LdtkSpriteSheetBundle, 28 | #[grid_coords] 29 | grid_coords: GridCoords, 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /book/src/how-to-guides/respawn-levels-and-worlds.md: -------------------------------------------------------------------------------- 1 | # Respawn Levels and Worlds 2 | Internally, `bevy_ecs_ldtk` uses a [`Respawn`](https://docs.rs/bevy_ecs_ldtk/0.12.0/bevy_ecs_ldtk/prelude/struct.Respawn.html) component on worlds and levels to assist in the spawning process. 3 | This can be leveraged by users to implement a simple level restart feature, or an even more heavy-handed world restart feature. 4 | 5 | This code is from the `collectathon` cargo example. 6 | 7 | ## Respawn the world 8 | To respawn the world, get the world's `Entity` and insert the `Respawn` component to it. 9 | This is especially easy if, like most users, you only have one world in your game. 10 | ```rust,no_run 11 | # use bevy::prelude::*; 12 | # use bevy_ecs_ldtk::prelude::*; 13 | {{ #include ../../../examples/collectathon/respawn.rs:33:42 }} 14 | ``` 15 | 16 | Note that this *will* respawn [worldly](../explanation/anatomy-of-the-world.html#worldly-entities) entities too. 17 | 18 | ## Respawn the currently-selected level 19 | Respawning a level works similarly to respawning the world. 20 | Get the level's `Entity` and insert the `Respawn` component to it. 21 | 22 | The optimal strategy for finding the level entity can differ depending on the game. 23 | For example, if the game should only spawn one level at a time, operate under that assumption and query for the only `LevelIid` entity. 24 | ```rust,no_run 25 | # use bevy::prelude::*; 26 | # use bevy_ecs_ldtk::prelude::*; 27 | fn respawn_only_level( 28 | mut commands: Commands, 29 | levels: Query>, 30 | input: Res> 31 | ) -> Result { 32 | if input.just_pressed(KeyCode::KeyL) { 33 | commands.entity(levels.single()?).insert(Respawn); 34 | } 35 | Ok(()) 36 | } 37 | ``` 38 | 39 | If the game spawns multiple levels and you want the one specified in the `LevelSelection`, you may need a more complex strategy. 40 | 41 | In the `collectathon` cargo example, the `LevelSelection` is always assumed to be of the `Iid` variety. 42 | If you share this assumption, get the `LevelIid` from the `LevelSelection` and then search for the matching level entity. 43 | ```rust,no_run 44 | # use bevy::prelude::*; 45 | # use bevy_ecs_ldtk::prelude::*; 46 | {{ #include ../../../examples/collectathon/respawn.rs:13:31 }} 47 | ``` 48 | 49 | However, if you cannot make the same assumption, access the `LdtkProject` asset data and search for the level matching your `LevelSelection`. 50 | There is a method on `LdtkProject` to perform this search. 51 | ```rust,no_run 52 | # use bevy::prelude::*; 53 | # use bevy_ecs_ldtk::prelude::*; 54 | {{ #include ../../../examples/collectathon/respawn.rs:13:17 }} 55 | ldtk_projects: Query<&LdtkProjectHandle>, 56 | ldtk_project_assets: Res>, 57 | ) -> Result { 58 | if input.just_pressed(KeyCode::KeyL) { 59 | if let Some(only_project) = ldtk_project_assets.get(ldtk_projects.single()?) { 60 | let level_selection_iid = LevelIid::new( 61 | only_project 62 | .find_raw_level_by_level_selection(&level_selection) 63 | .expect("spawned level should exist in project") 64 | .iid 65 | .clone(), 66 | ); 67 | 68 | for (level_entity, level_iid) in levels.iter() { 69 | if level_selection_iid == *level_iid { 70 | commands.entity(level_entity).insert(Respawn); 71 | } 72 | } 73 | 74 | } 75 | } 76 | Ok(()) 77 | } 78 | ``` 79 | 80 | Note that, unlike respawning the world, respawning the level will *not* respawn any [worldly](../explanation/anatomy-of-the-world.html#worldly-entities) entities. 81 | -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/README.md: -------------------------------------------------------------------------------- 1 | # Tile-based Game 2 | In this tutorial you will make a tile-based game with LDtk levels. 3 | Game entities will be locked to a grid of tiles like sokoban, or snake. 4 | You will go through the process of creating an LDtk project, loading the project into bevy, and adding gameplay. 5 | 6 | This tutorial does have an example associated with it in the [`bevy_ecs_ldtk` repository](https://github.com/trouv/bevy_ecs_ldtk): 7 | ```bash 8 | $ cargo run --example tile_based_game --release 9 | ``` 10 | 11 | ## Prerequisites 12 | You will need to perform the following setup/installations: 13 | - [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). 14 | - [LDtk installation](https://ldtk.io/versions/), for the version specified in the [compatibility chart](https://github.com/Trouv/bevy_ecs_ldtk#compatibility). 15 | 16 | You will also need some simple assets: 17 | - A tileset for the environment with at least a background tile, a wall tile, and a "goal"-ish tile. 18 | - A tileset for the the player. 19 | 20 | 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/). 21 | However, you will be able to follow this tutorial using any tilesets, so long as they have tiles appropriate for the above purposes. 22 | -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/create-your-ldtk-project.md: -------------------------------------------------------------------------------- 1 | # Create your LDtk project 2 | In this section, you will create a simple LDtk project suitable for tile-based gameplay. 3 | This project will have an IntGrid layer of walls, and an Entity layer for placing Player and Goal entities. 4 | It will also have AutoTile rules on top of the IntGrid layer defining the visuals of walls and backgrounds. 5 | If you already have an LDtk project suitable for tile-based gameplay - feel free to skip this section. 6 | However, note that some of the values specified in here will be used in the tutorial going forward, such as... 7 | - the name/location of the file (`assets/tile-based-game.ldtk`) 8 | - the identifiers of the Player and Goal entities (Player, Goal) 9 | - the IntGrid value of walls (1) 10 | 11 | For details about the tutorial in general, including prerequisites, please see the parent page. 12 | 13 | ## Create empty project 14 | Open the LDtk app and create a new project. 15 | For this tutorial, name the project `tile-based-game.ldtk`, and save it to your Bevy project's `assets` directory. 16 | 17 | ## Set the World layout 18 | In the **World** tab - set the **World layout** to Horizontal. 19 | This will make levels have a clear linear relationship in the editor, rather than a geographical one. 20 | 21 | ![world-layout](images/world-layout.png) 22 | 23 | ## Import tilesets 24 | Add your environment/player tilesets to the project, in the **Tilesets** tab. 25 | Make sure that the source image files for these tilesets are also in your Bevy project's `assets` directory. 26 | Name the tilesets "Environment" and "Player" respectively. 27 | For the SunnyLand assets - the Player tileset needs to have a tile size of 32 and the environment asset a tile size of 16. 28 | 29 | ![tilesets](images/tilesets.png) 30 | 31 | ## Add IntGrid layer for walls 32 | Add an IntGrid layer to the project, in the **Layers** tab. 33 | This layer will be used to define where the collisions are in the level. 34 | Call this layer "Walls". 35 | Make sure its grid size is 16. 36 | Finally, give it an **Auto-layer tileset** - pointing to the Environment tileset. 37 | 38 | ![wall-layer](images/wall-layer.png) 39 | 40 | ## Define autotiling for walls and backgrounds 41 | From the Walls layer definition, select **EDIT RULES** for the Auto-layer tileset. 42 | This is where you will define how LDtk should dynamically render the Walls layer of your levels based off the level's IntGrid values. 43 | 44 | First, define a catch-all rule that will place the background tile if no other rules are matched first. 45 | 1. Select **+ GROUP** to add a new empty rule group, and name it Background. 46 | 2. On the new group, select **+** to define the first rule. 47 | 3. In the top-right of the rule definition - select the tile you want to use as the background. 48 | 4. Since this is a catch-all rule, no changes to the rule-pattern are necessary. 49 | 50 | ![background-rule](images/background-rule.png) 51 | 52 | Next, define a rule that will catch any wall tile. 53 | You will be able to define more complex rules on top of this to make walls prettier, but it's good to start with a generic one first. 54 | 1. Create another new group, and name it Walls. 55 | 2. Click **+** on the Walls group to create its first rule. 56 | 3. Select the tile you want to use as a generic wall tile in the top-right. 57 | 4. Set the rule to be 1x1, and left-click the rule-pattern to place a wall tile. 58 | 59 | ![all-walls-rule](images/all-walls-rule.png) 60 | 61 | Now you will be able to place walls in your level and they will be automatically rendered using this tile. 62 | 63 | The following rule is optional, and will define the tile used for the edges of walls - specifically horizontal edges. 64 | 1. Create a new rule in the Walls group. 65 | 2. Select the tile you want to use as the left edges of a wall. 66 | 3. Use a 3x3 pattern, and place a wall tile in the center and a negative wall tile on the left (by right clicking the left-center tile). 67 | This will match any wall tiles that don't have a wall tile to their left. 68 | 4. On this new rule inside the group, enable the **X** option. 69 | This mirrors the rule in the x-direction, so that it works for the right edges of walls as well. 70 | 71 | ![horizontal-wall-edge-rule](images/horizontal-wall-edge-rule.png) 72 | 73 | You are welcome to add more rules to the Walls group with more complex patterns for defining the vertical edges or corners. 74 | This tutorial will not go into painstaking detail about creating these, but their definitions are shown below. 75 | One general recommendation is to order these rules from most-specific to least-specific, so that the rule matcher will resort to the catch-all rules last. 76 | 77 | A vertical wall edge rule - mirrored in the **Y** direction: 78 | 79 | ![vertical-wall-edge-rule](images/vertical-wall-edge-rule.png) 80 | 81 | An outer corner wall rule - mirrored in the **X** and **Y** directions: 82 | 83 | ![wall-outer-corner-rule](images/wall-outer-corner-rule.png) 84 | 85 | An inner corner wall rule - mirrored in the **X** and **Y** directions: 86 | 87 | ![wall-inner-corner-rule](images/wall-inner-corner-rule.png) 88 | 89 | Now you can enjoy placing walls in your level and watching LDtk auto-tile them for you! 90 | 91 |
92 | 93 | ## Add Entity layer 94 | Add an Entity layer to the project, again, in the **Layers** tab. 95 | This will be used to place less tiling-oriented game objects, like the player, or the goal. 96 | You do not need to make any modifications to the default entity layer for this tutorial, it should be called "Entities" and match the grid size of the Walls layer. 97 | 98 | ![entity-layer](images/entities-layer.png) 99 | 100 | Then, in the **Entities** tab, add a Player entity. 101 | Be sure to name it "Player" and set its editor visual to use a tile from the Player tileset. 102 | This will be important in the next section of the tutorial. 103 | For the SunnyLand assets - you will need to manually set its size to 16x16 so that it fits in a single tile on the grid. 104 | 105 | ![player-entity](images/player-entity.png) 106 | 107 | Lastly, add a Goal entity. 108 | Name it "Goal" and set its editor visual from a tileset as well. 109 | Again, this will be important in the next section of the tutorial. 110 | 111 | ![goal-entity](images/goal-entity.png) 112 | 113 | ## Design some levels 114 | In the following chapters, you will spawn this project in Bevy and implement gameplay code for it. 115 | The game will be simple - move the player around the grid, navigating the walls, and start the next level once they reach the goal. 116 | With this in mind, design a few levels for this game using the tools you have set up thus far. 117 | 118 | ![levels](images/levels.png) 119 | -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/all-walls-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/all-walls-rule.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/auto-tile-walls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/auto-tile-walls.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/background-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/background-rule.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/bevy-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/bevy-setup.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/bevy-sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/bevy-sprites.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/entities-layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/entities-layer.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/goal-entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/goal-entity.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/horizontal-wall-edge-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/horizontal-wall-edge-rule.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/levels.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/player-entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/player-entity.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/tilesets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/tilesets.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/vertical-wall-edge-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/vertical-wall-edge-rule.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/wall-inner-corner-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/wall-inner-corner-rule.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/wall-layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/wall-layer.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/wall-outer-corner-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/wall-outer-corner-rule.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/images/world-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trouv/bevy_ecs_ldtk/fcc7e291b2fa3a7c20dc0bdd4856227672f042db/book/src/tutorials/tile-based-game/images/world-layout.png -------------------------------------------------------------------------------- /book/src/tutorials/tile-based-game/spawn-your-ldtk-project-in-bevy.md: -------------------------------------------------------------------------------- 1 | # Spawn your LDtk project in Bevy 2 | In this section, you will load/spawn your LDtk project in Bevy, including spawning sprites for the LDtk entities. 3 | This tutorial will use the LDtk project created in the previous section. 4 | 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 previous section, such as... 5 | - the name/location of the file (`assets/tile-based-game.ldtk`) 6 | - the identifiers of the Player and Goal entities (Player, Goal) 7 | 8 | For details about the tutorial in general, including prerequisites, please see the parent page. 9 | 10 | ## Set up minimal Bevy App 11 | In the `main` function of your game, create a Bevy `App` with `DefaultPlugins` and `LdtkPlugin`. 12 | This code snippet also sets bevy's texture filtering to "nearest", which is good for pixelated games. 13 | ```rust,no_run 14 | use bevy::prelude::*; 15 | use bevy_ecs_ldtk::prelude::*; 16 | 17 | fn main() { 18 | App::new() 19 | .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) 20 | .add_plugins(LdtkPlugin) 21 | .run(); 22 | } 23 | ``` 24 | 25 | ## Spawn the camera and LdtkWorldBundle on startup 26 | Create a startup system that spawns a camera entity and a `LdtkWorldBundle` entity. 27 | The latter requires a `Handle`, which can be obtained by loading your LDtk project from the Bevy `AssetServer` resource. 28 | This code snippet also doubles the scale of the camera and adjusts its transform to make the level slightly easier to view in 720p. 29 | ```rust,no_run 30 | # use bevy::prelude::*; 31 | # use bevy_ecs_ldtk::prelude::*; 32 | fn main() { 33 | App::new() 34 | // other App builders 35 | {{#include ../../../../examples/tile_based_game.rs:11}} 36 | .run(); 37 | } 38 | 39 | {{#include ../../../../examples/tile_based_game.rs:29:43}} 40 | ``` 41 | 42 | Finally, insert the `LevelSelection` resource to tell the plugin to spawn the first level. 43 | Construct the `LevelSelection` using its `index` method to select the first level (0-indexed). 44 | ```rust,no_run 45 | # use bevy::prelude::*; 46 | # use bevy_ecs_ldtk::prelude::*; 47 | fn main() { 48 | App::new() 49 | // other App builders 50 | {{#include ../../../../examples/tile_based_game.rs:12}} 51 | .run(); 52 | } 53 | ``` 54 | 55 | Now, run the game with `$ cargo run --release` to see your first level spawning in Bevy! 56 | 57 | Is the rendered level different than shown in LDtk? See the [known limitations section](../../explanation/limitations.md) 58 | 59 | ![bevy-setup](images/bevy-setup.png) 60 | 61 | ## Spawn sprites for your LDtk entities 62 | You may have noticed that the Player and Goal are not rendered here. 63 | They are there, but they require a little more work to become visible. 64 | 65 | Create a `PlayerBundle` and `GoalBundle`, each with an `sprite_sheet` field. 66 | You will develop these bundles a little bit more in the next chapter, but for now they will be similar. 67 | Derive `LdtkEntity` for these bundles, and give the field a `#[sprite_sheet]` attribute. 68 | This trait implementation defines how these bundles should be spawned by the plugin. 69 | More specifically - they should be spawned as sprites identical to the entity's editor visual. 70 | ```rust,no_run 71 | # use bevy::prelude::*; 72 | # use bevy_ecs_ldtk::prelude::*; 73 | #[derive(Default, Bundle, LdtkEntity)] 74 | struct PlayerBundle { 75 | #[sprite_sheet] 76 | sprite_sheet: Sprite, 77 | } 78 | 79 | #[derive(Default, Bundle, LdtkEntity)] 80 | struct GoalBundle { 81 | #[sprite_sheet] 82 | sprite_sheet: Sprite, 83 | } 84 | ``` 85 | 86 | Finally, register these bundles to the app using `register_ldtk_entity`, and provide their LDtk identifier. 87 | When the plugin spawns entities with these identifiers, it will use the registered bundle. 88 | ```rust,no_run 89 | # use bevy::prelude::*; 90 | # use bevy_ecs_ldtk::prelude::*; 91 | fn main() { 92 | App::new() 93 | // other App builders 94 | {{#include ../../../../examples/tile_based_game.rs:13:14}} 95 | .run(); 96 | } 97 | # #[derive(Default, Bundle, LdtkEntity)] 98 | # struct PlayerBundle { 99 | # #[sprite_sheet] 100 | # sprite_sheet: Sprite, 101 | # } 102 | # #[derive(Default, Bundle, LdtkEntity)] 103 | # struct GoalBundle { 104 | # #[sprite_sheet] 105 | # sprite_sheet: Sprite, 106 | # } 107 | ``` 108 | 109 | Now run the game again - the sprites will appear this time. 110 | 111 | ![bevy-sprites](images/bevy-sprites.png) 112 | -------------------------------------------------------------------------------- /docs/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2021 Trevor Lovell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_ecs_ldtk::prelude::*; 3 | 4 | fn main() { 5 | App::new() 6 | .add_plugins( 7 | DefaultPlugins.set(ImagePlugin::default_nearest()), // prevents blurry sprites 8 | ) 9 | .add_plugins(LdtkPlugin) 10 | .add_systems(Startup, setup) 11 | .insert_resource(LevelSelection::index(0)) 12 | .register_ldtk_entity::("MyEntityIdentifier") 13 | .run(); 14 | } 15 | 16 | fn setup(mut commands: Commands, asset_server: Res) { 17 | commands.spawn(Camera2d); 18 | 19 | commands.spawn(LdtkWorldBundle { 20 | ldtk_handle: asset_server.load("my_project.ldtk").into(), 21 | ..Default::default() 22 | }); 23 | } 24 | 25 | #[derive(Default, Component)] 26 | struct ComponentA; 27 | 28 | #[derive(Default, Component)] 29 | struct ComponentB; 30 | 31 | #[derive(Default, Bundle, LdtkEntity)] 32 | pub struct MyBundle { 33 | a: ComponentA, 34 | b: ComponentB, 35 | #[sprite_sheet] 36 | sprite_sheet: Sprite, 37 | } 38 | -------------------------------------------------------------------------------- /examples/collectathon/README.md: -------------------------------------------------------------------------------- 1 | # Collectathon example 2 | A simple game where you collect coins. 3 | 4 | ```bash 5 | $ cargo run --example collectathon --release 6 | ``` 7 | 8 | ## Controls 9 | - WASD or arrow keys: movement 10 | - L: respawn level 11 | - R: respawn world 12 | -------------------------------------------------------------------------------- /examples/collectathon/coin.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_ecs_ldtk::prelude::*; 3 | 4 | /// Plugin for spawning coins and collecting them. 5 | pub struct CoinPlugin; 6 | 7 | impl Plugin for CoinPlugin { 8 | fn build(&self, app: &mut App) { 9 | app.add_systems(Update, collect) 10 | .register_ldtk_entity::("Coin"); 11 | } 12 | } 13 | 14 | /// Component marking coin entities. 15 | #[derive(Default, Component)] 16 | struct Coin; 17 | 18 | #[derive(Default, Bundle, LdtkEntity)] 19 | struct CoinBundle { 20 | coin: Coin, 21 | #[sprite_sheet] 22 | sprite_sheet: Sprite, 23 | } 24 | 25 | /// Component for entities that can collect coins. 26 | /// Stores the number of coins they have collected. 27 | #[derive(Default, Component)] 28 | pub struct Wallet { 29 | coins: u32, 30 | } 31 | 32 | const COLLECT_DISTANCE: f32 = 12.; 33 | 34 | fn collect( 35 | mut commands: Commands, 36 | mut wallets: Query<(&mut Wallet, &GlobalTransform)>, 37 | coins: Query<(Entity, &GlobalTransform), With>, 38 | ) { 39 | for (mut wallet, wallet_transform) in wallets.iter_mut() { 40 | for (coin_entity, coin_transform) in coins.iter() { 41 | // Global translations of new entities will always be 0 for one update. 42 | // This check prevents wallets near 0 collecting newly-spawned coins. 43 | if coin_transform.translation() == Vec3::ZERO { 44 | continue; 45 | } 46 | 47 | let distance = wallet_transform 48 | .translation() 49 | .distance(coin_transform.translation()); 50 | 51 | if distance <= COLLECT_DISTANCE { 52 | wallet.coins += 1; 53 | println!("Coins: {}", wallet.coins); 54 | 55 | commands.entity(coin_entity).despawn(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/collectathon/main.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_ecs_ldtk::prelude::*; 3 | 4 | mod coin; 5 | mod player; 6 | mod respawn; 7 | 8 | fn main() { 9 | App::new() 10 | .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) 11 | .add_plugins(LdtkPlugin) 12 | .insert_resource(LevelSelection::iid("34f51d20-8990-11ee-b0d1-cfeb0e9e30f6")) 13 | .insert_resource(LdtkSettings { 14 | level_spawn_behavior: LevelSpawnBehavior::UseWorldTranslation { 15 | load_level_neighbors: true, 16 | }, 17 | ..default() 18 | }) 19 | .add_systems(Startup, setup) 20 | .add_plugins(( 21 | coin::CoinPlugin, 22 | player::PlayerPlugin, 23 | respawn::RespawnPlugin, 24 | )) 25 | .run(); 26 | } 27 | 28 | fn setup(mut commands: Commands, asset_server: Res) { 29 | commands.spawn(( 30 | Camera2d, 31 | Projection::Orthographic(OrthographicProjection { 32 | scale: 0.5, 33 | ..OrthographicProjection::default_2d() 34 | }), 35 | )); 36 | 37 | let ldtk_handle = asset_server.load("collectathon.ldtk").into(); 38 | 39 | commands.spawn(LdtkWorldBundle { 40 | ldtk_handle, 41 | ..Default::default() 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /examples/collectathon/player.rs: -------------------------------------------------------------------------------- 1 | use crate::coin::Wallet; 2 | use bevy::prelude::*; 3 | use bevy_ecs_ldtk::prelude::*; 4 | 5 | /// Plugin for spawning the player and controlling them. 6 | pub struct PlayerPlugin; 7 | 8 | impl Plugin for PlayerPlugin { 9 | fn build(&self, app: &mut App) { 10 | app.add_systems(Update, (move_player, level_selection_follow_player)) 11 | .register_ldtk_entity::("Player"); 12 | } 13 | } 14 | 15 | /// Component marking the player entity. 16 | #[derive(Default, Component)] 17 | struct Player; 18 | 19 | #[derive(Default, Bundle, LdtkEntity)] 20 | struct PlayerBundle { 21 | player: Player, 22 | wallet: Wallet, 23 | #[worldly] 24 | worldly: Worldly, 25 | #[sprite_sheet] 26 | sprite_sheet: Sprite, 27 | } 28 | 29 | const MOVEMENT_SPEED: f32 = 96.; 30 | 31 | fn move_player( 32 | mut players: Query<&mut Transform, With>, 33 | input: Res>, 34 | time: Res