commit
a0f3e44245
9 changed files with 4451 additions and 0 deletions
@ -0,0 +1,10 @@
|
||||
[package] |
||||
name = "rust-bevy-test" |
||||
version = "0.1.0" |
||||
authors = ["Astro <astro@spaceboyz.net>"] |
||||
edition = "2021" |
||||
|
||||
[dependencies] |
||||
rand = "0.8" |
||||
bevy = { version = "0.6", features = ["jpeg"] } |
||||
heron = { git = "https://github.com/jcornaz/heron.git", features = ["3d"] } |
File diff suppressed because one or more lines are too long
@ -0,0 +1,34 @@
|
||||
let |
||||
mozilla = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz); |
||||
nixpkgs = import <nixpkgs> { overlays = [ mozilla ]; }; |
||||
in |
||||
|
||||
with nixpkgs; |
||||
|
||||
mkShell { |
||||
buildInputs = [ |
||||
alsaLib |
||||
cmake |
||||
udev |
||||
freetype |
||||
latest.rustChannels.nightly.rust |
||||
#rustc cargo |
||||
expat |
||||
openssl |
||||
pkgconfig |
||||
python3 |
||||
vulkan-validation-layers |
||||
xlibs.libX11 |
||||
]; |
||||
|
||||
APPEND_LIBRARY_PATH = lib.makeLibraryPath [ |
||||
vulkan-loader |
||||
xlibs.libXcursor |
||||
xlibs.libXi |
||||
xlibs.libXrandr |
||||
]; |
||||
|
||||
shellHook = '' |
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$APPEND_LIBRARY_PATH" |
||||
''; |
||||
} |
@ -0,0 +1,101 @@
|
||||
use bevy::prelude::*; |
||||
|
||||
use crate::player::Player; |
||||
|
||||
#[derive(Component)] |
||||
pub struct PlayerCamera; |
||||
|
||||
#[derive(Component)] |
||||
pub struct Light; |
||||
|
||||
|
||||
pub fn setup(mut commands: Commands) { |
||||
let camera_transform = Transform::from_xyz(-10.0, 10.0, 20.0) |
||||
.looking_at(Vec3::ZERO, Vec3::Y); |
||||
commands.spawn() |
||||
.insert_bundle(PerspectiveCameraBundle { |
||||
transform: camera_transform, |
||||
..Default::default() |
||||
}) |
||||
.insert(PlayerCamera); |
||||
|
||||
|
||||
// light
|
||||
let hilight_transform = Transform::from_xyz(0.0, 200.0, 0.0); |
||||
commands.spawn() |
||||
.insert_bundle(PointLightBundle { |
||||
transform: hilight_transform, |
||||
point_light: PointLight { |
||||
range: 2000.0, |
||||
radius: 2000.0, |
||||
intensity: 4000.0, |
||||
shadows_enabled: true, |
||||
..Default::default() |
||||
}, |
||||
..Default::default() |
||||
}) |
||||
.insert(Light); |
||||
} |
||||
|
||||
pub fn track_players( |
||||
time: Res<Time>, |
||||
mut queries: QuerySet<( |
||||
QueryState<&Transform, With<Player>>, |
||||
QueryState<&mut Transform, With<PlayerCamera>>, |
||||
QueryState<&mut Transform, With<Light>>, |
||||
)>, |
||||
) { |
||||
let mut min_x = None; |
||||
let mut max_x = None; |
||||
let mut max_y = None; |
||||
let mut min_z = None; |
||||
let mut max_z = None; |
||||
for transform in queries.q0().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 max_y.map_or(true, |max_y| t.y > max_y) { |
||||
max_y = Some(t.y); |
||||
} |
||||
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(target) = max_y.and_then(|max_y| { |
||||
let dist = 10.0f32.max( |
||||
max_x? - min_x? |
||||
).max( |
||||
(max_z? - min_z?) / 2.0 |
||||
); |
||||
Some(Vec3::new(min_x? - dist, max_y + dist, max_z? + 2.0 * dist)) |
||||
}) { |
||||
for mut camera_transform in queries.q1().iter_mut() { |
||||
let t = &mut camera_transform.translation; |
||||
*t = *t + time.delta_seconds() * (target - *t) / 2.0; |
||||
} |
||||
} |
||||
|
||||
if let Some(light_pos) = max_y.and_then(|max_y| { |
||||
Some(Vec3::new( |
||||
(max_x? + min_x?) / 2.0, |
||||
max_y + 8.0, |
||||
(max_z? + min_z?) / 2.0 + 10.0, |
||||
)) |
||||
}) { |
||||
for ref mut light_transform in queries.q2().iter_mut() { |
||||
light_transform.translation = light_pos; |
||||
} |
||||
} else { |
||||
println!("No player to target!"); |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
use bevy::{ |
||||
prelude::*, |
||||
app::AppExit, |
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, |
||||
}; |
||||
use heron::prelude::*; |
||||
|
||||
mod camera; |
||||
mod map; |
||||
mod player; |
||||
|
||||
#[derive(PhysicsLayer)] |
||||
pub enum Layer { |
||||
Map, |
||||
Player, |
||||
} |
||||
|
||||
fn main() { |
||||
App::new() |
||||
.add_plugin(LogDiagnosticsPlugin::default()) |
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default()) |
||||
.add_plugins(DefaultPlugins) |
||||
.add_plugin(PhysicsPlugin::default()) |
||||
.insert_resource(Gravity::from(Vec3::new(0.0, -9.81, 0.0))) |
||||
.insert_resource(ClearColor(Color::rgb(0.7, 0.7, 1.0))) |
||||
.add_startup_system(camera::setup) |
||||
.add_system(camera::track_players) |
||||
.add_startup_system(map::setup) |
||||
.add_system(map::build) |
||||
.add_system(map::collide) |
||||
.add_startup_system(player::setup) |
||||
.add_system(player::input) |
||||
.add_system(exit_on_escape) |
||||
.add_system(log_collisions) |
||||
// .add_system(keyboard_input_camera)
|
||||
.run(); |
||||
} |
||||
|
||||
|
||||
fn exit_on_escape(keyboard_input: Res<Input<KeyCode>>, mut exit: EventWriter<AppExit>) { |
||||
if keyboard_input.pressed(KeyCode::Escape) { |
||||
exit.send(AppExit); |
||||
} |
||||
} |
||||
|
||||
fn log_collisions(mut events: EventReader<CollisionEvent>) { |
||||
for event in events.iter() { |
||||
match event { |
||||
CollisionEvent::Started(d1, d2) => { |
||||
println!("Collision started between {:?} and {:?}", d1, d2) |
||||
} |
||||
CollisionEvent::Stopped(d1, d2) => { |
||||
println!("Collision stopped between {:?} and {:?}", d1, d2) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,193 @@
|
||||
use rand::prelude::*; |
||||
use bevy::{ |
||||
prelude::*, |
||||
}; |
||||
use heron::prelude::*; |
||||
use crate::Layer; |
||||
|
||||
pub struct LevelResources { |
||||
soil_material: Handle<StandardMaterial>, |
||||
grass_material: Handle<StandardMaterial>, |
||||
last_build: Option<Time>, |
||||
} |
||||
|
||||
#[derive(Component)] |
||||
pub struct GroundContact(pub usize); |
||||
|
||||
impl std::default::Default for GroundContact { |
||||
fn default() -> Self { |
||||
GroundContact(0) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Component)] |
||||
pub struct Ground { |
||||
/// (x0, width)
|
||||
x: (f32, f32), |
||||
/// ground top
|
||||
y: f32, |
||||
/// (z0, 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, |
||||
) |
||||
} |
||||
|
||||
pub fn to_box(&self) -> shape::Box { |
||||
let half_width = self.x.1 / 2.0; |
||||
let half_height = Self::HEIGHT / 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::HEIGHT / 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()), |
||||
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>, |
||||
grounds: Query<&Ground>, |
||||
) { |
||||
// 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 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; |
||||
} |
||||
} |
||||
|
||||
if !collision { |
||||
add_ground(&mut commands, &mut meshes, &res, ground); |
||||
} |
||||
} |
||||
} |
||||
res.last_build = Some(time.clone()); |
||||
} |
||||
} |
||||
|
||||
fn add_ground( |
||||
commands: &mut Commands, |
||||
meshes: &mut ResMut<Assets<Mesh>>, |
||||
res: &ResMut<LevelResources>, |
||||
ground: Ground, |
||||
) { |
||||
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() |
||||
}) |
||||
.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) |
||||
} |
||||
|
||||
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 { |
||||
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); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
} |
@ -0,0 +1,162 @@
|
||||
use std::f32::consts::PI; |
||||
use bevy::prelude::*; |
||||
use heron::prelude::*; |
||||
use crate::{ |
||||
map::GroundContact, |
||||
Layer, |
||||
}; |
||||
|
||||
#[derive(Component)] |
||||
pub struct Player { |
||||
rotation: f32, |
||||
} |
||||
|
||||
pub fn setup( |
||||
mut commands: Commands, |
||||
asset_server: Res<AssetServer>, |
||||
) { |
||||
let mesh1 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_duck.gltf#Mesh0/Primitive0"); |
||||
let mesh2 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_duck.gltf#Mesh0/Primitive1"); |
||||
let mesh3 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_duck.gltf#Mesh0/Primitive2"); |
||||
let mesh4 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_duck.gltf#Mesh1/Primitive0"); |
||||
let material1 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_duck.gltf#Material0"); |
||||
let material2 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_duck.gltf#Material1"); |
||||
let material3 = asset_server.load("Mini-Game Variety Pack/Models/Characters/gltf/character_duck.gltf#Material2"); |
||||
|
||||
let transform = Transform::from_xyz(0.0, 12.0, 0.0); |
||||
commands.spawn() |
||||
.insert(RigidBody::Dynamic) |
||||
.insert(CollisionLayers::none() |
||||
.with_group(Layer::Player) |
||||
.with_masks(&[Layer::Player, Layer::Map]) |
||||
) |
||||
.insert(CollisionShape::Cone { |
||||
half_height: 0.7, |
||||
radius: 0.4, |
||||
}) |
||||
.insert(PhysicMaterial { |
||||
restitution: 0.0, |
||||
density: 8.0, |
||||
friction: 1.0, |
||||
}) |
||||
.insert(RotationConstraints::lock()) |
||||
.insert(Velocity::default()) |
||||
.insert(Player { |
||||
rotation: 0.0, |
||||
}) |
||||
.insert(GroundContact::default()) |
||||
.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); |
||||
children.spawn_bundle(PbrBundle { |
||||
mesh: mesh4.clone(), |
||||
material: material1.clone(), |
||||
transform, |
||||
..Default::default() |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
pub fn input(time: Res<Time>, input: Res<Input<KeyCode>>, mut players: Query<(&mut Velocity, &mut Player, &mut Transform, &GroundContact)>) { |
||||
let x; |
||||
let z; |
||||
let target_rotation; |
||||
match [KeyCode::Right, KeyCode::Left, KeyCode::Up, KeyCode::Down].map(|k| input.pressed(k)) { |
||||
[false, false, false, true] => { |
||||
z = 1.0; |
||||
x = 0.0; |
||||
target_rotation = Some(0.0); |
||||
} |
||||
[false, true, false, true] => { |
||||
z = 0.7; |
||||
x = -0.7; |
||||
target_rotation = Some(1.75 * PI); |
||||
} |
||||
[false, true, false, false] => { |
||||
z = 0.0; |
||||
x = -1.0; |
||||
target_rotation = Some(1.5 * PI); |
||||
} |
||||
[false, true, true, false] => { |
||||
z = -0.7; |
||||
x = -0.7; |
||||
target_rotation = Some(1.25 * PI); |
||||
} |
||||
[false, false, true, false] => { |
||||
z = -1.0; |
||||
x = 0.0; |
||||
target_rotation = Some(1.0 * PI); |
||||
} |
||||
[true, false, true, false] => { |
||||
z = -0.7; |
||||
x = 0.7; |
||||
target_rotation = Some(0.75 * PI); |
||||
} |
||||
[true, false, false, false] => { |
||||
z = 0.0; |
||||
x = 1.0; |
||||
target_rotation = Some(0.5 * PI); |
||||
} |
||||
[true, false, false, true] => { |
||||
z = 0.7; |
||||
x = 0.7; |
||||
target_rotation = Some(0.25 * PI); |
||||
} |
||||
_ => { |
||||
z = 0.0; |
||||
x = 0.0; |
||||
target_rotation = None; |
||||
} |
||||
} |
||||
|
||||
let y = if input.pressed(KeyCode::Space) { |
||||
1.6 |
||||
} else if x != 0.0 || z != 0.0 { |
||||
// walk bobbing
|
||||
0.3 |
||||
} else { |
||||
0.0 |
||||
}; |
||||
|
||||
const SPEED: f32 = 3.0; |
||||
let target_velocity = SPEED * Vec3::new(x, y, z); |
||||
for (mut velocity, mut player, mut transform, contact) in players.iter_mut() { |
||||
if contact.0 > 0 && velocity.linear.y.abs() < 0.2 { |
||||
velocity.linear = target_velocity; |
||||
} |
||||
|
||||
if let Some(target_rotation) = target_rotation { |
||||
if (player.rotation - 2.0 * PI - target_rotation).abs() < (player.rotation - target_rotation).abs() { |
||||
player.rotation -= 2.0 * PI; |
||||
} |
||||
if (player.rotation + 2.0 * PI - target_rotation).abs() < (player.rotation - target_rotation).abs() { |
||||
player.rotation += 2.0 * PI; |
||||
} |
||||
player.rotation += 10.0 * time.delta_seconds() * (target_rotation - player.rotation); |
||||
transform.rotation = Quat::from_axis_angle( |
||||
Vec3::Y, |
||||
player.rotation |
||||
); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue