【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,
}
ReadRapierContext
と WriteRapierContext
現在の正しい取得方法: 現在のバージョンでは、RapierContext
を取得するための専用のSystemParam
が用意されています。
-
ReadRapierContext
: 物理ワールドの状態を読み取る(変更しない)ためのSystemParam
。 -
WriteRapierContext
: 物理ワールドの状態を書き換える(変更する)ためのSystemParam
。
ReadRapierContext
)
1. 読み取り専用の場合 (物理ワールドの状態を問い合わせるだけで、変更を加えない場合はReadRapierContext
を使います。
手順:
- システムの引数に
rapier_context: ReadRapierContext
を追加します。 - システム内で
.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);
}
}
WriteRapierContext
)
2. 書き込みを行う場合 (もし物理ワールドの状態を直接変更するような高度な操作が必要な場合はWriteRapierContext
を使います。
手順:
- システムの引数に
mut rapier_context: WriteRapierContext
を追加します。 - システム内で
.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