wip: chunk spawner
parent
cf66379a23
commit
64c15be224
15
src/game.rs
15
src/game.rs
|
|
@ -2,9 +2,15 @@ use bevy::prelude::*;
|
|||
use bevy_inspector_egui::*;
|
||||
use bevy_rapier2d::prelude::*;
|
||||
|
||||
use crate::{
|
||||
terrain2d::{Chunk2D, Terrain2D, Terrain2DPlugin},
|
||||
util::Vector2I,
|
||||
};
|
||||
|
||||
use self::{camera::GameCameraPlugin, kinematic::KinematicPlugin, player::PlayerPlugin};
|
||||
|
||||
pub mod camera;
|
||||
pub mod chunk;
|
||||
pub mod kinematic;
|
||||
pub mod player;
|
||||
|
||||
|
|
@ -16,11 +22,20 @@ pub fn init() {
|
|||
.add_plugin(WorldInspectorPlugin::new())
|
||||
.add_plugin(KinematicPlugin)
|
||||
.add_plugin(GameCameraPlugin)
|
||||
.add_plugin(Terrain2DPlugin)
|
||||
// .add_plugin(PlayerPlugin)
|
||||
// .add_startup_system(setup_debug_ground)
|
||||
.add_startup_system(setup_debug_terrain)
|
||||
.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) {
|
||||
// Static ground
|
||||
commands
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -17,8 +17,11 @@ pub struct Terrain2DPlugin;
|
|||
|
||||
impl Plugin for Terrain2DPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(Terrain2D::new())
|
||||
.add_system(emit_terrain_events);
|
||||
app.register_type::<Chunk2DIndex>()
|
||||
.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)]
|
||||
pub struct Terrain2D {
|
||||
chunk_map: HashMap<ChunkIndex, Chunk>,
|
||||
chunk_map: HashMap<ChunkIndex, Chunk2D>,
|
||||
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.events.push(TerrainEvent::ChunkAdded(index))
|
||||
}
|
||||
|
|
@ -67,38 +70,38 @@ impl Terrain2D {
|
|||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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) {
|
||||
Some(chunk) => chunk.get_texel(&global_to_local(global)),
|
||||
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) {
|
||||
Some(chunk) => chunk.get_texel(&global_to_local(global)),
|
||||
None => None,
|
||||
|
|
@ -110,7 +113,7 @@ impl Terrain2D {
|
|||
match self.index_to_chunk_mut(&index) {
|
||||
Some(chunk) => chunk.set_texel(&global_to_local(global), id),
|
||||
None => {
|
||||
let mut chunk = Chunk::new();
|
||||
let mut chunk = Chunk2D::new();
|
||||
chunk.set_texel(&global_to_local(global), id);
|
||||
self.add_chunk(index, chunk);
|
||||
}
|
||||
|
|
@ -121,35 +124,35 @@ impl Terrain2D {
|
|||
pub fn local_to_texel_index(position: &Vector2I) -> Option<usize> {
|
||||
match position.x >= 0
|
||||
&& position.y >= 0
|
||||
&& position.x < Chunk::SIZE.x
|
||||
&& position.y < Chunk::SIZE.y
|
||||
&& position.x < Chunk2D::SIZE.x
|
||||
&& 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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn texel_index_to_local(i: usize) -> Vector2I {
|
||||
Vector2I {
|
||||
x: i as i32 % Chunk::SIZE.x,
|
||||
y: i as i32 / Chunk::SIZE.y,
|
||||
x: i as i32 % Chunk2D::SIZE.x,
|
||||
y: i as i32 / Chunk2D::SIZE.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_to_local(position: &Vector2I) -> Vector2I {
|
||||
Vector2I {
|
||||
x: wrapping_remainder(position.x, Chunk::SIZE.x),
|
||||
y: wrapping_remainder(position.y, Chunk::SIZE.y),
|
||||
x: wrapping_remainder(position.x, Chunk2D::SIZE.x),
|
||||
y: wrapping_remainder(position.y, Chunk2D::SIZE.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_to_chunk_index(position: &Vector2I) -> ChunkIndex {
|
||||
Vector2I {
|
||||
x: wrapping_quotient(position.x, Chunk::SIZE.x),
|
||||
y: wrapping_quotient(position.y, Chunk::SIZE.y),
|
||||
x: wrapping_quotient(position.x, Chunk2D::SIZE.x),
|
||||
y: wrapping_quotient(position.y, Chunk2D::SIZE.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunk_index_to_global(chunk_pos: &ChunkIndex) -> Vector2I {
|
||||
*chunk_pos * Chunk::SIZE
|
||||
*chunk_pos * Chunk2D::SIZE
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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;
|
||||
|
||||
|
|
@ -9,13 +22,13 @@ pub struct ChunkRect {
|
|||
pub max: Vector2I,
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub texels: [Texel; (Self::SIZE_X * Self::SIZE_Y) as usize],
|
||||
pub struct Chunk2D {
|
||||
pub texels: [Texel2D; (Self::SIZE_X * Self::SIZE_Y) as usize],
|
||||
// TODO: handle multiple dirty rects
|
||||
pub dirty_rect: Option<ChunkRect>,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
impl Chunk2D {
|
||||
pub const SIZE_X: usize = 64;
|
||||
pub const SIZE_Y: usize = 64;
|
||||
pub const SIZE: Vector2I = Vector2I {
|
||||
|
|
@ -23,15 +36,15 @@ impl Chunk {
|
|||
y: Self::SIZE_Y as i32,
|
||||
};
|
||||
|
||||
pub fn new() -> Chunk {
|
||||
Chunk {
|
||||
pub fn new() -> Chunk2D {
|
||||
Chunk2D {
|
||||
texels: Self::new_texel_array(),
|
||||
dirty_rect: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_texel_array() -> [Texel; Self::SIZE_X * Self::SIZE_Y] {
|
||||
[Texel::default(); Self::SIZE_X * Self::SIZE_Y]
|
||||
pub fn new_texel_array() -> [Texel2D; Self::SIZE_X * Self::SIZE_Y] {
|
||||
[Texel2D::default(); Self::SIZE_X * Self::SIZE_Y]
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +85,7 @@ impl Chunk {
|
|||
self.mark_dirty(position);
|
||||
}
|
||||
let update_neighbours = self.texels[i].is_empty()
|
||||
!= (Texel {
|
||||
!= (Texel2D {
|
||||
id,
|
||||
..self.texels[i]
|
||||
})
|
||||
|
|
@ -80,7 +93,7 @@ impl Chunk {
|
|||
self.texels[i].id = id;
|
||||
// Update neighbour mask
|
||||
if update_neighbours {
|
||||
for offset in Texel::NEIGHBOUR_OFFSET_VECTORS {
|
||||
for offset in Texel2D::NEIGHBOUR_OFFSET_VECTORS {
|
||||
// Flip neighbour's bit
|
||||
match self.get_texel_option_mut(&(*position + offset)) {
|
||||
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) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub use u8 as NeighbourMask;
|
|||
use crate::util::Vector2I;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Texel {
|
||||
pub struct Texel2D {
|
||||
pub id: TexelID,
|
||||
/// bitmask of empty/non-empty neighbours, see NEIGHBOUR_OFFSET_VECTORS for the order
|
||||
pub neighbour_mask: NeighbourMask,
|
||||
|
|
@ -16,14 +16,14 @@ pub struct Texel {
|
|||
lazy_static! {
|
||||
pub static ref NEIGHBOUR_INDEX_MAP: HashMap<Vector2I, u8> = {
|
||||
let mut map = HashMap::new();
|
||||
for i in 0..Texel::NEIGHBOUR_OFFSET_VECTORS.len() {
|
||||
map.insert(Texel::NEIGHBOUR_OFFSET_VECTORS[i], i as u8);
|
||||
for i in 0..Texel2D::NEIGHBOUR_OFFSET_VECTORS.len() {
|
||||
map.insert(Texel2D::NEIGHBOUR_OFFSET_VECTORS[i], i as u8);
|
||||
}
|
||||
map
|
||||
};
|
||||
}
|
||||
|
||||
impl Texel {
|
||||
impl Texel2D {
|
||||
pub const EMPTY: TexelID = 0;
|
||||
pub const NEIGHBOUR_OFFSET_VECTORS: [Vector2I; 4] = [
|
||||
Vector2I { x: 0, y: 1 },
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
use core::{fmt, ops};
|
||||
|
||||
use bevy::reflect::Reflect;
|
||||
|
||||
pub trait VectorComponent:
|
||||
Sized
|
||||
+ Copy
|
||||
+ Ord
|
||||
+ Reflect
|
||||
+ fmt::Display
|
||||
+ ops::Add<Output = Self>
|
||||
+ ops::Neg<Output = Self>
|
||||
|
|
@ -17,6 +20,7 @@ impl<T> VectorComponent for T where
|
|||
T: Sized
|
||||
+ Copy
|
||||
+ Ord
|
||||
+ Reflect
|
||||
+ fmt::Display
|
||||
+ ops::Neg<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 x: T,
|
||||
pub y: T,
|
||||
|
|
|
|||
Loading…
Reference in New Issue