├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── resources ├── assets │ ├── terrainTiles_default.png │ └── tiled_base64_zlib.tmx └── display_config.ron └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/rust,intellij,visualstudiocode 3 | 4 | ### Intellij ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | .idea/ 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/modules.xml 36 | # .idea/*.iml 37 | # .idea/modules 38 | 39 | # CMake 40 | cmake-build-*/ 41 | 42 | # Mongo Explorer plugin 43 | .idea/**/mongoSettings.xml 44 | 45 | # File-based project format 46 | *.iws 47 | 48 | # IntelliJ 49 | out/ 50 | 51 | # mpeltonen/sbt-idea plugin 52 | .idea_modules/ 53 | 54 | # JIRA plugin 55 | atlassian-ide-plugin.xml 56 | 57 | # Cursive Clojure plugin 58 | .idea/replstate.xml 59 | 60 | # Crashlytics plugin (for Android Studio and IntelliJ) 61 | com_crashlytics_export_strings.xml 62 | crashlytics.properties 63 | crashlytics-build.properties 64 | fabric.properties 65 | 66 | # Editor-based Rest Client 67 | .idea/httpRequests 68 | 69 | # Android studio 3.1+ serialized cache file 70 | .idea/caches/build_file_checksums.ser 71 | 72 | ### Intellij Patch ### 73 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 74 | 75 | # *.iml 76 | # modules.xml 77 | # .idea/misc.xml 78 | # *.ipr 79 | 80 | # Sonarlint plugin 81 | .idea/sonarlint 82 | 83 | ### Rust ### 84 | # Generated by Cargo 85 | # will have compiled files and executables 86 | /target/ 87 | 88 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 89 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 90 | Cargo.lock 91 | 92 | # These are backup files generated by rustfmt 93 | **/*.rs.bk 94 | 95 | ### VisualStudioCode ### 96 | .vscode/* 97 | !.vscode/settings.json 98 | !.vscode/tasks.json 99 | !.vscode/launch.json 100 | !.vscode/extensions.json 101 | 102 | 103 | # End of https://www.gitignore.io/api/rust,intellij,visualstudiocode 104 | 105 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tiled-amethyst-example" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | amethyst = "0.15.0" 9 | tiled = "0.9.1" 10 | 11 | [features] 12 | default = ["vulkan", "tiles"] 13 | empty = ["amethyst/empty"] 14 | metal = ["amethyst/metal"] 15 | vulkan = ["amethyst/vulkan"] 16 | tiles = ["amethyst/tiles"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Teemu N 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple [rs-tiled](https://github.com/mattyhall/rs-tiled) [Amethyst](https://github.com/amethyst/amethyst) Example 2 | 3 | This example shows a simple way to draw a (TMX) map made with [Tiled](https://www.mapeditor.org/) in a Amethyst project. 4 | 5 | ## Notes: 6 | The tileset in `resources/assets/terrainTiles_default.png` is from [Kenney](https://kenney.nl/) 7 | 8 | The `resources/assets/tiled_base64_zlib.tmx` map file is made with Tiled version 1.3.3 and the tileset is included in it with the "Embed in map" option being selected. -------------------------------------------------------------------------------- /resources/assets/terrainTiles_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaakkaDev/Tiled-Amethyst-Example/400dbd575bea3260b3a87ca206348a066653e59e/resources/assets/terrainTiles_default.png -------------------------------------------------------------------------------- /resources/assets/tiled_base64_zlib.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | eJyFjzEKwEAIBNXWMglXpkj+/8ZYGFiGA4tBXFdW3cy8yCK6Zmvk1+njjvq4T5/mUqcWmCXypj92PuZNubzxEE70ytXcxVss4ZH5B+qvA5M= 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/display_config.ron: -------------------------------------------------------------------------------- 1 | ( 2 | title: "Tiled Amethyst Example", 3 | dimensions: Some((640, 640)), 4 | max_dimensions: None, 5 | min_dimensions: None, 6 | multisampling: 1, 7 | visibility: true, 8 | vsync: true, 9 | ) 10 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_imports)] 2 | 3 | extern crate amethyst; 4 | extern crate tiled; 5 | 6 | use std::fs::File; 7 | use std::io::BufReader; 8 | use std::path::Path; 9 | 10 | use amethyst::{ 11 | assets::{AssetStorage, Loader}, 12 | core::transform::{Transform, TransformBundle}, 13 | ecs::prelude::Entity, 14 | input::{get_key, is_close_requested, is_key_down, VirtualKeyCode}, 15 | prelude::*, 16 | renderer::{ 17 | Camera, 18 | ImageFormat, 19 | plugins::{RenderFlat2D, RenderToWindow}, 20 | RenderingBundle, 21 | rendy::hal::command::ClearColor, 22 | Sprite, sprite::TextureCoordinates, SpriteRender, SpriteSheet, SpriteSheetFormat, Texture, types::DefaultBackend, 23 | }, 24 | tiles::{FlatEncoder, MortonEncoder2D, RenderTiles2D}, 25 | utils::application_root_dir, 26 | window::{DisplayConfig, ScreenDimensions, Window}, 27 | }; 28 | use tiled::{LayerData, parse}; 29 | 30 | 31 | pub fn initialize_camera(world: &mut World) -> Entity { 32 | let (width, height) = { 33 | let dim = world.read_resource::(); 34 | (dim.width(), dim.height()) 35 | }; 36 | 37 | let mut transform = Transform::default(); 38 | // Camera z = 10.0 is usually a good starting point 39 | transform.set_translation_xyz(width * 0.5, height * 0.5, 10.0); 40 | 41 | world 42 | .create_entity() 43 | .with(Camera::standard_2d(width, height)) 44 | .with(transform) 45 | .build() 46 | } 47 | 48 | 49 | struct GameplayState; 50 | 51 | impl<'a, 'b> State, StateEvent> for GameplayState { 52 | fn on_start(&mut self, data: StateData) { 53 | let world = data.world; 54 | 55 | // We need the camera to actually see anything 56 | initialize_camera(world); 57 | 58 | // Load the tiled map the "crude" way 59 | load_map(world); 60 | } 61 | 62 | fn handle_event(&mut self, _: StateData<'_, GameData<'_, '_>>, event: StateEvent) -> Trans, StateEvent> { 63 | if let StateEvent::Window(event) = &event { 64 | if is_close_requested(&event) || is_key_down(&event, VirtualKeyCode::Escape) { 65 | return Trans::Quit 66 | } 67 | } 68 | Trans::None 69 | } 70 | 71 | fn update(&mut self, data: StateData<'_, GameData<'_, '_>>) -> Trans, StateEvent> { 72 | data.data.update(&data.world); 73 | Trans::None 74 | } 75 | } 76 | 77 | fn load_map(world: &mut World) { 78 | // Get texture handle for the tileset image 79 | let texture_handle = { 80 | let loader = world.read_resource::(); 81 | let texture_storage = world.read_resource::>(); 82 | loader.load( 83 | "assets/terrainTiles_default.png", 84 | ImageFormat::default(), 85 | (), 86 | &texture_storage 87 | ) 88 | }; 89 | 90 | // Load the tiled map 91 | let file = File::open(&Path::new("resources/assets/tiled_base64_zlib.tmx")).unwrap(); 92 | let reader = BufReader::new(file); 93 | let map = parse(reader).unwrap(); 94 | 95 | if let Some(map_tileset) = map.get_tileset_by_gid(1) { 96 | // 64 in this cases 97 | let tile_width = map_tileset.tile_width as i32; 98 | // 64 in this case 99 | let tile_height = map_tileset.tile_height as i32; 100 | // 640 in this case 101 | let tileset_width = &map_tileset.images[0].width; 102 | // 256 in this case 103 | let tileset_height = &map_tileset.images[0].height; 104 | // 4 columns 105 | let tileset_sprite_columns = tileset_width / tile_width as i32; 106 | // 2 rows 107 | let tileset_sprite_rows = tileset_height / tile_height as i32; 108 | 109 | // A place to store the tile sprites in 110 | let mut tile_sprites: Vec = Vec::new(); 111 | 112 | // The x-axis needs to be reversed for TextureCoordinates 113 | for x in 0..tileset_sprite_rows { 114 | for y in 0..tileset_sprite_columns { 115 | let tileset_w = *&map_tileset.images[0].width as u32; 116 | let tileset_h = *&map_tileset.images[0].height as u32; 117 | let sprite_w = tile_width as u32; 118 | let sprite_h = tile_height as u32; 119 | let offset_x = (y * tile_width) as u32; 120 | let offset_y = (x * tile_height) as u32; 121 | let offsets = [0.0; 2]; 122 | // Create a new `Sprite` 123 | let sprite = Sprite::from_pixel_values( 124 | tileset_w, 125 | tileset_h, 126 | sprite_w, 127 | sprite_h, 128 | offset_x, 129 | offset_y, 130 | offsets, 131 | false, 132 | false 133 | ); 134 | 135 | tile_sprites.push(sprite); 136 | } 137 | } 138 | 139 | // A sheet of sprites.. so all the tile sprites 140 | let sprite_sheet = SpriteSheet { 141 | texture: texture_handle, 142 | sprites: tile_sprites 143 | }; 144 | 145 | // Insert the sprite sheet, which consists of all the tile sprites, 146 | // into world resources for later use 147 | let sprite_sheet_handle = { 148 | let loader = world.read_resource::(); 149 | let sprite_sheet_storage = world.read_resource::>(); 150 | 151 | loader.load_from_data(sprite_sheet, (), &sprite_sheet_storage) 152 | }; 153 | 154 | // Now that all the tile sprites/textures are loaded in 155 | // we can start drawing the tiles for our viewing pleasure 156 | // NOTE: Only rendering the first layer 157 | let layer: &tiled::Layer = &map.layers[0]; 158 | 159 | if let LayerData::Finite(tiles) = &layer.tiles { 160 | // Loop the row first and then the individual tiles on that row 161 | // and then switch to the next row 162 | // y = row number 163 | // x = column number 164 | // IMPORTANT: Bottom left is 0,0 so the tiles list needs to be reversed with .rev() 165 | for (y, row) in tiles.iter().rev().enumerate().clone() { 166 | for (x, &tile) in row.iter().enumerate() { 167 | // Do nothing with empty tiles 168 | if tile.gid == 0 { 169 | continue; 170 | } 171 | 172 | // Tile ids start from 1 but tileset sprites start from 0 173 | let tile_id = tile.gid - 1; 174 | 175 | // Sprite for the tile 176 | let tile_sprite = SpriteRender { 177 | sprite_sheet: sprite_sheet_handle.clone(), 178 | sprite_number: tile_id as usize, 179 | }; 180 | 181 | // Where we should draw the tile? 182 | let mut tile_transform = Transform::default(); 183 | let x_coord = x * tile_width as usize; 184 | let y_coord = (y as f32 * tile_height as f32) + tile_height as f32; 185 | // Offset the positions by half the tile size so they're nice and snuggly on the screen 186 | // Alternatively could use the Sprite offsets instead: [-32.0, 32.0]. Depends on the use case I guess. 187 | let offset_x = tile_width as f32/2.0; 188 | let offset_y = -tile_height as f32/2.0; 189 | 190 | tile_transform.set_translation_xyz( 191 | offset_x + x_coord as f32, 192 | offset_y + y_coord as f32, 193 | 1.0 194 | ); 195 | 196 | // Create the tile entity 197 | world 198 | .create_entity() 199 | .with(tile_transform) 200 | .with(tile_sprite) 201 | .build(); 202 | } 203 | 204 | } 205 | } 206 | } 207 | } 208 | 209 | fn main() -> Result<(), amethyst::Error> { 210 | amethyst::Logger::from_config(Default::default()) 211 | .level_for("gfx_backend_vulkan", amethyst::LogLevelFilter::Warn) 212 | .start(); 213 | 214 | let app_root = application_root_dir()?; 215 | let resources = app_root.join("resources"); 216 | let display_config_path = resources.join("display_config.ron"); 217 | 218 | let game_data = GameDataBuilder::default() 219 | .with_bundle(TransformBundle::new())? 220 | .with_bundle( 221 | RenderingBundle::::new() 222 | .with_plugin( 223 | RenderToWindow::from_config_path(display_config_path)? 224 | .with_clear([0.00196, 0.23726, 0.21765, 1.0]), 225 | ) 226 | .with_plugin(RenderFlat2D::default()) 227 | )?; 228 | 229 | let mut game = Application::build(resources, GameplayState)?.build(game_data)?; 230 | 231 | game.run(); 232 | Ok(()) 233 | } 234 | --------------------------------------------------------------------------------