You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello everyone, A noob here trying to learn about the engine.
I am currently working on physics and inputs. I use Rapier to handle the physics of my "Dynamic" objects and implement my own physics for the player's movement. As far as I know, that's how it's meant to be done.
The guide has a strategy of "frame interpolation" in which you divide your physics into fixed timesteps and then interpolate the Transform each frame between past and present physical positions, that way you obtain a smooth rendering.
The issue arises when I tell rapier to do the same with dynamic objects. There is an option that lets the Rapier engine use the interpolation strategy.
TimestepMode::Interpolated
You end up with scenarios like the one in the picture, where if I move my player very slowly(press and release the key), he can slide off the ball, and the ball will still be there floating in the void.
Things to note in here is that the ball is a RigidBody::Dynamic and the player is a RigidBody::KinematicPositionBased.
Here is my code(just ignore the asset in the player's definition):
use bevy::{color::palettes::css::RED, prelude::*};
use bevy_rapier3d::prelude::*;
use bevy::input::keyboard::KeyboardInput;
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
struct AccumulatedInput(Vec2);
/// A vector representing the player's velocity in the physics simulation.
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
struct Velocity(Vec3);
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
struct PhysicalTranslation(Vec3);
/// The value [`PhysicalTranslation`] had in the last fixed timestep.
/// Used for interpolation in the `interpolate_rendered_transform` system.
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
struct PreviousPhysicalTranslation(Vec3);
fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut query: Query<(&mut AccumulatedInput, &mut Velocity)>,
) {
/// Since Bevy's default 2D camera setup is scaled such that
/// one unit is one pixel, you can think of this as
/// "How many pixels per second should the player move?"
const SPEED: f32 = 2.0;
for (mut input, mut velocity) in query.iter_mut() {
if keyboard_input.pressed(KeyCode::KeyW) {
input.y += 1.0;
}
if keyboard_input.pressed(KeyCode::KeyS) {
input.y -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyA) {
input.x -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyD) {
input.x += 1.0;
}
// Need to normalize and scale because otherwise
// diagonal movement would be faster than horizontal or vertical movement.
// This effectively averages the accumulated input.
velocity.0 = Vec3::new(input.x, 0.0, input.y).normalize_or_zero() * SPEED;
}
}
fn advance_physics(
fixed_time: Res<Time<Fixed>>,
mut query: Query<(
&mut PhysicalTranslation,
&mut PreviousPhysicalTranslation,
&mut AccumulatedInput,
&Velocity,
)>,
) {
for (
mut current_physical_translation,
mut previous_physical_translation,
mut input,
velocity,
) in query.iter_mut()
{
previous_physical_translation.0 = current_physical_translation.0;
current_physical_translation.0 += velocity.0 * fixed_time.delta_secs();
// Reset the input accumulator, as we are currently consuming all input that happened since the last fixed timestep.
input.0 = Vec2::ZERO;
}
}
fn interpolate_rendered_transform(
fixed_time: Res<Time<Fixed>>,
mut query: Query<(
&mut Transform,
&PhysicalTranslation,
&PreviousPhysicalTranslation,
)>,
) {
for (mut transform, current_physical_translation, previous_physical_translation) in
query.iter_mut()
{
let previous = previous_physical_translation.0;
let current = current_physical_translation.0;
// The overstep fraction is a value between 0 and 1 that tells us how far we are between two fixed timesteps.
let alpha = fixed_time.overstep_fraction();
let rendered_translation = previous.lerp(current, alpha);
transform.translation = rendered_translation;
}
}
fn print_keyboard_events(
mut keyboard_events: EventReader<KeyboardInput>
){
for keyboard_event in keyboard_events.read() {
info!("{:?}", keyboard_event);
}
}
#[derive(Component)]
struct Person;
fn move_person(
mut query: Query<&mut Transform, With<Person>>,
time: Res<Time>
){
for mut transform in &mut query {
//transform.translation.x = transform.translation.x + 1.0*time.delta().as_secs_f32();
let rotation_speed = std::f32::consts::PI / 4.0; // 45° in radians
let delta_rotation = Quat::from_rotation_y(rotation_speed * time.delta().as_secs_f32());
transform.rotation *= delta_rotation;
}
}
fn spawn_player(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
asset_server: Res<AssetServer>
){
let texture: Handle<Image> = asset_server.load("branding/icon.png");
let player_bundle = (
Person,
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(materials.add(
StandardMaterial {
base_color_texture: Some(texture),
..default()
}
)),
AccumulatedInput::default(),
Velocity::default(),
PhysicalTranslation::default(),
PreviousPhysicalTranslation::default(),
Transform::from_xyz(0.0, 0.5, 0.0),
RigidBody::KinematicPositionBased,
Collider::cuboid(0.5, 0.5, 0.5),
TransformInterpolation { start: None, end: None },
);
commands.spawn(player_bundle);
}
fn spawn_camera(
mut commands: Commands,
){
let camera_bundle = (
Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
);
commands.spawn(camera_bundle);
}
fn spawn_light(
mut commands: Commands,
){
let light_bundle = (
PointLight {
shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
);
commands.spawn(light_bundle);
}
fn spawn_ball(
mut commands: Commands,
mut materials: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
){
let ball = (
Mesh3d(meshes.add(Sphere::new(1.0))),
MeshMaterial3d(materials.add(
StandardMaterial{
base_color: RED.into(),
..Default::default()
}
)),
Transform::from_xyz(0.0, 3.0, 0.0),
RigidBody::Dynamic,
Collider::ball(1.0),
Restitution::coefficient(0.7),
TransformInterpolation { start: None, end: None },
);
commands.spawn(ball);
}
fn spawn_floor(
mut commands: Commands
){
let floor = (
Collider::cuboid(100.0, 0.1, 100.0),
Transform::from_xyz(0.0, 0.0, 0.0)
);
commands.spawn(floor);
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(RapierPhysicsPlugin::<()>::default())
.add_plugins(RapierDebugRenderPlugin::default())
.insert_resource(TimestepMode::Interpolated { dt: 1.0/ 60.0, time_scale: 1.0, substeps: 1 })
.add_systems(Startup, (spawn_camera, spawn_light, spawn_player, spawn_floor, spawn_ball))
.add_systems(FixedUpdate, advance_physics)
.add_systems(RunFixedMainLoop, (
print_keyboard_events.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop),
handle_input.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop),
interpolate_rendered_transform.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop)
))
.run();
}
PS. I think I found the solution while writing this.
You have to use RigidBody::KinematicVelocityBased instead of RigidBody::KinematicPositionBased
Anyway, I will still create the post for anyone that faces the same issue in the future.
And maybe someone can explain why use KinematicVelocityBased and not KinematicPositionBased because the documentation in rapier said this:
RigidBodyType::KinematicPositionBased: Indicates that the body position must not be altered by the physics engine. The user is free to set its next position and the body velocity will be deduced at each update accordingly to ensure a realistic behavior of dynamic bodies in contact with it. This is typically used for moving platforms, elevators, etc.
RigidBodyType::KinematicVelocityBased: Indicates that the body velocity must not be altered by the physics engine. The user is free to set its velocity and the next body position will be deduced at each update accordingly to ensure a realistic behavior of dynamic bodies in contact with it. This is typically used for moving platforms, elevators, etc.
Both position-based and velocity-based kinematic bodies are mostly the same. Choosing between both is mostly a matter of preference between position-based control and velocity-based control.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hello everyone, A noob here trying to learn about the engine.
I am currently working on physics and inputs. I use Rapier to handle the physics of my "Dynamic" objects and implement my own physics for the player's movement. As far as I know, that's how it's meant to be done.
By following this guide: https://github.com/bevyengine/bevy/blob/latest/examples/movement/physics_in_fixed_timestep.rs
I learnt a good way of doing physics in fixed timesteps and managing the inputs.
The guide has a strategy of "frame interpolation" in which you divide your physics into fixed timesteps and then interpolate the Transform each frame between past and present physical positions, that way you obtain a smooth rendering.
The issue arises when I tell rapier to do the same with dynamic objects. There is an option that lets the Rapier engine use the interpolation strategy.
TimestepMode::Interpolated
You end up with scenarios like the one in the picture, where if I move my player very slowly(press and release the key), he can slide off the ball, and the ball will still be there floating in the void.
Things to note in here is that the ball is a RigidBody::Dynamic and the player is a RigidBody::KinematicPositionBased.
Here is my code(just ignore the asset in the player's definition):
PS. I think I found the solution while writing this.
You have to use RigidBody::KinematicVelocityBased instead of RigidBody::KinematicPositionBased
Anyway, I will still create the post for anyone that faces the same issue in the future.
And maybe someone can explain why use KinematicVelocityBased and not KinematicPositionBased because the documentation in rapier said this:
Beta Was this translation helpful? Give feedback.
All reactions