├── .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 |
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 |
--------------------------------------------------------------------------------