Skip to main content

macroquad_tiled/
lib.rs

1use nanoserde::DeJson;
2
3use macroquad::prelude::*;
4
5use std::collections::HashMap;
6
7mod error;
8mod tiled;
9
10pub use error::Error;
11pub use tiled::layer::Property;
12
13#[derive(Debug, Clone)]
14pub struct Object {
15    /// If not null - the object is (probably) a tile
16    pub gid: Option<u32>,
17
18    pub world_x: f32,
19    pub world_y: f32,
20    pub world_w: f32,
21    pub world_h: f32,
22
23    pub tile_x: u32,
24    pub tile_y: u32,
25    pub tile_w: u32,
26    pub tile_h: u32,
27
28    pub name: String,
29
30    pub properties: HashMap<String, String>,
31}
32
33#[derive(Debug)]
34pub struct Tile {
35    /// id in the tileset
36    pub id: u32,
37    /// Each tile belongs to one tileset
38    pub tileset: String,
39    /// "type" from tiled
40    pub attrs: String,
41}
42
43#[derive(Debug, Default)]
44pub struct Layer {
45    pub objects: Vec<Object>,
46    pub width: u32,
47    pub height: u32,
48    pub data: Vec<Option<Tile>>,
49    /// imagelayer
50    pub opacity: f32,
51    pub image: Option<Texture2D>,
52    pub offsetx: Option<f32>,
53    pub offsety: Option<f32>,
54}
55
56#[derive(Debug)]
57pub struct TileSet {
58    pub texture: Texture2D,
59
60    pub tilewidth: i32,
61    pub tileheight: i32,
62    pub columns: u32,
63    pub spacing: i32,
64    pub margin: i32,
65}
66
67impl TileSet {
68    fn sprite_rect(&self, ix: u32) -> Rect {
69        let sw = self.tilewidth as f32;
70        let sh = self.tileheight as f32;
71        let sx = (ix % self.columns) as f32 * (sw + self.spacing as f32) + self.margin as f32;
72        let sy = (ix / self.columns) as f32 * (sh + self.spacing as f32) + self.margin as f32;
73
74        // TODO: configure tiles margin
75        Rect::new(sx + 1.1, sy + 1.1, sw - 2.2, sh - 2.2)
76    }
77}
78
79#[derive(Debug)]
80pub struct Map {
81    pub layers: HashMap<String, Layer>,
82    pub tilesets: HashMap<String, TileSet>,
83
84    /// Deserialized json as is
85    pub raw_tiled_map: tiled::Map,
86}
87
88impl Map {
89    pub fn spr(&self, tileset: &str, sprite: u32, dest: Rect) {
90        if self.tilesets.contains_key(tileset) == false {
91            panic!(
92                "No such tileset: {}, tilesets available: {:?}",
93                tileset,
94                self.tilesets.keys()
95            )
96        }
97        let tileset = &self.tilesets[tileset];
98        let spr_rect = tileset.sprite_rect(sprite);
99
100        draw_texture_ex(
101            &tileset.texture,
102            dest.x,
103            dest.y,
104            WHITE,
105            DrawTextureParams {
106                dest_size: Some(vec2(dest.w, dest.h)),
107                source: Some(Rect::new(
108                    spr_rect.x - 1.0,
109                    spr_rect.y - 1.0,
110                    spr_rect.w + 2.0,
111                    spr_rect.h + 2.0,
112                )),
113                ..Default::default()
114            },
115        );
116    }
117
118    pub fn spr_ex(&self, tileset: &str, source: Rect, dest: Rect) {
119        let tileset = &self.tilesets[tileset];
120
121        draw_texture_ex(
122            &tileset.texture,
123            dest.x,
124            dest.y,
125            WHITE,
126            DrawTextureParams {
127                dest_size: Some(vec2(dest.w, dest.h)),
128                source: Some(source),
129                ..Default::default()
130            },
131        );
132    }
133
134    pub fn contains_layer(&self, layer: &str) -> bool {
135        self.layers.contains_key(layer)
136    }
137
138    pub fn draw_tiles(&self, layer: &str, dest: Rect, source: impl Into<Option<Rect>>) {
139        assert!(self.layers.contains_key(layer), "No such layer: {}", layer);
140
141        let source = source.into().unwrap_or(Rect::new(
142            0.,
143            0.,
144            self.raw_tiled_map.width as f32,
145            self.raw_tiled_map.height as f32,
146        ));
147        let layer = &self.layers[layer];
148
149        let spr_width = dest.w / source.w;
150        let spr_height = dest.h / source.h;
151
152        let mut separated_by_ts: HashMap<&str, Vec<(&Tile, Rect)>> = HashMap::new();
153
154        for y in source.y as u32..source.y as u32 + source.h as u32 {
155            for x in source.x as u32..source.x as u32 + source.w as u32 {
156                if let Some(tile) = &layer
157                    .data
158                    .get((y * layer.width + x) as usize)
159                    .unwrap_or(&None)
160                {
161                    if !separated_by_ts.contains_key(tile.tileset.as_str()) {
162                        separated_by_ts.insert(&tile.tileset, vec![]);
163                    }
164
165                    let pos = vec2(
166                        (x - source.x as u32) as f32 / source.w * dest.w + dest.x,
167                        (y - source.y as u32) as f32 / source.h * dest.h + dest.y,
168                    );
169                    separated_by_ts
170                        .get_mut(tile.tileset.as_str())
171                        .unwrap()
172                        .push((&tile, Rect::new(pos.x, pos.y, spr_width, spr_height)));
173                }
174            }
175        }
176
177        for (tileset, tileset_layer) in &separated_by_ts {
178            for (tile, rect) in tileset_layer {
179                self.spr(tileset, tile.id, *rect);
180            }
181        }
182    }
183
184    pub fn draw_imglayer(&self, layer: &str, dest: Rect, source: Option<Rect>) {
185        assert!(self.layers.contains_key(layer), "No such layer: {}", layer);
186        let layer = &self.layers[layer];
187        assert!(layer.image.is_some(), "No texture found.");
188        let img_texture = layer.image.clone().unwrap();
189        let dest_width_frac =
190            img_texture.width() / (self.raw_tiled_map.width * self.raw_tiled_map.tilewidth) as f32;
191        let dest_height_frac =
192            img_texture.height() / (self.raw_tiled_map.height * self.raw_tiled_map.height) as f32;
193
194        let source = source.unwrap_or(Rect::new(0., 0., img_texture.width(), img_texture.height()));
195        draw_texture_ex(
196            &img_texture,
197            (layer.offsetx.unwrap() - source.x) / source.w * dest.w + dest.x,
198            (layer.offsety.unwrap() - source.y) / source.h * dest.h + dest.y,
199            Color {
200                r: 255.,
201                g: 255.,
202                b: 255.,
203                a: layer.opacity,
204            },
205            DrawTextureParams {
206                dest_size: Some(vec2(dest.w * dest_width_frac, dest.h * dest_height_frac)),
207                source: Some(source),
208                ..Default::default()
209            },
210        );
211    }
212
213    pub fn tiles(&self, layer: &str, rect: impl Into<Option<Rect>>) -> TilesIterator {
214        assert!(self.layers.contains_key(layer), "No such layer: {}", layer);
215
216        let rect = rect.into().unwrap_or(Rect::new(
217            0.,
218            0.,
219            self.raw_tiled_map.width as f32,
220            self.raw_tiled_map.height as f32,
221        ));
222        TilesIterator::new(&self.layers[layer], rect)
223    }
224
225    pub fn get_tile(&self, layer: &str, x: u32, y: u32) -> &Option<Tile> {
226        assert!(self.layers.contains_key(layer), "No such layer: {}", layer);
227
228        let layer = &self.layers[layer];
229
230        if x >= layer.width || y >= layer.height {
231            return &None;
232        }
233
234        &layer.data[(y * layer.width + x) as usize]
235    }
236}
237
238pub struct TilesIterator<'a> {
239    rect: Rect,
240    current: (u32, u32),
241    layer: &'a Layer,
242}
243
244impl<'a> TilesIterator<'a> {
245    fn new(layer: &'a Layer, rect: Rect) -> Self {
246        let current = (rect.x as u32, rect.y as u32);
247
248        TilesIterator {
249            layer,
250            rect,
251            current,
252        }
253    }
254}
255
256impl<'a> Iterator for TilesIterator<'a> {
257    type Item = (u32, u32, &'a Option<Tile>);
258
259    fn next(&mut self) -> Option<Self::Item> {
260        let next_x;
261        let next_y;
262
263        if self.current.0 + 1 >= self.rect.x as u32 + self.rect.w as u32 {
264            next_x = self.rect.x as u32;
265            next_y = self.current.1 + 1;
266        } else {
267            next_x = self.current.0 + 1;
268            next_y = self.current.1;
269        }
270
271        if next_y >= self.rect.y as u32 + self.rect.h as u32 {
272            return None;
273        }
274
275        let res = Some((
276            self.current.0,
277            self.current.1,
278            &self.layer.data[(self.current.1 * self.layer.width + self.current.0) as usize],
279        ));
280        self.current = (next_x, next_y);
281        res
282    }
283}
284
285/// Load Tiled tile map from given json string.
286/// "data" is a tiled json content.
287/// "textures" is a map from the name used in the json to macroquad texture.
288/// "external_tilesets" is a map of tileset name to tileset json content.
289/// "external_tilesets" is used when in tiled the "source" field is used instead of embedded tileset.
290pub fn load_map(
291    data: &str,
292    textures: &[(&str, Texture2D)],
293    external_tilesets: &[(&str, &str)],
294) -> Result<Map, error::Error> {
295    let map: tiled::Map = DeJson::deserialize_json(data)?;
296
297    let mut layers = HashMap::new();
298    let mut tilesets = HashMap::new();
299    let mut map_tilesets = vec![];
300
301    for tileset in &map.tilesets {
302        let tileset = if tileset.source.is_empty() {
303            tileset.clone()
304        } else {
305            let tileset_data = external_tilesets
306                .iter()
307                .find(|(name, _)| *name == &tileset.source)
308                .unwrap();
309            let mut map_tileset: tiled::Tileset = DeJson::deserialize_json(&tileset_data.1)?;
310            map_tileset.firstgid = tileset.firstgid;
311            map_tileset
312        };
313
314        let texture = &textures
315            .iter()
316            .find(|(name, _)| *name == tileset.image)
317            .ok_or(error::Error::TextureNotFound {
318                texture: tileset.image.clone(),
319            })?
320            .1;
321
322        tilesets.insert(
323            tileset.name.clone(),
324            TileSet {
325                texture: texture.clone(),
326                columns: tileset.columns as _,
327                margin: tileset.margin,
328                spacing: tileset.spacing,
329                tilewidth: tileset.tilewidth,
330                tileheight: tileset.tileheight,
331            },
332        );
333
334        map_tilesets.push(tileset);
335    }
336
337    for layer in &map.layers {
338        if layers.contains_key(&layer.name) {
339            return Err(error::Error::NonUniqueLayerName {
340                layer: layer.name.clone(),
341            });
342        }
343
344        let tile_width = map.tilewidth as f32;
345        let tile_height = map.tileheight as f32;
346
347        let mut objects = vec![];
348        for object in &layer.objects {
349            objects.push(Object {
350                gid: object.gid,
351                world_x: object.x,
352                world_y: object.y,
353                world_w: object.width,
354                world_h: object.height,
355
356                tile_x: (object.x / tile_width) as u32,
357                tile_y: (object.y / tile_height) as u32,
358                tile_w: (object.width / tile_width) as u32,
359                tile_h: (object.height / tile_height) as u32,
360                name: object.name.clone(),
361                properties: object
362                    .properties
363                    .iter()
364                    .map(|property| (property.name.to_string(), property.value.to_string()))
365                    .collect(),
366            });
367        }
368
369        let find_tileset = |tile: u32| {
370            map_tilesets.iter().find(|tileset| {
371                tile >= tileset.firstgid && tile < tileset.firstgid + tileset.tilecount
372            })
373        };
374
375        layers.insert(
376            layer.name.clone(),
377            match layer.ty.as_str() {
378                "tilelayer" | "objectlayer" => Layer {
379                    objects,
380                    width: layer.width,
381                    height: layer.height,
382                    data: layer
383                        .data
384                        .iter()
385                        .map(|tile| {
386                            find_tileset(*tile).map(|tileset| {
387                                let attrs = tileset
388                                    .tiles
389                                    .iter()
390                                    .find(|t| t.id as u32 == *tile - tileset.firstgid)
391                                    .and_then(|tile| tile.ty.clone())
392                                    .unwrap_or("".to_owned());
393
394                                Tile {
395                                    id: *tile - tileset.firstgid,
396                                    tileset: tileset.name.clone(),
397                                    attrs,
398                                }
399                            })
400                        })
401                        .collect::<Vec<_>>(),
402                    opacity: layer.opacity,
403                    ..Default::default()
404                },
405                "imagelayer" => {
406                    let img_name = layer.image.clone().unwrap();
407                    if img_name == "" {
408                        continue;
409                    }
410                    let offsetx = match layer.offsetx {
411                        Some(x) => Some(x as f32),
412                        None => Some(0f32),
413                    };
414                    let offsety = match layer.offsety {
415                        Some(y) => Some(y as f32),
416                        None => Some(0f32),
417                    };
418                    let img_texture = &textures
419                        .iter()
420                        .find(|(name, _)| *name == img_name)
421                        .ok_or(error::Error::TextureNotFound { texture: img_name })?
422                        .1;
423
424                    Layer {
425                        image: Some(img_texture.clone()),
426                        opacity: layer.opacity,
427                        offsetx,
428                        offsety,
429                        ..Default::default()
430                    }
431                }
432                layer_type => {
433                    return Err(error::Error::LayerTypeNotFound {
434                        layer_type: layer_type.to_string(),
435                    })
436                }
437            },
438        );
439    }
440
441    Ok(Map {
442        layers,
443        tilesets,
444        raw_tiled_map: map,
445    })
446}