2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🦀ひとりRustとBevyでゲーム開発🕊️Advent Calendar 2024

Day 17

【Rustのまほう2】#17 プラグイン

Last updated at Posted at 2024-12-16

このひとりアドベントカレンダーでは当初はクソ真面目なことに事前に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を呼んでリソースを初期化したりします。例えば私の場合、以下のようなプラグインを定義して、カメラの初期化や更新のシステムをひとつのプラグインにまとめてあります。

camera.rs
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),
        );
    }
}

実際のゲーム開発ではゲーム本体には無数のシステムが定義されますが、私は関連のあるシステムをプラグインとして整理してあります。そうしたプラグインの塊としてゲームを構成しているのですが、別にプラグインにしなくてもシステムを直接登録することもできるので、こうする必要はなかったかもしれません。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?