overhaul map generation

This commit is contained in:
Astro 2022-01-14 23:38:17 +01:00
parent a0f3e44245
commit 1985db2d17
3 changed files with 222 additions and 76 deletions

View File

@ -31,8 +31,7 @@ fn main() {
.add_startup_system(player::setup)
.add_system(player::input)
.add_system(exit_on_escape)
.add_system(log_collisions)
// .add_system(keyboard_input_camera)
// .add_system(log_collisions)
.run();
}

View File

@ -1,16 +1,41 @@
use std::collections::HashMap;
use rand::prelude::*;
use bevy::{
prelude::*,
};
use heron::prelude::*;
use crate::Layer;
use crate::{
player::Player,
Layer,
};
pub struct LevelResources {
soil_material: Handle<StandardMaterial>,
grass_material: Handle<StandardMaterial>,
bridge_material: Handle<StandardMaterial>,
last_build: Option<Time>,
}
const BUILD_RANGE: f32 = 70.0;
const ISLAND_SPACING: f32 = 30.0;
const GRASS_HEIGHT: f32 = 0.3;
const BRIDGE_WIDTH: f32 = 2.0;
#[derive(Component)]
pub struct Island {
/// x grid
u: i32,
/// z grid
v: i32,
}
#[derive(Component)]
pub struct Bridge {
from: (i32, i32),
to: (i32, i32),
}
#[derive(Component)]
pub struct GroundContact(pub usize);
@ -22,38 +47,26 @@ impl std::default::Default for GroundContact {
#[derive(Debug, Component)]
pub struct Ground {
/// (x0, width)
/// (center x, width)
x: (f32, f32),
/// ground top
y: f32,
/// (z0, width)
/// (center y, height)
y: (f32, f32),
/// (center z, width)
z: (f32, f32),
}
impl Ground {
const HEIGHT: f32 = 2.5;
pub fn overlaps(&self, other: &Self) -> bool {
self.x.0 + self.x.1 >= other.x.0 &&
self.x.0 <= other.x.0 + other.x.1 &&
self.z.0 + self.z.1 >= other.z.0 &&
self.z.0 <= other.z.0 + other.z.1
}
pub fn to_transform(&self) -> Transform {
let half_width = self.x.1 / 2.0;
let half_height = Self::HEIGHT / 2.0;
let half_depth = self.z.1 / 2.0;
Transform::from_xyz(
self.x.0 + half_width,
self.y + half_height,
self.z.0 + half_depth,
self.x.0,
self.y.0,
self.z.0,
)
}
pub fn to_box(&self) -> shape::Box {
let half_width = self.x.1 / 2.0;
let half_height = Self::HEIGHT / 2.0;
let half_height = self.y.1 / 2.0;
let half_depth = self.z.1 / 2.0;
shape::Box {
min_x: - half_width,
@ -67,7 +80,7 @@ impl Ground {
pub fn half_extends(&self) -> Vec3 {
let half_width = self.x.1 / 2.0;
let half_height = Self::HEIGHT / 2.0;
let half_height = self.y.1 / 2.0;
let half_depth = self.z.1 / 2.0;
Vec3::new(half_width, half_height, half_depth)
}
@ -80,6 +93,7 @@ pub fn setup(
let res = LevelResources {
soil_material: materials.add(Color::rgb(0.8, 0.7, 0.1).into()),
grass_material: materials.add(Color::rgb(0.2, 1.0, 0.25).into()),
bridge_material: materials.add(Color::rgb(0.5, 0.4, 0.1).into()),
last_build: None,
};
commands.insert_resource(res);
@ -90,76 +104,213 @@ pub fn build(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut res: ResMut<LevelResources>,
grounds: Query<&Ground>,
islands: Query<(Entity, &Island)>,
bridges: Query<(Entity, &Bridge)>,
player_transforms: Query<&Transform, With<Player>>,
) {
// let now = time.seconds_since_startup();
if res.last_build.is_none() //res.last_build.as_ref().map_or(true, |last_build| now - last_build.seconds_since_startup() >= 0.1)
let now = time.seconds_since_startup();
if res.last_build.as_ref().map_or(true, |last_build| now - last_build.seconds_since_startup() >= 0.1)
{
let mut rng = rand::thread_rng();
for z in -9..10 {
for x in -9..10 {
let ground = Ground {
x: (2.0 * x as f32, 2.0),
y: rng.gen_range::<f32, _>(0.0..2.0),
z: (2.0 * z as f32, 2.0),
};
let mut collision = false;
for other in grounds.iter() {
if ground.overlaps(other) {
// println!("collision: {:?}x{:?} && {:?}x{:?}", ground.x, ground.z, other.x, other.z);
collision = true;
break;
let mut min_x = None;
let mut max_x = None;
let mut min_z = None;
let mut max_z = None;
for transform in player_transforms.iter() {
let t = &transform.translation;
if min_x.map_or(true, |min_x| t.x < min_x) {
min_x = Some(t.x);
}
if max_x.map_or(true, |max_x| t.x > max_x) {
max_x = Some(t.x);
}
if min_z.map_or(true, |min_z| t.z < min_z) {
min_z = Some(t.z);
}
if max_z.map_or(true, |max_z| t.z > max_z) {
max_z = Some(t.z);
}
}
if let Some((u_range, v_range)) = Some(()).and_then(|()| {
Some((
((min_x? - BUILD_RANGE) / ISLAND_SPACING) as i32
..=
((max_x? + BUILD_RANGE) / ISLAND_SPACING) as i32,
((min_z? - BUILD_RANGE) / ISLAND_SPACING) as i32
..=
((max_z? + BUILD_RANGE) / ISLAND_SPACING) as i32
))
}) {
// collect already-built island
let mut islands_built = HashMap::new();
for (entity, island) in islands.iter() {
islands_built.insert((island.u, island.v), entity);
}
// collect already-built bridges
let mut bridges_built = HashMap::new();
for (entity, bridge) in bridges.iter() {
bridges_built.insert((bridge.from, bridge.to), entity);
}
// build new islands where needed
let mut rng = rand::thread_rng();
for v in v_range {
for u in u_range.clone() {
let x = u as f32 * ISLAND_SPACING;
let z = v as f32 * ISLAND_SPACING;
if islands_built.remove(&(u, v)).is_none() {
let step = rng.gen_range(0.2..1.0);
let top = step + rng.gen_range(0.5..1.0);
let height = rng.gen_range(4.0..128.0);
let width = rng.gen_range(8.0..20.0);
let depth = rng.gen_range(8.0..15.0);
// central hill
let center = Ground {
x: (x, width),
y: ((top - height) / 2.0, height + top),
z: (z, depth),
};
// steps
let north = Ground {
x: (x, rng.gen_range(BRIDGE_WIDTH..width)),
y: ((step - height) / 2.0, height + step),
z: (z - (depth / 2.0) - 1.0, 2.0),
};
let south = Ground {
x: (x, rng.gen_range(BRIDGE_WIDTH..width)),
y: ((step - height) / 2.0, height + step),
z: (z + (depth / 2.0) + 1.0, 2.0),
};
let east = Ground {
x: (x - (width / 2.0) - 1.0, 2.0),
y: ((step - height) / 2.0, height + step),
z: (z, rng.gen_range(BRIDGE_WIDTH..depth)),
};
let west = Ground {
x: (x + (width / 2.0) + 1.0, 2.0),
y: ((step - height) / 2.0, height + step),
z: (z, rng.gen_range(BRIDGE_WIDTH..depth)),
};
let grounds = vec![center, north, south, east, west];
let island = Island { u, v };
add_island(&mut commands, &mut meshes, &res, grounds, island);
}
if bridges_built.remove(&((u, v), (u + 1, v))).is_none() {
let ground = Ground {
x: (x + 0.5 * ISLAND_SPACING, ISLAND_SPACING),
y: (0.0, 0.25),
z: (z, BRIDGE_WIDTH),
};
let bridge = Bridge {
from: (u, v),
to: (u + 1, v),
};
add_bridge(&mut commands, &mut meshes, &res, ground, bridge);
}
if bridges_built.remove(&((u, v), (u, v + 1))).is_none() {
let ground = Ground {
x: (x, BRIDGE_WIDTH),
y: (0.0, 0.25),
z: (z + 0.5 * ISLAND_SPACING, ISLAND_SPACING),
};
let bridge = Bridge {
from: (u, v),
to: (u, v + 1),
};
add_bridge(&mut commands, &mut meshes, &res, ground, bridge);
}
}
}
if !collision {
add_ground(&mut commands, &mut meshes, &res, ground);
}
// remove remaining built islands where not needed
for (_, entity) in islands_built.into_iter() {
commands.entity(entity).despawn_recursive();
}
// remove remaining built bridges where not needed
for (_, entity) in bridges_built.into_iter() {
commands.entity(entity).despawn();
}
}
res.last_build = Some(time.clone());
}
}
fn add_ground(
fn add_island(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
res: &ResMut<LevelResources>,
grounds: Vec<Ground>,
island: Island,
) {
commands.spawn()
.insert(GlobalTransform::default())
.insert(Transform::default())
.insert(island)
.with_children(|children| {
for ground in grounds {
let transform = ground.to_transform();
let bounds = ground.to_box();
let mut soil_box = bounds.clone();
soil_box.max_y = soil_box.max_y - GRASS_HEIGHT;
let mut grass_box = bounds.clone();
grass_box.min_y = soil_box.max_y;
let soil_mesh = meshes.add(Mesh::from(soil_box));
let grass_mesh = meshes.add(Mesh::from(grass_box));
children.spawn()
.insert(RigidBody::Static)
.insert(CollisionShape::Cuboid {
border_radius: None,
half_extends: ground.half_extends(),
})
// .insert(ground)
.insert(GlobalTransform::default())
.insert(transform)
.with_children(|children| {
children.spawn_bundle(PbrBundle {
mesh: soil_mesh,
material: res.soil_material.clone(),
..Default::default()
});
children.spawn_bundle(PbrBundle {
mesh: grass_mesh,
material: res.grass_material.clone(),
..Default::default()
});
});
}
});
}
fn add_bridge(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
res: &ResMut<LevelResources>,
ground: Ground,
bridge: Bridge,
) {
let mut transform = ground.to_transform();
let bounds = ground.to_box();
let mut soil_box = bounds.clone();
soil_box.max_y = soil_box.min_y + 0.7 * (soil_box.max_y - soil_box.min_y);
let mut grass_box = bounds.clone();
grass_box.min_y = soil_box.max_y;
let soil_mesh = meshes.add(Mesh::from(soil_box));
commands.spawn()
.insert_bundle(PbrBundle {
mesh: soil_mesh,
material: res.soil_material.clone(),
transform: transform.clone(),
..Default::default()
});
let grass_mesh = meshes.add(Mesh::from(grass_box));
commands.spawn()
.insert_bundle(PbrBundle {
mesh: grass_mesh,
material: res.grass_material.clone(),
transform: transform.clone(),
..Default::default()
})
let transform = ground.to_transform();
let bridge_mesh = meshes.add(Mesh::from(ground.to_box()));
commands.spawn_bundle(PbrBundle {
mesh: bridge_mesh,
material: res.bridge_material.clone(),
transform,
..Default::default()
})
.insert(bridge)
.insert(RigidBody::Static)
.insert(CollisionShape::Cuboid {
border_radius: None,
half_extends: ground.half_extends(),
})
.insert(ground);
});
}
pub fn collide(mut events: EventReader<CollisionEvent>, mut contacted: Query<&mut GroundContact>) {
fn is_map(layers: CollisionLayers) -> bool {
layers.contains_group(Layer::Map)

View File

@ -48,23 +48,19 @@ pub fn setup(
.insert(GlobalTransform::default())
.insert(transform)
.with_children(|children| {
let transform = Transform::default();
children.spawn_bundle(PbrBundle {
mesh: mesh1.clone(),
material: material1.clone(),
transform,
..Default::default()
});
children.spawn_bundle(PbrBundle {
mesh: mesh2.clone(),
material: material2.clone(),
transform,
..Default::default()
});
children.spawn_bundle(PbrBundle {
mesh: mesh3.clone(),
material: material3.clone(),
transform,
..Default::default()
});
let transform = Transform::from_translation(-0.71 * Vec3::Y);