👀

D言語でGodotでD言語くんをグルグルさせる

2023/12/09に公開

この記事はD言語くん Advent Calendar 2023の9日目の記事であり、同時にD言語 Advent Calendar 2023の7日目の記事です。

今年にD言語くんをOSSのゲームエンジンGodot 4.2 でグルグルさせたので、その手順をまとめます。

今回のコード(Godot 4.1で少し古い)はこちらです。

https://github.com/outlandkarasu-sandbox/dman-godot

Godotとは

https://godotengine.org/

Godot(ゴドー)とは、OSSのゲームエンジンです。プロプライエタリなソフトウェアとしてはUnityUnreal Engineがメジャーです。
Godotもそれらと同様に、ゲームエディターを備え、各種の素材とスクリプトを組み合わせてゲームを作ることができます。
現状ではPC/Android/iOSなどのプラットフォームに対応しているようです。

Godotの特色

Godotはメジャーなゲームエンジンに比べれば機能は少ないのですが、触ってみた感じでは、その代わりに非常に軽量にできているようです。
また、開発が活発に行われており、新機能も次々に実装されているようです。

今年Unityで問題になったように、プロプライエタリなゲームエンジンは突然のライセンス変更などのリスクがありますが、OSSライセンス(MIT License)であるGodotならその懸念は基本的にはありません。
技術的な点では、ソースコードが公開されておりフォークやカスタマイズも自由という事で、いざとなったら自分で手を入れられるのは結構利点である気がします。

Switchなどコンシューマー機の対応は公式には行われていませんが、サードパーティーによるサポートはいくつか存在するようです。

GodotはC++で実装されており、CのAPIを使って多くのプログラミング言語でプラグインや拡張機能を開発することが可能になっています。
D言語にもバインディングライブラリが存在するので、この記事ではそちらを利用してみます。

Godotを使ってみる

まずはD言語のことは置いておいて、D言語くんを普通にグルグルさせることをGodot 4.2だけで行ってみます。

なお、使用環境はWindowsです。

インストール

Godotのインストールは非常に簡単です。インストーラーを使うか、またはダウンロードした実行ファイルをそのまま実行するだけです。
公式サイトのダウンロードページからダウンロードして実行すれば終わりですね。
配置先も、ちょっと試すだけであればデスクトップなどで全く問題ありません。

https://godotengine.org/download/

起動・プロジェクト作成

ダウンロードしたGodot(consoleじゃない方)を起動するとこんな画面が出るので、とりあえずNewでプロジェクトを作ってみます。

空のフォルダを作ってそちらを選択するのが良いようです。
(なお画像のパスは架空です)

いかにもゲームエンジンといった画面が出てきました。私は過去にUnityを多少触ったことがあるのですが、それにかなり近いですね。Unityを使ったことのある人なら初見で触っていけそうです。

3Dシーン作成

今回は3Dのゲーム(というかD言語くん)を作りたいので、とりあえず、Create 3D Root Nodeで3Dシーンを作ってみます。

3Dルートノードを置いてシーンファイルroot.tscnとして保存しました。

モデルのインポート

さて、さっそくD言語くんを読み込んでみましょう。

こんなところにglTF2形式のD言語くんモデルがあるので、こちらを活用します。

このD言語くんモデルは、うえしたさん作のものを変換したものです。

オリジナルのファイルの格納場所
https://3d.nicovideo.jp/works/td28301

モデルファイルとついでにテクスチャのpngをダウンロードして、Godotに読み込んでみます。

ダウンロードしたファイルをドラグ&ドロップで左下のFileSystemに放り込みました。

ダブルクリックで開こうとしてみます。すると、以下のエラーが出ます。

Dmanというフォルダの中にテクスチャを格納しろということで、フォルダを掘って格納します。FileSystemで右クリックし、Create NewFolder...でフォルダを作成します。

準備が整うと、見事に右側のInspectorにD言語くんの雄姿が現れました!

シーンへの配置

さて、モデルのインポートができたのでシーンに配置していきましょう。

Godotのシーンはノードによる再帰的な階層構造になっています。Unityのゲームオブジェクトに近いと思いますが、よりシンプルで柔軟な雰囲気がします。
ルートノードの下にそれらしいノードを配置していきましょう。

ルートノードであるNode3Dで右クリックし、Add Child Node...、出てきたダイアログでCharacterBody3Dを選択します。

