剑舞bevy_rapier2d_例子分析

动力学箱子 + 静态刚体地面.

箱子受重力影响。

use bevy::prelude::*;
use bevy_rapier2d::prelude::*;

fn main() {
    App::new()
          .insert_resource(ClearColor(Color::srgb(
            0xF9 as f32 / 255.0,
            0xF9 as f32 / 255.0,
            0xFF as f32 / 255.0,
        )))
            .add_plugins((
            DefaultPlugins,
            RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0),
            RapierDebugRenderPlugin::default(),
        ))
            .add_systems(Startup, (setup_graphics, setup_physics))
          .run();
}

pub fn setup_graphics(mut commands: Commands) {
    commands.spawn((Camera2d, Transform::from_xyz(0.0, 20.0, 0.0)));
}

pub fn setup_physics(mut commands: Commands) {
    /*
     * Ground
     */
    let ground_size = 500.0;
    let ground_height = 10.0;

    commands.spawn((
        Transform::from_xyz(0.0, 0.0 * -ground_height, 0.0),
        Collider::cuboid(ground_size, ground_height),
    ));

    /*
     * Create the cubes
     */
    let num = 8;
    let rad = 10.0;

    let shift = rad * 2.0 + rad;
    let centerx = shift * (num / 2) as f32;
    let centery = shift / 2.0;

    let mut offset = -(num as f32) * (rad * 2.0 + rad) * 0.5;

    for j in 0usize..20 {
        for i in 0..num {
            let x = i as f32 * shift - centerx + offset;
            let y = j as f32 * shift + centery + 30.0;

            commands.spawn((
                Transform::from_xyz(x, y, 0.0),
                RigidBody::Dynamic,
                Collider::cuboid(rad, rad),
            ));
        }

        offset -= 0.05 * rad * (num as f32 - 1.0);
    }
}

碰撞器变大会有一个爆炸效果。

use bevy::prelude::*;
use bevy_rapier2d::prelude::*;

#[derive(Component, Default)]
pub struct Despawn;
#[derive(Component, Default)]
pub struct Resize;

#[derive(Resource, Default)]
pub struct DespawnResource {
    timer: Timer,
}

#[derive(Resource, Default)]
pub struct ResizeResource {
    timer: Timer,
}

fn main() {
    App::new()
        .insert_resource(ClearColor(Color::srgb(
            0xF9 as f32 / 255.0,
            0xF9 as f32 / 255.0,
            0xFF as f32 / 255.0,
        )))
        .insert_resource(DespawnResource::default())
        .insert_resource(ResizeResource::default())
        .add_plugins((
            DefaultPlugins,
            RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0),
            RapierDebugRenderPlugin::default(),
        ))
        .add_systems(Startup, (setup_graphics, setup_physics))
        .add_systems(Update, (despawn, resize))
        .run();
}

pub fn setup_graphics(
    mut commands: Commands,
    mut despawn: ResMut<DespawnResource>,
    mut resize: ResMut<ResizeResource>,
) {
    resize.timer = Timer::from_seconds(6.0, TimerMode::Once);
    despawn.timer = Timer::from_seconds(11.0, TimerMode::Once);

    commands.spawn((Camera2d, Transform::from_xyz(0.0, 20.0, 0.0)));
}

pub fn setup_physics(mut commands: Commands) {
    /*
     * Ground
     */
    let ground_size = 250.0;

    commands.spawn((Collider::cuboid(ground_size, 12.0), Despawn));

    commands.spawn((
        Transform::from_xyz(ground_size, ground_size * 2.0, 0.0),
        Collider::cuboid(12.0, ground_size * 2.0),
    ));

    commands.spawn((
        Transform::from_xyz(-ground_size, ground_size * 2.0, 0.0),
        Collider::cuboid(12.0, ground_size * 2.0),
    ));

    /*
     * Create the cubes
     */
    let num = 20;
    let rad = 5.0;

    let shift = rad * 2.0;
    let centerx = shift * (num as f32) / 2.0;
    let centery = shift / 2.0;

    for i in 0..num {
        for j in 0usize..num * 5 {
            let x = i as f32 * shift - centerx;
            let y = j as f32 * shift + centery + 2.0;

            let mut entity = commands.spawn((
                Transform::from_xyz(x, y, 0.0),
                RigidBody::Dynamic,
                Collider::cuboid(rad, rad),
            ));

            if (i + j * num) % 100 == 0 {
                entity.insert(Resize);
            }
        }
    }
}

