[Godot] ターミナルで GDScript を実行する。
きっかけ
テストを実行したり、ファイルを生成したり、をコマンドラインでできたらうれしいな。
と思って、とりあえずターミナルで GDScript を実行する方法を調べたら、
公式ドキュメント に、以下のようにしたらいいよと書いてありました。
#!/usr/bin/env -S godot -s
extends SceneTree
func _init():
print("Hello!")
quit()
$ chmod +x sayhello.gd
$ ./sayhello.gd
確かに実行できた!
で、めでたしめでたし。
とはならなかったので、書きます。
先に結論を書くと、GDScript を直接呼ばずにシーンを実行するシェルスクリプトを書きました。
godot
コマンドへの PATH を通す
前提: macOS の場合
brew
経由で入れる方法もあるみたいですが、自分はシンボリックリンクを張りました。
$ ln -sf /Applications/Godot.app/Contents/MacOS/Godot /usr/local/bin/godot
Windows (WSL) の場合
自分は D:\Apps\Godot_v4.5-stable_win64.exe
に保存したので、それを呼ぶスクリプトを用意しました。
#!/bin/sh
/mnt/d/Apps/Godot_v4.5-stable_win64.exe/Godot_v4.5-stable_win64_console.exe $@
GDScript の直接実行でうまくいかなかったこと
- Godot エディタ上で保存すると、
chmod +x
で変えたパーミッションが元に戻る。 - 標準出力にいろいろ出てくる。
- autoloads で設定した変数が読み込まれない。
chmod +x
で変えたパーミッションが元に戻る。
1. Godot エディタ上で保存すると、よし、動くの確認したし、やりたいこと書いてくぞ。
と Godot エディタ上でスクリプトを修正すると以下のエラー。
(※macOSの場合。Windows(WSL)だとファイルシステムの違いでパーミッションは常に777なので無関係)
$ ./sayhello.gd
zsh: permission denied: ./sayhello.gd
そういう仕様? Issue は無さそう。(とりあえず Issue に上げました。 一瞬、自己解決したかと思って思わずクローズしてしまうミスをしました。慣れないことをした。 )
まあ、直接実行せずにシェルスクリプト経由で実行すればいいだけなので、あまり気にせず進めました。
#!/bin/sh
godot -s sayhello.gd
2. 標準出力にいろいろ出てくる。
$ ./sayhello.gd
Godot Engine v4.5.stable.official.876b29033 - https://godotengine.org
Metal 3.2 - Forward+ - Using Device #0: Apple - Apple M1 (Apple7)
Hello!
(略)
埋もれる。。。
--quiet
オプションを渡すと静かになるよ。
$ godot --quiet -s ./sayhello.gd
と書いてありましたが標準出力に何も出なくなる(一応printerr
なら出てくるが、それを使うのも違う)。
単純に、自分が書いたもの以外を消したいが素直にはできなそう。
3. autoloads で設定した変数が読み込まれない。
例えば、以下のようなファイルをプロジェクト設定からグローバル(autoloads)に設定します。
extends Node
#class_name FooBar
static func do_something() -> void:
print_debug('FooBar do_something')
func _ready() -> void:
print_debug('FooBar ready')
これを実行するスクリプトを書きます。
#!/usr/bin/env -S godot -s
extends SceneTree
func _init():
print("Hello!")
FooBar.do_something()
quit()
問題なさそうですが、実行するとエラーになります。
$ godot -s ./xxx.gd
SCRIPT ERROR: Compile Error: Identifier not found: FooBar
ただし、FooBar.do_something()
のところをコメントアウトし、以下のように書き換えると、_ready()
は呼ばれてました。
#!/usr/bin/env -S godot -s
extends SceneTree
func _init():
print("Hello!")
#FooBar.do_something()
quit()
$ godot -s ./xxx.gd
Hello!
FooBar ready
どうも実行したスクリプトが autoloads よりも先に実行される? っぽい挙動でした。
GDScript を直接実行せず、シーンを実行する
$ godot --headless xxx.tscn
でシーンを実行できるので、先ほどの GDScript をアタッチしただけの Node のシーンを作成して試してみましたが、autoloads で設定した変数も参照エラーは出ずに呼べました。
なので、シーンを実行するように書き換えたのですが、
今度はコマンドライン引数が受け取れなくなりました。(悲しい)
GDScript を直接 $ godot -s ./xxx.gd
で実行していた際は、OS.get_cmdline_args()
でコマンドライン引数を受け取れていましたが、シーンだとだめっぽい? しょうがないので環境変数で渡すようにしました。
できたもの
3つファイルを用意します。
dev
├── runner
├── runner.gd
└── runner.tscn
dev/runner
シェルスクリプトを用意します。
#!/bin/sh
GODOT_RUNNER="$@" godot --headless ./dev/runner.tscn \
| grep GODOT_RUNNER_LOG \
| sed 's/^GODOT_RUNNER_LOG | //'
コマンドライン引数は渡せないので、環境変数 GODOT_RUNNER
に突っ込むようにしました。
また、標準出力に出したいものは、GODOT_RUNNER_LOG | xxx
という形式のものを grep することにしました。
dev/runner.gd
extends Node
class_name Runner
static func log(txt) -> void:
print("GODOT_RUNNER_LOG | %s" % txt)
# = Runner
#
# $ ./dev/runner do_something
#
# will execute:
#
# $ GODOT_RUNNER='do_something' godot --headless ./dev/runner.tscn
#
func _ready() -> void:
var env = OS.get_environment("GODOT_RUNNER")
if not env: printerr("GODOT_RUNNER is not set.")
var args = env.split(' ')
match args[0]:
'do_something': FooBar.do_something()
get_tree().quit()
ログに出したいものは
Runner.log("Something happened.")
という形で明示的に書くようにしました。
dev/runner.tscn
単純に Node のシーンに上記のスクリプトをアタッチしているだけです。
[gd_scene load_steps=2 format=3 uid="uid://bm57ljnlmq0hi"]
[ext_resource type="Script" uid="uid://beuy8y175u38h" path="res://dev/runner.gd" id="1_6yldl"]
[node name="Runner" type="Node"]
script = ExtResource("1_6yldl")
使い方
上記3ファイルを用意することで、
$ ./dev/runner do_something
という形で、GDScript を実行できるようになりました。 やったー。
以上です。
Discussion