このダイアログのノード階層は、classの階層構造のような感じで、上位がスーパークラス、下位がサブクラスになっているようです。最初はどこに何があるか戸惑いますが、クラス階層を上から辿って探すと思うと分かりやすい気がします。

さて、CharacterBody3Dは単にゲームに出てくるキャラクターのガワだけで、まだ中身となる見た目や外形がありません。それで警告マークがついています。外形となるMeshInstance3DCollisionShape3Dを追加してみます。

さらに、右側のInspectorで色々設定して、読み込んだD言語くんモデルを表示したり、コリジョンの形状を設定したりして頑張ります。

その結果、こちらのような感じにできました。

これは完成では!と思って、右上のスタートボタンを押してゲームを開始してみます。

……肝心のカメラを置いていませんでした。ルートノードにカメラを追加してみます。

暗い!ライトも追加します。

スポットライトを追加して適当に調整。
ちなみに、カメラもライトも3D空間上に座標を持つノードなので、Node3Dの派生クラス?になっていますね。

D言語くん良い感じになってきました!次はいよいよグルグル回転させてみましょう。

D言語くんグルグル

ノードに動作を与えるのはスクリプトです。Godotには独自のGDScriptというPython的なスクリプト言語があるので、とりあえずはそちらを使ってみます。

右下のNodeScriptからNew Scriptを選択します。

ダイアログが出てきてテンプレート等を選べるので、とりあえず適当そうなNode: Defaultにしてみます。本気でキャラクターとして動かす場合はたぶんCharacterBody3D: Basic Movementが良いでしょうが……。

こんな感じでスクリプトが作られました。見た目はまんまPythonですね。
_processという関数が毎フレーム呼ばれるそうなので、オンラインドキュメント等を参考に、deltaを元に回転するようにしてみます。

基本的にはクラスのリファレンスを見れば何とかなるようです。
https://docs.godotengine.org/en/4.2/classes/index.html

今回スクリプトを設定しているCharacterBody3DNode3Dでもあるので、簡単そうなNode3Dの機能で動かしてみます。
ずばりrotate_yというメソッドがあるようなので、それを使ってみます。

https://docs.godotengine.org/en/4.2/classes/class_node3d.html#class-node3d-method-rotate-y

こんなので大丈夫なんだろうか……。

やった!やりました!

D言語で動かす

ようやく本題です……。

先ほどのスクリプトはGodot Engineの中でサクッと動かすにはとても手軽で便利ですが、D言語erとしてはどうしてもD言語を使いたい……。

というか、複雑なロジックであったり、科学技術系計算が絡んだり、外部IOをたくさん行ったり、などなど、ゲームエンジンの外でD言語でロジックを作りこみたい場合はたくさんあると思われます。

というわけで、先ほどのスクリプトの動きをD言語でやってみましょう!

godot-dlang

GodotのAPIをD言語から使用したり、Godot向けの拡張機能GDExtensionを開発するためには、バインディングライブラリのgodot-dlangを使用します。

以前はgodot-dというパッケージでリリースされていましたが、メンテナンスがされていないようで、現在はフォークしたgodot-dlangの方がメンテナンスされているようです。

godot-dlangにある手順で、Godot内で動かせる拡張機能を作っていってみましょう。

D言語インストール

まずはD言語をインストールしていないと始まりません……。今回はWindows環境で、トラブルの少なそうな公式実装DMDを使っていきます。

詳しいインストール方法は手前味噌ですがこちらを参照してください。
下記ページを参考にインストーラーでインストールすれば問題ないはず……。

https://zenn.dev/outlandkarasu/articles/341f4d3fc9add9

ちなみに、VSCodeにはD言語拡張が用意されているので、開発にはVSCodeも使っていきます。こちらの手順も上記記事から辿れるようになっているので参照してみてください。
(別に手順が必要なほど難しくないですが……)

D言語プロジェクト作成

とりあえずdub initで空のプロジェクトを作成し、そこから始めていきます。

先述のgodot-dlangを追加していきましょう。

# 手順通りgodot-dlangのCLIを使えるようにfetch
$ dub fetch godot-dlang
    Fetching godot-dlang 0.5.0
    Finished godot-dlang fetched
             Please note that you need to use `dub run <pkgname>` or add it to dependencies of your package to actually use/run it. 

