物理エンジンの設定項目で、密度や摩擦、外力などはまあイメージしやすいと思います。比較的わかりにくい項目に衝突グループの設定があります。現実世界の再現という意味ではあまり使う必要がないのですが、ゲームではこの衝突グループの設定は非常に重要なので解説しておきます。
衝突グループとは
衝突グループ とは、どの物体がどの物体と衝突し干渉するかを制御するものです。え?どの物体どうしでも衝突者するでしょ?と思うかもしれませんが、それは現実世界の話であって、ゲーム世界では少し事情が異なります。
たとえば、敵キャラクターが発射した弾丸が別の敵キャラクターに衝突してしまうと、あっさり同士討ちが始まってしまってゲームになりません。またプレイヤーキャラクターが自分の撃った弾丸に自分が当たって自傷ダメージを受けたりしかねません。さらに、弾丸が地面に散らばっている金塊にまで衝突したら、敵に当たる前に地面の金塊に当たって弾丸が消滅してしまいます。
このように、ゲームでは物理法則を無視してある種の物体同士が衝突せずにすり抜ける設定ができたほうがいいです。この設定が衝突グループです。
衝突グループの設定
Rapierでは衝突グループはあらかじめ32個用意されていて、これらを各物体に割り当てていきます。Group::GROUP_1
などの数字ではわかりづらいので、先にエイリアスを用意しておきます。
pub const ENTITY_GROUP: Group = Group::GROUP_1;
pub const WALL_GROUP: Group = Group::GROUP_3;
pub const WITCH_GROUP: Group = Group::GROUP_5;
pub const WITCH_BULLET_GROUP: Group = Group::GROUP_6;
pub const ENEMY_GROUP: Group = Group::GROUP_6;
pub const ENEMY_BULLET_GROUP: Group = Group::GROUP_7;
pub const MAGIC_CIRCLE_GROUP: Group = Group::GROUP_8;
pub const SENSOR_GROUP: Group = Group::GROUP_9;
pub const DOOR_GROUP: Group = Group::GROUP_10;
pub const RABBIT_GROUP: Group = Group::GROUP_11;
それから、各剛体にCollisionGroups
コンポーネントとして割り当てます。最初の引数memberships
は自分が所属する衝突グループ、2つ目の引数filters
が自分がどの衝突グループと衝突するかです。衝突グループどうしはビット演算のOR |
で複数のグループを指定できます。
CollisionGroups::new(
match actor_group {
ActorGroup::Enemy => ENEMY_GROUP,
ActorGroup::Player => WITCH_GROUP,
},
match actor_group {
ActorGroup::Enemy => WITCH_BULLET_GROUP,
ActorGroup::Player => ENEMY_BULLET_GROUP,
} | ENTITY_GROUP
| WALL_GROUP
| WITCH_GROUP
| ENEMY_GROUP
| RABBIT_GROUP,
),
私が作っているゲームでは、各キャラクターはプレイヤーと敵側に分類されます。味方のプレイヤー側として召喚されることもあるので、スライムなどのモンスターも味方の衝突グループWITCH_GROUP
と条件分岐で切り替わるようになっています。また、そのキャラクターが味方側の場合は味方の弾丸の衝突グループWITCH_BULLET_GROUP
には衝突しないし、その逆もまた然り、と条件分岐で切り替えています。どのキャラクター同士も身体はぶつかりますし、壁にもぶつかるので、ENTITY_GROUP | WALL_GROUP | WITCH_GROUP | ENEMY_GROUP | RABBIT_GROUP
とそのほかの衝突グループを列挙しています。
同様に弾丸にも衝突グループを設定します。弾丸を発射したキャラクターの種別に応じて、次のようにmemberships
とfilters
を設定しわけています。
memberships: match actor.actor_group {
ActorGroup::Player => WITCH_BULLET_GROUP,
ActorGroup::Enemy => ENEMY_BULLET_GROUP,
},
filters: match actor.actor_group {
ActorGroup::Player => ENEMY_GROUP,
ActorGroup::Enemy => WITCH_GROUP,
} | ENTITY_GROUP
| WALL_GROUP
| RABBIT_GROUP,
さらに設定を工夫するなら、たとえばfilters
からWALL_GROUP
を取り除けば、壁をすりぬけて移動できる幽霊型モンスターも作れるでしょう。
またショップの店員は、ショップの外に押し出そうとしても見えない壁に阻まれて外に行かないようになっています。というのも、プレイヤーが未精算の商品を持っているとショップの扉が開かなくなり万引きができないようになっているのですが、その際に店員が扉の外にいると会計ができなくなって詰んでしまうからです。こういう制御も衝突グループで行っています。
参考資料
Rapierの公式ドキュメントを隅々まで目を通しましょう。見落としがあるとハマって死にます。
少し古いですが、以下の資料も参考になります。