GDScript と tscn を Tree-sitter で解析して、Asciidoctor で UML 入りドキュメントを自動生成する。
Godot Engine Advent Calendar 2025 の 24 日目の記事です。
きっかけ
みなさんは Godot でプロジェクトを作成していて、これってどうやって動いているんだっけ? と、全体像がわからなくなることはありませんか?
私はよくわからなくなるので、PlantUML でクラス図を作ることがあります。
ただ、書くのがめんどくさかったり、メンテし続けるのが億劫になるので、
ER図の自動生成みたいに、ドキュメントを自動生成したくなりました。
Godot のシーンファイルである *.tscn は、テキストファイルなので、これをうまいことパースしてドキュメントを自動生成出来ないかな。と思って、作ってみました。
できたもの
gd-doc というものを作成しました。
動かしてみた動画
デモサイト
『ゲーム開発初心者がGodotで2Dプラットフォームを勉強用にクローンする制作日誌』 で作成中の Godot プロジェクトに対して、ドキュメントを自動生成して、GitHub Pages にデプロイしてみました。
使用技術
全体像
以下、処理概要です。
-
$ gd-doc installで、Nanoc で動作する構成のテンプレートをコピーして配置。(赤のライン) - 上記で作成されたディレクトリ上で、
$ rakeコマンドを実行してドキュメントを生成。- Godot のプロジェクト配下のファイルを読み込んで、Tree-sitter でパース、AsciiDoc ファイルを生成。(緑のライン)
- Nanoc が Asciidoctor を呼び出して HTML に変換。その後、Web サーバを起動。(青のライン)
-
http://localhost:3001 にアクセスしてドキュメントを閲覧。
-
PlantUML の埋め込みコードは Asciidoctor が
<img src="https://kroki.io/xxx" />に変換しているので、ブラウザ経由で Kroki にアクセスして図を動的に生成。
-
PlantUML の埋め込みコードは Asciidoctor が

