355 lines
12 KiB
Rust
355 lines
12 KiB
Rust
use std::collections::HashMap;
|
|
use rand::prelude::*;
|
|
use bevy::{
|
|
prelude::*,
|
|
};
|
|
use heron::prelude::*;
|
|
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);
|
|
|
|
impl std::default::Default for GroundContact {
|
|
fn default() -> Self {
|
|
GroundContact(0)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Component)]
|
|
pub struct Ground {
|
|
/// (center x, width)
|
|
x: (f32, f32),
|
|
/// (center y, height)
|
|
y: (f32, f32),
|
|
/// (center z, width)
|
|
z: (f32, f32),
|
|
}
|
|
|
|
impl Ground {
|
|
pub fn to_transform(&self) -> Transform {
|
|
Transform::from_xyz(
|
|
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.y.1 / 2.0;
|
|
let half_depth = self.z.1 / 2.0;
|
|
shape::Box {
|
|
min_x: - half_width,
|
|
min_y: - half_height,
|
|
min_z: - half_depth,
|
|
max_x: half_width,
|
|
max_y: half_height,
|
|
max_z: half_depth,
|
|
}
|
|
}
|
|
|
|
pub fn half_extends(&self) -> Vec3 {
|
|
let half_width = self.x.1 / 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)
|
|
}
|
|
}
|
|
|
|
pub fn setup(
|
|
mut commands: Commands,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
) {
|
|
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);
|
|
}
|
|
|
|
pub fn build(
|
|
time: Res<Time>,
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut res: ResMut<LevelResources>,
|
|
islands: Query<(Entity, &Island)>,
|
|
bridges: Query<(Entity, &Bridge)>,
|
|
player_transforms: Query<&Transform, With<Player>>,
|
|
) {
|
|
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 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(7.0..18.0);
|
|
let depth = rng.gen_range(7.0..18.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.5, 3.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.5, 3.0),
|
|
};
|
|
let east = Ground {
|
|
x: (x - (width / 2.0) - 1.5, 3.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.5, 3.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_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(CollisionLayers::none()
|
|
.with_group(Layer::Map)
|
|
.with_masks(&[Layer::Player])
|
|
)
|
|
.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 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(CollisionLayers::none()
|
|
.with_group(Layer::Map)
|
|
.with_masks(&[Layer::Player])
|
|
)
|
|
.insert(CollisionShape::Cuboid {
|
|
border_radius: None,
|
|
half_extends: ground.half_extends(),
|
|
});
|
|
}
|
|
|
|
|
|
pub fn collide(mut events: EventReader<CollisionEvent>, mut contacted: Query<&mut GroundContact>) {
|
|
fn is_map(layers: CollisionLayers) -> bool {
|
|
layers.contains_group(Layer::Map)
|
|
}
|
|
|
|
events
|
|
.iter()
|
|
// We care about when the entities "start" to collide
|
|
.filter_map(|event| {
|
|
let (entity_1, entity_2) = event.rigid_body_entities();
|
|
let (layers_1, layers_2) = event.collision_layers();
|
|
if ! is_map(layers_1) && is_map(layers_2) {
|
|
Some((event, entity_1))
|
|
} else if is_map(layers_1) && ! is_map(layers_2) {
|
|
Some((event, entity_2))
|
|
} else {
|
|
None
|
|
}
|
|
}).for_each(|(event, entity)| {
|
|
match event {
|
|
CollisionEvent::Started(_, _) => {
|
|
if let Ok(mut contact) = contacted.get_mut(entity) {
|
|
contact.0 = contact.0.saturating_add(1);
|
|
}
|
|
}
|
|
CollisionEvent::Stopped(_, _) => {
|
|
if let Ok(mut contact) = contacted.get_mut(entity) {
|
|
contact.0 = contact.0.saturating_sub(1);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|