# プロジェクトの依存関係にも追加
$ dub add godot-dlang
             Adding dependency godot-dlang ~>0.5.0

Godotバインディングライブラリビルド

Godotのバインディングライブラリは、GodotによりAPI情報を出力させて、そちらを元にgodot-dlangのツールでソースコードを生成してビルドする仕組みになっています。
ライブラリの利用前に一度だけビルド手順を実行する必要があります。
(バージョンアップ時などもたぶん必要)

ビルドの手順はgodot-dlangのドキュメントにまとめられているので、そちらを参考に進めます。

API情報出力

$ cd {Godot4展開フォルダ}
$ Godot_v4.2-stable_win64.exe --dump-extension-api

# ファイルができたことを確認
$ ls extension_api.json

godot-dlangでビルド

先ほどdub fetchしたgodot-dlangdub runで実行します。
extension_api.jsonを参照する必要があるので、Godotのディレクトリで実行するとよいと思います。
以下はコマンド実行例です。

$  dub run godot-dlang:generator -- -j extension_api.json -o
             Building package godot-dlang:generator in C:\Users\ikemen\AppData\Local\dub\packages\godot-dlang\0.5.0\godot-dlang\
    Fetching mir-core 1.7.0 (getting selected version)
    Starting Performing "debug" build using C:\D\dmd2\windows\bin64\dmd.exe for x86_64.
    Building mir-core 1.7.0: building configuration [library]
    Building mir-algorithm 3.21.0: building configuration [default]
    Building asdf 0.7.17: building configuration [library]
C:\Users\ikemen\AppData\Local\dub\packages\asdf\0.7.17\asdf\source\asdf\asdf.d(168,19): Deprecation: cannot throw object of qualified type `immutable(EmptyAsdfException)`
    Building dxml 0.4.4: building configuration [library]
  Up-to-date godot-dlang:util 0.5.0: target for configuration [library] is up to date.
    Building libdparse 0.20.0: building configuration [library]
    Building godot-dlang:generator 0.5.0: building configuration [application]
     Linking generator
    Finished To force a rebuild of up-to-date targets, run again with --force
     Running ../../AppData/Local/dub/packages/godot-dlang/0.5.0/godot-dlang/lib/generator.exe -j extension_api.json -o
["C:\\Users\\ikemen\\AppData\\Local\\dub\\packages\\godot-dlang\\0.5.0\\godot-dlang\\lib\\generator.exe"]
Outputting to default directory C:\Users\ikemen\AppData\Local\dub\packages\godot-dlang\0.5.0\godot-dlang\classes...
Done! API bindings written to 'C:\Users\ikemen\AppData\Local\dub\packages\godot-dlang\0.5.0\godot-dlang\classes'

こちらの手順が完了すると、D言語のプロジェクトでビルドが行えるようになります。

# D言語のプロジェクトに移動
$ cd C:\Path\to\godot-dman
$ dub build
    Starting Performing "debug" build using C:\D\dmd2\windows\bin64\dmd.exe for x86_64.
  Up-to-date godot-dlang:util 0.5.0: target for configuration [library] is up to date.
  Up-to-date utf_bc 0.2.1: target for configuration [library] is up to date.
    Building godot-dlang 0.5.0: building configuration [library]
   Pre-build Running commands
    Building godot-dman ~master: building configuration [application]
     Linking godot-dman
    Finished To force a rebuild of up-to-date targets, run again with --force

D言語でコーディング

これまでの手順でD言語のGodotバインディングライブラリが用意できました。
それでは、いよいよD言語でGodot拡張機能 GDExtensionを書いていきましょう!

sourceフォルダ配下にモジュールを作っていきます。

source/dman.d
/**
D言語くんモジュール
*/
module dman;

// 必要なライブラリをインポート
import godot.api.script : GodotScript;
import godot.characterbody3d : CharacterBody3D;

/** 
D言語くんノード
*/
class Dman : GodotScript!CharacterBody3D {
    // 必要なライブラリをインポート
    private {
        import godot.api.udas : Method;
        import godot.engine : Engine;
    }

    /** 
    回転スピード
    D言語はenumキーワードでコンパイル時定数として定義できます。
    */
    private enum SPEED = 1.0;