導入方法
0. Ruby と Tree-sitter をインストールする
Ruby のインストールは割愛。
Tree-sitter は、以下コマンドでインストールします。
Ubuntu (Windows WSL) の場合
$ sudo apt install libtree-sitter-dev
macOS の場合
$ brew install tree-sitter-cli
1. GitHub から gd-doc リポジトリをクローンする
どこか適当なところに落としてください。
ただし、このディレクトリは gd-doc コマンドのためにずっと使うので、Downloads とか適当すぎるところは避けて、管理しやすい場所がよいと思います。
$ git clone --depth 1 --recurse-submodules git@github.com:tkmfujise/gd-doc.git
$ cd gd-doc
2. $ bundle install で Gem をインストールする
Ruby をインストールしていない人は、要インストール。
Gem をインストールします。
$ bundle install
3. $ rake build で GDScript と GodotResource の Tree-sitter のパーサをビルドする
Tree-sitter をインストールしていない人は要インストール。手順.0 参照
GDScript (.gd) と GodotResource (.tscn, .tres, .godot) をパースできるようにするため、Tree-sitter をビルドします。
$ rake build
$ ls tree-sitters/
Ubuntu (Windows WSL) の場合
gdscript.so godot-resource.so
macOS の場合
gdscript.dylib godot-resource.dylib
4. $ gd-doc コマンドを使えるように PATH を通す
$ gd-doc コマンドをどこからでも呼べるようにするため、bin/gd-doc のシンボリックリンクを作成します。
$ ln -s $(realpath bin/gd-doc) ~/.local/bin/gd-doc
もしくは
$ ln -s $(realpath bin/gd-doc) /usr/local/bin/gd-doc
以下のように表示できたら成功です。
$ gd-doc --help
Commands:
gd-doc install [DIRECTORY] # Install docs
gd-doc repl # Start REPL
gd-doc update # Update docs
gd-doc upgrade # Upgrade gd-doc
ドキュメントの生成方法
$ gd-doc コマンドのセットアップが終われば、後は簡単です。
$ cd your_godot_project
$ gd-doc install
$ cd gd_doc
$ bundle install ※ `gd-doc install` の実行自体が初回の場合のみ。一度入れた後は不要。
$ rake
で、ドキュメントが生成できます。
以下、順に説明します。
1. $ gd-doc install コマンドを実行
Godot プロジェクトに移動して、$ gd-doc install コマンドを実行します。
$ cd your_godot_project
$ gd-doc install
解説
$ gd-doc install コマンドを実行すると gd_doc ディレクトリが作成されます。
$ ls gd_doc/
Gemfile Rules layouts tmp
Gemfile.lock config.rb lib
Rakefile content nanoc.yaml
これは、Nanoc の構成ファイルに、以下の 2 ファイルが加わったものになります。
- Rakefile
- config.rb
Rakefile には、
AsciiDoc ファイルの生成
→ HTMLファイルへのコンパイル
→ サーバの起動
という一連の処理を順に行なうタスクを、デフォルトタスクとして定義しています。
desc 'Update *adoc files and compile, then run server'
task :default => %i[update compile server]
desc 'Update *.adoc files'
task :update do
sh 'gd-doc update'
end
desc 'Compile *.adoc files to *.html'
task :compile do
sh 'bundle exec nanoc'
end
desc 'Run server'
task :server do
sh 'bundle exec nanoc view --host 0.0.0.0 --port 3001'
end
# 略
config.rb では、以下の設定が行なえます。
-
project_dir: Godot プロジェクトのディレクトリを指定します。デフォルトでは、Godot プロジェクトの直下にgd_docディレクトリを作成するのを想定していますが、この値を変更すればどこに設置しても大丈夫です。 -
doc_dir: ドキュメントフォルダを指定します。(※ここは基本的に変更することは無いと思います) -
ignoring_paths: ドキュメントの生成から除外するディレクトリを指定します。addons をデフォルトで除外してますが、コメントアウトして含めるようにすれば、気になるアドオンの処理も確認できます。
GdDoc.configure do |config|
# = Configure the Godot project directory
# config.project_dir = '../' # Path to project.godot directory
config.project_dir = '../'
# = Configure the documentation directory to compile
# config.doc_dir = '.' # Current directory
config.doc_dir = '.'
# = Configure the paths to ignore during compilation
# config.ignoring_paths = ['addons', 'test', 'tmp']
config.ignoring_paths = [
'addons',
'test',
'tmp',
]
end
2. $ rake コマンドを実行する
先ほど作成された gd_doc ディレクトリに移動して、$ rake コマンドを実行すると、ドキュメントが生成されて、Web サーバが起動します。
$ cd gd_doc
$ rake
解説
$ rake コマンドを実行すると、以下のような標準出力が出てくると思います。
gd-doc update
write ./content/index.adoc
write ./content/scenes/path/to/scene_name.tscn.adoc
⋮
bundle exec nanoc
Loading site… done
Compiling site…
create [0.01s] output/path/to/scene_name.tscn/index.html
⋮
Site compiled in 3.00s.
bundle exec nanoc view --host 0.0.0.0 --port 3001
View the site at http://0.0.0.0:3001/
Puma starting in single mode...
* Puma version: 7.1.0 ("Neon Witch")
* Ruby version: ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
* Min threads: 0
* Max threads: 5
* Environment: development
* PID: 165097
* Listening on http://0.0.0.0:3001
Use Ctrl-C to stop
$ rake コマンド内では、以下の処理が順に実行されます。
-
$ gd-doc update- Godot プロジェクト配下のファイルを収集
- Tree-sitter でパース
- パースした結果を組み立てて、
*.adocファイルを生成し、contentディレクトリに設置
-
$ bundle exec nanoc-
contentディレクトリの内容を HTML ファイルにコンパイルして、outputディレクトリに出力します。 -
content内で削除されたファイルがあれば、outputからも削除します。
-
-
$ bundle exec nanoc view --host 0.0.0.0 --port 3001-
3001番ポートで、Web サーバ(Puma) を起動します。 - 違うポートで起動したい場合は、
Rakefileを書き換えて使ってください。
-
3. ブラウザでアクセスする
以上でドキュメントは生成されています。
改めて手順をおさらいすると、以下になります。
$ cd your_godot_project
$ gd-doc install
$ cd gd_doc
$ bundle install ※ `gd-doc install` の実行自体が初回の場合のみ。一度入れた後は不要。
$ rake
http://localhost:3001 にアクセスすると、生成されたドキュメントが閲覧できます。



