You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
6.6 KiB
Rust

use bevy::{
color::palettes::css::{BLUE, GREEN},
render::mesh::{SphereKind, SphereMeshBuilder},
};
use bevy_rapier3d::rapier::prelude::CollisionEventFlags;
use games::*;
/// Example showing using Rapier3d to do area intersection
///
/// Have you ever wanted to detect if a character is within some bounds?
/// This shows how to do that.
fn main() {
App::new()
.add_plugins(BaseGamePlugin)
.init_resource::<InsideArea>()
.add_systems(
Startup,
(
setup_physics_scene,
setup_ui,
position_camera.after(setup_camera),
),
)
.add_systems(
Update,
(
control_ball,
process_events,
sync_resource_to_ui::<InsideArea>.run_if(resource_changed::<InsideArea>),
),
)
.run();
}
/// Which area the sphere is in/touching
#[derive(Component, Debug, Clone)]
enum AreaMarker {
Green,
Blue,
}
/// Setup a basic scene with:
/// * Floor
/// * A light for dramatic shadows
/// * A sphere that can move
/// * Two cubes that designate area intersection
fn setup_physics_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Create the ground.
// Make this a physical looking object
commands.spawn((
Collider::cuboid(100.0, 0.1, 100.0),
Transform::from_xyz(0.0, -1.0, 0.0),
Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(50.0)))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: RED.into(),
..Default::default()
})),
));
commands.spawn((
SpotLight {
color: WHITE.into(),
intensity: 10_000_000.0,
shadows_enabled: true,
..default()
},
Transform::from_xyz(0.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
));
// Create the bouncing ball.
commands.spawn((
RigidBody::Dynamic,
GravityScale(1.0),
Collider::ball(0.5),
ColliderMassProperties::Mass(2.0),
Restitution::coefficient(0.7),
ExternalImpulse::default(),
Transform::from_xyz(0.0, 4.0, 0.0),
ActiveEvents::all(),
Mesh3d(meshes.add(SphereMeshBuilder::new(
0.5,
SphereKind::Uv {
sectors: 10,
stacks: 10,
},
))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: WHITE.into(),
..Default::default()
})),
));
// Create a box that the ball can pass into/through
commands.spawn((
Sensor,
Collider::cuboid(1.0, 1.0, 1.0),
Transform::from_xyz(0.0, 0.0, 2.0),
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: GREEN.with_alpha(0.5).into(),
alpha_mode: AlphaMode::Blend,
..Default::default()
})),
AreaMarker::Green,
));
// Create a box that the ball can pass into/through
commands.spawn((
Sensor,
Collider::cuboid(1.0, 1.0, 1.0),
Transform::from_xyz(0.0, 0.0, -2.0),
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: BLUE.with_alpha(0.5).into(),
alpha_mode: AlphaMode::Blend,
..Default::default()
})),
AreaMarker::Blue,
));
}
/// Setup the UI for this example that indicates if the game "sees" the sphere
/// is inside the cube
fn setup_ui(mut commands: Commands) {
commands.spawn((
Node {
align_self: AlignSelf::Start,
justify_self: JustifySelf::Center,
..default()
},
Text("Placeholder".into()),
SyncResource::<InsideArea>::default(),
));
}
/// Position the world camera to get a better view
fn position_camera(mut query: Query<&mut Transform, (With<Camera>, With<Camera3d>)>) {
query.iter_mut().for_each(|mut t| {
*t = Transform::from_xyz(10.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y);
})
}
/// Control the ball based on arrow keys
fn control_ball(keys: Res<ButtonInput<KeyCode>>, mut ball: Single<&mut ExternalImpulse>) {
if keys.pressed(KeyCode::ArrowUp) {
ball.torque_impulse = Vec3::new(0.0, 0.0, 0.1);
}
if keys.pressed(KeyCode::ArrowDown) {
ball.torque_impulse = Vec3::new(0.0, 0.0, -0.1);
}
if keys.pressed(KeyCode::ArrowLeft) {
ball.torque_impulse = Vec3::new(0.1, 0.0, 0.0);
}
if keys.pressed(KeyCode::ArrowRight) {
ball.torque_impulse = Vec3::new(-0.1, 0.0, 0.0);
}
}
/// Maps CollisionEvent to a game-specific construct (did X enter/exit an area)
#[derive(Debug)]
enum WithinBounds {
Enter(Entity, Entity),
Exit(Entity, Entity),
}
/// Resource tracking what box the sphere is in
#[derive(Resource, Debug, Default, Clone)]
struct InsideArea(Option<AreaMarker>);
impl Display for InsideArea {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.0 {
Some(AreaMarker::Blue) => write!(f, "Ball IS touching the BLUE box"),
Some(AreaMarker::Green) => write!(f, "Ball IS touching the GREEN box"),
None => write!(f, "Ball is NOT touching ANY box"),
}
}
}
/// Read collision events and procss them into resource updates to be displayed to the user
fn process_events(
mut events: EventReader<CollisionEvent>,
areas: Query<&AreaMarker>,
mut inside: ResMut<InsideArea>,
) {
events
.read()
.filter_map(|collision_event| match collision_event {
CollisionEvent::Started(a, b, flags) => {
(*flags == CollisionEventFlags::SENSOR).then_some(WithinBounds::Enter(*a, *b))
}
CollisionEvent::Stopped(a, b, flags) => {
(*flags == CollisionEventFlags::SENSOR).then_some(WithinBounds::Exit(*a, *b))
}
})
.for_each(|within_bounds| {
match within_bounds {
WithinBounds::Enter(a, b) => {
if let Ok(x) = areas.get(a) {
inside.0 = Some(x.clone());
} else if let Ok(x) = areas.get(b) {
inside.0 = Some(x.clone());
}
}
WithinBounds::Exit(a, b) => {
if areas.contains(a) || areas.contains(b) {
inside.0 = None;
}
}
}
})
}