bevy_tiled_prototype 0.2.5

A plugin for rendering tiled maps.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
use crate::{
    objects::ObjectGroup, utils::project_iso, utils::project_ortho, ChunkBundle, MapLayer,
    TileMapChunk, TilesetLayer,
};
use anyhow::Result;
use bevy::{
    prelude::*,
    reflect::TypeUuid,
    utils::{HashMap, HashSet},
};
use std::{
    io::BufReader,
    path::{Path, PathBuf},
};
// objects include these by default for now
pub use tiled;
pub use tiled::LayerData;
pub use tiled::ObjectShape;
pub use tiled::Properties;
pub use tiled::PropertyValue;

// An asset for maps
#[derive(Debug, TypeUuid)]
#[uuid = "5f6fbac8-3f52-424e-a928-561667fea074"]
pub struct Map {
    pub map: tiled::Map,
    pub meshes: Vec<(u32, u32, Mesh)>,
    pub layers: Vec<MapLayer>,
    pub groups: Vec<ObjectGroup>,
    pub tile_size: Vec2,
    pub image_folder: std::path::PathBuf,
    pub asset_dependencies: Vec<PathBuf>,
}

impl Map {
    pub fn center(&self, origin: Transform) -> Transform {
        let tile_size = Vec2::new(self.map.tile_width as f32, self.map.tile_height as f32);
        let map_center = Vec2::new(self.map.width as f32 / 2.0, self.map.height as f32 / 2.0);
        match self.map.orientation {
            tiled::Orientation::Orthogonal => {
                let center = project_ortho(map_center, tile_size.x, tile_size.y);
                Transform::from_matrix(
                    origin.compute_matrix() * Mat4::from_translation(-center.extend(0.0)),
                )
            }
            tiled::Orientation::Isometric => {
                let center = project_iso(map_center, tile_size.x, tile_size.y);
                Transform::from_matrix(
                    origin.compute_matrix() * Mat4::from_translation(-center.extend(0.0)),
                )
            }
            _ => panic!("Unsupported orientation {:?}", self.map.orientation),
        }
    }

    pub fn try_from_bytes(asset_folder: &Path, asset_path: &Path, bytes: Vec<u8>) -> Result<Map> {
        #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
        let root_dir = bevy::asset::FileAssetIo::get_root_path();
        #[cfg(any(target_arch = "wasm32", target_os = "android"))]
        let root_dir = PathBuf::from("");

        let map = tiled::parse_with_path(
            BufReader::new(bytes.as_slice()),
            &root_dir.join(&asset_folder.join(asset_path)),
        )?;

        let mut layers = Vec::new();
        let mut groups = Vec::new();

        // this only works if gids are uniques across all maps used - todo move into ObjectGroup?
        let mut tile_gids: HashMap<u32, u32> = Default::default();

        for tileset in &map.tilesets {
            for i in tileset.first_gid..(tileset.first_gid + tileset.tilecount.unwrap_or(1)) {
                tile_gids.insert(i, tileset.first_gid);
            }
        }

        let mut object_gids: HashSet<u32> = Default::default();
        for object_group in map.object_groups.iter() {
            // recursively creates objects in the groups:
            let tiled_o_g = ObjectGroup::new_with_tile_ids(object_group, &tile_gids);
            // keep track of which objects will need to have tiles loaded
            tiled_o_g.objects.iter().for_each(|o| {
                tile_gids.get(&o.gid).map(|first_gid| {
                    object_gids.insert(*first_gid);
                });
            });
            groups.push(tiled_o_g);
        }

        let tile_size = Vec2::new(map.tile_width as f32, map.tile_height as f32);
        let image_folder: PathBuf = asset_path.parent().unwrap().into();
        let mut asset_dependencies = Vec::new();

        for layer in map.layers.iter() {
            if !layer.visible {
                continue;
            }
            let mut tileset_layers = Vec::new();

            for tileset in map.tilesets.iter() {
                let tile_path = image_folder.join(tileset.images.first().unwrap().source.as_str());
                asset_dependencies.push(tile_path);

                tileset_layers.push(TilesetLayer::new(&map, &layer, &tileset));
            }

            let layer = MapLayer { tileset_layers };
            layers.push(layer);
        }

        let mut meshes = Vec::new();
        for (layer_id, layer) in layers.iter().enumerate() {
            for tileset_layer in layer.tileset_layers.iter() {
                for x in 0..tileset_layer.chunks.len() {
                    let chunk_x = &tileset_layer.chunks[x];
                    for y in 0..chunk_x.len() {
                        if let Some(mesh) = chunk_x[y].build_uv_mesh(tileset_layer.tileset_guid) {
                            meshes.push((layer_id as u32, tileset_layer.tileset_guid, mesh));
                        };
                    }
                }
            }
        }

        let map = Map {
            map,
            meshes,
            layers,
            groups,
            tile_size,
            image_folder,
            asset_dependencies,
        };

        Ok(map)
    }
}

