このひとりアドベントカレンダーでは当初はクソ真面目なことに事前に25日分の記事の計画をしていたんですが、そこまで力を入れて書いても仕方ないなーと思って、ゆるふわ無計画で書くことにしました。そのせいで、そういえばプラグインの解説を忘れていました。
Bevyにおける プラグイン はシステムやイベントなどの定義をひとつにまとめた構造体です。Bevyの本体はほとんど機能を持っておらず、プラグインを追加していくことで初めて様々な機能を使うことができます。
プラグインの追加
サードパーティープラグインの機能を追加したいときは、add_plugins
を呼ぶことでプラグインを追加できます。たとえば、私は物理エンジンに bevy_rapier
を使っています。Bevy界隈ではもっともよく使われている物理エンジンです。
これをゲームに追加して使えるようにするには、以下のようにRapierPhysicsPlugin
を追加します。またこのとき縮尺などの初期設定もできます。
app.add_plugins(
RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(PIXELS_PER_METER)
.in_fixed_schedule()
.with_custom_initialization(RapierContextInitialization::NoAutomaticRapierContext),
)
なお、例えば縦横100
ピクセルの四角形に縦横100
の剛体を仕込んでシミュレーションを実行してみると、非常にゆっくりと地面に落下します。デフォルトではRapier世界の長さ1
は1メートルで、重力定数は9.8
です。巨大なゴジラが緩慢な動作に見えたり、巨大な天空の城ラピュタが崩壊してゆっくり海に落下してるように見えるのと同じ理屈で、四角形は縦横100メートルの大きさの物体の落下はゆっくりに見えてしまいます。そこで pixels_per_meter
で縮尺を変えて 1
を 1センチメートルに設定すると、100
ピクセル四方の物体は1メートル四方の物体と同じようにポロっと落ちるようになり直感的になります。
物理エンジンは設定が複雑で落とし穴も多いので、ドキュメントにはよく目を通しておきましょう。簡単なゲームであれば自力で衝突判定を実装することもできます。ちなみに、私が書いた2022年のアドベントカレンダーでは、いわゆるプラットフォーマー、つまりマリオみたいな横スクロールアクションゲームを作っていますが、それでは自力で衝突判定を実装しています。
ただし、
そうやって作られたゲームは、たとえばプレイヤーキャラクターがリフトに乗ったときにリフトが動くと、プレイヤーキャラクターの位置はそのままで滑り落ちる、リフトに乗り続けるにはリフトと同じ速度で走り続けなくてはならない、みたいな奇妙なことが起きやすいです。結局は物理エンジンをちゃんと使ったほうが実装が楽だと思います。
DefaultPlugins
初期状態ではウィンドウを開く機能やスプライトを表示する機能のようなゲーム開発の最低限の機能すらありません。このため、通常は最初に以下のようにDefaultPlugins
という プラグイングループ を追加して、最低限のプラグイン群を追加します。
fn main() {
App::new().add_plugins(DefaultPlugins).run();
}
DefaultPlugins
には、ウィンドウを表示するための WindowPlugin
や、アセットを読み込むための AssetPlugin
、スプライトを表示するための SpritePlugin
などが含まれます。
また、set
を呼ぶことでこれらのプラグイングループの設定を上書きできます。たとえば、私の作っているゲームはドット絵感を強調した2Dゲームなので、カメラで拡大縮小したときも nearest neighbor で補完してドット絵のギザギザ感をわざと出すようにしています。この設定を行うには、以下のようにして ImagePlugin
の設定を上書きします。
app.add_plugins(
DefaultPlugins.set(ImagePlugin::default_nearest())
);
また、ウィンドウのタイトルやウィンドウサイズ変更の可否などは、WindowPlugin
を上書きします。
.set(WindowPlugin {
primary_window: Some(Window {
position: WindowPosition::Centered(MonitorSelection::Current),
cursor_options: CursorOptions {
visible: cfg!(feature = "debug"),
..default()
},
title: format!("{} 0.1", CRATE_NAME).to_string(),
resizable: false,
enabled_buttons: EnabledButtons {
close: true,
maximize: false,
minimize: true,
},
focused: true,
..default()
}),
..default()
})
プラグインの定義
プラグインは自分で定義するには、構造体を定義してPlugin
トレイトを実装します。build
関数を実装して、そこでadd_systems
を呼んでシステムを追加したり、init_resource
を呼んでリソースを初期化したりします。例えば私の場合、以下のようなプラグインを定義して、カメラの初期化や更新のシステムをひとつのプラグインにまとめてあります。
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnEnter(GameState::Setup), setup_camera);
app.add_systems(
FixedUpdate,
(update_camera_position, update_camera_brightness)
.run_if(in_state(GameState::InGame))
.before(PhysicsSet::SyncBackend),
);
}
}
実際のゲーム開発ではゲーム本体には無数のシステムが定義されますが、私は関連のあるシステムをプラグインとして整理してあります。そうしたプラグインの塊としてゲームを構成しているのですが、別にプラグインにしなくてもシステムを直接登録することもできるので、こうする必要はなかったかもしれません。