Create basic interactive physics system
This commit is contained in:
parent
6108f140b6
commit
6732f5575c
3 changed files with 190 additions and 129 deletions
91
Cargo.lock
generated
91
Cargo.lock
generated
|
@ -85,7 +85,7 @@ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy 0.7.35",
|
||||
|
@ -718,7 +718,7 @@ dependencies = [
|
|||
"derive_more",
|
||||
"glam",
|
||||
"itertools",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"rand_distr",
|
||||
"serde",
|
||||
"smallvec",
|
||||
|
@ -1051,7 +1051,7 @@ checksum = "63c2174d43a0de99f863c98a472370047a2bfa7d1e5cec8d9d647fb500905d9d"
|
|||
dependencies = [
|
||||
"ahash",
|
||||
"bevy_utils_proc_macros",
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"hashbrown 0.14.5",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
|
@ -1409,7 +1409,7 @@ version = "0.1.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
@ -1606,6 +1606,7 @@ dependencies = [
|
|||
"avian2d",
|
||||
"bevy",
|
||||
"log",
|
||||
"rand 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1882,10 +1883,22 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
version = "0.14.0"
|
||||
|
@ -1904,7 +1917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -3036,6 +3049,12 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "radsort"
|
||||
version = "0.1.1"
|
||||
|
@ -3049,8 +3068,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy 0.8.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3060,7 +3090,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3069,7 +3109,16 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3079,7 +3128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3833,7 +3882,7 @@ version = "1.12.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -3865,6 +3914,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
|
@ -4480,6 +4538,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.8"
|
||||
|
|
|
@ -33,3 +33,4 @@ log = { version = "*", features = [
|
|||
"max_level_debug",
|
||||
"release_max_level_warn",
|
||||
] }
|
||||
rand = "0.9.0"
|
||||
|
|
219
src/main.rs
219
src/main.rs
|
@ -1,152 +1,145 @@
|
|||
//! This example showcases how `Transform` interpolation or extrapolation can be used
|
||||
//! to make movement appear smooth at fixed timesteps.
|
||||
//!
|
||||
//! To produce consistent, frame rate independent behavior, physics by default runs
|
||||
//! in the `FixedPostUpdate` schedule with a fixed timestep, meaning that the time between
|
||||
//! physics ticks remains constant. On some frames, physics can either not run at all or run
|
||||
//! more than once to catch up to real time. This can lead to visible stutter for movement.
|
||||
//!
|
||||
//! `Transform` interpolation resolves this issue by updating `Transform` at every frame in between
|
||||
//! physics ticks to smooth out the visual result. The interpolation is done from the previous position
|
||||
//! to the current physics position, which keeps movement smooth, but has the downside of making movement
|
||||
//! feel slightly delayed as the rendered result lags slightly behind the true positions.
|
||||
//!
|
||||
//! `Transform` extrapolation works similarly, but instead of using the previous positions, it predicts
|
||||
//! the next positions based on velocity. This makes movement feel much more responsive, but can cause
|
||||
//! jumpy results when the prediction is wrong, such as when the velocity of an object is suddenly altered.
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use avian2d::{math::*, prelude::*};
|
||||
use avian2d::{math::Vector, prelude::*};
|
||||
use bevy::{
|
||||
color::palettes::{
|
||||
css::WHITE,
|
||||
tailwind::{CYAN_400, LIME_400, RED_400},
|
||||
tailwind::{LIME_400, RED_400},
|
||||
},
|
||||
input::common_conditions::input_pressed,
|
||||
prelude::*,
|
||||
};
|
||||
use rand::Rng;
|
||||
|
||||
const AREA_WIDTH: f32 = 750.;
|
||||
const PLAYER_SIZE: f32 = 30.;
|
||||
|
||||
fn main() {
|
||||
let mut app = App::new();
|
||||
|
||||
// Interpolation and extrapolation functionality is enabled by the `PhysicsInterpolationPlugin`.
|
||||
// It is included in the `PhysicsPlugins` by default.
|
||||
app.add_plugins((
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
PhysicsPlugins::default().with_length_unit(50.0),
|
||||
));
|
||||
|
||||
// By default, interpolation must be enabled for each entity manually
|
||||
// by adding the `TransformInterpolation` component.
|
||||
//
|
||||
// It can also be enabled for all rigid bodies with `PhysicsInterpolationPlugin::interpolate_all()`:
|
||||
//
|
||||
// app.add_plugins(PhysicsPlugins::default().set(PhysicsInterpolationPlugin::interpolate_all()));
|
||||
|
||||
// Set gravity.
|
||||
app.insert_resource(Gravity(Vector::NEG_Y * 900.0));
|
||||
|
||||
// Set the fixed timestep to just 10 Hz for demonstration purposes.
|
||||
app.insert_resource(Time::from_hz(10.0));
|
||||
|
||||
// Setup the scene and UI, and update text in `Update`.
|
||||
app.add_systems(Startup, (setup_scene, setup_balls))
|
||||
PhysicsPlugins::default()
|
||||
.with_length_unit(50.0)
|
||||
.set(PhysicsInterpolationPlugin::interpolate_all()),
|
||||
))
|
||||
.add_systems(
|
||||
Startup,
|
||||
(setup_scene, setup_player, setup_balls, setup_walls),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
change_timestep,
|
||||
// Reset the scene when the 'R' key is pressed.
|
||||
reset_balls.run_if(input_pressed(KeyCode::KeyR)),
|
||||
),
|
||||
);
|
||||
|
||||
// Run the app.
|
||||
app.run();
|
||||
(move_player, quit.run_if(input_pressed(KeyCode::KeyQ))),
|
||||
)
|
||||
.insert_resource(Gravity(Vector::ZERO))
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Ball;
|
||||
|
||||
fn setup_scene(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
) {
|
||||
// Spawn a camera.
|
||||
fn setup_scene(mut commands: Commands) {
|
||||
commands.spawn(Camera2d);
|
||||
|
||||
// Spawn the ground.
|
||||
commands.spawn((
|
||||
Name::new("Ground"),
|
||||
RigidBody::Static,
|
||||
Collider::rectangle(500.0, 20.0),
|
||||
Restitution::new(0.99).with_combine_rule(CoefficientCombine::Max),
|
||||
Transform::from_xyz(0.0, -300.0, 0.0),
|
||||
Mesh2d(meshes.add(Rectangle::new(500.0, 20.0))),
|
||||
MeshMaterial2d(materials.add(Color::from(WHITE))),
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[require(Collider, Mesh2d, MeshMaterial2d<ColorMaterial>, Restitution(|| Restitution::new(1.0)), RigidBody, TransformInterpolation, Transform)]
|
||||
struct GameObject;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[require(GameObject, RigidBody(|| RigidBody::Dynamic))]
|
||||
struct Ball;
|
||||
|
||||
fn setup_balls(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
) {
|
||||
let circle = Circle::new(30.0);
|
||||
let mesh = meshes.add(circle);
|
||||
|
||||
// This entity uses transform interpolation.
|
||||
let mut rng = rand::rng();
|
||||
for _ in 0..20 {
|
||||
let circle = Circle::new(rng.random_range(10.0..(PLAYER_SIZE - 5.)));
|
||||
let mut transform = Transform::from_xyz(
|
||||
0.,
|
||||
rng.random_range((PLAYER_SIZE + 5.)..(AREA_WIDTH / 2. - PLAYER_SIZE)),
|
||||
0.,
|
||||
);
|
||||
transform.rotate_around(
|
||||
Vec3::ZERO,
|
||||
Quat::from_rotation_z(rng.random_range(0.0..(PI * 2.))),
|
||||
);
|
||||
commands.spawn((
|
||||
Name::new("Interpolation"),
|
||||
Ball,
|
||||
RigidBody::Dynamic,
|
||||
Collider::from(circle),
|
||||
TransformInterpolation,
|
||||
Transform::from_xyz(-100.0, 300.0, 0.0),
|
||||
Mesh2d(mesh.clone()),
|
||||
MeshMaterial2d(materials.add(Color::from(CYAN_400)).clone()),
|
||||
Mesh2d(meshes.add(circle)),
|
||||
MeshMaterial2d(materials.add(Color::from(RED_400))),
|
||||
transform,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// This entity uses transform extrapolation.
|
||||
commands.spawn((
|
||||
Name::new("Extrapolation"),
|
||||
Ball,
|
||||
RigidBody::Dynamic,
|
||||
Collider::from(circle),
|
||||
TransformExtrapolation,
|
||||
Transform::from_xyz(0.0, 300.0, 0.0),
|
||||
Mesh2d(mesh.clone()),
|
||||
MeshMaterial2d(materials.add(Color::from(LIME_400)).clone()),
|
||||
));
|
||||
#[derive(Component, Default)]
|
||||
#[require(Ball, RigidBody(|| RigidBody::Dynamic))]
|
||||
struct Player;
|
||||
|
||||
// This entity is simulated in `FixedUpdate` without any smoothing.
|
||||
fn setup_player(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
) {
|
||||
let circle = Circle::new(PLAYER_SIZE);
|
||||
commands.spawn((
|
||||
Name::new("No Interpolation"),
|
||||
Ball,
|
||||
RigidBody::Dynamic,
|
||||
Player,
|
||||
Collider::from(circle),
|
||||
Transform::from_xyz(100.0, 300.0, 0.0),
|
||||
Mesh2d(mesh.clone()),
|
||||
MeshMaterial2d(materials.add(Color::from(RED_400)).clone()),
|
||||
Mesh2d(meshes.add(circle)),
|
||||
MeshMaterial2d(materials.add(Color::from(LIME_400))),
|
||||
));
|
||||
}
|
||||
|
||||
/// Despawns all balls and respawns them.
|
||||
fn reset_balls(mut commands: Commands, query: Query<Entity, With<Ball>>) {
|
||||
for entity in &query {
|
||||
commands.entity(entity).despawn();
|
||||
#[derive(Component, Default)]
|
||||
#[require(GameObject, RigidBody(|| RigidBody::Static))]
|
||||
struct Wall;
|
||||
|
||||
fn setup_walls(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
) {
|
||||
let thickness = 20.;
|
||||
let width = AREA_WIDTH + thickness;
|
||||
|
||||
for i in 0..4 {
|
||||
let mut transform = Transform::from_xyz(0., AREA_WIDTH / 2., 0.);
|
||||
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_z((i as f32) * PI / 2.));
|
||||
commands.spawn((
|
||||
Wall,
|
||||
Collider::rectangle(width, thickness),
|
||||
Mesh2d(meshes.add(Rectangle::new(width, thickness))),
|
||||
MeshMaterial2d(materials.add(Color::from(WHITE))),
|
||||
transform,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
commands.run_system_cached(setup_balls);
|
||||
fn move_player(
|
||||
time: Res<Time>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut player: Query<&mut LinearVelocity, With<Player>>,
|
||||
) {
|
||||
let acceleration = 500.;
|
||||
|
||||
let mut velocity = player.single_mut();
|
||||
let delta_time = time.delta_secs();
|
||||
|
||||
if keyboard_input.any_pressed([KeyCode::KeyW, KeyCode::ArrowUp]) {
|
||||
velocity.y += acceleration * delta_time;
|
||||
}
|
||||
if keyboard_input.any_pressed([KeyCode::KeyS, KeyCode::ArrowDown]) {
|
||||
velocity.y -= acceleration * delta_time;
|
||||
}
|
||||
if keyboard_input.any_pressed([KeyCode::KeyA, KeyCode::ArrowLeft]) {
|
||||
velocity.x -= acceleration * delta_time;
|
||||
}
|
||||
if keyboard_input.any_pressed([KeyCode::KeyD, KeyCode::ArrowRight]) {
|
||||
velocity.x += acceleration * delta_time;
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the timestep of the simulation when the up or down arrow keys are pressed.
|
||||
fn change_timestep(mut time: ResMut<Time<Fixed>>, keyboard_input: Res<ButtonInput<KeyCode>>) {
|
||||
if keyboard_input.pressed(KeyCode::ArrowUp) {
|
||||
let new_timestep = (time.delta_secs_f64() * 0.975).max(1.0 / 255.0);
|
||||
time.set_timestep_seconds(new_timestep);
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::ArrowDown) {
|
||||
let new_timestep = (time.delta_secs_f64() * 1.025).min(1.0 / 5.0);
|
||||
time.set_timestep_seconds(new_timestep);
|
||||
}
|
||||
fn quit(mut exit: EventWriter<AppExit>) {
|
||||
exit.send(AppExit::Success);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue