use bevy::asset::AssetEvent;
use bevy::ecs::message::{Message, MessageReader, MessageWriter};
use bevy::prelude::*;
use bevy_ecs_tilemap::prelude::*;
use bevy_map_core::MapProject;
use std::collections::HashMap;
use std::path::Path;
use uuid::Uuid;
fn normalize_asset_path(path: &str) -> String {
let normalized = path.replace('\\', "/");
let lower = normalized.to_lowercase();
if let Some(idx) = lower.find("/assets/") {
return normalized[idx + 8..].to_string();
}
if lower.starts_with("assets/") {
return normalized[7..].to_string();
}
let path_obj = Path::new(&normalized);
if !path_obj.has_root() && !normalized.contains(':') {
return normalized;
}
if let Some(file_name) = Path::new(&normalized).file_name() {
if let Some(name) = file_name.to_str() {
warn!(
"Could not find 'assets' directory in path '{}', using filename '{}'",
path, name
);
return name.to_string();
}
}
normalized
}
pub use bevy_map_animation;
pub use bevy_map_autotile;
pub use bevy_map_core;
pub use bevy_map_dialogue;
pub mod camera;
pub mod collision;
pub mod entity_input;
pub mod entity_physics;
pub mod entity_registry;
pub mod entity_sprite;
pub mod loader;
pub mod render;
pub use camera::{clamp_camera_to_bounds, setup_camera_bounds_from_map, CameraBounds};
pub use collision::{MapCollider, MapCollisionPlugin};
pub use entity_input::{
CustomInput, EntityInputSpawned, MapEntityInputPlugin, PlatformerInput, TopDownInput,
TwinStickInput,
};
pub use entity_physics::{EntityPhysicsSpawned, MapEntityPhysicsPlugin};
pub use entity_registry::{
attach_dialogues, Dialogue, EntityProperties, EntityRegistry, MapEntityExt, MapEntityMarker,
MapEntityType,
};
pub use entity_sprite::{EntitySpriteSetup, EntitySpriteSpawned, MapEntitySpritePlugin};
pub use loader::{MapLoadError, MapProjectLoader};
pub use render::{complete_sprite_loads, spawn_sprite_components, SpriteSlot};
pub use bevy_map_dialogue::{
DialogueChoice, DialogueChoiceEvent, DialogueEndEvent, DialogueHandle, DialogueNode,
DialogueNodeType, DialogueRunner, DialogueTree, StartDialogueEvent,
};
pub use bevy_map_animation::{
AnimatedSprite, AnimationCustomEvent, AnimationDef, AnimationEventExt, AnimationParticleEvent,
AnimationSoundEvent, AnimationTrigger, AnimationTriggerEvent, AnimationTriggerRegistry,
AnimationTriggerType, AnimationTriggered, AnimationWindow, AnimationWindowChanged,
AnimationWindowEvent, AnimationWindowRegistry, AnimationWindowType, LoopMode,
SpriteAnimationPlugin, SpriteData, TriggerPayload, WindowPhase, WindowTracker,
};
pub struct MapRuntimePlugin;
impl Plugin for MapRuntimePlugin {
fn build(&self, app: &mut App) {
app.add_plugins(TilemapPlugin)
.add_plugins(bevy_map_dialogue::DialoguePlugin)
.add_plugins(bevy_map_animation::SpriteAnimationPlugin)
.init_asset::<MapProject>()
.init_asset_loader::<MapProjectLoader>()
.init_resource::<EntityRegistry>()
.init_resource::<MapDialogues>()
.add_message::<SpawnMapEvent>()
.add_message::<SpawnMapProjectEvent>()
.add_message::<MapSpawnedEvent>()
.add_systems(Update, handle_spawn_map_events)
.add_systems(Update, handle_spawn_map_project_events)
.add_systems(
Update,
(
initialize_map_handles,
handle_map_handle_spawning,
handle_map_hot_reload,
)
.chain(),
)
.add_systems(Update, spawn_sprite_components)
.add_systems(Update, complete_sprite_loads)
.add_systems(Update, attach_dialogues)
.add_systems(Update, setup_camera_bounds_from_map)
.add_systems(PostUpdate, clamp_camera_to_bounds)
.add_systems(
Update,
(
initialize_animated_sprite_handles,
handle_animated_sprite_loading,
)
.chain(),
)
.add_systems(
Update,
(
initialize_dialogue_tree_handles,
handle_dialogue_tree_loading,
)
.chain(),
);
}
}
#[derive(Component)]
pub struct MapHandle(pub Handle<MapProject>);
#[derive(Component)]
pub struct MapRoot {
pub handle: Handle<MapProject>,
pub textures: TilesetTextures,
}
#[derive(Component, Default)]
struct MapHandleState {
loading_textures: bool,
textures: Option<TilesetTextures>,
spawned: bool,
}
fn initialize_map_handles(mut commands: Commands, query: Query<Entity, Added<MapHandle>>) {
for entity in query.iter() {
commands.entity(entity).insert(MapHandleState::default());
}
}
fn handle_map_handle_spawning(
mut commands: Commands,
asset_server: Res<AssetServer>,
map_assets: Res<Assets<MapProject>>,
mut query: Query<(Entity, &MapHandle, &mut MapHandleState, Option<&Transform>)>,
entity_registry: Res<EntityRegistry>,
mut map_dialogues: ResMut<MapDialogues>,
) {
for (entity, map_handle, mut state, _transform) in query.iter_mut() {
let Some(project) = map_assets.get(&map_handle.0) else {
continue;
};
if !state.loading_textures {
info!(
"MapProject '{}' loaded, queueing texture loads...",
project.level.name
);
let mut textures = TilesetTextures::new();
textures.load_from_project(project, &asset_server);
info!(
"Queued {} tileset images, {} sprite sheets for loading",
textures.images.len(),
textures.sprite_sheet_images.len()
);
state.textures = Some(textures);
state.loading_textures = true;
}
let Some(textures) = &state.textures else {
continue;
};
if !textures.all_loaded(&asset_server) {
static LAST_LOG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let last = LAST_LOG.load(std::sync::atomic::Ordering::Relaxed);
if now > last {
LAST_LOG.store(now, std::sync::atomic::Ordering::Relaxed);
textures.log_loading_state(&asset_server);
}
continue;
}
if state.spawned {
continue;
}
info!(
"Spawning map '{}' with {} layers, {} tilesets",
project.level.name,
project.level.layers.len(),
project.tilesets.len()
);
map_dialogues.load_from_project(project);
let map_entity = spawn_map_project(
&mut commands,
project,
textures,
Transform::default(), Some(&entity_registry),
);
commands.entity(map_entity).insert(MapRoot {
handle: map_handle.0.clone(),
textures: textures.clone(),
});
commands.entity(entity).add_child(map_entity);
state.spawned = true;
info!("Spawned map: {}", project.level.name);
}
}
fn handle_map_hot_reload(
mut commands: Commands,
mut asset_events: MessageReader<AssetEvent<MapProject>>,
mut query: Query<(Entity, &MapHandle, &mut MapHandleState)>,
children_query: Query<&Children>,
map_root_query: Query<(Entity, &MapRoot)>,
) {
for event in asset_events.read() {
let AssetEvent::Modified { id } = event else {
continue;
};
for (entity, map_handle, mut state) in query.iter_mut() {
if map_handle.0.id() != *id {
continue;
}
info!("Hot-reloading map asset");
if let Ok(children) = children_query.get(entity) {
for child in children.iter() {
if map_root_query.get(child).is_ok() {
commands.entity(child).despawn();
}
}
}
state.spawned = false;
state.loading_textures = false;
state.textures = None;
}
}
}
pub trait MapCommandsExt {
fn spawn_map(&mut self, asset_server: &AssetServer, path: impl Into<String>) -> Entity;
}
impl MapCommandsExt for Commands<'_, '_> {
fn spawn_map(&mut self, asset_server: &AssetServer, path: impl Into<String>) -> Entity {
self.spawn((
MapHandle(asset_server.load(path.into())),
MapHandleState::default(),
Transform::default(),
Visibility::default(),
))
.id()
}
}
#[derive(Debug, Clone, Default)]
pub struct TilesetTextures {
images: HashMap<(Uuid, usize), Handle<Image>>,
sprite_sheet_images: HashMap<Uuid, Handle<Image>>,
pub tile_size: f32,
}
impl TilesetTextures {
pub fn new() -> Self {
Self::default()
}
pub fn load_from_project(
&mut self,
project: &bevy_map_core::MapProject,
asset_server: &AssetServer,
) {
for (tileset_id, image_index, path) in project.image_paths() {
let asset_path = normalize_asset_path(path);
let handle = asset_server.load(asset_path);
self.images.insert((tileset_id, image_index), handle);
}
for (sprite_sheet_id, path) in project.sprite_sheet_paths() {
let asset_path = normalize_asset_path(path);
let handle = asset_server.load(asset_path);
self.sprite_sheet_images.insert(sprite_sheet_id, handle);
}
if let Some(tileset) = project.tilesets.values().next() {
self.tile_size = tileset.tile_size as f32;
}
}
pub fn get(&self, tileset_id: Uuid, image_index: usize) -> Option<&Handle<Image>> {
self.images.get(&(tileset_id, image_index))
}
pub fn insert(&mut self, tileset_id: Uuid, image_index: usize, handle: Handle<Image>) {
self.images.insert((tileset_id, image_index), handle);
}
pub fn get_sprite_sheet(&self, sprite_sheet_id: Uuid) -> Option<&Handle<Image>> {
self.sprite_sheet_images.get(&sprite_sheet_id)
}
pub fn insert_sprite_sheet(&mut self, sprite_sheet_id: Uuid, handle: Handle<Image>) {
self.sprite_sheet_images.insert(sprite_sheet_id, handle);
}
pub fn all_loaded(&self, asset_server: &AssetServer) -> bool {
use bevy::asset::LoadState;
let tilesets_loaded = self.images.values().all(|handle| {
match asset_server.get_load_state(handle.id()) {
Some(LoadState::Loaded) => true,
Some(LoadState::Failed(_)) => {
true
}
_ => false,
}
});
let sprite_sheets_loaded = self.sprite_sheet_images.values().all(|handle| {
matches!(
asset_server.get_load_state(handle.id()),
Some(LoadState::Loaded) | Some(LoadState::Failed(_))
)
});
tilesets_loaded && sprite_sheets_loaded
}
pub fn log_loading_state(&self, asset_server: &AssetServer) {
use bevy::asset::LoadState;
for ((tileset_id, image_index), handle) in &self.images {
let state = asset_server.get_load_state(handle.id());
match state {
Some(LoadState::Loaded) => {}
Some(LoadState::Failed(ref err)) => {
warn!(
"Tileset {:?} image {}: FAILED - {}",
tileset_id, image_index, err
);
}
Some(LoadState::Loading) => {
info!(
"Tileset {:?} image {}: still loading...",
tileset_id, image_index
);
}
state => {
info!(
"Tileset {:?} image {}: state {:?}",
tileset_id, image_index, state
);
}
}
}
for (sprite_sheet_id, handle) in &self.sprite_sheet_images {
let state = asset_server.get_load_state(handle.id());
match state {
Some(LoadState::Loaded) => {}
Some(LoadState::Failed(ref err)) => {
warn!("Sprite sheet {:?}: FAILED - {}", sprite_sheet_id, err);
}
_ => {
info!("Sprite sheet {:?}: state {:?}", sprite_sheet_id, state);
}
}
}
}
}
#[derive(Resource, Default, Debug, Clone)]
pub struct MapDialogues {
pub dialogues: HashMap<String, bevy_map_dialogue::DialogueTree>,
}
impl MapDialogues {
pub fn get(&self, id: &str) -> Option<&bevy_map_dialogue::DialogueTree> {
self.dialogues.get(id)
}
pub fn contains(&self, id: &str) -> bool {
self.dialogues.contains_key(id)
}
pub fn ids(&self) -> impl Iterator<Item = &str> {
self.dialogues.keys().map(|s| s.as_str())
}
pub fn load_from_project(&mut self, project: &MapProject) {
self.dialogues = project.dialogues.clone();
info!(
"Loaded {} dialogue(s) from map project",
self.dialogues.len()
);
}
pub fn clear(&mut self) {
self.dialogues.clear();
}
}
#[derive(Component)]
pub struct AnimatedSpriteHandle {
pub map: Handle<MapProject>,
pub sprite_sheet_name: String,
pub initial_animation: String,
pub scale: Option<f32>,
}
impl AnimatedSpriteHandle {
pub fn new(map: Handle<MapProject>, sprite_sheet: &str, animation: &str) -> Self {
Self {
map,
sprite_sheet_name: sprite_sheet.to_string(),
initial_animation: animation.to_string(),
scale: None,
}
}
pub fn with_scale(mut self, scale: f32) -> Self {
self.scale = Some(scale);
self
}
}
#[derive(Component, Default)]
struct AnimatedSpriteHandleState {
texture_loading: bool,
texture_handle: Option<Handle<Image>>,
sprite_data_handle: Option<Handle<bevy_map_animation::SpriteData>>,
frame_size: Option<(u32, u32)>,
completed: bool,
}
fn initialize_animated_sprite_handles(
mut commands: Commands,
query: Query<Entity, Added<AnimatedSpriteHandle>>,
) {
for entity in query.iter() {
commands
.entity(entity)
.insert(AnimatedSpriteHandleState::default());
}
}
fn handle_animated_sprite_loading(
mut commands: Commands,
asset_server: Res<AssetServer>,
map_assets: Res<Assets<MapProject>>,
mut sprite_data_assets: ResMut<Assets<bevy_map_animation::SpriteData>>,
mut query: Query<(
Entity,
&AnimatedSpriteHandle,
&mut AnimatedSpriteHandleState,
)>,
) {
use bevy::asset::LoadState;
for (entity, handle, mut state) in query.iter_mut() {
if state.completed {
continue;
}
let Some(project) = map_assets.get(&handle.map) else {
continue;
};
let Some(sprite_data) = project.sprite_sheet_by_name(&handle.sprite_sheet_name) else {
warn!(
"Sprite sheet '{}' not found in project",
handle.sprite_sheet_name
);
state.completed = true;
continue;
};
if !state.texture_loading {
let asset_path = normalize_asset_path(&sprite_data.sheet_path);
let texture: Handle<Image> = asset_server.load(asset_path);
state.texture_handle = Some(texture);
state.sprite_data_handle = Some(sprite_data_assets.add(sprite_data.clone()));
state.frame_size = Some((sprite_data.frame_width, sprite_data.frame_height));
state.texture_loading = true;
}
let Some(texture_handle) = &state.texture_handle else {
continue;
};
if !matches!(
asset_server.get_load_state(texture_handle.id()),
Some(LoadState::Loaded)
) {
continue;
}
let sprite_data_handle = state.sprite_data_handle.clone().unwrap();
let (frame_w, frame_h) = state.frame_size.unwrap();
let mut animated = bevy_map_animation::AnimatedSprite::new(sprite_data_handle);
animated.play(&handle.initial_animation);
let custom_size = handle
.scale
.map(|s| Vec2::new(frame_w as f32 * s, frame_h as f32 * s));
commands.entity(entity).insert((
Sprite {
image: texture_handle.clone(),
rect: Some(Rect::new(0.0, 0.0, frame_w as f32, frame_h as f32)),
custom_size,
..default()
},
animated,
));
state.completed = true;
info!(
"Auto-loaded animated sprite: {} (animation: {})",
handle.sprite_sheet_name, handle.initial_animation
);
}
}
#[derive(Component)]
pub struct DialogueTreeHandle {
pub map: Handle<MapProject>,
pub dialogue_name: String,
}
impl DialogueTreeHandle {
pub fn new(map: Handle<MapProject>, dialogue_name: &str) -> Self {
Self {
map,
dialogue_name: dialogue_name.to_string(),
}
}
}
#[derive(Component, Default)]
struct DialogueTreeHandleState {
completed: bool,
}
fn initialize_dialogue_tree_handles(
mut commands: Commands,
query: Query<Entity, Added<DialogueTreeHandle>>,
) {
for entity in query.iter() {
commands
.entity(entity)
.insert(DialogueTreeHandleState::default());
}
}
fn handle_dialogue_tree_loading(
mut commands: Commands,
map_assets: Res<Assets<MapProject>>,
mut dialogue_assets: ResMut<Assets<bevy_map_dialogue::DialogueTree>>,
mut query: Query<(Entity, &DialogueTreeHandle, &mut DialogueTreeHandleState)>,
) {
for (entity, handle, mut state) in query.iter_mut() {
if state.completed {
continue;
}
let Some(project) = map_assets.get(&handle.map) else {
continue;
};
let Some(dialogue) = project.dialogue_by_name(&handle.dialogue_name) else {
warn!("Dialogue '{}' not found in project", handle.dialogue_name);
state.completed = true;
continue;
};
let dialogue_handle = dialogue_assets.add(dialogue.clone());
commands
.entity(entity)
.insert(DialogueHandle(dialogue_handle));
state.completed = true;
info!("Auto-loaded dialogue: {}", handle.dialogue_name);
}
}
#[derive(Message)]
pub struct SpawnMapEvent {
pub level: bevy_map_core::Level,
pub transform: Transform,
pub tile_size: f32,
pub tileset_textures: Vec<Handle<Image>>,
}
#[derive(Message)]
pub struct MapSpawnedEvent {
pub map_entity: Entity,
}
#[derive(Component, Default)]
pub struct RuntimeMap {
pub level_name: String,
}
#[derive(Component)]
pub struct MapLayerIndex(pub usize);
fn handle_spawn_map_events(
mut commands: Commands,
mut spawn_events: MessageReader<SpawnMapEvent>,
mut spawned_events: MessageWriter<MapSpawnedEvent>,
entity_registry: Res<EntityRegistry>,
) {
for event in spawn_events.read() {
let map_entity = spawn_map(
&mut commands,
&event.level,
event.tile_size,
&event.tileset_textures,
event.transform,
Some(&entity_registry),
);
spawned_events.write(MapSpawnedEvent { map_entity });
}
}
pub fn spawn_map(
commands: &mut Commands,
level: &bevy_map_core::Level,
tile_size: f32,
tileset_textures: &[Handle<Image>],
transform: Transform,
entity_registry: Option<&EntityRegistry>,
) -> Entity {
let map_entity = commands
.spawn((
RuntimeMap {
level_name: level.name.clone(),
},
transform,
Visibility::default(),
))
.id();
for (layer_index, layer) in level.layers.iter().enumerate() {
if let bevy_map_core::LayerData::Tiles { tiles, .. } = &layer.data {
if tiles.is_empty() {
continue;
}
let texture_handle = if layer_index < tileset_textures.len() {
tileset_textures[layer_index].clone()
} else if !tileset_textures.is_empty() {
tileset_textures[0].clone()
} else {
continue;
};
let map_size = TilemapSize {
x: level.width,
y: level.height,
};
let tilemap_tile_size = TilemapTileSize {
x: tile_size,
y: tile_size,
};
let grid_size: TilemapGridSize = tilemap_tile_size.into();
let mut tile_storage = TileStorage::empty(map_size);
let tilemap_entity = commands.spawn_empty().id();
for y in 0..level.height {
for x in 0..level.width {
let idx = (y * level.width + x) as usize;
if let Some(&Some(tile_index)) = tiles.get(idx) {
let tile_pos = TilePos { x, y };
let tile_entity = commands
.spawn(TileBundle {
position: tile_pos,
tilemap_id: TilemapId(tilemap_entity),
texture_index: TileTextureIndex(tile_index),
..default()
})
.id();
tile_storage.set(&tile_pos, tile_entity);
}
}
}
let map_type = TilemapType::Square;
let layer_z = layer_index as f32 * 0.1;
commands.entity(tilemap_entity).insert((
TilemapBundle {
grid_size,
map_type,
size: map_size,
storage: tile_storage,
texture: TilemapTexture::Single(texture_handle),
tile_size: tilemap_tile_size,
transform: Transform::from_xyz(0.0, 0.0, layer_z),
..default()
},
MapLayerIndex(layer_index),
));
commands.entity(map_entity).add_child(tilemap_entity);
}
}
if let Some(registry) = entity_registry {
registry.spawn_all(commands, &level.entities, transform);
}
map_entity
}
pub fn set_tile(
commands: &mut Commands,
tile_storage: &mut TileStorage,
tilemap_entity: Entity,
x: u32,
y: u32,
tile_index: Option<u32>,
) {
let tile_pos = TilePos { x, y };
if let Some(tile_entity) = tile_storage.get(&tile_pos) {
commands.entity(tile_entity).despawn();
tile_storage.remove(&tile_pos);
}
if let Some(index) = tile_index {
let tile_entity = commands
.spawn(TileBundle {
position: tile_pos,
tilemap_id: TilemapId(tilemap_entity),
texture_index: TileTextureIndex(index),
..default()
})
.id();
tile_storage.set(&tile_pos, tile_entity);
}
}
#[derive(Message)]
pub struct SpawnMapProjectEvent {
pub project: bevy_map_core::MapProject,
pub textures: TilesetTextures,
pub transform: Transform,
}
fn handle_spawn_map_project_events(
mut commands: Commands,
mut spawn_events: MessageReader<SpawnMapProjectEvent>,
mut spawned_events: MessageWriter<MapSpawnedEvent>,
entity_registry: Res<EntityRegistry>,
mut map_dialogues: ResMut<MapDialogues>,
) {
for event in spawn_events.read() {
map_dialogues.load_from_project(&event.project);
let map_entity = spawn_map_project(
&mut commands,
&event.project,
&event.textures,
event.transform,
Some(&entity_registry),
);
spawned_events.write(MapSpawnedEvent { map_entity });
}
}
pub fn spawn_map_project(
commands: &mut Commands,
project: &bevy_map_core::MapProject,
textures: &TilesetTextures,
transform: Transform,
entity_registry: Option<&EntityRegistry>,
) -> Entity {
let level = &project.level;
let tile_size = textures.tile_size;
let map_entity = commands
.spawn((
RuntimeMap {
level_name: level.name.clone(),
},
transform,
Visibility::default(),
))
.id();
for (layer_index, layer) in level.layers.iter().enumerate() {
info!("Processing layer {}: '{}'", layer_index, layer.name);
if let bevy_map_core::LayerData::Tiles {
tileset_id, tiles, ..
} = &layer.data
{
info!(
" Layer {} is a tile layer with {} tiles, tileset {}",
layer_index,
tiles.len(),
tileset_id
);
if tiles.is_empty() {
info!(" Layer {} has empty tiles array, skipping", layer_index);
continue;
}
let Some(tileset) = project.get_tileset(*tileset_id) else {
warn!(
"Layer {} references missing tileset {}",
layer_index, tileset_id
);
continue;
};
info!(
" Found tileset '{}' with {} images",
tileset.name,
tileset.images.len()
);
let mut tiles_by_image: HashMap<usize, Vec<(u32, u32, u32)>> = HashMap::new();
for y in 0..level.height {
for x in 0..level.width {
let idx = (y * level.width + x) as usize;
if let Some(&Some(virtual_tile_index)) = tiles.get(idx) {
if let Some((image_index, local_tile_index)) =
tileset.virtual_to_local(virtual_tile_index)
{
tiles_by_image.entry(image_index).or_default().push((
x,
y,
local_tile_index,
));
}
}
}
}
for (image_index, image_tiles) in tiles_by_image {
info!(
"Layer {}: Spawning {} tiles from tileset {} image {}",
layer_index,
image_tiles.len(),
tileset_id,
image_index
);
let Some(texture_handle) = textures.get(*tileset_id, image_index) else {
warn!(
"Missing texture for tileset {} image {}",
tileset_id, image_index
);
continue;
};
let map_size = TilemapSize {
x: level.width,
y: level.height,
};
let tilemap_tile_size = TilemapTileSize {
x: tile_size,
y: tile_size,
};
let grid_size: TilemapGridSize = tilemap_tile_size.into();
let mut tile_storage = TileStorage::empty(map_size);
let tilemap_entity = commands.spawn_empty().id();
for (x, y, local_tile_index) in image_tiles {
let tile_pos = TilePos { x, y };
let tile_entity = commands
.spawn(TileBundle {
position: tile_pos,
tilemap_id: TilemapId(tilemap_entity),
texture_index: TileTextureIndex(local_tile_index),
..default()
})
.id();
tile_storage.set(&tile_pos, tile_entity);
}
let layer_z = layer_index as f32 * 0.1 + image_index as f32 * 0.01;
commands.entity(tilemap_entity).insert((
TilemapBundle {
grid_size,
map_type: TilemapType::Square,
size: map_size,
storage: tile_storage,
texture: TilemapTexture::Single(texture_handle.clone()),
tile_size: tilemap_tile_size,
transform: Transform::from_xyz(0.0, 0.0, layer_z),
..default()
},
MapLayerIndex(layer_index),
));
commands.entity(map_entity).add_child(tilemap_entity);
}
} else {
info!(
" Layer {} is not a tile layer (entity layer or other)",
layer_index
);
}
}
if let Some(registry) = entity_registry {
registry.spawn_all(commands, &level.entities, transform);
}
map_entity
}