pub fn despawn(
    mut commands: Commands,
    time: Res<Time>,
    mut despawn: ResMut<DespawnResource>,
    query: Query<Entity, With<Despawn>>,
) {
    if despawn.timer.tick(time.delta()).just_finished() {
        for e in &query {
            commands.entity(e).despawn();
        }
    }
}

pub fn resize(
    mut commands: Commands,
    time: Res<Time>,
    mut resize: ResMut<ResizeResource>,
    query: Query<Entity, With<Resize>>,
) {
    if resize.timer.tick(time.delta()).just_finished() {
        for e in &query {
            commands.entity(e).insert(Collider::cuboid(20.0, 20.0));
        }
    }
}

为了效果不那么一眨眼就不见,修改物理具体和游戏距离的映射。 RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(2.0),.

pub fn display_events(
    mut collision_events: EventReader<CollisionEvent>,
    mut contact_force_events: EventReader<ContactForceEvent>,
) {
    for collision_event in collision_events.read() {
        println!("Received collision event: {collision_event:?}");
    }

    for contact_force_event in contact_force_events.read() {
        println!("Received contact force event: {contact_force_event:?}");
    }
}

pub fn setup_physics(mut commands: Commands) {
    /*
     * Ground
     */
    // 碰到地面就会有碰撞事件。
    commands.spawn((
        Transform::from_xyz(0.0, -24.0, 0.0),
        Collider::cuboid(80.0, 20.0),
    ));

    // 传感器仅做检测,不参与力的交互。
    // 进出传感器都会发送事件。
    commands.spawn((
        Transform::from_xyz(0.0, 100.0, 0.0),
        Collider::cuboid(80.0, 30.0),
        Sensor,
    ));

    commands.spawn((
        Transform::from_xyz(0.0, 260.0, 0.0),
        RigidBody::Dynamic,
        Collider::cuboid(10.0, 10.0),
        ActiveEvents::COLLISION_EVENTS,
        ContactForceEventThreshold(10.0),
    ));
}

高度500有个对象是旋转锁定A;高度300有个对象是平移锁定B.

A落下碰到B,B开始旋转;A继续落到地面,且保持旋转角度不变。

旋转锁定对象从500落到300,出发300

pub fn setup_physics(mut commands: Commands) {
    /*
     * The ground
     */
    let ground_size = 500.0;
    let ground_height = 10.0;

    commands.spawn((
        Transform::from_xyz(0.0, -ground_height, 0.0),
        Collider::cuboid(ground_size, ground_height),
    ));

    /*
     * A rectangle that only rotate.
     */
    commands.spawn((
        Transform::from_xyz(0.0, 300.0, 0.0),
        RigidBody::Dynamic,
        LockedAxes::TRANSLATION_LOCKED,
        Collider::cuboid(200.0, 60.0),
    ));

    /*
     * A tilted cuboid that cannot rotate.
     */
    commands.spawn((
        Transform::from_xyz(50.0, 500.0, 0.0).with_rotation(Quat::from_rotation_z(1.0)),
        RigidBody::Dynamic,
        LockedAxes::ROTATION_LOCKED,
        Collider::cuboid(60.0, 40.0),
    ));
}

一个动力学刚体带多个碰撞器

commands
  .spawn((Transform::from_xyz(x, y, 0.0), RigidBody::Dynamic))
    .with_children(|children| {
      children.spawn(Collider::cuboid(rad * 10.0, rad));
      children.spawn((
          Transform::from_xyz(rad * 10.0, rad * 10.0, 0.0),
          Collider::cuboid(rad, rad * 10.0),
      ));
      children.spawn((
          Transform::from_xyz(-rad * 10.0, rad * 10.0, 0.0),
          Collider::cuboid(rad, rad * 10.0),
      ));
  });

零重力,通过修改速度来控制移动。

// The float value is the player movement speed in 'pixels/second'.
#[derive(Component)]
pub struct Player(f32);

pub fn spawn_player(
    mut commands: Commands,
    mut rapier_config: Query<&mut RapierConfiguration>,
) -> Result<()> {
    let mut rapier_config = rapier_config.single_mut()?;
    // Set gravity to 0.0 and spawn camera.
    rapier_config.gravity = Vec2::ZERO;
    commands.spawn(Camera2d);

    let sprite_size = 100.0;

    // Spawn entity with `Player` struct as a component for access in movement query.
    commands.spawn((
        Sprite {
            color: Color::srgb(0.0, 0.0, 0.0),
            custom_size: Some(Vec2::new(sprite_size, sprite_size)),
            ..Default::default()
        },
        RigidBody::Dynamic,
        Velocity::zero(),
        Collider::ball(sprite_size / 2.0),
        Player(100.0),
    ));

    Ok(())
}

