pub mod asset;
pub mod chunking;
pub mod loader;
pub mod storage;
use crate::{prelude::*, tiled::event::TiledMessageWriters};
use bevy::{asset::RecursiveDependencyLoadState, prelude::*};
#[derive(Component, Reflect, Clone, Debug, Deref)]
#[reflect(Component, Debug)]
#[require(
TiledWorldStorage,
TiledWorldChunking,
TiledMapLayerZOffset,
TiledMapImageRepeatMargin,
TilemapAnchor,
TilemapRenderSettings,
Visibility,
Transform
)]
pub struct TiledWorld(pub Handle<TiledWorldAsset>);
#[derive(Component, Default, Reflect, Copy, Clone, Debug)]
#[reflect(Component, Default, Debug)]
pub struct RespawnTiledWorld;
pub(crate) fn plugin(app: &mut bevy::prelude::App) {
app.register_type::<TiledWorld>();
app.register_type::<RespawnTiledWorld>();
app.add_systems(
PreUpdate,
process_loaded_worlds.in_set(TiledPreUpdateSystems::ProcessLoadedWorlds),
);
app.add_systems(
PostUpdate,
handle_world_events.in_set(TiledPostUpdateSystems::HandleWorldAssetEvents),
);
app.add_plugins((
asset::plugin,
loader::plugin,
storage::plugin,
chunking::plugin,
));
}
fn process_loaded_worlds(
asset_server: Res<AssetServer>,
mut commands: Commands,
worlds: Res<Assets<TiledWorldAsset>>,
mut world_query: Query<
(Entity, &TiledWorld, &mut TiledWorldStorage),
Or<(
Changed<TiledWorld>,
// If a world settings change, force a respawn so they can be taken into account
Changed<TilemapAnchor>,
Changed<TiledMapLayerZOffset>,
Changed<TiledMapImageRepeatMargin>,
Changed<TilemapRenderSettings>,
With<RespawnTiledWorld>,
// Not needed to react to changes on TiledWorldChunking:
// it's read each frame by world_chunking() system
)>,
>,
mut message_writers: TiledMessageWriters,
) {
for (world_entity, world_handle, mut world_storage) in world_query.iter_mut() {
if let Some(load_state) = asset_server.get_recursive_dependency_load_state(&world_handle.0)
{
if !load_state.is_loaded() {
if let RecursiveDependencyLoadState::Failed(_) = load_state {
error!(
"World failed to load, despawn it (handle = {:?} / entity = {:?})",
world_handle.0, world_entity
);
commands.entity(world_entity).despawn();
} else {
debug!(
"World is not fully loaded yet, will try again next frame (handle = {:?} / entity = {:?})",
world_handle.0, world_entity
);
commands.entity(world_entity).insert(RespawnTiledWorld);
}
continue;
}
let Some(tiled_world) = worlds.get(&world_handle.0) else {
error!("Cannot get a valid TiledWorld out of Handle<TiledWorld>: has the last strong reference to the asset been dropped ? (handle = {:?} / entity = {:?})", world_handle.0, world_entity);
commands.entity(world_entity).despawn();
continue;
};
debug!(
"World has finished loading, spawn world maps (handle = {:?})",
world_handle.0
);
world_storage.clear(&mut commands);
commands
.entity(world_entity)
.insert(Name::new(format!(
"TiledWorld: {}",
tiled_world.world.source.display()
)))
.remove::<RespawnTiledWorld>();
TiledEvent::new(world_entity, WorldCreated)
.with_world(world_entity, world_handle.0.id())
.send(&mut commands, &mut message_writers.world_created);
}
}
}
fn handle_world_events(
mut commands: Commands,
mut world_events: MessageReader<AssetEvent<TiledWorldAsset>>,
world_query: Query<(Entity, &TiledWorld)>,
) {
for event in world_events.read() {
match event {
AssetEvent::Modified { id } => {
info!("World changed: {id}");
for (world_entity, world_handle) in world_query.iter() {
if world_handle.0.id() == *id {
commands.entity(world_entity).insert(RespawnTiledWorld);
}
}
}
AssetEvent::Removed { id } => {
info!("World removed: {id}");
for (world_entity, world_handle) in world_query.iter() {
if world_handle.0.id() == *id {
commands.entity(world_entity).despawn();
}
}
}
_ => continue,
}
}
}