#[derive(Default)]
pub struct TiledMapCenter(pub bool);

pub struct MapRoot; // used so consuming application can query for parent

pub struct DebugConfig {
    pub enabled: bool,
    pub material: Option<Handle<ColorMaterial>>,
}

impl Default for DebugConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            material: Default::default(),
        }
    }
}

/// A bundle of tiled map entities.
#[derive(Bundle)]
pub struct TiledMapBundle {
    pub map_asset: Handle<Map>,
    pub parent_option: Option<Entity>,
    pub materials: HashMap<u32, Handle<ColorMaterial>>,
    pub atlases: HashMap<u32, Handle<TextureAtlas>>,
    pub origin: Transform,
    pub center: TiledMapCenter,
    pub debug_config: DebugConfig,
    pub created_entities: CreatedMapEntities,
}

impl Default for TiledMapBundle {
    fn default() -> Self {
        Self {
            map_asset: Handle::default(),
            parent_option: None,
            materials: HashMap::default(),
            atlases: HashMap::default(),
            center: TiledMapCenter::default(),
            origin: Transform::default(),
            debug_config: Default::default(),
            created_entities: Default::default(),
        }
    }
}

#[derive(Default, Debug)]
pub struct CreatedMapEntities {
    // maps layer id and tileset_gid to mesh entities
    created_layer_entities: HashMap<(usize, u32), Vec<Entity>>,
    // maps object guid to texture atlas sprite entity
    created_object_entities: HashMap<u32, Vec<Entity>>,
}

