👌

【Bevy 0.15 & Rapier2D 0.29 対応】`RapierContext`の正しい取得方法と使い方

に公開

無駄に正解にたどり着くまで苦労しました。
基本はコードよめですね。

まとめ ReadRapierContextを使う

rapier_context: ReadRapierContext,
詳しく知りたい場合は、rapier_context_systemparam.rsを読む/読ませるといい。

以下、Gemini Pro 2.5による説明

Bevyで物理演算ライブラリbevy_rapierを使っていると、物理ワールドの状態にアクセスしたくなる場面がよくあります。例えば、「特定の座標にレイを飛ばして何かに当たっているか調べたい」「現在接触しているオブジェクトのペアをすべて取得したい」といったケースです。

bevy_rapierのバージョンアップに伴い、その取得方法は変化してきました。この記事では、Bevy 0.15とRapier 0.29の環境で推奨されている、SystemParamを使ったRapierContextの正しい取得方法と実践的な使い方を分かりやすく解説します。

RapierContextの取得方法の変遷

bevy_rapierの歴史の中で、RapierContextの取得方法は以下のように変わりました。

  • 古いバージョン: システムの引数で Res<RapierContext>NonSend<RapierContext> を使ってアクセスしていました。
  • 現在のバージョン (Bevy 0.15 & Rapier 0.29): 専用のSystemParamである ReadRapierContext または WriteRapierContext を使ってアクセスします。

古いチュートリアルやコードサンプルで見られる Res<RapierContext> などの方法は、現在のバージョンでは動作しないため注意が必要です。

RapierContextとは?

RapierContextは、物理シミュレーションに関する様々なデータや機能へのアクセスを一つにまとめた、便利なヘルパー構造体です。

これを使うことで、物理ワールドの状態(コライダー、剛体、ジョイントなど)に、一つの窓口からアクセスできるようになり、コードが非常にすっきりとします。

// RapierContextが内包する主な要素
pub struct RapierContext<'a> {
    /// 物理エンジン全体の状態
    pub simulation: &'a RapierContextSimulation,
    /// コライダーの集合
    pub colliders: &'a RapierContextColliders,
    /// ジョイントの集合
    pub joints: &'a RapierContextJoints,
    /// レイキャストなどのクエリを実行するパイプライン
    pub query_pipeline: &'a RapierQueryPipeline,
    /// 剛体の集合
    pub rigidbody_set: &'a RapierRigidBodySet,
}

現在の正しい取得方法: ReadRapierContextWriteRapierContext

現在のバージョンでは、RapierContextを取得するための専用のSystemParamが用意されています。

  • ReadRapierContext: 物理ワールドの状態を読み取る(変更しない)ためのSystemParam
  • WriteRapierContext: 物理ワールドの状態を書き換える(変更する)ためのSystemParam

1. 読み取り専用の場合 (ReadRapierContext)

物理ワールドの状態を問い合わせるだけで、変更を加えない場合はReadRapierContextを使います。

手順:

  1. システムの引数に rapier_context: ReadRapierContext を追加します。
  2. システム内で .single() メソッドを呼び出し、RapierContext のインスタンスを取得します。
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;

fn raycast_system(
    // 1. システムの引数として ReadRapierContext を受け取る
    rapier_context: ReadRapierContext,
) {
    // 2. .single() を呼び出して RapierContext を取得
    let context = rapier_context.single();

    let ray_origin = Vec2::ZERO;
    let ray_dir = Vec2::new(0.0, -1.0);
    let max_toi = 1000.0; // Time of Impact (衝突までの時間/距離)
    let solid = true;
    let filter = QueryFilter::default();

    // 取得した context を使って物理クエリを実行
    if let Some((entity, toi)) = context.cast_ray(
        ray_origin, ray_dir, max_toi, solid, filter
    ) {
        println!("レイがEntity {:?} に距離 {} で衝突しました。", entity, toi);
    }
}

2. 書き込みを行う場合 (WriteRapierContext)

もし物理ワールドの状態を直接変更するような高度な操作が必要な場合はWriteRapierContextを使います。

手順:

  1. システムの引数に mut rapier_context: WriteRapierContext を追加します。
  2. システム内で .single_mut() メソッドを呼び出し、RapierContextMut のインスタンスを取得します。
fn advanced_physics_system(
    // 1. システムの引数として mut WriteRapierContext を受け取る
    mut rapier_context: WriteRapierContext,
) {
    // 2. .single_mut() を呼び出して RapierContextMut を取得
    let mut context_mut = rapier_context.single_mut();
    
    // context_mut を使って物理ワールドを直接操作する
    // (例: context_mut.simulation.step_simulation(...) など)
}```

### 実践例:接触している全てのオブジェクトペアを取得する

それでは、より実践的な例として、現在接触している全ての`StackBox`コンポーネントを持つエンティティのペアを検出するシステムを見てみましょう。

```rust
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;

#[derive(Component)]
struct StackBox;

fn collision_detection_system(
    boxes: Query<Entity, With<StackBox>>,
    rapier_context: ReadRapierContext,
) {
    let context = rapier_context.single();

    // 1. 現在の接触ペアをすべてイテレートする
    for contact_pair in context.simulation.narrow_phase.contact_pairs() {
        
        // 2. ColliderHandle から Entity に変換する
        //    .colliders が2回続く点に注意!
        let entity1 = context.colliders.colliders.get(contact_pair.collider1)
            .map(|c| Entity::from_bits(c.user_data as u64));
        let entity2 = context.colliders.colliders.get(contact_pair.collider2)
            .map(|c| Entity::from_bits(c.user_data as u64));

        if let (Some(e1), Some(e2)) = (entity1, entity2) {
            // 接触した両方が StackBox かどうかをチェック
            if boxes.get(e1).is_ok() && boxes.get(e2).is_ok() {
                println!("StackBox {:?} と {:?} が接触しました!", e1, e2);
            }
        }
    }
}

なぜ context.colliders.colliders と2回続くのか?

このコードを見て「なぜ.collidersが2回も?」と疑問に思うかもしれません。これはバグではなく、bevy_rapierの設計によるものです。

  • 1つ目の context.colliders: RapierContextCollidersというBevy連携用の便利な道具箱です。BevyのEntityとRapierのColliderHandleを対応付ける情報などもここに含まれています。
  • 2つ目の .colliders: RapierContextCollidersの中に入っている、Rapier物理エンジン本体が直接使う純粋なコライダーの集合データ (ColliderSet) です。

個々のコライダー情報にアクセスする.get()メソッドは、後者のColliderSetが持っているため、このような記述になります。

まとめ

bevy_rapierで物理ワールドにアクセスするための要点をおさらいしましょう。

  • 現在の方法: システムの引数には、読み取りなら ReadRapierContext、書き込みなら WriteRapierContext を使う。
  • 取得手順: システム内で .single() (読み取り) または .single_mut() (書き込み) を呼び出して、実際のコンテキストを取得する。
  • 古い方法: Res<RapierContext>NonSend<RapierContext> は過去のものであり、現在のバージョンでは使わない。
  • context.colliders.colliders: プロパティ名が重複して見えるのは、役割の違うものを区別するための正しい仕様。

この現代的な方法をマスターすれば、Bevy 0.15とRapier 0.29の環境で、より高度な物理シミュレーションをクリーンなコードで実装できるようになるはずです。

Discussion