use bevy::{math::Vec3Swizzles, prelude::*, utils::HashSet}; use bevy_ecs_tilemap::prelude::*; mod helpers; /// Press WASD to move the camera around, and watch as chunks spawn/despawn in response. const TILE_SIZE: TilemapTileSize = TilemapTileSize { x: 16.0, y: 16.0 }; // For this example, don't choose too large a chunk size. const CHUNK_SIZE: UVec2 = UVec2 { x: 4, y: 4 }; // Render chunk sizes are set to 4 render chunks per user specified chunk. const RENDER_CHUNK_SIZE: UVec2 = UVec2 { x: CHUNK_SIZE.x * 2, y: CHUNK_SIZE.y * 2, }; fn spawn_chunk(commands: &mut Commands, asset_server: &AssetServer, chunk_pos: IVec2) { let tilemap_entity = commands.spawn_empty().id(); let mut tile_storage = TileStorage::empty(CHUNK_SIZE.into()); // Spawn the elements of the tilemap. for x in 0..CHUNK_SIZE.x { for y in 0..CHUNK_SIZE.y { let tile_pos = TilePos { x, y }; let tile_entity = commands .spawn(TileBundle { position: tile_pos, tilemap_id: TilemapId(tilemap_entity), ..Default::default() }) .id(); commands.entity(tilemap_entity).add_child(tile_entity); tile_storage.set(&tile_pos, tile_entity); } } let transform = Transform::from_translation(Vec3::new( chunk_pos.x as f32 * CHUNK_SIZE.x as f32 * TILE_SIZE.x, chunk_pos.y as f32 * CHUNK_SIZE.y as f32 * TILE_SIZE.y, 0.0, )); let texture_handle: Handle = asset_server.load("tiles.png"); commands.entity(tilemap_entity).insert(TilemapBundle { grid_size: TILE_SIZE.into(), size: CHUNK_SIZE.into(), storage: tile_storage, texture: TilemapTexture::Single(texture_handle), tile_size: TILE_SIZE, transform, render_settings: TilemapRenderSettings { render_chunk_size: RENDER_CHUNK_SIZE, ..Default::default() }, ..Default::default() }); } fn startup(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); } fn camera_pos_to_chunk_pos(camera_pos: &Vec2) -> IVec2 { let camera_pos = camera_pos.as_ivec2(); let chunk_size: IVec2 = IVec2::new(CHUNK_SIZE.x as i32, CHUNK_SIZE.y as i32); let tile_size: IVec2 = IVec2::new(TILE_SIZE.x as i32, TILE_SIZE.y as i32); camera_pos / (chunk_size * tile_size) } fn spawn_chunks_around_camera( mut commands: Commands, asset_server: Res, camera_query: Query<&Transform, With>, mut chunk_manager: ResMut, ) { for transform in camera_query.iter() { let camera_chunk_pos = camera_pos_to_chunk_pos(&transform.translation.xy()); for y in (camera_chunk_pos.y - 2)..(camera_chunk_pos.y + 2) { for x in (camera_chunk_pos.x - 2)..(camera_chunk_pos.x + 2) { if !chunk_manager.spawned_chunks.contains(&IVec2::new(x, y)) { chunk_manager.spawned_chunks.insert(IVec2::new(x, y)); spawn_chunk(&mut commands, &asset_server, IVec2::new(x, y)); } } } } } fn despawn_outofrange_chunks( mut commands: Commands, camera_query: Query<&Transform, With>, chunks_query: Query<(Entity, &Transform)>, mut chunk_manager: ResMut, ) { for camera_transform in camera_query.iter() { for (entity, chunk_transform) in chunks_query.iter() { let chunk_pos = chunk_transform.translation.xy(); let distance = camera_transform.translation.xy().distance(chunk_pos); if distance > 320.0 { let x = (chunk_pos.x / (CHUNK_SIZE.x as f32 * TILE_SIZE.x)).floor() as i32; let y = (chunk_pos.y / (CHUNK_SIZE.y as f32 * TILE_SIZE.y)).floor() as i32; chunk_manager.spawned_chunks.remove(&IVec2::new(x, y)); commands.entity(entity).despawn_recursive(); } } } } #[derive(Default, Debug, Resource)] struct ChunkManager { pub spawned_chunks: HashSet, } fn main() { App::new() .add_plugins( DefaultPlugins .set(WindowPlugin { primary_window: Some(Window { title: String::from("Basic Chunking Example"), ..Default::default() }), ..default() }) .set(ImagePlugin::default_nearest()), ) .add_plugins(TilemapPlugin) .insert_resource(ChunkManager::default()) .add_systems(Startup, startup) .add_systems(Update, helpers::camera::movement) .add_systems(Update, spawn_chunks_around_camera) .add_systems(Update, despawn_outofrange_chunks) .run(); }