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},
};
pub use tiled;
pub use tiled::LayerData;
pub use tiled::ObjectShape;
pub use tiled::Properties;
pub use tiled::PropertyValue;
#[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();
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() {
let tiled_o_g = ObjectGroup::new_with_tile_ids(object_group, &tile_gids);
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;
pub struct DebugConfig {
pub enabled: bool,
pub material: Option<Handle<ColorMaterial>>,
}
impl Default for DebugConfig {
fn default() -> Self {
Self {
enabled: false,
material: Default::default(),
}
}
}
#[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 {
created_layer_entities: HashMap<(usize, u32), Vec<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 } => {
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()
{
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()),
);
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)) {
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;
}
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 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<_>>();
created_entities
.created_layer_entities
.remove(&(layer_id, tileset_layer.tileset_guid))
.map(|entities| {
for entity in entities.iter() {
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() {
let chunk_entity = commands
.spawn_bundle(ChunkBundle {
chunk: TileMapChunk {
layer_id: layer_id as f32,
},
material: material_handle.clone(),
mesh: mesh.clone(),
map_parent: map_handle.clone(),
transform: layer_transform,
..Default::default()
})
.id();
created_entities
.created_layer_entities
.entry((layer_id, *tileset_guid))
.or_insert_with(|| Vec::new())
.push(chunk_entity);
chunk_entities.push(chunk_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| {
for entity in entities.iter() {
commands.entity(*entity).despawn();
}
});
}
if !object_group.visible {
continue;
}
let mut object_entities: Vec<Entity> = Default::default();
for object in object_group.objects.iter() {
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();
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 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);
}
}
}
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>,
}