【Godot】Editor拡張する (Inspector Plugin編)

みなさんEditor拡張沼に浸かっていますか?
ゲームエンジンと言ったらEditor拡張ですよね。

Godot Engineも例に漏れず、強力なEditor拡張が行えます。
↓こんなこともできました。

今回は↑のようなインスペクタ拡張を行う方法を紹介したいと思います。

公式の参考ページ

docs.godotengine.org

キャラを増やすボタンをインスペクタに置く

ボタンを押してキャラを増やすシンプルなEditor拡張を行います。

f:id:ueshita:20211001212428g:plain

1. プラグインの体裁を整える

インスペクタを拡張するのでInspector Pluginを作成します。
Inspector PluginをGodotエディタで実行するためには、Godotプラグインの体裁を整える必要があります。
今回はMyPluginという名前で作りました。

まずプロジェクトルートから次のディレクトリを掘ります。

  • res://addons/MyPlugin/

ここに次のファイルを追加します。

  • plugin.cfg
[plugin]

name="MyPlugin"
description="A fantastic editor plugin."
author=""
version="1.0.0"
script="plugin.gd"
  • plugin.gd
tool
extends EditorPlugin

func _enter_tree():
    pass

func _exit_tree():
    pass

そうすると、プロジェクト設定のプラグイン一覧に追加されるので 有効 にチェックを入れます。

f:id:ueshita:20211001214635p:plain

2. Inspector Pluginを作成する

Inspector Pluginのスクリプトを書きます。
今回はインスペクタにカスタムコントロールとしてボタンを追加して、ボタンが押されたらキャラをランダムに配置するようにしました。

  • MyInspectorPlugin.gd
tool
extends EditorInspectorPlugin
class_name MyInspectorPlugin

var target: MyCharactor

# 選択されたノード、ファイルがこのスクリプトの対象かどうか判定する
func can_handle(object: Object) -> bool:
    # オブジェクトのクラスがMyCharactorだったら対象とする
    return object is MyCharactor

# インスペクタに表示されるときに1回呼ばれる
func parse_begin(object: Object) -> void:
    # インスペクターに表示しているオブジェクトを保存
    target = object
    # ボタンを作る
    var button = Button.new()
    button.text = "押すと増える"
    # イベント登録
    button.connect("pressed", self, "_button_pressed")
    # UIのカスタムコントロールとして追加
    add_custom_control(button)

# ボタンが押されると呼ばれる
func _button_pressed():
    # エディタのルートノード
    var root = target.get_tree().edited_scene_root
    # キャラをロードしてインスタンス化する
    var chara = load("res://models/Dman.fbx").instance() as Spatial
    # 位置をランダムに配置する
    chara.transform.origin = Vector3(rand_range(-3, 3), rand_range(-3, 3), rand_range(-3, 3))
    # ルートの子として追加
    root.add_child(chara)
    # キャラのオーナーを設定 (Editorでは必要)
    chara.set_owner(root)
    print("増えたぞ")

今回、Insupector Pluginの対象判定のためにクラス型を使っているため、対象になる空のクラスを作りました。

  • MyCharactor.gd
extends Node
class_name MyCharactor

3. 動作確認する

f:id:ueshita:20211001220914p:plain

適当なノードに先ほどの空クラスのスクリプトをアタッチしています。
このノードをクリックするとインスペクタにボタンが追加されていると思います。

このボタンを押しまくると…

f:id:ueshita:20211001221106p:plain

D言語くんが大量増殖しました!

なんか上手くいかないときは、プラグインの有効チェックを外して再度有効にするとリロードが行われます。

インスペクタで3Dレンダリングを行う

EditorInspectorPluginのadd_custom_controlにはControlノードを突っ込めるため、ぶっちゃけ何でもできます。
例えばノードやアセットの3Dプレビュー機能が欲しかったりしたら、ViewportContainerを使って3Dレンダリングを行うこともできます。

f:id:ueshita:20211001223600p:plain

1. Controlシーンを作る

今回はレスポンシブルにするため、ルートノードの型をVBoxContainerにしました。

f:id:ueshita:20211001224349p:plain

ViewportContainer

f:id:ueshita:20211001224645p:plain

  • Stretchにチェックを入れる
  • Min SizeのYを設定しないと、Viewportがゼロサイズになってしまうので適当な値を入れる

Viewport

f:id:ueshita:20211001224836p:plain

  • Own Worldにチェックを入れる

3Dシーン

Camera, DirectionalLight, 適当なキャラを配置します。

2. スクリプトを作る

CharaPreview.gd

動かないのは面白くないので、tool属性を付けたスクリプトをキャラにアタッチしました。

tool
extends Spatial

func _process(delta: float):
    transform = transform.rotated(Vector3.FORWARD, deg2rad(360.0) * delta)

MyInspectorPlugin.gd

作成したシーンをロードしてadd_custom_controlに突っ込むスクリプトを書きます。

tool
extends EditorInspectorPlugin
class_name MyInspectorPlugin

var target: MyCharactor
var scene: Control

# 選択されたノード、ファイルがこのスクリプトの対象かどうか判定する
func can_handle(object: Object) -> bool:
    # オブジェクトのクラスがMyCharactorだったら対象とする
    return object is MyCharactor

# インスペクタに表示されるときに1回呼ばれる
func parse_begin(object: Object) -> void:
    # インスペクターに表示しているオブジェクトを保存
    target = object
    # シーンを追加する
    scene = load("res://addons/MyPlugin/MyInspectorPlugin.tscn").instance()
    # ボタン押下のイベント登録
    #scene.get_node("Button").connect("pressed", self, "_button_pressed")
    # UIのカスタムコントロールとして追加
    add_custom_control(scene)

3. 動作確認する

インスペクタで荒ぶるD言語くんが見れました。

f:id:ueshita:20211001223453g:plain

おわりに

Godot EngineではInspector Pluginでなんでも出来ることが分かりました。
この調子でゲーム制作がどんどん効率化できそうです。

しかし、効率よくゲームを作るためEditor拡張していたら夢中になってしまい、いつまで経ってもゲームが出来上がらないなんてことは避けましょうね。

おまけ

Effekseerプラグインに実例があるので、よかったらご参考ください。 https://github.com/effekseer/EffekseerForGodot3/tree/main/Dev/Godot/addons/effekseer