jcornaz / heron

[DISCONTINUED] An ergonomic physics API for bevy games
MIT License
293 stars 44 forks source link

Character controller #96

Open yaymalaga opened 3 years ago

yaymalaga commented 3 years ago

Right now, the only way to control kinematic bodies is by manually changing their transform. However, collisions are not taken into account by default, delegating that logic to the final user. This would be really useful to have even in basic games, such as platformers, where the player needs to collide with the floor.

For instance, Godot includes some high-level functions to handle this behavior, as explained in its documentation: Movement and collision and Detecting collisions.

This was discussed a while ago in the Bevy's discord physics channel, where Rapier's developer said:

Almost no physics engines compute contacts between two non-dynamic bodies (however kinematic bodies do affect dynamic bodies). So you are supposed to use shape-casting to detect collision with the static environment. The godot "move_and_collide" and "move_and_slide" are just higher-level operations which are based on shape-casting.

jcornaz commented 3 years ago

Yes, I aggree. But sadly that's rapier behavior.

And I'm not eager to solve this problem in heron. I'd rather see it solved in rapier first, and then add the API to use it in heron.

After all, even if move_and_slide is "higher-level", it is not especially related to bevy. Any user of rapier may want this feature, regardless of the game engine being used.

To be honest, I had the same feeling when I started to use rapier (as I'm coming from Godot). But I realized, that instead of using a kinematic body for a player character, I could use a dynamic body, and only make sure to redefine the body velocity before each physics step. Maybe it is just that a "kinematic body" is the best choice for a player character in godot (because of that higher level API), but with rapier it is "dynamic body" which is the best choice for a player character.

If you really want this feature, you can propose a PR. But, if that's the case, your efforts may be better spent on adding this feature to rapier directly.

Maybe later, after addressing more pressing issues (offset, multiple-shapes-per-body, ray-cast, layers, etc.), I may have a closer look at this. But when that day will come, I'll probably consider contribute the feature to rapier directly.

yaymalaga commented 3 years ago

Makes total sense, in fact I was checking Rapier's roadmap for this year just now and it says:

We also intend to implement some higher-level features like a character controller and a vehicle controller. Both should be available starting October. Most games will need controllers obeying their specific custom rules, so it is impossible to design a universal solution. Our goal is to provide something useful to start with, to serve as a basis to more specific character and vehicle controllers.

As you suggests, maybe it's better to just wait for that feature to land, and then rework it in Heron if necessary, or to contribute it directly to Rapier.

zicklag commented 3 years ago

But I realized, that instead of using a kinematic body for a player character, I could use a dynamic body, and only make sure to redefine the body velocity before each physics step. Maybe it is just that a "kinematic body" is the best choice for a player character in godot (because of that higher level API), but with rapier it is "dynamic body" which is the best choice for a player character.

For what it's worth, this is the approach I've been taking and it is working well so far:

https://user-images.githubusercontent.com/25393315/124199659-07c2f200-da99-11eb-83c8-e4ebdff5dbf4.mp4

I just set the velocity of the player every frame, and if I try to push him into a wall he just stops, as you would expect. In the video it only looks like he's moving jaggedly because I'm changing my direction diagonal as I push into the blue triangle.

Here's the code if anybody wants to check it out. ( keep in mind I have some of my own extension to the physics for generating sprite collision shapes in that example )

edgarssilva commented 3 years ago

I think this should be fixed in Issue #140

Whimfoome commented 2 years ago

With RigidBody::KinematicVelocityBased, it detects collisions, but doesn't stop on them and falls right through them.

https://user-images.githubusercontent.com/42871796/154855725-a3637ab8-7e5e-4bd2-ba53-33b68483a4d7.mp4

// Paltform
fn spawn_world(
    mut commands: Commands,
) {
    // The ground
    let size = Vec2::new(1000.0, 50.0);
    commands
        // Spawn a bundle that contains at least a `GlobalTransform`
        .spawn_bundle(SpriteBundle {
            sprite: Sprite {
                color: Color::WHITE,
                custom_size: Some(size),
                ..Default::default()
            },
            transform: Transform::from_translation(Vec3::new(0.0, -300.0, 0.0)),
            ..Default::default()
        })
        // Make it a rigid body
        .insert(RigidBody::Static)
        // Attach a collision shape
        .insert(CollisionShape::Cuboid {
            half_extends: size.extend(0.0) / 2.0,
            border_radius: None,
        });
}
// Player
#[derive(Default, Component)]
struct Movement {
    gravity: f32,
}

#[derive(Bundle)]
struct PhysicsBundle {
    movement: Movement,
    rigidbody: RigidBody,
    collision: CollisionShape,
    motion_velocity: Velocity,
}

pub fn setup(
    mut commands: Commands, 
) {
    // ....... some unrelated code

    .insert_bundle(PhysicsBundle {
        movement: Movement { 
            gravity: 37.5,
            ..Default::default()
        },
        rigidbody: RigidBody::KinematicVelocityBased,
        collision: CollisionShape::Cuboid {
            half_extends: Vec2::new(32.0 * 6.0, 32.0 * 6.0).extend(0.0) / 2.0,
            border_radius: None,
        },
        motion_velocity: Velocity::default(),
    });
}

fn movement_system(
    mut query: Query<(&mut Movement, &mut Velocity)>,
) {
    let (mut movement, mut motion_velocity) = query.single_mut();

    motion_velocity.linear.y -= movement.gravity;
}

Is there a similar function to Godot's move_and_slide to handle all that aabb + slopes + other things logic

Edit: Another question, how to get delta time as now it seems that the faster the computer a user has, the faster the physics are?

jcornaz commented 2 years ago

With RigidBody::KinematicVelocityBased, it detects collisions, but doesn't stop on them and falls right through them.

That's expected behavior. If it is kinematic, it means you are controlling the movement, and the phyiscs engine will not alter that, not even to prevent inter-penetration.

Is there a similar function to Godot's move_and_slide to handle all that aabb + slopes + other things logic

No. Mostly because rapier doesn't have that functionallity. And I would prefer to see that solved in rapier first (see my first answer in this issue)

Edit: Another question, how to get delta time as now it seems that the faster the computer a user has, the faster the physics are?

If you use the default settings you may use the time resource normally. For more complex setups, you may retrieve the configured time from the PhysicsSteps resource.

agg23 commented 2 years ago

It's particularly unclear how to handle this in situations with gravity. Simply zeroing out velocity every tick isn't sufficient, as you lose gravity, and it's possible (though lower priority) that you do want some non-static bodies to impart forces on your character.

There's a recently opened issue on Rapier for this: https://github.com/dimforge/rapier/issues/307