【Rustのまほう2】#23 物理エンジンの初期化

適度なネタが思いつかなかったので、一番大きな話題ともいえる物理エンジンについて触れておきます。

大抵のアクションゲームでは、たとえばキャラクターと壁の当たり判定や、ジャンプしたときに放物線にそって動く計算などが必要になるでしょう。簡単な動きだけでいいなら自力でコーディングしてもいいですし、実際私もおととし書いたアドベントカレンダーでは自力で当たり判定を書いています。

https://qiita.com/hiruberuto/items/c3a40731f44a986fdd8b

でも、自力で実装したアクションというのはかなりショボいです。そしてそうやって自力で実装したアクションゲームだと、キャラクターとリフトの摩擦が考慮されておらず[リフトに乗ったらリフトだけが移動して取り残されたキャラクターが落下](https://nlab.itmedia.co.jp/nl/articles/2411/24/news037.html)というバグがよく起こったりします。いろんな挙動をなるべく自然に見せたかったら、結局物理エンジンを導入するのが無難かなと思います。

一方で、物理エンジンは設定項目が多く、設定を間違えると変な動きをするわけに原因がさっぱりわからない、と非常に厄介です。かなり覚悟を決めてかかる必要があります。ぶっちゃけアクションゲームはこの物理エンジン設定バトルが大きいので、慣れていない人はアクションゲームを作るのは避けたほうがいい気がします。私はアクションが一番好きなのでどうしてもアクションゲームにしてしまうのですが……。

# 物理エンジンの選択

BevyやRust界隈だと、Rapierという物理エンジンが最もメジャーだと思います。RapierはBevyとの統合も比較的こなれています。

https://github.com/dimforge/bevy_rapier


それ以外の物理エンジンだと、Avianというのがあります。これは別の物理エンジンを作っていた作者が新たにBevy向けに作り直したという新しいエンジンです。かなり期待が持てそうなのですが、まだ出てきて間もないので今回はパスしました。いつか試してみたいです。

https://github.com/Jondolf/avian

https://joonaa.dev/blog/06/avian-0-1





# bevy_rapier2dの初期化

rapierではプラグインを追加するときにいくつか設定項目があります。デフォルトでは長さ`1`が1メートルになっており、たとえば縦横`16`ピクセルのスプライトに縦横`16`の剛体を設定すると、高さ16メートルのビルが倒れるときのようなゆっくりとした動きに見えます。これでは奇妙なので、`pixels_per_meter`で縮尺を調整するといいでしょう。私は`0.01`を設定して、縦横`16`ピクセルは縦横`16`センチメートルにしてあります。

```rust
app.add_plugins(
    RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(PIXELS_PER_METER)
        .in_fixed_schedule()
        .with_custom_initialization(RapierContextInitialization::NoAutomaticRapierContext),
)
```

また、シミュレーションステップ間隔を固定する`in_fixed_schedule()`や、Rapierのワールドのコンテキストを自力で設定する`with_custom_initialization`を設定しています。

# ワールドの初期化

最新版のRapier 0.27から物理エンジン世界はエンティティとなり、アプリケーション全体で複数のワールドを持てるようになりました。複数のワールドなんてどういうときに使うのか想像つきませんが……。

`with_custom_initialization`を呼んで自力で初期化する場合は、以下のようにして `RapierContext`をspawnします。この`RapierContext`がRapierの物理世界であり、この中にさまざまな剛体がツリー構造で収められているイメージです。デフォルトのコンテキストを指定するため、`DefaultRapierContext`も付けておきます。また、この物理世界は`RapierConfiguration`で重力を設定できます。デフォルトでは垂直方向に`-0.98`の重力加速度が設定されていますが、今回私が作っているゲームは見下ろし型なので重力加速度はゼロとします。

```rust
fn setup_rapier_context(mut commands: Commands) {
    commands.spawn((
        Name::new("default rapier context"),
        DefaultRapierContext,
        RapierContext::default(),
        RapierConfiguration {
            gravity: Vec2::ZERO,
            physics_pipeline_active: true,
            query_pipeline_active: true,
            scaled_shape_subdivision: 10,
            force_update_from_transform_changes: false,
        },
    ));
}
```

見下ろし型のゲームでもこの重力を設定すれば、キャラクターが風に流されるような効果を出すこともできるでしょう。



# 物理シミュレーションの停止

ゲームのポーズなど、物理シミュレーションを一時的に止めたいときがあります。その場合は`RapierConfiguration`の`physics_pipeline_active`を`false`に設定すればOKです。




# 剛体の設定

剛体の設定については、以前の記事で少しだけ触れました。`RigidBody::Dynamic`を設定すれば、それだけでその物体は剛体として振舞うようになり、重力に従って落ちたり、ぶつかったら跳ね返ったりするようになります。

https://qiita.com/hiruberuto/items/eeaea6b36fd82cf65573


https://qiita.com/hiruberuto/items/e57952e2fa3fc9e85226

剛体の種類は以下の四種類です。

- `Dynamic` 普通の剛体。私のゲームでは、動き回るキャラクターはすべて`Dynamic`です。また、宝箱や灯篭などの一見動かなそうなエンティティも`Dynamic`にして、密度を大きくして動きにくくしてあります。なので灯篭を魔法で攻撃するとちょっと動いたりします。なんかいろいろ動いたほうが画面が賑やかで楽しいからです。
- `Fixed` 絶対に移動しない物体。壁などは`Fixed`です。
- `KinematicPositionBased` プログラムで動的に位置を指定する形で移動することがある物体。私のゲームでは扉が`KinematicPositionBased`になっています。
- `KinematicVelocityBased` プログラムで速度を指定する形で移動する物体。魔法の弾丸が`KinematicVelocityBased`になっています。この剛体は他の物体が当たって移動方向が変わったりしませんので、一定速度で動く弾丸に向いています。速度を細かく制御しやすいので、プレイヤーキャラクターを`KinematicVelocityBased`にする手もありそうです。



剛体として設定されたエンティティは、摩擦や密度、外力や衝突グループなど、さまざまな大量の設定項目があります。とても全部は紹介しきれませんが、次回以降に少しずつ紹介していくかもしれません。



# 参考資料

bevy_rapier2dは Bevy 0.15 に合わせてAPIが破壊的に変更されており、最新版に対応したドキュメントや記事などは、日本語圏はもとより英語圏にも存在しません！頑張ってサンプルコードから学びましょう。

https://github.com/dimforge/bevy_rapier/blob/master/bevy_rapier2d/examples/boxes2.rs