diff --git a/Cargo.lock b/Cargo.lock index 2549bdc..105cf2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -506,16 +506,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "bevy_ecs_tilemap" -version = "0.12.0" -source = "git+https://github.com/MeoMix/bevy_ecs_tilemap?branch=main#d66cf2a79106421e973b27a3b7aba7435f4c378e" -dependencies = [ - "bevy", - "log", - "regex", -] - [[package]] name = "bevy_egui" version = "0.24.0" @@ -2693,7 +2683,6 @@ name = "rendering" version = "0.1.0" dependencies = [ "bevy", - "bevy_ecs_tilemap", "bevy_turborand", "simulation", ] @@ -2911,6 +2900,8 @@ name = "symbiants_pkg" version = "0.1.0" dependencies = [ "bevy", + "bevy_egui", + "bevy_save", "bevy_turborand", "console_error_panic_hook", "fs_extra", diff --git a/Cargo.toml b/Cargo.toml index 0c73f2b..bfa032a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,9 @@ bevy = { version = "0.12.1", default-features = false, features = [ "default_font", "png", ] } + +bevy_egui = { version = "0.24.0" } +bevy_save = { version = "0.13.0" } bevy_turborand = { version = "0.7.0" } # WASM builds require extra dependencies for logging and persisting state to local storage. diff --git a/assets/shaders/world.wgsl b/assets/shaders/world.wgsl new file mode 100644 index 0000000..a22eeec --- /dev/null +++ b/assets/shaders/world.wgsl @@ -0,0 +1,25 @@ +#import bevy_sprite::mesh2d_bindings::mesh + +struct MapData { + world_size: vec2, + tile_size: vec2 + atlas_size: vec2, + +} + +@group(1) @binding(0) +var map: MapData; + +@group(1) @binding(1) +var atlas_texture: texture2d; + +#import bevy_sprite::mesh2d_bindings::{Vertex, VertexOutput} + +@vertex +fn vertex_main(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + out.position = map.tile_size * in.position; + out.uv = in.uv * map.atlas_size; + return out; +} + diff --git a/rendering/Cargo.toml b/rendering/Cargo.toml index 2c97084..da131f8 100644 --- a/rendering/Cargo.toml +++ b/rendering/Cargo.toml @@ -19,7 +19,4 @@ bevy = { version = "0.12.1", default-features = false, features = [ "default_font", "png", ] } -bevy_ecs_tilemap = { git = "https://github.com/MeoMix/bevy_ecs_tilemap", branch = "main", features = [ - "atlas", -] } bevy_turborand = { version = "0.7.0" } diff --git a/rendering/src/common/mod.rs b/rendering/src/common/mod.rs index eab7a9d..16d8595 100644 --- a/rendering/src/common/mod.rs +++ b/rendering/src/common/mod.rs @@ -1,5 +1,4 @@ use bevy::{prelude::*, utils::HashMap}; -use bevy_ecs_tilemap::tiles::TilePos; use simulation::common::{grid::Grid, position::Position, Zone}; @@ -212,11 +211,3 @@ pub fn on_despawn( } } } - -// TODO: Better home? -pub fn grid_to_tile_pos(grid: &Grid, position: Position) -> TilePos { - TilePos { - x: position.x as u32, - y: (grid.height() - position.y - 1) as u32, - } -} diff --git a/rendering/src/lib.rs b/rendering/src/lib.rs index d740da7..691165f 100644 --- a/rendering/src/lib.rs +++ b/rendering/src/lib.rs @@ -25,22 +25,18 @@ use self::{ cleanup_background, spawn_background, spawn_background_tilemap, update_sky_background, Background, BackgroundTilemap, }, - element::{ - cleanup_elements, on_spawn_element, on_update_element_exposure, - on_update_element_position, rerender_elements, - sprite_sheet::{ - check_element_sprite_sheet_loaded, start_load_element_sprite_sheet, ElementTilemap, - }, + element::sprite_sheet::{ + check_element_sprite_sheet_loaded, start_load_element_sprite_sheet, ElementTilemap, }, nest::{mark_nest_hidden, mark_nest_visible}, pheromone::{ cleanup_pheromones, on_spawn_pheromone, on_update_pheromone_visibility, rerender_pheromones, }, + world::WorldViewPlugin, }, }; use bevy::prelude::*; -use bevy_ecs_tilemap::TilemapPlugin; use pointer::{handle_pointer_tap, initialize_pointer_resources, remove_pointer_resources}; use simulation::{ app_state::AppState, @@ -67,7 +63,7 @@ pub struct RenderingPlugin; /// This occurs because the simulation may tick multiple times before rendering systems run. impl Plugin for RenderingPlugin { fn build(&self, app: &mut App) { - app.add_plugins((RenderingCameraPlugin, TilemapPlugin)); + app.add_plugins((RenderingCameraPlugin, WorldViewPlugin)); app.add_state::(); // Keep this off to prevent spritesheet bleed at various `projection.scale` levels. @@ -160,7 +156,7 @@ fn build_nest_systems(app: &mut App) { Update, ( // Spawn - (on_spawn_ant, on_spawn_element, on_spawn_pheromone), + (on_spawn_ant, on_spawn_pheromone), // Despawn ( on_despawn::, @@ -186,8 +182,6 @@ fn build_nest_systems(app: &mut App) { ), on_ant_wake_up, on_tick_emote, - on_update_element_position, - on_update_element_exposure, on_update_pheromone_visibility, ), ) @@ -202,7 +196,6 @@ fn build_nest_systems(app: &mut App) { ( (spawn_background_tilemap, apply_deferred, spawn_background).chain(), rerender_ants, - rerender_elements, rerender_pheromones, mark_nest_visible, ) @@ -234,7 +227,6 @@ fn build_nest_systems(app: &mut App) { cleanup_ants, despawn_view_by_model::, despawn_view::, - cleanup_elements, despawn_view_by_model::, cleanup_pheromones, ) diff --git a/rendering/src/nest_rendering/background/mod.rs b/rendering/src/nest_rendering/background/mod.rs index 4501095..113f1aa 100644 --- a/rendering/src/nest_rendering/background/mod.rs +++ b/rendering/src/nest_rendering/background/mod.rs @@ -1,7 +1,6 @@ use bevy::prelude::*; -use bevy_ecs_tilemap::prelude::*; -use crate::common::{grid_to_tile_pos, VisibleGrid}; +use crate::common::VisibleGrid; use simulation::{ app_state::AppState, @@ -96,7 +95,6 @@ fn get_sky_gradient_color( } pub fn update_sky_background( - mut sky_tile_query: Query<(&mut TileColor, &Position), With>, // TODO: Relying on Local<> while trying to support "Reset Sandbox" without the ability to remove systems entirely is challenging. // Probably rewrite this to be a resource instead of a Local. mut last_run_time_info: Local, @@ -143,129 +141,21 @@ pub fn update_sky_background( sunrise_decimal_hours, sunset_decimal_hours, ); - for (mut tile_color, position) in sky_tile_query.iter_mut() { - let t_y: f32 = position.y as f32 / nest.surface_level() as f32; - let color = interpolate_color(north_color, south_color, t_y); - - *tile_color = color.into(); - } *last_run_time_info = time_info; } pub fn spawn_background_tilemap(mut commands: Commands, nest_query: Query<&Grid, With>) { - let grid = nest_query.single(); - - let map_size = TilemapSize { - x: grid.width() as u32, - y: grid.height() as u32, - }; - let grid_size = TilemapGridSize { x: 1.0, y: 1.0 }; - let map_type = TilemapType::default(); - - commands.spawn(( - BackgroundTilemap, - TilemapBundle { - grid_size, - size: map_size, - storage: TileStorage::empty(map_size), - physical_tile_size: TilemapPhysicalTileSize { x: 1.0, y: 1.0 }, - // Doesn't need to be 128x128 here since not reading from spritesheet. - tile_size: TilemapTileSize { x: 1.0, y: 1.0 }, - map_type: TilemapType::Square, - // Background tiles go at z: 0 because they should render behind elements/ants. - transform: get_tilemap_center_transform(&map_size, &grid_size, &map_type, 0.0), - ..Default::default() - }, - )); + } // Spawn non-interactive background (sky blue / tunnel brown) pub fn spawn_background( mut commands: Commands, nest_query: Query<(&Grid, &Nest)>, - mut tilemap_query: Query<(Entity, &mut TileStorage), With>, story_time: Res, ) { - let (grid, nest) = nest_query.single(); - let air_height = nest.surface_level() + 1; - - let (tilemap_entity, mut tile_storage) = tilemap_query.single_mut(); - let current_decimal_hours = story_time.as_time_info().get_decimal_hours(); - let (sunrise_decimal_hours, sunset_decimal_hours) = - story_time.get_sunrise_sunset_decimal_hours(); - - let (north_color, south_color) = get_sky_gradient_color( - current_decimal_hours, - sunrise_decimal_hours, - sunset_decimal_hours, - ); - - let width = grid.width(); - let height = air_height; - - for x in 0..width { - for y in 0..height { - let position = Position::new(x, y); - - let t_y: f32 = position.y as f32 / nest.surface_level() as f32; - let color = interpolate_color(north_color, south_color, t_y); - let tile_pos = grid_to_tile_pos(grid, position); - - let tile_entity = commands - .spawn(( - TileBundle { - position: tile_pos, - tilemap_id: TilemapId(tilemap_entity), - color: color.into(), - ..default() - }, - position, - SkyBackground, - Background, - AtNest, - )) - .id(); - - tile_storage.set(&tile_pos, tile_entity); - } - } - - // Create background sprites - let width = grid.width(); - let height = grid.height() - air_height; - let y_offset = air_height; - - let top_color: Color = Color::rgba(0.373, 0.290, 0.165, 1.0); - let bottom_color = Color::rgba(0.24, 0.186, 0.106, 1.0); - - for x in 0..width { - for y in 0..height { - let position = Position::new(x, y + y_offset); - - let color = interpolate_color(top_color, bottom_color, y as f32 / height as f32); - - let tile_pos = grid_to_tile_pos(grid, position); - - let tile_entity = commands - .spawn(( - TileBundle { - position: tile_pos, - tilemap_id: TilemapId(tilemap_entity), - color: color.into(), - ..default() - }, - position, - TunnelBackground, - Background, - AtNest, - )) - .id(); - - tile_storage.set(&tile_pos, tile_entity); - } - } } pub fn cleanup_background() { diff --git a/rendering/src/nest_rendering/element/mod.rs b/rendering/src/nest_rendering/element/mod.rs index a6836a1..5221cb6 100644 --- a/rendering/src/nest_rendering/element/mod.rs +++ b/rendering/src/nest_rendering/element/mod.rs @@ -1,9 +1,8 @@ pub mod sprite_sheet; use self::sprite_sheet::{get_element_index, ElementTilemap}; -use crate::common::{grid_to_tile_pos, ModelViewEntityMap, VisibleGrid}; +use crate::common::{ModelViewEntityMap, VisibleGrid}; use bevy::prelude::*; -use bevy_ecs_tilemap::prelude::*; use simulation::{ common::{grid::Grid, position::Position}, nest_simulation::{ @@ -11,192 +10,3 @@ use simulation::{ nest::{AtNest, Nest}, }, }; - -/// When an Element model is added to the simulation, render an associated Element sprite. -/// This *only* handles the initial rendering of the Element sprite. Updates are handled by other systems. -pub fn on_spawn_element( - mut element_query: Query< - (&Position, &Element, &ElementExposure, Entity), - (Added, With, Without), - >, - nest_query: Query<&Grid, With>, - mut commands: Commands, - mut tilemap_query: Query<(Entity, &mut TileStorage), With>, - mut model_view_entity_map: ResMut, - visible_grid: Res, -) { - let visible_grid_entity = match visible_grid.0 { - Some(visible_grid_entity) => visible_grid_entity, - None => return, - }; - - // Early exit when Nest isn't visible because there's no view to update. - // Exit, rather than skipping system run, to prevent change detection from becoming backlogged. - let grid = match nest_query.get(visible_grid_entity) { - Ok(grid) => grid, - Err(_) => return, - }; - - for (element_position, element, element_exposure, element_model_entity) in - element_query.iter_mut() - { - spawn_element_sprite( - element_model_entity, - element, - element_position, - element_exposure, - &grid, - &mut commands, - &mut tilemap_query, - &mut model_view_entity_map, - ); - } -} - -/// When an Element model has its Position updated, reflect the change in Position by updating the Translation -/// on its associated view. Update TileStorage to reflect the change in position, too. -/// This does not include the initial spawn of the Element model, which is handled by `on_spawn_element`. -/// This relies on Ref instead of Changed to be able to filter against `is_added()` -pub fn on_update_element_position( - element_query: Query<(Ref, Entity), (With, Without)>, - nest_query: Query<&Grid, With>, - mut commands: Commands, - mut tilemap_query: Query<&mut TileStorage, With>, - model_view_entity_map: Res, - visible_grid: Res, -) { - let visible_grid_entity = match visible_grid.0 { - Some(visible_grid_entity) => visible_grid_entity, - None => return, - }; - - // Early exit when Nest isn't visible because there's no view to update. - // Exit, rather than skipping system run, to prevent change detection from becoming backlogged. - let grid = match nest_query.get(visible_grid_entity) { - Ok(grid) => grid, - Err(_) => return, - }; - - let mut tile_storage = tilemap_query.single_mut(); - - for (element_position, element_model_entity) in element_query.iter() { - // `on_spawn_element` handles `Added` - if element_position.is_added() || !element_position.is_changed() { - continue; - } - - let element_view_entity = match model_view_entity_map.get(&element_model_entity) { - Some(&element_view_entity) => element_view_entity, - None => panic!("Expected to find view entity for model entity."), - }; - - let tile_pos = grid_to_tile_pos(grid, *element_position); - commands.entity(element_view_entity).insert(tile_pos); - // NOTE: This leaves the previous `tile_pos` stale, but that's fine because it's just Air which isn't rendered. - // TODO: Consider benefits of tracking PreviousPosition in Element and using that to clear stale tile_pos. - tile_storage.set(&tile_pos, element_view_entity); - } -} - -/// When an Element model has its ElementExposure updated, reflect the change in Position by updating the TileTextureIndex -/// on its associated view. -/// This does not include the initial spawn of the Element model, which is handled by `on_spawn_element`. -/// This relies on Ref instead of Changed to be able to filter against `is_added()` -pub fn on_update_element_exposure( - element_query: Query<(Ref, &Element, Entity), (With, Without)>, - nest_query: Query<&Grid, With>, - mut commands: Commands, - model_view_entity_map: Res, - visible_grid: Res, -) { - let visible_grid_entity = match visible_grid.0 { - Some(visible_grid_entity) => visible_grid_entity, - None => return, - }; - - // Early exit when Nest isn't visible because there's no view to update. - // Exit, rather than skipping system run, to prevent change detection from becoming backlogged. - if nest_query.get(visible_grid_entity).is_err() { - return; - } - - for (element_exposure, element, element_model_entity) in element_query.iter() { - // `on_spawn_element` handles `Added` - if element_exposure.is_added() || !element_exposure.is_changed() { - continue; - } - - let element_view_entity = match model_view_entity_map.get(&element_model_entity) { - Some(&element_view_entity) => element_view_entity, - None => panic!("Expected to find view entity for model entity."), - }; - - let texture_index = TileTextureIndex(get_element_index(*element_exposure, *element) as u32); - - commands.entity(element_view_entity).insert(texture_index); - } -} - -/// When user switches to a different scene (Nest->Crater) all Nest views are despawned. -/// Thus, when switching back to Nest, all Elements need to be redrawn once. Their underlying models -/// have not been changed or added, though, so a separate rerender system is needed. -pub fn rerender_elements( - mut element_query: Query< - (&Position, &Element, &ElementExposure, Entity), - (With, Without), - >, - nest_query: Query<&Grid, With>, - mut commands: Commands, - mut tilemap_query: Query<(Entity, &mut TileStorage), With>, - mut model_view_entity_map: ResMut, -) { - let grid = nest_query.single(); - - for (element_position, element, element_exposure, entity) in element_query.iter_mut() { - spawn_element_sprite( - entity, - element, - element_position, - element_exposure, - &grid, - &mut commands, - &mut tilemap_query, - &mut model_view_entity_map, - ); - } -} - -pub fn cleanup_elements() { - // TODO: remove ElementTextureAtlasHandle and ElementSpriteSheetHandle if committing to the full cleanup process -} - -/// Non-System Helper Functions: - -/// Spawn an Element Sprite at the given Position. Update ModelViewEntityMap and TileStorage to reflect the new view. -fn spawn_element_sprite( - element_model_entity: Entity, - element: &Element, - element_position: &Position, - element_exposure: &ElementExposure, - grid: &Grid, - commands: &mut Commands, - tilemap_query: &mut Query<(Entity, &mut TileStorage), With>, - model_view_entity_map: &mut ResMut, -) { - let (tilemap_entity, mut tile_storage) = tilemap_query.single_mut(); - let tile_pos = grid_to_tile_pos(grid, *element_position); - - let tile_bundle = ( - AtNest, - TileBundle { - position: tile_pos, - tilemap_id: TilemapId(tilemap_entity), - texture_index: TileTextureIndex(get_element_index(*element_exposure, *element) as u32), - ..default() - }, - ); - - let element_view_entity = commands.spawn(tile_bundle).id(); - model_view_entity_map.insert(element_model_entity, element_view_entity); - tile_storage.set(&tile_pos, element_view_entity); -} diff --git a/rendering/src/nest_rendering/element/sprite_sheet.rs b/rendering/src/nest_rendering/element/sprite_sheet.rs index d40ce91..ea99cb5 100644 --- a/rendering/src/nest_rendering/element/sprite_sheet.rs +++ b/rendering/src/nest_rendering/element/sprite_sheet.rs @@ -1,5 +1,4 @@ use bevy::{asset::LoadState, prelude::*}; -use bevy_ecs_tilemap::prelude::*; use simulation::{ app_state::AppState, @@ -46,25 +45,25 @@ pub fn check_element_sprite_sheet_loaded( // TODO: I'm not convinced I should spawn ElementTilemap here. // Creating a Tilemap from resources is distinct from loading its associated assets. - let grid_size = TilemapGridSize { x: 1.0, y: 1.0 }; - let map_type = TilemapType::default(); - let map_size = TilemapSize { x: 144, y: 144 }; + // let grid_size = TilemapGridSize { x: 1.0, y: 1.0 }; + // let map_type = TilemapType::default(); + // let map_size = TilemapSize { x: 144, y: 144 }; - commands.spawn(( - ElementTilemap, - TilemapBundle { - grid_size, - map_type, - size: map_size, - storage: TileStorage::empty(map_size), - texture: TilemapTexture::Single(element_sprite_sheet_handle.0.clone()), - tile_size: TilemapTileSize { x: 128.0, y: 128.0 }, - physical_tile_size: TilemapPhysicalTileSize { x: 1.0, y: 1.0 }, - // Element tiles go at z: 1 because they should appear above the background which is rendered at z: 0. - transform: get_tilemap_center_transform(&map_size, &grid_size, &map_type, 1.0), - ..default() - }, - )); + // commands.spawn(( + // ElementTilemap, + // TilemapBundle { + // grid_size, + // map_type, + // size: map_size, + // storage: TileStorage::empty(map_size), + // texture: TilemapTexture::Single(element_sprite_sheet_handle.0.clone()), + // tile_size: TilemapTileSize { x: 128.0, y: 128.0 }, + // physical_tile_size: TilemapPhysicalTileSize { x: 1.0, y: 1.0 }, + // // Element tiles go at z: 1 because they should appear above the background which is rendered at z: 0. + // transform: get_tilemap_center_transform(&map_size, &grid_size, &map_type, 1.0), + // ..default() + // }, + // )); next_state.set(AppState::TryLoadSave); } diff --git a/rendering/src/nest_rendering/mod.rs b/rendering/src/nest_rendering/mod.rs index 923a981..85805b9 100644 --- a/rendering/src/nest_rendering/mod.rs +++ b/rendering/src/nest_rendering/mod.rs @@ -3,3 +3,4 @@ pub mod background; pub mod element; pub mod nest; pub mod pheromone; +pub mod world; diff --git a/rendering/src/nest_rendering/world/mod.rs b/rendering/src/nest_rendering/world/mod.rs new file mode 100644 index 0000000..3bdd12e --- /dev/null +++ b/rendering/src/nest_rendering/world/mod.rs @@ -0,0 +1,16 @@ +pub mod world_map; +pub use self::world_map::{create_new_world, initialize_tile_worlds, WorldMap}; + +use bevy::{prelude::*, sprite::Material2dPlugin}; + +#[derive(Default)] +pub struct WorldViewPlugin; + +impl Plugin for WorldViewPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(Material2dPlugin::::default()); + app.add_systems(Update, (create_new_world, initialize_tile_worlds).chain()); + } + + +} diff --git a/rendering/src/nest_rendering/world/world_map.rs b/rendering/src/nest_rendering/world/world_map.rs new file mode 100644 index 0000000..4f78c91 --- /dev/null +++ b/rendering/src/nest_rendering/world/world_map.rs @@ -0,0 +1,100 @@ +use bevy::log::debug; + +use bevy::render; +use bevy::{ + prelude::*, + render::render_resource::{AsBindGroup, ShaderRef, ShaderType}, + sprite::Material2d, +}; + +#[derive(ShaderType, Clone, Debug, Reflect, AsBindGroup)] +pub struct MapDataUniform { + pub map_size: UVec2, + pub tile_size: Vec2, + pub atlas_size: Vec2, + pub transform_local: Mat3, + pub transform_world: Mat3, + pub size_world: Vec2, +} + +impl Default for MapDataUniform { + fn default() -> Self { + Self { + map_size: default(), + tile_size: default(), + atlas_size: default(), + transform_local: Mat3::IDENTITY, + transform_world: default(), + size_world: default(), + } + } +} + +#[derive(Component, Asset, Default, Debug, Clone, Reflect, AsBindGroup)] +#[reflect(Component)] +pub struct WorldMap { + #[uniform(0)] + pub map_data: MapDataUniform, + + #[storage(32)] + pub tile_map: Vec, + + #[texture(33)] + #[sampler(34)] + pub atlas: Handle, +} + +impl Material2d for WorldMap { + fn fragment_shader() -> ShaderRef { + "shaders/world.wgsl".into() + } +} + +impl WorldMap { + pub fn update(&mut self, images: &Assets) -> bool { + let atlas = match images.get(&self.atlas) { + Some(atlas) => atlas, + None => return false, + }; + + self.map_data.atlas_size = atlas.size().as_vec2(); + true + } +} + +// TODO: This probably needs to stay here, while the rest of world handling +// can be moved to the parent module so it can be re-used for crater rendering. +pub fn create_new_world( + mut commands: Commands, + asset_server: Res, + mut materials: ResMut>, +) { + let world_map = WorldMap { + map_data: MapDataUniform { + tile_size: Vec2::new(32.0, 32.0), + ..Default::default() + }, + tile_map: vec![0; 100], + atlas: asset_server.load("textures/element/sprite_sheet.png"), + }; + + materials.add(world_map); +} + +pub fn initialize_tile_worlds( + mut assets: ResMut>, + worlds: Query<(Entity, &Handle)>, + images: Res>, +) { + debug!(target: "world", "Initializing tile worlds"); + for (entity, handle) in worlds.iter() { + let Some(map) = assets.get_mut(handle) else { + warn!(target: "world", "WorldMap not loaded yet: {:?}", handle); + continue; + // Not loaded yet, probably. Need to handle. + }; + + map.update(images.as_ref()); + warn!(target: "world", "WorldMap updated: {:?}", map); + } +} diff --git a/simulation/src/common/grid/mod.rs b/simulation/src/common/grid/mod.rs index 347db30..581a29c 100644 --- a/simulation/src/common/grid/mod.rs +++ b/simulation/src/common/grid/mod.rs @@ -1,5 +1,7 @@ pub mod elements_cache; +use bevy::prelude::*; + use self::elements_cache::ElementsCache; use crate::common::position::Position; use bevy::prelude::*;