pub fn process_loaded_tile_maps(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut map_events: EventReader<AssetEvent<Map>>,
    mut ready_events: EventWriter<ObjectReadyEvent>,
    mut map_ready_events: EventWriter<MapReadyEvent>,
    mut maps: ResMut<Assets<Map>>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
    mut query: Query<(
        Entity,
        &TiledMapCenter,
        &Handle<Map>,
        &Option<Entity>,
        &mut HashMap<u32, Handle<ColorMaterial>>,
        &mut HashMap<u32, Handle<TextureAtlas>>,
        &Transform,
        &mut DebugConfig,
        &mut CreatedMapEntities,
    )>,
) {
    let mut changed_maps = HashSet::<Handle<Map>>::default();
    for event in map_events.iter() {
        match event {
            AssetEvent::Created { handle } => {
                changed_maps.insert(handle.clone());
            }
            AssetEvent::Modified { handle } => {
                changed_maps.insert(handle.clone());
            }
            AssetEvent::Removed { handle } => {
                // if mesh was modified and removed in the same update, ignore the modification
                // events are ordered so future modification events are ok
                changed_maps.remove(handle);
            }
        }
    }

    let mut new_meshes = HashMap::<&Handle<Map>, Vec<(u32, u32, Handle<Mesh>)>>::default();

    for changed_map in changed_maps.iter() {
        let map = maps.get_mut(changed_map).unwrap();

        for (_, _, map_handle, _, mut materials_map, mut texture_atlas_map, _, _, _) in
            query.iter_mut()
        {
            // only deal with currently changed map
            if map_handle != changed_map {
                continue;
            }

            for tileset in &map.map.tilesets {
                if !materials_map.contains_key(&tileset.first_gid) {
                    let texture_path = map
                        .image_folder
                        .join(tileset.images.first().unwrap().source.as_str());
                    let texture_handle = asset_server.load(texture_path);
                    materials_map.insert(
                        tileset.first_gid,
                        materials.add(texture_handle.clone().into()),
                    );

                    // only generate texture_atlas for tilesets used in objects
                    let object_gids: Vec<_> = map
                        .groups
                        .iter()
                        .flat_map(|og| og.objects.iter().map(|o| o.tileset_gid))
                        .collect();
                    if object_gids.contains(&Some(tileset.first_gid)) {
                        // For simplicity use textureAtlasSprite for object layers
                        // these insertions should be limited to sprites referenced by objects
                        let tile_width = tileset.tile_width as f32;
                        let tile_height = tileset.tile_height as f32;
                        let image = tileset.images.first().unwrap();
                        let texture_width = image.width as f32;
                        let texture_height = image.height as f32;
                        let columns = (texture_width / tile_width).floor() as usize;
                        let rows = (texture_height / tile_height).floor() as usize;

                        let has_new = (0..(columns * rows) as u32).fold(false, |total, next| {
                            total || !texture_atlas_map.contains_key(&(tileset.first_gid + next))
                        });
                        if has_new {
                            let atlas = TextureAtlas::from_grid(
                                texture_handle.clone(),
                                Vec2::new(tile_width, tile_height),
                                columns,
                                rows,
                            );
                            let atlas_handle = texture_atlases.add(atlas);
                            for i in 0..(columns * rows) as u32 {
                                if texture_atlas_map.contains_key(&(tileset.first_gid + i)) {
                                    continue;
                                }
                                // println!("insert: {}", tileset.first_gid + i);
                                texture_atlas_map
                                    .insert(tileset.first_gid + i, atlas_handle.clone());
                            }
                        }
                    }
                }
            }
        }

        for mesh in map.meshes.drain(0..map.meshes.len()) {
            let handle = meshes.add(mesh.2);
            if new_meshes.contains_key(changed_map) {
                let mesh_list = new_meshes.get_mut(changed_map).unwrap();
                mesh_list.push((mesh.0, mesh.1, handle));
            } else {
                let mut mesh_list = Vec::new();
                mesh_list.push((mesh.0, mesh.1, handle));
                new_meshes.insert(changed_map, mesh_list);
            }
        }
    }

    for (
        _,
        center,
        map_handle,
        optional_parent,
        materials_map,
        texture_atlas_map,
        origin,
        mut debug_config,
        mut created_entities,
    ) in query.iter_mut()
    {
        if new_meshes.contains_key(map_handle) {
            let map = maps.get(map_handle).unwrap();

            let tile_map_transform = if center.0 {
                map.center(origin.clone())
            } else {
                origin.clone()
            };

            let mesh_list = new_meshes.get_mut(map_handle).unwrap();

            for (layer_id, layer) in map.layers.iter().enumerate() {
                for tileset_layer in layer.tileset_layers.iter() {
                    let material_handle = materials_map.get(&tileset_layer.tileset_guid).unwrap();
                    // let mut mesh_list = mesh_list.iter_mut().filter(|(mesh_layer_id, _)| *mesh_layer_id == layer_id as u32).drain(0..mesh_list.len()).collect::<Vec<_>>();
                    let chunk_mesh_list = mesh_list
                        .iter()
                        .filter(|(mesh_layer_id, tileset_guid, _)| {
                            *mesh_layer_id == layer_id as u32
                                && *tileset_guid == tileset_layer.tileset_guid
                        })
                        .collect::<Vec<_>>();

                    // removing entities consumes the record of created entities
                    created_entities
                        .created_layer_entities
                        .remove(&(layer_id, tileset_layer.tileset_guid))
                        .map(|entities| {
                            // println!("Despawning previously-created mesh for this chunk");
                            for entity in entities.iter() {
                                // println!("calling despawn on {:?}", entity);
                                commands.entity(*entity).despawn();
                            }
                        });
                    let mut chunk_entities: Vec<Entity> = Default::default();
                    let layer_transform = tile_map_transform
                        * Transform::from_translation(Vec3::new(
                            tileset_layer.offset_x,
                            -tileset_layer.offset_y,
                            0.0,
                        ));

                    for (_, tileset_guid, mesh) in chunk_mesh_list.iter() {
                        // TODO: Sadly bevy doesn't support multiple meshes on a single entity with multiple materials.
                        // Change this once it does.

                        // Instead for now spawn a new entity per chunk.
                        let chunk_entity = commands
                            .spawn_bundle(ChunkBundle {
                                chunk: TileMapChunk {
                                    // TODO: Support more layers here..
                                    layer_id: layer_id as f32,
                                },
                                material: material_handle.clone(),
                                mesh: mesh.clone(),
                                map_parent: map_handle.clone(),
                                transform: layer_transform,
                                ..Default::default()
                            })
                            .id();

                        // println!("added created_entry after spawn");
                        created_entities
                            .created_layer_entities
                            .entry((layer_id, *tileset_guid))
                            .or_insert_with(|| Vec::new())
                            .push(chunk_entity);
                        chunk_entities.push(chunk_entity);
                    }
                    // if parent was passed in add children and mark it as MapRoot (temp until map bundle returns real entity)
                    if let Some(parent_entity) = optional_parent {
                        commands
                            .entity(parent_entity.clone())
                            .push_children(&chunk_entities)
                            .insert(MapRoot);
                    }
                }
            }

            if debug_config.enabled && debug_config.material.is_none() {
                debug_config.material =
                    Some(materials.add(ColorMaterial::from(Color::rgba(0.4, 0.4, 0.9, 0.5))));
            }
            for object_group in map.groups.iter() {
                for object in object_group.objects.iter() {
                    created_entities
                        .created_object_entities
                        .remove(&object.gid)
                        .map(|entities| {
                            // println!("Despawning previously-created object sprite");
                            for entity in entities.iter() {
                                // println!("calling despawn on {:?}", entity);
                                commands.entity(*entity).despawn();
                            }
                        });
                }
                if !object_group.visible {
                    continue;
                }

                let mut object_entities: Vec<Entity> = Default::default();

                // TODO: use object_group.name, opacity, colour (properties)
                for object in object_group.objects.iter() {
                    // println!("in object_group {}, object {:?}, grp: {}", object_group.name, &object.tileset_gid, object.gid);
                    let atlas_handle = object
                        .tileset_gid
                        .and_then(|tileset_gid| texture_atlas_map.get(&tileset_gid));

                    let entity = object
                        .spawn(
                            &mut commands,
                            atlas_handle,
                            &map.map,
                            map_handle.clone(),
                            &tile_map_transform,
                            &debug_config,
                        )
                        .id();
                    // when done spawning, fire event
                    let evt = ObjectReadyEvent {
                        entity: entity.clone(),
                        map_handle: map_handle.clone(),
                        map_entity_option: optional_parent.clone(),
                    };
                    ready_events.send(evt);

                    created_entities
                        .created_object_entities
                        .entry(object.gid)
                        .or_insert_with(|| Vec::new())
                        .push(entity);
                    object_entities.push(entity);
                }

                // if parent was passed in add children
                if let Some(parent_entity) = optional_parent {
                    commands
                        .entity(parent_entity.clone())
                        .push_children(&object_entities);
                }
            }
            let evt = MapReadyEvent {
                map_handle: map_handle.clone(),
                map_entity_option: optional_parent.clone(),
            };
            map_ready_events.send(evt);
        }
    }
}

// events fired when entity has been created

pub struct ObjectReadyEvent {
    pub entity: Entity,
    pub map_handle: Handle<Map>,
    pub map_entity_option: Option<Entity>,
}

pub struct MapReadyEvent {
    pub map_handle: Handle<Map>,
    pub map_entity_option: Option<Entity>,
}