wip: chunk spawner

fix/collision-refresh
hheik 2022-12-02 01:03:33 +02:00
parent cf66379a23
commit 64c15be224
6 changed files with 161 additions and 40 deletions

View File

@ -2,9 +2,15 @@ use bevy::prelude::*;
use bevy_inspector_egui::*; use bevy_inspector_egui::*;
use bevy_rapier2d::prelude::*; use bevy_rapier2d::prelude::*;
use crate::{
terrain2d::{Chunk2D, Terrain2D, Terrain2DPlugin},
util::Vector2I,
};
use self::{camera::GameCameraPlugin, kinematic::KinematicPlugin, player::PlayerPlugin}; use self::{camera::GameCameraPlugin, kinematic::KinematicPlugin, player::PlayerPlugin};
pub mod camera; pub mod camera;
pub mod chunk;
pub mod kinematic; pub mod kinematic;
pub mod player; pub mod player;
@ -16,11 +22,20 @@ pub fn init() {
.add_plugin(WorldInspectorPlugin::new()) .add_plugin(WorldInspectorPlugin::new())
.add_plugin(KinematicPlugin) .add_plugin(KinematicPlugin)
.add_plugin(GameCameraPlugin) .add_plugin(GameCameraPlugin)
.add_plugin(Terrain2DPlugin)
// .add_plugin(PlayerPlugin) // .add_plugin(PlayerPlugin)
// .add_startup_system(setup_debug_ground) // .add_startup_system(setup_debug_ground)
.add_startup_system(setup_debug_terrain)
.run(); .run();
} }
fn setup_debug_terrain(mut terrain: ResMut<Terrain2D>) {
terrain.add_chunk(Vector2I { x: 0, y: 0 }, Chunk2D::new());
terrain.add_chunk(Vector2I { x: 1, y: 0 }, Chunk2D::new());
terrain.add_chunk(Vector2I { x: 0, y: 1 }, Chunk2D::new());
terrain.add_chunk(Vector2I { x: 1, y: 1 }, Chunk2D::new());
}
fn setup_debug_ground(mut commands: Commands) { fn setup_debug_ground(mut commands: Commands) {
// Static ground // Static ground
commands commands

17
src/game/chunk.rs Normal file
View File

@ -0,0 +1,17 @@
use bevy::prelude::*;
use crate::terrain2d::ChunkIndex;
pub struct ChunkPlugin {}
#[derive(Reflect, Component, Default)]
#[reflect(Component)]
pub struct Chunk {
pub index: ChunkIndex,
}
#[derive(Bundle)]
pub struct ChunkBundle {
pub chunk: Chunk,
pub sprite_bundle: SpriteBundle,
}

View File

@ -17,8 +17,11 @@ pub struct Terrain2DPlugin;
impl Plugin for Terrain2DPlugin { impl Plugin for Terrain2DPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(Terrain2D::new()) app.register_type::<Chunk2DIndex>()
.add_system(emit_terrain_events); .insert_resource(Terrain2D::new())
.add_event::<TerrainEvent>()
.add_system(emit_terrain_events)
.add_system(chunk_spawner);
} }
} }
@ -45,7 +48,7 @@ pub enum TerrainEvent {
#[derive(Default, Resource)] #[derive(Default, Resource)]
pub struct Terrain2D { pub struct Terrain2D {
chunk_map: HashMap<ChunkIndex, Chunk>, chunk_map: HashMap<ChunkIndex, Chunk2D>,
events: Vec<TerrainEvent>, events: Vec<TerrainEvent>,
} }
@ -57,7 +60,7 @@ impl Terrain2D {
} }
} }
pub fn add_chunk(&mut self, index: ChunkIndex, chunk: Chunk) { pub fn add_chunk(&mut self, index: ChunkIndex, chunk: Chunk2D) {
self.chunk_map.insert(index, chunk); self.chunk_map.insert(index, chunk);
self.events.push(TerrainEvent::ChunkAdded(index)) self.events.push(TerrainEvent::ChunkAdded(index))
} }
@ -67,38 +70,38 @@ impl Terrain2D {
self.chunk_map.remove(&index); self.chunk_map.remove(&index);
} }
pub fn chunk_iter(&self) -> Iter<ChunkIndex, Chunk> { pub fn chunk_iter(&self) -> Iter<ChunkIndex, Chunk2D> {
self.chunk_map.iter() self.chunk_map.iter()
} }
pub fn chunk_iter_mut(&mut self) -> IterMut<ChunkIndex, Chunk> { pub fn chunk_iter_mut(&mut self) -> IterMut<ChunkIndex, Chunk2D> {
self.chunk_map.iter_mut() self.chunk_map.iter_mut()
} }
pub fn index_to_chunk(&self, index: &ChunkIndex) -> Option<&Chunk> { pub fn index_to_chunk(&self, index: &ChunkIndex) -> Option<&Chunk2D> {
self.chunk_map.get(index) self.chunk_map.get(index)
} }
pub fn index_to_chunk_mut(&mut self, index: &ChunkIndex) -> Option<&mut Chunk> { pub fn index_to_chunk_mut(&mut self, index: &ChunkIndex) -> Option<&mut Chunk2D> {
self.chunk_map.get_mut(index) self.chunk_map.get_mut(index)
} }
pub fn global_to_chunk(&self, global: &Vector2I) -> Option<&Chunk> { pub fn global_to_chunk(&self, global: &Vector2I) -> Option<&Chunk2D> {
self.index_to_chunk(&global_to_chunk_index(global)) self.index_to_chunk(&global_to_chunk_index(global))
} }
pub fn global_to_chunk_mut(&mut self, global: &Vector2I) -> Option<&mut Chunk> { pub fn global_to_chunk_mut(&mut self, global: &Vector2I) -> Option<&mut Chunk2D> {
self.index_to_chunk_mut(&global_to_chunk_index(global)) self.index_to_chunk_mut(&global_to_chunk_index(global))
} }
pub fn global_to_texel(&self, global: &Vector2I) -> Option<Texel> { pub fn global_to_texel(&self, global: &Vector2I) -> Option<Texel2D> {
match self.global_to_chunk(global) { match self.global_to_chunk(global) {
Some(chunk) => chunk.get_texel(&global_to_local(global)), Some(chunk) => chunk.get_texel(&global_to_local(global)),
None => None, None => None,
} }
} }
pub fn global_to_texel_mut(&mut self, global: &Vector2I) -> Option<Texel> { pub fn global_to_texel_mut(&mut self, global: &Vector2I) -> Option<Texel2D> {
match self.global_to_chunk(global) { match self.global_to_chunk(global) {
Some(chunk) => chunk.get_texel(&global_to_local(global)), Some(chunk) => chunk.get_texel(&global_to_local(global)),
None => None, None => None,
@ -110,7 +113,7 @@ impl Terrain2D {
match self.index_to_chunk_mut(&index) { match self.index_to_chunk_mut(&index) {
Some(chunk) => chunk.set_texel(&global_to_local(global), id), Some(chunk) => chunk.set_texel(&global_to_local(global), id),
None => { None => {
let mut chunk = Chunk::new(); let mut chunk = Chunk2D::new();
chunk.set_texel(&global_to_local(global), id); chunk.set_texel(&global_to_local(global), id);
self.add_chunk(index, chunk); self.add_chunk(index, chunk);
} }
@ -121,35 +124,35 @@ impl Terrain2D {
pub fn local_to_texel_index(position: &Vector2I) -> Option<usize> { pub fn local_to_texel_index(position: &Vector2I) -> Option<usize> {
match position.x >= 0 match position.x >= 0
&& position.y >= 0 && position.y >= 0
&& position.x < Chunk::SIZE.x && position.x < Chunk2D::SIZE.x
&& position.y < Chunk::SIZE.y && position.y < Chunk2D::SIZE.y
{ {
true => Some(position.y as usize * Chunk::SIZE_X + position.x as usize), true => Some(position.y as usize * Chunk2D::SIZE_X + position.x as usize),
false => None, false => None,
} }
} }
pub fn texel_index_to_local(i: usize) -> Vector2I { pub fn texel_index_to_local(i: usize) -> Vector2I {
Vector2I { Vector2I {
x: i as i32 % Chunk::SIZE.x, x: i as i32 % Chunk2D::SIZE.x,
y: i as i32 / Chunk::SIZE.y, y: i as i32 / Chunk2D::SIZE.y,
} }
} }
pub fn global_to_local(position: &Vector2I) -> Vector2I { pub fn global_to_local(position: &Vector2I) -> Vector2I {
Vector2I { Vector2I {
x: wrapping_remainder(position.x, Chunk::SIZE.x), x: wrapping_remainder(position.x, Chunk2D::SIZE.x),
y: wrapping_remainder(position.y, Chunk::SIZE.y), y: wrapping_remainder(position.y, Chunk2D::SIZE.y),
} }
} }
pub fn global_to_chunk_index(position: &Vector2I) -> ChunkIndex { pub fn global_to_chunk_index(position: &Vector2I) -> ChunkIndex {
Vector2I { Vector2I {
x: wrapping_quotient(position.x, Chunk::SIZE.x), x: wrapping_quotient(position.x, Chunk2D::SIZE.x),
y: wrapping_quotient(position.y, Chunk::SIZE.y), y: wrapping_quotient(position.y, Chunk2D::SIZE.y),
} }
} }
pub fn chunk_index_to_global(chunk_pos: &ChunkIndex) -> Vector2I { pub fn chunk_index_to_global(chunk_pos: &ChunkIndex) -> Vector2I {
*chunk_pos * Chunk::SIZE *chunk_pos * Chunk2D::SIZE
} }

View File

@ -1,5 +1,18 @@
use super::{local_to_texel_index, Texel, TexelID, NEIGHBOUR_INDEX_MAP}; use super::{local_to_texel_index, Terrain2D, TerrainEvent, Texel2D, TexelID, NEIGHBOUR_INDEX_MAP};
use crate::util::Vector2I; use crate::util::Vector2I;
use bevy::{prelude::*, render::render_resource::Extent3d};
#[derive(Reflect, Component, Default)]
#[reflect(Component)]
pub struct Chunk2DIndex {
pub index: ChunkIndex,
}
#[derive(Bundle, Default)]
pub struct ChunkBundle {
pub chunk: Chunk2DIndex,
pub sprite_bundle: SpriteBundle,
}
pub type ChunkIndex = Vector2I; pub type ChunkIndex = Vector2I;
@ -9,13 +22,13 @@ pub struct ChunkRect {
pub max: Vector2I, pub max: Vector2I,
} }
pub struct Chunk { pub struct Chunk2D {
pub texels: [Texel; (Self::SIZE_X * Self::SIZE_Y) as usize], pub texels: [Texel2D; (Self::SIZE_X * Self::SIZE_Y) as usize],
// TODO: handle multiple dirty rects // TODO: handle multiple dirty rects
pub dirty_rect: Option<ChunkRect>, pub dirty_rect: Option<ChunkRect>,
} }
impl Chunk { impl Chunk2D {
pub const SIZE_X: usize = 64; pub const SIZE_X: usize = 64;
pub const SIZE_Y: usize = 64; pub const SIZE_Y: usize = 64;
pub const SIZE: Vector2I = Vector2I { pub const SIZE: Vector2I = Vector2I {
@ -23,15 +36,15 @@ impl Chunk {
y: Self::SIZE_Y as i32, y: Self::SIZE_Y as i32,
}; };
pub fn new() -> Chunk { pub fn new() -> Chunk2D {
Chunk { Chunk2D {
texels: Self::new_texel_array(), texels: Self::new_texel_array(),
dirty_rect: None, dirty_rect: None,
} }
} }
pub fn new_texel_array() -> [Texel; Self::SIZE_X * Self::SIZE_Y] { pub fn new_texel_array() -> [Texel2D; Self::SIZE_X * Self::SIZE_Y] {
[Texel::default(); Self::SIZE_X * Self::SIZE_Y] [Texel2D::default(); Self::SIZE_X * Self::SIZE_Y]
} }
pub fn mark_all_dirty(&mut self) { pub fn mark_all_dirty(&mut self) {
@ -58,11 +71,11 @@ impl Chunk {
} }
} }
pub fn get_texel(&self, position: &Vector2I) -> Option<Texel> { pub fn get_texel(&self, position: &Vector2I) -> Option<Texel2D> {
local_to_texel_index(position).map(|i| self.texels[i]) local_to_texel_index(position).map(|i| self.texels[i])
} }
pub fn get_texel_option_mut(&mut self, position: &Vector2I) -> Option<&mut Texel> { pub fn get_texel_option_mut(&mut self, position: &Vector2I) -> Option<&mut Texel2D> {
local_to_texel_index(position).map(|i| &mut self.texels[i]) local_to_texel_index(position).map(|i| &mut self.texels[i])
} }
@ -72,7 +85,7 @@ impl Chunk {
self.mark_dirty(position); self.mark_dirty(position);
} }
let update_neighbours = self.texels[i].is_empty() let update_neighbours = self.texels[i].is_empty()
!= (Texel { != (Texel2D {
id, id,
..self.texels[i] ..self.texels[i]
}) })
@ -80,7 +93,7 @@ impl Chunk {
self.texels[i].id = id; self.texels[i].id = id;
// Update neighbour mask // Update neighbour mask
if update_neighbours { if update_neighbours {
for offset in Texel::NEIGHBOUR_OFFSET_VECTORS { for offset in Texel2D::NEIGHBOUR_OFFSET_VECTORS {
// Flip neighbour's bit // Flip neighbour's bit
match self.get_texel_option_mut(&(*position + offset)) { match self.get_texel_option_mut(&(*position + offset)) {
Some(mut neighbour) => { Some(mut neighbour) => {
@ -92,3 +105,72 @@ impl Chunk {
} }
} }
} }
pub fn chunk_spawner(
mut commands: Commands,
mut terrain_events: EventReader<TerrainEvent>,
mut images: ResMut<Assets<Image>>,
terrain: Res<Terrain2D>,
chunk_query: Query<(Entity, &Chunk2DIndex)>,
) {
for terrain_event in terrain_events.iter() {
match terrain_event {
TerrainEvent::ChunkAdded(chunk_index) => {
let mut data = Vec::with_capacity(Chunk2D::SIZE_X * Chunk2D::SIZE_Y * 4);
for _y in 0..Chunk2D::SIZE_Y {
for _x in 0..Chunk2D::SIZE_X {
data.push(0x00);
data.push(0x00);
data.push(0x00);
data.push(0x00);
}
}
let image = Image::new(
Extent3d {
width: Chunk2D::SIZE_X as u32,
height: Chunk2D::SIZE_Y as u32,
depth_or_array_layers: 1,
},
bevy::render::render_resource::TextureDimension::D2,
data,
bevy::render::render_resource::TextureFormat::Rgba8Unorm,
);
images.add(image);
let pos = Vec2::from(*chunk_index * Chunk2D::SIZE);
commands
.spawn(ChunkBundle {
chunk: Chunk2DIndex {
index: *chunk_index,
},
sprite_bundle: SpriteBundle {
sprite: Sprite {
color: Color::rgb(
0.25 + (chunk_index.x % 4) as f32 * 0.25,
0.25 + (chunk_index.y % 4) as f32 * 0.25,
0.75,
),
custom_size: Some(Vec2::from(Chunk2D::SIZE)),
..default()
},
transform: Transform::from_translation(Vec3::new(pos.x, pos.y, 0.0)),
..default()
},
})
.insert(Name::new(format!(
"Chunk {},{}",
chunk_index.x, chunk_index.y
)));
}
TerrainEvent::ChunkRemoved(chunk_index) => {
for (entity, chunk) in chunk_query.iter() {
if chunk.index == *chunk_index {
commands.entity(entity).despawn_recursive();
}
}
}
TerrainEvent::TexelsUpdated(chunk_index, rect) => {}
}
}
}

View File

@ -7,7 +7,7 @@ pub use u8 as NeighbourMask;
use crate::util::Vector2I; use crate::util::Vector2I;
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub struct Texel { pub struct Texel2D {
pub id: TexelID, pub id: TexelID,
/// bitmask of empty/non-empty neighbours, see NEIGHBOUR_OFFSET_VECTORS for the order /// bitmask of empty/non-empty neighbours, see NEIGHBOUR_OFFSET_VECTORS for the order
pub neighbour_mask: NeighbourMask, pub neighbour_mask: NeighbourMask,
@ -16,14 +16,14 @@ pub struct Texel {
lazy_static! { lazy_static! {
pub static ref NEIGHBOUR_INDEX_MAP: HashMap<Vector2I, u8> = { pub static ref NEIGHBOUR_INDEX_MAP: HashMap<Vector2I, u8> = {
let mut map = HashMap::new(); let mut map = HashMap::new();
for i in 0..Texel::NEIGHBOUR_OFFSET_VECTORS.len() { for i in 0..Texel2D::NEIGHBOUR_OFFSET_VECTORS.len() {
map.insert(Texel::NEIGHBOUR_OFFSET_VECTORS[i], i as u8); map.insert(Texel2D::NEIGHBOUR_OFFSET_VECTORS[i], i as u8);
} }
map map
}; };
} }
impl Texel { impl Texel2D {
pub const EMPTY: TexelID = 0; pub const EMPTY: TexelID = 0;
pub const NEIGHBOUR_OFFSET_VECTORS: [Vector2I; 4] = [ pub const NEIGHBOUR_OFFSET_VECTORS: [Vector2I; 4] = [
Vector2I { x: 0, y: 1 }, Vector2I { x: 0, y: 1 },

View File

@ -1,9 +1,12 @@
use core::{fmt, ops}; use core::{fmt, ops};
use bevy::reflect::Reflect;
pub trait VectorComponent: pub trait VectorComponent:
Sized Sized
+ Copy + Copy
+ Ord + Ord
+ Reflect
+ fmt::Display + fmt::Display
+ ops::Add<Output = Self> + ops::Add<Output = Self>
+ ops::Neg<Output = Self> + ops::Neg<Output = Self>
@ -17,6 +20,7 @@ impl<T> VectorComponent for T where
T: Sized T: Sized
+ Copy + Copy
+ Ord + Ord
+ Reflect
+ fmt::Display + fmt::Display
+ ops::Neg<Output = T> + ops::Neg<Output = T>
+ ops::Add<Output = T> + ops::Add<Output = T>
@ -26,7 +30,7 @@ impl<T> VectorComponent for T where
{ {
} }
#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Reflect)]
pub struct Vector2<T: VectorComponent> { pub struct Vector2<T: VectorComponent> {
pub x: T, pub x: T,
pub y: T, pub y: T,