各種設定方法
Godot プロジェクト外のディレクトリからドキュメントを生成する場合
例えば、以下のようなディレクトリ構成で動かしたい場合
.
├── gd_doc
│ └── config.rb
└── godot_project
└── project.godot
config.rb の project_dir を変更すれば動作します。
GdDoc.configure do |config|
config.project_dir = '../godot_project'
# 略
end
GitHub Pages にデプロイする場合
GitHub Pages は、docs ディレクトリがあればそこを見て動作可能なので、docs ディレクトリに HTML を出力するようにします。
nanoc.yaml の output_dir を変更すれば、デフォルトの output ディレクトリ以外に出力できます。
output_dir: ../docs # Godot プロジェクト直下の docs に出力する場合
# もしくは、
output_dir: docs # gd_doc ディレクトリ直下の docs に出力する場合
Kroki を kroki.io ではなく、自前で用意する場合
デフォルトでは、PlantUML を描画するのに、https://kroki.io にアクセスします。
外部サイトにデータを渡して描画するので、それを避けたい人もいると思います。
自前で用意するには、Kroki の Docker イメージがあるので、それを使うことで、例えば http://localhost:8000 で起動できます。
Kroki サーバの用意ができたら、Rules ファイルの kroki-server-url を修正することで変更できます。
compile '/**/*.adoc' do
filter :asciidoctor, \
attributes: {
'kroki-server-url' => 'http://localhost:8000'
# 'kroki-server-url' => 'https://kroki.io',
'source-highlighter' => 'rouge',
'rouge-css' => 'class',
}
layout '/default.*'
filter :relativize_paths, type: :html
# 略
レイアウトや CSS、JS を変えたい場合
gd_doc ディレクトリ配下のうち、
- content/css
- content/js
- content/index.adoc
- content/resources
- content/scenes
- content/scripts
- content/assets
以外は、初回の $ gd-doc install した後は、$ rake コマンドで更新しません。
.
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── Rules
├── config.rb
├── content
│ ├── css ※ `$ rake` で書き換わる
│ ├── js ※ `$ rake` で書き換わる
│ ├── index.adoc ※ `$ rake` で書き換わる
│ ├── assets ※ `$ rake` で書き換わる
│ ├── resources ※ `$ rake` で書き換わる
│ ├── scenes ※ `$ rake` で書き換わる
│ └── scripts ※ `$ rake` で書き換わる
├── layouts
│ ├── contents-menu.html
│ ├── default.html
│ ├── footer.html
│ └── header.html
├── lib
│ ├── boot.rb
│ └── helper.rb
├── nanoc.yaml
└── tmp
なので、layouts や 上記以外の content 配下を修正することで、見た目や動きを変えることができます。
技術解説
長々と、使い方メインの記事になってしまいましたが、大部分は Tree-sitter と Nanoc、 Asciidoctor のおかげで動いているので、特に語ることも無いかなというのが正直なところですが。
パース処理に関して少しだけ解説します。
*.tscn ファイルについて
Godot のシーンファイルである *.tscn は、以下のような形式になっています。INI や TOML によく似た形式のファイルになっています。
[gd_scene load_steps=42 format=3 uid="uid://c8777f6ryw7re"]
[ext_resource type="Script" uid="uid://dgfjtnknt37a2" path="res://src/player/player.gd" id="1_5irfl"]
[ext_resource type="Texture2D" uid="uid://cp4bljrqpo6mr" path="res://assets/images/player/Player_idle.png" id="2_byvol"]
[ext_resource type="PackedScene" uid="uid://dsnowtvsnihd3" path="res://src/player/ghost_effect/ghost_effect.tscn" id="2_xv5vk"]
[ext_resource type="Texture2D" uid="uid://cdjwu0xy56bnb" path="res://assets/images/player/Player_run.png" id="3_htsrw"]
[ext_resource type="Shader" uid="uid://c8ii4dnxptp45" path="res://src/player/player.gdshader" id="4_b13k1"]
[ext_resource type="Texture2D" uid="uid://ci3bufub70pg2" path="res://assets/images/player/Player_jump.png" id="4_gf1li"]
[ext_resource type="Texture2D" uid="uid://dd4nn447lspus" path="res://assets/images/player/Player_fall.png" id="5_vvr2x"]
[ext_resource type="Texture2D" uid="uid://do4etxl4kmat3" path="res://assets/images/player/effects/Burst.png" id="8_l271a"]
[ext_resource type="PackedScene" uid="uid://bb3rohjwfowwf" path="res://src/player/camera/camera.tscn" id="8_x42xx"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_jm5te"]
shader = ExtResource("4_b13k1")
shader_parameter/dashed = false
# 略
[node name="AnimationTree" type="AnimationTree" parent="."]
unique_name_in_owner = true
root_node = NodePath("%AnimationTree/..")
tree_root = SubResource("AnimationNodeStateMachine_mwkb6")
anim_player = NodePath("../AnimationPlayer")
parameters/Fall/blend_position = 0.195104
parameters/Idle/blend_position = -0.27003
parameters/Jump/blend_position = 0.414688
parameters/Run/blend_position = -0.997033
[connection signal="dashed" from="." to="." method="_on_dashed"]
[connection signal="area_entered" from="FailureCollision" to="." method="_on_failure_collision_area_entered"]
[connection signal="area_entered" from="FrameSubject" to="." method="_on_frame_subject_area_entered"]
[connection signal="area_exited" from="FrameSubject" to="." method="_on_frame_subject_area_exited"]
シーンに関する情報は、上記ファイルにすべて入っています。
なので、このファイルをパースして情報を整理すれば、子ノードの情報や、インスペクタで上書きされた値、シグナルの設定などが取得できます。
Tree-sitter について
Tree-sitter は、Neovim や Helix エディタのシンタックスハイライトのために使われてるという知識しかなかったのですが、Ruby バインディングがあってとても助かりました。
以下のように使いました。
require 'tree_stand'
module GdDoc
class Parser
class << self
attr_accessor :name, :extensions, :store_raw_data
def build
new(files[0])
end
def build_all
files.map{|file| new(file) }
end
def parser
TreeStand::Parser.new(name)
end
def files
targets = extensions.map{|ext| "#{GdDoc.config.project_dir_absolute}/**/*.#{ext}" }
Dir[*targets].reject{|path|
next true if File.directory?(path)
rel_path = relativized_path(path)
GdDoc.config.ignoring_paths.any?{|str| rel_path.to_s.start_with? str }
}
end
def relativized_path(path)
Pathname(path).realpath.relative_path_from(GdDoc.config.project_dir_absolute)
end
end
include TreeNodeHelper
attr_accessor(
:file,
:path,
:raw_data,
)
def initialize(file)
print("Readling file: #{file}...\r") if GdDoc.config.log_verbose
self.file = file
self.path = "res://#{GdDoc::Parser.relativized_path(file)}"
root = self.class.parser.parse_string(File.read(file)).root_node
self.raw_data = root.text if self.class.store_raw_data
initializer
parse(root)
end
def initializer
# Override this method
end
def parse(root)
# Override this method
end
def relative_path
path.delete_prefix 'res://'
end
end
end
module GdDoc
class Scene < Parser
self.name = 'godot-resource'
self.extensions = ['tscn']
# 略
attr_accessor(
:uid,
:script_path,
:script,
:sections,
:root_node,
:child_nodes,
:connections,
)
def initializer
self.sections = []
self.child_nodes = []
self.connections = []
end
def parse(root)
root.children.each do |child|
case child.type
when :section
self.sections << TreeNode::Section.new(child)
end
end
self.uid = value_of('gd_scene', 'uid')
self.script_path = sections.map(&:script_path).compact[0]
build_nodes
build_connections
end
# 略
end
end
module GdDoc
class Script < Parser
self.name = 'gdscript'
self.extensions = ['gd']
self.store_raw_data = true
attr_accessor(
:extends,
:class_name,
:signals,
:functions,
:variables,
:constants,
)
def initializer
self.signals = []
self.functions = []
self.variables = []
self.constants = []
end
def parse(root)
root.each do |child|
case child.type
when :extends_statement
self.extends = dig(child, :type, :identifier)&.text
when :class_name_statement
self.class_name = dig(child, :name)&.text
when :signal_statement
self.signals << TreeNode::Signal.new(child)
when :function_definition
self.functions << TreeNode::Function.new(child)
when :variable_statement
self.variables << TreeNode::Variable.new(child)
when :const_statement
self.constants << TreeNode::Constant.new(child)
end
end
end
end
end
余談
実は、*.tscn ファイルをパースして、PlantUML でクラス図を自動生成するというアイデア自体は元からあり、過去2回挑戦してました。
1回目 は、私が Godot を触り始めた 2022 年ごろに Godot 3 で Action RPG を作成するチュートリアル動画 (※その動画の GitHub リポジトリはこちら) を写経しながら、あまりにわからなかったのでチュートリアルそっちのけで、正規表現と PEG パーサ でパースして作成したりしてました。
2回目は、Godot 4 に上がった際に、GDScript の仕様が変わって前のやつが動かなくなったのと、そもそも良い出来ではなかったので、Julia の PEG パーサ と Documenter.jl で作ろうとしてました。
いずれも満足する出来ではなく、パフォーマンスが悪かったり、開発し続ける気力と実力もなく、結局手で PlantUML を描いてました。
特に大きかったのは、*.tscn ファイルだけだったら、まだ正規表現と PEG パーサで対応できそうな感じなんですが、GDScript でシグナルを紐づけているところを図示しようと思うと、ちゃんと GDScript を字句解析・構文解析してパースする必要があったからです。
今回、Tree-sitter を使いましたが、その辺のめんどくさいところはやらなくて済み、パースされた結果を組み立てて AsciiDoc に落とし込むだけなのが良かったです。
(※と言っても、GDScript 側でシグナルを紐づけている箇所の図示はまだできていません。今後対応したい。)
この形なら今後もメンテ出来そうだなと。
やっぱり、パーサは自分で書くものじゃないなと。改めて思いました。
今後について
とりあえず動くようになったという状態に近いので、
いろいろとやりたいことが、まだまだあります。
- GDScript 側でシグナルを紐づけている箇所の図示
- 検索機能の追加(
Fuse.jsとか入れればいいのかな?) - AnimationPlayer の内容をシーケンス図で図示
- Collision Layer に関する情報の出力
- スタッツの表示
- その他いろいろ
ちょっとずつ加えていきたいと思います。
最後に
ここまで書いといてなんですが。
たぶん、これを使おうとする奇特な方はほとんどいないかなと。
自分がそうなので。
手順的に
- Ruby のインストール
- tree-sitter のインストール
- git clone
- ln -s で gd-doc コマンドのシンボリックリンク追加
まで行くところが、めんどくさそうだし、いろいろ入れないといけないのが躊躇するかなと。
$ gd-doc コマンドが使えるようになるまでできたら、後は Rakefile とか dry-cli で簡単に使えるようにできた自信はあるので、興味を持った方がいれば試してもらえると幸いです。
gd-doc 自体の更新を取り込むのも、わざわざインストールしたディレクトリに移動して $ git pull しなくても、$ gd-doc upgrade コマンドを用意しています。
$ gd-doc upgrade コマンドの中身
module GdDoc::Commands
class Upgrade < Dry::CLI::Command
desc 'Upgrade gd-doc'
def call(*)
cd GdDoc::ROOT_DIR do
sh 'git pull'
sh 'git submodule update --init --recursive'
sh 'bundle install'
sh 'rake build'
end
end
end
end
特に Godot 初心者の方で、自分が Godot を触り始めた 2022 年頃と同じような疑問を抱いた方がいたら、GitHub で公開されている Godot 製のプロジェクトとかに対して $ gd-doc install コマンドでドキュメントを生成して勉強の役に立ててもらえたらうれしいなと。
何か要望とか不具合あれば、コメントで教えてください。
以上です。
Discussion