entevsbaer/src/map.rs

424 lines
16 KiB
Rust

use std::collections::HashMap;
use rand::prelude::*;
use bevy::{
prelude::*,
};
use heron::prelude::*;
use crate::{
enemy::Enemy,
player::Player,
off_map::CanFallOffMap,
Layer,
};
pub struct LevelResources {
soil_material: Handle<StandardMaterial>,
grass_material: Handle<StandardMaterial>,
bridge_material: Handle<StandardMaterial>,
pub egg_material: Handle<StandardMaterial>,
pub egg_mesh: Handle<Mesh>,
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, Clone)]
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 meshes: ResMut<Assets<Mesh>>,
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()),
egg_material: materials.add(Color::rgb(1.0, 1.0, 0.9).into()),
egg_mesh: meshes.add(Mesh::from(shape::Icosphere {
radius: crate::player::EGG_RADIUS,
subdivisions: 8,
})),
last_build: None,
};
commands.insert_resource(res);
}
pub fn build(
time: Res<Time>,
mut commands: Commands,
asset_server: Res<AssetServer>,
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, &asset_server, &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,
asset_server: &Res<AssetServer>,
meshes: &mut ResMut<Assets<Mesh>>,
res: &ResMut<LevelResources>,
grounds: Vec<Ground>,
island: Island,
) {
commands.spawn()
.insert(GlobalTransform::default())
.insert(Transform::default())
.insert(island.clone())
.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, Layer::Projectile])
)
.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()
});
});
}
if island.u != 0 || island.v != 0 {
// enemy
let mesh1 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Mesh0/Primitive0");
let mesh2 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Mesh0/Primitive1");
let mesh3 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Mesh1/Primitive0");
let material1 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Material0");
let material2 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_bear.gltf#Material1");
let translation = grounds[0].to_transform().translation;
let half_extends = grounds[0].half_extends();
let transform = Transform::from_translation(translation + (half_extends.y + 3.0) * Vec3::Y);
children.spawn()
.insert(GlobalTransform::default())
.insert(transform)
.insert(RigidBody::Dynamic)
.insert(CollisionLayers::none()
.with_group(Layer::Player)
.with_masks(&[Layer::Player, Layer::Projectile, Layer::Map])
)
.insert(CollisionShape::Cone {
half_height: 0.85,
radius: 0.7,
})
.insert(PhysicMaterial {
restitution: 1.0,
density: 500.0,
friction: 0.0,
})
.insert(RotationConstraints::lock())
.insert(Velocity::default())
.insert(GroundContact::default())
.insert(CanFallOffMap)
.insert(Enemy {
rotation: 0.0,
bounds: (translation.x - half_extends.x + 1.0..=translation.x + half_extends.x - 1.0, translation.z - half_extends.z + 1.0..=translation.z + half_extends.z - 1.0),
patrol_target: None,
})
.with_children(|children| {
children.spawn_bundle(PbrBundle {
mesh: mesh1.clone(),
material: material1.clone(),
..Default::default()
});
children.spawn_bundle(PbrBundle {
mesh: mesh2.clone(),
material: material2.clone(),
..Default::default()
});
let transform = Transform::from_translation(-0.71 * Vec3::Y);
children.spawn_bundle(PbrBundle {
mesh: mesh3.clone(),
material: material2.clone(),
transform,
..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, Layer::Projectile])
)
.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);
}
}
}
});
}