pub fn player_movement(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut player_info: Query<(&Player, &mut Velocity)>,
) {
    for (player, mut rb_vels) in &mut player_info {
        let up = keyboard_input.any_pressed([KeyCode::KeyW, KeyCode::ArrowUp]);
        let down = keyboard_input.any_pressed([KeyCode::KeyS, KeyCode::ArrowDown]);
        let left = keyboard_input.any_pressed([KeyCode::KeyA, KeyCode::ArrowLeft]);
        let right = keyboard_input.any_pressed([KeyCode::KeyD, KeyCode::ArrowRight]);

        let x_axis = -(left as i8) + right as i8;
        let y_axis = -(down as i8) + up as i8;

        let mut move_delta = Vec2::new(x_axis as f32, y_axis as f32);
        if move_delta != Vec2::ZERO {
            move_delta /= move_delta.length();
        }

        // Update the velocity on the rigid_body_component,
        // the bevy_rapier plugin will update the Sprite transform.
        rb_vels.linvel = move_delta * player.0;
    }
}

首先用物理钩子的实现替换了NoUserData,有相同标记组件的会走到解算阶段。

use bevy::{ecs::system::SystemParam, prelude::*};
use bevy_rapier2d::prelude::*;

#[derive(PartialEq, Eq, Clone, Copy, Component)]
enum CustomFilterTag {
    GroupA,
    GroupB,
}

// A custom filter that allows contacts only between rigid-bodies with the
// same user_data value.
// Note that using collision groups would be a more efficient way of doing
// this, but we use custom filters instead for demonstration purpose.
#[derive(SystemParam)]
struct SameUserDataFilter<'w, 's> {
    tags: Query<'w, 's, &'static CustomFilterTag>,
}

impl BevyPhysicsHooks for SameUserDataFilter<'_, '_> {
    fn filter_contact_pair(&self, context: PairFilterContextView) -> Option<SolverFlags> {
        if self.tags.get(context.collider1()).ok().copied()
            == self.tags.get(context.collider2()).ok().copied()
        {
            Some(SolverFlags::COMPUTE_IMPULSES)
        } else {
            None
        }
    }
}

fn main() {
    App::new()
        .insert_resource(ClearColor(Color::srgb(
            0xF9 as f32 / 255.0,
            0xF9 as f32 / 255.0,
            0xFF as f32 / 255.0,
        )))
        .add_plugins((
            DefaultPlugins,
            RapierPhysicsPlugin::<SameUserDataFilter>::pixels_per_meter(100.0),
            RapierDebugRenderPlugin::default(),
        ))
        .add_systems(Startup, (setup_graphics, setup_physics))
        .run();
}

fn setup_graphics(mut commands: Commands) {
    commands.spawn((Camera2d, Transform::from_xyz(0.0, 20.0, 0.0)));
}

pub fn setup_physics(mut commands: Commands) {
    /*
     * Ground
     */
    let ground_size = 100.0;

    commands.spawn((
        Transform::from_xyz(0.0, -100.0, 0.0),
        Collider::cuboid(ground_size, 12.0),
        CustomFilterTag::GroupA,
    ));

    commands.spawn((
        Transform::from_xyz(0.0, 0.0, 0.0),
        Collider::cuboid(ground_size, 12.0),
        CustomFilterTag::GroupB,
    ));

    /*
     * Create the cubes
     */
    let num = 4;
    let rad = 5.0;

    let shift = rad * 2.0;
    let centerx = shift * (num as f32) / 2.0;
    let centery = shift / 2.0;
    let mut group_id = 0;
    let tags = [CustomFilterTag::GroupA, CustomFilterTag::GroupB];
    let colors = [Hsla::hsl(220.0, 1.0, 0.3), Hsla::hsl(260.0, 1.0, 0.7)];

    for i in 0..num {
        for j in 0usize..num * 5 {
            let x = (i as f32 + j as f32 * 0.2) * shift - centerx;
            let y = j as f32 * shift + centery + 20.0;
            group_id += 1;

            commands.spawn((
                Transform::from_xyz(x, y, 0.0),
                RigidBody::Dynamic,
                Collider::cuboid(rad, rad),
                ActiveHooks::FILTER_CONTACT_PAIRS,
                tags[group_id % 2],
                ColliderDebugColor(colors[group_id % 2]),
            ));
        }
    }
}

这个估计很少有人用到。

另外还有个debug的toggle.