うどんてっくメモ

技術的なメモをまったりと

Rust製プラグインで動くGodotのサンプルゲームを公開しました

はじめに

Rust製プラグインで動くGodotのサンプルrungame_sample_godotを公開しました。

rungame_sample_godot

github.com

Godotのプロジェクトとプラグイン、そして必要なアセットが入ったリポジトリです。 submoduleとして後述するrungame_sample_rustを参照しています。

シーン配置とスクリプトのバインドだけを行っており、GDScriptなどのRust以外の実装は行っていません。 基本的にすべてRustのプラグインを読み込み、その動作に委ねます。

オブジェクトの位置や当たり判定などは雑に動けばいいや程度の作りになっています。
ちなみにアセット自体はMagicaVoxelで自分で適当に作ったものです。

rungame_sample_rust

GDNativeのRustライブラリを活用したプラグインです。

github.com

ゲームロジックであるプレイヤーの移動やルールの処理などを行っています。

Player

プレイヤーはPlayerというstructで表現し、入力をもとにRust側での速度を反映したのちに、Godot側のRigidbodyの更新を呼び出しています。

#[export]
fn _physics_process(&mut self, owner: &RigidBody, delta: f64) {
    // 終了時に飛んできた際の処理
    if self.is_active == false {
        owner.set_linear_velocity(Vector3::zero());
        owner.set_angular_velocity(Vector3::zero());
        return;
    }
    
    // Godotの入力
    let input = Input::godot_singleton();
    
    // 単純な加速処理
    if self.move_velocity.z < self.max_forward_speed {
        self.move_velocity.z += self.move_acceleration * delta as f32; 
    } else if self.move_velocity.z > self.max_forward_speed {
        self.move_velocity.z -= self.move_acceleration * delta as f32;
    };
    
    // 左右の移動速度処理
    self.move_velocity.x = 0.;
    if input.is_action_pressed("ui_left") {
        self.move_velocity.x += self.move_horizontal_speed;
    }

    if input.is_action_pressed("ui_right") {
        self.move_velocity.x -= self.move_horizontal_speed;
    }

    // Rust側の速度をGodot側に反映
    owner.set_linear_velocity(self.move_velocity);
}

Field

ステージ内の加速ゾーンや障害物はFieldというPlayerに干渉するtraitとして実装しています。

// コース上のPlayerに干渉するtrait
pub trait Field {
    fn on_player_entered(&self, player: &mut Player);
}

この実装部分で共通する部分をいい感じにmacroで共通化しようと思ったのですが、あえて至極シンプルにしようとコピペしています。

Rule

ゲーム全体のルールはRuleというstructでゲーム状況と時間を更新します。

#[export]
fn _physics_process(&mut self, owner: &Node, delta: f64) {
    let screen = &mut self.screen;
    // ゲームステートの監視
    match &self.state {
        GameState::Ready => {
            // カウントダウンするUIの更新
            if let Some(start_timer) = &self.start_timer {
                screen.set_countdown(start_timer.time_left() as i64);
            }
        }
        GameState::Game => {
            // 時間の更新
            self.time += delta;
            screen.set_time(self.time);

            let player = unsafe {
                owner
                    .get_node_as_instance::<Player>("World/Player")
                    .expect("Playerが取得できなかった")
            };

            player.map(|player, _| {
                screen.set_player_speed(player.move_velocity.z as f64)
            }).expect("Playerを参照できなかった");
        }
        GameState::Over => {}
    }
}

色々とコメントは残してあるので、詳細な実装はリポジトリを確認してみてください。また、間違っている部分などあればぜひ指摘いただけると大変助かります。

おわりに

Rustでゲームを実装してみたい!という選択肢のうち、一番実装環境が整っているのがGodot+Rustでの開発かなと思います。
BevyなどのRust製ゲームエンジンの開発も進んではいるものの、まだGUIのグラフィカルなエディタや便利なカスタマイズなどは充実していない印象です。 とりあえずRustをゲームで書いてみたい方にはおすすめできる選択肢です。

参考文献

環境構築に当たって、以下のリンクを参考にさせていただきました。

Godot EngineからRustを呼ぶ

また、実装に当たって、以下のリンクを参考にさせていただきました。

Godot + Rust + wasmによる3Dブラウザゲームの作り方またはRustはゲーム制作向き言語なのかの考察的な何か - Qiita

Getting Started - The godot-rust Book

gdnative - Rust

Godot API — Godot Engine (stable)の日本語のドキュメント