use avian3d::prelude::*;
use bevy::prelude::*;
use bevy::window::{CursorGrabMode, CursorOptions};
use bevy_pretty_nice_input::{binding1d, binding2d, prelude::*};
fn main() -> AppExit {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_cursor_options: Some(CursorOptions {
visible: false,
grab_mode: CursorGrabMode::Locked,
..default()
}),
..default()
}))
.add_plugins(PhysicsPlugins::default())
.add_plugins(PrettyNiceInputPlugin)
.add_systems(Startup, (setup_env, setup_player))
.add_systems(Update, (walk, update_grounded))
.add_observer(look)
.add_observer(jump)
.add_observer(exit_app)
.run()
}
fn setup_env(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let white = materials.add(StandardMaterial {
base_color: Color::WHITE,
..Default::default()
});
commands.spawn((
Transform::from_xyz(0.0, -1.0, 0.0),
RigidBody::Static,
Collider::cuboid(50.0, 1.0, 50.0),
Mesh3d(meshes.add(Cuboid::new(100.0, 2.0, 100.0))),
MeshMaterial3d(white.clone()),
));
commands.spawn((
Mesh3d(meshes.add(Sphere::new(1.0))),
MeshMaterial3d(white.clone()),
));
commands.spawn((
DirectionalLight {
illuminance: 10000.0,
..Default::default()
},
Transform::from_rotation(Quat::from_euler(
EulerRot::XYZ,
-std::f32::consts::FRAC_PI_4,
std::f32::consts::FRAC_PI_8,
0.0,
)),
));
}
#[derive(Component)]
pub struct Player {
speed: f32,
sprint_speed: f32,
jump_force: f32,
look_sensitivity: f32,
camera: Entity,
collider: Entity,
}
#[derive(Component)]
pub struct Pitch(f32);
fn setup_player(mut commands: Commands) {
let camera = commands
.spawn((
Camera3d::default(),
Projection::Perspective(PerspectiveProjection {
fov: 70.0f32.to_radians(),
..default()
}),
Transform::from_xyz(0.0, 1.6, 0.0),
Pitch(0.0),
))
.id();
let collider = commands
.spawn((
Collider::capsule(0.25, 1.5), Transform::from_xyz(0.0, 1.0, 0.0),
))
.id();
commands
.spawn((
Player {
speed: 5.0,
sprint_speed: 10.0,
jump_force: 5.0,
look_sensitivity: 0.002,
camera,
collider,
},
RigidBody::Dynamic,
Transform::default(),
Visibility::default(),
LockedAxes::ROTATION_LOCKED,
(
input_transition!(() <=> (>Walking), Axis2D[binding2d::wasd()]),
input_transition!(() <=> (Sprinting), Axis1D[binding1d::left_shift()]),
input!(Look, Axis2D[binding2d::mouse_move()]),
input!(
Jump,
Axis1D[binding1d::space()],
[
ButtonPress::default(),
InputBuffer::new(0.2),
FilterBuffered::<Grounded>::default(),
Cooldown::new(0.5),
ResetBuffer,
]
),
input!(ExitApp, Axis1D[binding1d::key(KeyCode::Escape)]),
),
ComponentBuffer::<Grounded>::observe(0.2),
))
.add_children(&[camera, collider]);
}
#[derive(Component, TryFromActionData)]
#[action_data(Axis2D)]
pub struct Walking(Vec2);
#[derive(Component, Default)]
pub struct Sprinting;
fn walk(
mut players: Query<(
&mut LinearVelocity,
&Player,
&GlobalTransform,
Option<&Walking>,
Has<Sprinting>,
)>,
) {
for (mut velocity, player, transform, walking, sprinting) in players.iter_mut() {
let walk_direction = walking
.map(|w| Vec3::new(w.0.x, 0.0, -w.0.y).clamp_length_max(1.0))
.unwrap_or_default();
let walk_speed = if sprinting {
player.sprint_speed
} else {
player.speed
};
let walk_velocity = walk_direction * walk_speed;
let vertical_velocity = velocity.project_onto(transform.up().as_vec3());
let horizontal_velocity = transform.rotation() * walk_velocity;
**velocity = vertical_velocity + horizontal_velocity;
}
}
#[derive(Action)]
pub struct Look;
fn look(
look: On<Pressed<Look>>,
mut players: Query<(&mut Transform, &Player)>,
mut cameras: Query<(&mut Transform, &mut Pitch), Without<Player>>,
) -> Result<()> {
let (mut player_transform, player) = players.get_mut(look.input)?;
let (mut camera_transform, mut pitch) = cameras.get_mut(player.camera)?;
let input = look.data.as_2d_ok()?;
let delta_yaw = -input.x * player.look_sensitivity;
let delta_pitch = -input.y * player.look_sensitivity;
player_transform.rotate_local_y(delta_yaw);
pitch.0 =
(pitch.0 + delta_pitch).clamp(-std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_2);
camera_transform.rotation = Quat::from_axis_angle(Vec3::X, pitch.0);
Ok(())
}
#[derive(Action)]
pub struct Jump;
fn jump(
jump: On<JustPressed<Jump>>,
mut players: Query<(&mut LinearVelocity, &GlobalTransform, &Player)>,
) -> Result<()> {
let (mut velocity, transform, player) = players.get_mut(jump.input)?;
**velocity = transform.up() * player.jump_force;
Ok(())
}
#[derive(Component)]
pub struct Grounded;
fn update_grounded(
mut bodies: Query<(Entity, &GlobalTransform, &Player)>,
spatial_query: SpatialQuery,
mut commands: Commands,
) -> Result {
for (player, transform, body) in bodies.iter_mut() {
let hit = spatial_query.cast_ray(
transform.translation() + transform.up() * 0.05,
transform.down(),
0.25,
true,
&SpatialQueryFilter::default().with_excluded_entities([body.collider]),
);
if hit.is_some() {
commands.entity(player).insert(Grounded);
} else {
commands.entity(player).remove::<Grounded>();
}
}
Ok(())
}
#[derive(Action)]
pub struct ExitApp;
fn exit_app(_exit: On<Pressed<ExitApp>>, mut app_exit: MessageWriter<AppExit>) {
app_exit.write(AppExit::Success);
}