    /**
    毎フレーム呼び出される処理。

    Params:
        delta = 今回のフレームの移動量
    */
    @Method
    void _process(double delta) {
        // エディタ上では処理をスキップ
        if (Engine.isEditorHint)
            return;

        // Y軸周りに回転
        rotateY(delta * SPEED);
    }
}

// プラグイン登録用のライブラリのインポート
// 本当は部分的にimportしたいが、mixinの中のコードが色々依存していて煩雑なので……。
import core.stdc.stdint : uint32_t;
import godot.api.register;

// D言語くんノードをプラグインとして登録
mixin GodotNativeLibrary!(
    // プラグイン接頭辞
    "dman", 

    // プラグインとして公開するクラス。複数指定可能
    Dman,
);

GDExtensionはDLLとする必要があるため、dub.jsonにも設定を追記します。

{
    "targetType": "dynamicLibrary", // これを追加
    "dependencies": {
        "godot-dlang": "~>0.5.0"
    },
    "name": "godot-dman"
}

"targetType": "dynamicLibrary"とすることで、DLLがビルドされるようになります。
さらに、デフォルトで生成されていたsource/app.dは削除します。

$ rm source/app.d

これでdllファイルとしてビルドが成功するようになっているはずです。

$ dub build
    Starting Performing "debug" build using C:\D\dmd2\windows\bin64\dmd.exe for x86_64.
  Up-to-date godot-dlang:util 0.5.0: target for configuration [library] is up to date.
  Up-to-date utf_bc 0.2.1: target for configuration [library] is up to date.
  Up-to-date godot-dlang 0.5.0: target for configuration [library] is up to date.
    Building godot-dman ~master: building configuration [library]
     Linking godot-dman
    Finished To force a rebuild of up-to-date targets, run again with --force

# ビルドしたDLLの確認
$ ls godot-dman.dll

GDExtension設定ファイル作成

作ったDLLをGodotで使用するために、さらに設定ファイルの作成が必要です。

dman.gdextension
[configuration]

entry_symbol = "dman_gdextension_entry"
compatibility_minimum = 4.2

[libraries]

linux.64 = "libgodot-dman.so"
windows.64 = "godot-dman.dll"

entry_symboldmanの部分は、mixin GodotNativeLibraryで指定した名称と合わせる必要があります。
またcompatibility_minimumgodot-dlangのドキュメントに記載がありませんでしたが、追加した方が良さそうなものです。(無いとGodotで警告が出る)

Godotへの読み込み

作ったDLLとgdextensionファイルをGodotへ読み込みます。

これも単にFileSystemにドラグ&ドロップするだけです。
ドラグ&ドロップ後には、Godotの再起動が必要です。

DLLの方が見えないのが気になりますが、ファイルとしては普通にコピーされているようです。

dmanノードの利用

さて、先ほどD言語で作成したdmanは、CharacterBody3Dの派生クラスとして実装しました。という事は、今まで使っていたCharacterBody3Dの代わりに使えるということです。

読み込んだ拡張は、組み込みのノードと同様にGodotのノードとして使えます!

これをシーンに追加して、メッシュやコライダーを移動させましょう。

拡張機能を使ったD言語の雄姿です。一見以前のCharacterBody3Dと変わりませんが、画面右下のScriptemptyであるのがミソです。このノードにはスクリプトがありません。

しかし、実行すると、スクリプトがあった時と同じように回転します!

(動作は先ほどと同じなので動画省略)

まとめ

長くなってしまいましたが、Godotを使い、さらにD言語でGDExtensionを作る方法のさわりを紹介しました。

GDExtensionは修正したら毎回Godotの再起動が必要なため、ゲームそのものの開発には厳しい気がしますが、前に言ったように複雑なロジックやIO・Cの外部ライブラリなどを活用する場合に適していそうです。

手順として書くと大変そうですが、ゲームエンジンとほかのプログラミング言語での拡張機能を組み合わせる、という点ではかなりスムーズな部類に入ると感じました。
GDExtension呼び出しやD言語からのAPI呼び出しに多分オーバーヘッドがあるため、パフォーマンス面で確認は必要ですが、この程度の難易度であればロジック部分をD言語で書くことは全然アリだと思います。

少し古いですが、今回のようなことを行ったコードはこちらです。

https://github.com/outlandkarasu-sandbox/dman-godot

年末年始、良かったら皆さんも触ってみてください!

Discussion