D言語でGodotでD言語くんをグルグルさせる
この記事はD言語くん Advent Calendar 2023の9日目の記事であり、同時にD言語 Advent Calendar 2023の7日目の記事です。
今年にD言語くんをOSSのゲームエンジンGodot 4.2 でグルグルさせたので、その手順をまとめます。
今回のコード(Godot 4.1で少し古い)はこちらです。
Godotとは
Godot(ゴドー)とは、OSSのゲームエンジンです。プロプライエタリなソフトウェアとしてはUnityやUnreal 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のインストールは非常に簡単です。インストーラーを使うか、またはダウンロードした実行ファイルをそのまま実行するだけです。
公式サイトのダウンロードページからダウンロードして実行すれば終わりですね。
配置先も、ちょっと試すだけであればデスクトップなどで全く問題ありません。
起動・プロジェクト作成
ダウンロードしたGodot(consoleじゃない方)を起動するとこんな画面が出るので、とりあえずNew
でプロジェクトを作ってみます。
空のフォルダを作ってそちらを選択するのが良いようです。
(なお画像のパスは架空です)
いかにもゲームエンジンといった画面が出てきました。私は過去にUnityを多少触ったことがあるのですが、それにかなり近いですね。Unityを使ったことのある人なら初見で触っていけそうです。
3Dシーン作成
今回は3Dのゲーム(というかD言語くん)を作りたいので、とりあえず、Create 3D Root Node
で3Dシーンを作ってみます。
3Dルートノードを置いてシーンファイルroot.tscn
として保存しました。
モデルのインポート
さて、さっそくD言語くんを読み込んでみましょう。
こんなところにglTF2形式のD言語くんモデルがあるので、こちらを活用します。
このD言語くんモデルは、うえしたさん作のものを変換したものです。
オリジナルのファイルの格納場所
モデルファイルとついでにテクスチャのpngをダウンロードして、Godotに読み込んでみます。
ダウンロードしたファイルをドラグ&ドロップで左下のFileSystem
に放り込みました。
ダブルクリックで開こうとしてみます。すると、以下のエラーが出ます。
Dman
というフォルダの中にテクスチャを格納しろということで、フォルダを掘って格納します。FileSystem
で右クリックし、Create New
でFolder...
でフォルダを作成します。
準備が整うと、見事に右側のInspector
にD言語くんの雄姿が現れました!
シーンへの配置
さて、モデルのインポートができたのでシーンに配置していきましょう。
Godotのシーンはノードによる再帰的な階層構造になっています。Unityのゲームオブジェクトに近いと思いますが、よりシンプルで柔軟な雰囲気がします。
ルートノードの下にそれらしいノードを配置していきましょう。
ルートノードであるNode3D
で右クリックし、Add Child Node...
、出てきたダイアログでCharacterBody3D
を選択します。
このダイアログのノード階層は、classの階層構造のような感じで、上位がスーパークラス、下位がサブクラスになっているようです。最初はどこに何があるか戸惑いますが、クラス階層を上から辿って探すと思うと分かりやすい気がします。
さて、CharacterBody3D
は単にゲームに出てくるキャラクターのガワだけで、まだ中身となる見た目や外形がありません。それで警告マークがついています。外形となるMeshInstance3D
とCollisionShape3D
を追加してみます。
さらに、右側のInspectorで色々設定して、読み込んだD言語くんモデルを表示したり、コリジョンの形状を設定したりして頑張ります。
その結果、こちらのような感じにできました。
これは完成では!と思って、右上のスタートボタンを押してゲームを開始してみます。
……肝心のカメラを置いていませんでした。ルートノードにカメラを追加してみます。
暗い!ライトも追加します。
スポットライトを追加して適当に調整。
ちなみに、カメラもライトも3D空間上に座標を持つノードなので、Node3D
の派生クラス?になっていますね。
D言語くん良い感じになってきました!次はいよいよグルグル回転させてみましょう。
D言語くんグルグル
ノードに動作を与えるのはスクリプトです。Godotには独自のGDScriptというPython的なスクリプト言語があるので、とりあえずはそちらを使ってみます。
右下のNode
のScript
からNew Script
を選択します。
ダイアログが出てきてテンプレート等を選べるので、とりあえず適当そうなNode: Default
にしてみます。本気でキャラクターとして動かす場合はたぶんCharacterBody3D: Basic Movement
が良いでしょうが……。
こんな感じでスクリプトが作られました。見た目はまんまPythonですね。
_process
という関数が毎フレーム呼ばれるそうなので、オンラインドキュメント等を参考に、delta
を元に回転するようにしてみます。
基本的にはクラスのリファレンスを見れば何とかなるようです。
今回スクリプトを設定しているCharacterBody3D
はNode3D
でもあるので、簡単そうなNode3D
の機能で動かしてみます。
ずばり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を使っていきます。
詳しいインストール方法は手前味噌ですがこちらを参照してください。
下記ページを参考にインストーラーでインストールすれば問題ないはず……。
ちなみに、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-dlang
をdub 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
フォルダ配下にモジュールを作っていきます。
/**
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で使用するために、さらに設定ファイルの作成が必要です。
[configuration]
entry_symbol = "dman_gdextension_entry"
compatibility_minimum = 4.2
[libraries]
linux.64 = "libgodot-dman.so"
windows.64 = "godot-dman.dll"
entry_symbol
のdman
の部分は、mixin GodotNativeLibrary
で指定した名称と合わせる必要があります。
またcompatibility_minimum
はgodot-dlang
のドキュメントに記載がありませんでしたが、追加した方が良さそうなものです。(無いとGodotで警告が出る)
Godotへの読み込み
作ったDLLとgdextension
ファイルをGodotへ読み込みます。
これも単にFileSystem
にドラグ&ドロップするだけです。
ドラグ&ドロップ後には、Godotの再起動が必要です。
DLLの方が見えないのが気になりますが、ファイルとしては普通にコピーされているようです。
dmanノードの利用
さて、先ほどD言語で作成したdman
は、CharacterBody3D
の派生クラスとして実装しました。という事は、今まで使っていたCharacterBody3D
の代わりに使えるということです。
読み込んだ拡張は、組み込みのノードと同様にGodotのノードとして使えます!
これをシーンに追加して、メッシュやコライダーを移動させましょう。
拡張機能を使ったD言語の雄姿です。一見以前のCharacterBody3D
と変わりませんが、画面右下のScript
がempty
であるのがミソです。このノードにはスクリプトがありません。
しかし、実行すると、スクリプトがあった時と同じように回転します!
(動作は先ほどと同じなので動画省略)
まとめ
長くなってしまいましたが、Godotを使い、さらにD言語でGDExtensionを作る方法のさわりを紹介しました。
GDExtensionは修正したら毎回Godotの再起動が必要なため、ゲームそのものの開発には厳しい気がしますが、前に言ったように複雑なロジックやIO・Cの外部ライブラリなどを活用する場合に適していそうです。
手順として書くと大変そうですが、ゲームエンジンとほかのプログラミング言語での拡張機能を組み合わせる、という点ではかなりスムーズな部類に入ると感じました。
GDExtension呼び出しやD言語からのAPI呼び出しに多分オーバーヘッドがあるため、パフォーマンス面で確認は必要ですが、この程度の難易度であればロジック部分をD言語で書くことは全然アリだと思います。
少し古いですが、今回のようなことを行ったコードはこちらです。
年末年始、良かったら皆さんも触ってみてください!
Discussion