Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

エディタでコードを実行する

@tool とは?

``@ tool``は強力なコード行で、スクリプトの先頭に追加すると、それがエディタ内で実行されるようになります。また、スクリプトの部分ごとにそれをエディタ内で実行するのか、ゲーム内で実行するのか、それとも両方で実行するのかも決めることができます。

多くの目的のためにそれを使用できますが、レベル設計をするときに、予測が難しいものを視覚的に提示するためにとても役立ちます。使用例は次のとおりです:

  • 物理(重力)の影響を受ける砲弾を撃つ大砲がある場合、砲弾の軌道をエディタ内で表示することで、レベル設計をはるかに簡単にできます。

  • ジャンプの高さが異なるジャンプパッドがある場合、プレイヤーがジャンプした場合に到達する最大ジャンプ高を描画でき、レベル設計も容易になります。

  • プレイヤーがスプライトを使用せず、コードを使用して描画を行う場合、エディタ内でその描画コードを実行するようにしてプレイヤーを見られます。

危険

@tool スクリプトはエディタ内で実行され、現在編集されているシーンのシーンツリーにアクセスできます。これは強力な機能ですが警告も伴います。エディタには @tool スクリプトの誤用の可能性に対する保護が含まれていないためです。特に Node.queue_free を介してシーンツリーを操作するときは非常に注意してください、エディタがそれに関連するロジックを実行している間にノードを解放するとクラッシュする可能性があります。

@toolの使用方法

スクリプトをツールにするには、コードの先頭に @tool アノテーションを追加します。

現在の実行環境がエディタ内なのかどうかを確認するには、Engine.is_editor_hint() を使用します。

たとえば、あるコードをエディタでのみ実行したい場合は、次のようにします:

if Engine.is_editor_hint():
    # Code to execute when in editor.

反対に、ゲームでのみコードを実行したい場合は、単純に同じプログラムを反転します:

if not Engine.is_editor_hint():
    # Code to execute when in game.

上記の2つの条件のいずれにも該当しないコード部分はエディタとゲームの両方で実行されます。

_process() 関数の全体像はこのようになります:

func _process(delta):
    if Engine.is_editor_hint():
        # Code to execute in editor.

    if not Engine.is_editor_hint():
        # Code to execute in game.

    # Code to execute both in editor and in game.

重要な情報

The general rule is that any other GDScript that your tool script uses must *also* be a tool. The editor is not able to construct instances from GDScript files without @tool, which means you cannot call methods or reference member variables from them otherwise. However, since static methods, constants and enums can be used without creating an instance, it is possible to call them or reference them from a @tool script onto other non-tool scripts. One exception to this are static variables. If you try to read a static variable's value in a script that does not have @tool, it will always return null but won't print a warning or error when doing so. This restriction does not apply to static methods, which can be called regardless of whether the target script is in tool mode.

Extending a @tool script does not automatically make the extending script a @tool. Omitting @tool from the extending script will disable tool behavior from the super class. Therefore, the extending script should also specify the @tool annotation.

Modifications in the editor are permanent, with no undo/redo possible. For example, in the next section when we remove the script, the node will keep its rotation. Be careful to avoid making unwanted modifications. Consider setting up version control to avoid losing work in case you make a mistake.

デバッグ

While the debugger and breakpoints cannot be used directly with tool scripts, it is possible to launch a new instance of the editor and debug from there. To do this, navigate to Debug > Customize Run Instances... and specify --editor in Main Run Args.

See デバッグ ツールの概要 for more information.

Additionally, you can use print statements to display the contents of variables instead.

@tool を試してみる

シーンに Sprite2D ノードを追加し、テクスチャをGodotアイコンに設定します。スクリプトをアタッチして開き、次のように変更します:

@tool
extends Sprite2D

func _process(delta):
    rotation += PI * delta

スクリプトを保存してエディタに戻りましょう。これで、オブジェクトが回転するようになるはずです。ゲームを実行してみると、ゲーム内でも回転します。

警告

You may need to restart the editor. This is a known bug found in all Godot 4 versions: GH-66381.

../../_images/rotating_in_editor.gif

注釈

変化がみられない場合は、シーンをリロードしましょう(閉じてからもう一度開きます)。

次に、どのコードがいつ実行されるのかを選択しましょう。_process() 関数を次のように変更します:

func _process(delta):
    if Engine.is_editor_hint():
        rotation += PI * delta
    else:
        rotation -= PI * delta

スクリプトを保存します。これで、オブジェクトはエディタでは時計回りに回転しますが、ゲームを実行すると、反時計回りに回転します。

変数の編集

スクリプトにspeed変数を追加しエクスポートしましょう。speedを更新し回転角度もリセットするため set(new_speed) セッターを追加します。これはインスペクタから入力があった時に実行され、 _process() を修正して回転速度を反映します。

@tool
extends Sprite2D


@export var speed = 1:
    # Update speed and reset the rotation.
    set(new_speed):
        speed = new_speed
        rotation = 0


func _process(delta):
    rotation += PI * delta * speed

注釈

Code from other nodes doesn't run in the editor. Your access to other nodes is limited. You can access the tree and nodes, and their default properties, but you can't access user variables. If you want to do so, other nodes have to run in the editor too.

Getting notified when arrays or dictionaries change

You can use an Array or Dictionary as an @export variable. In a @tool script, you can react to any changes to that collection by using a setter. Normally, at runtime, such a setter is only called when you assign to the variable, but when you modify an Array or Dictionary in the inspector, the setter will also be called.

@tool
class_name MyTool
extends Node

@export var my_array = []:
    set(new_array):
        my_array = new_array
        print("My array just changed!")

@export var my_dictionary = {}:
    set(new_dictionary):
        my_dictionary = new_dictionary
        print("My dictionary just changed!")

リソース変更の通知を受け取る

Sometimes you want your tool to use a resource. However, when you change a property of that resource in the editor, the set() method of your tool will not be called.

@tool
class_name MyTool
extends Node

@export var resource: MyResource:
    set(new_resource):
        resource = new_resource
        _on_resource_set()

# This will only be called when you create, delete, or paste a resource.
# You will not get an update when tweaking properties of it.
func _on_resource_set():
    print("My resource was set!")

この問題をかいくぐるにはまずリソースをツールにする必要があり、それがプロパティが設定された時にいつでも changed シグナルを発信するようにします:

# Make Your Resource a tool.
@tool
class_name MyResource
extends Resource

@export var property = 1:
    set(new_setting):
        property = new_setting
        # Emit a signal when the property is changed.
        changed.emit()

次に新しいリソースが設定された時のシグナルを接続します:

@tool
class_name MyTool
extends Node

@export var resource: MyResource:
    set(new_resource):
        resource = new_resource
        # Connect the changed signal as soon as a new resource is being added.
        if resource != null:
            resource.changed.connect(_on_resource_changed)

func _on_resource_changed():
    print("My resource just changed!")

最後に、シグナルを接続解除するのを忘れないようにしましょう。古いリソースが別の場所で使われ変更されると必要ない更新が起きてしまいます。

@export var resource: MyResource:
    set(new_resource):
        # Disconnect the signal if the previous resource was not null.
        if resource != null:
            resource.changed.disconnect(_on_resource_changed)
        resource = new_resource
        if resource != null:
            resource.changed.connect(_on_resource_changed)

ノード設定の警告を報告する

Godotは ノード設定警告システムを使いノードの設定が不適切なことをユーザーに警告します。ノードが適切に設定されていないとき、シーンドックのノード名の横に黄色の警告マークが現れます。アイコンにカーソルをホバーするかクリックすると、警告メッセージがポップアップします。この機能はあなたやチームがシーン設定をするときにミスを避けるために使えます。

ノード設定警告を使うとき、警告に影響したり消したりする可能性がある値を変更する時には: ref:`update_configuration_warnings<class_Node_method_update_configuration_warnings>`を呼ぶ必要があります。デフォルトでは、警告はシーンを閉じて再度開いたときにのみ更新されます。

# Use setters to update the configuration warning automatically.
@export var title = "":
    set(p_title):
        if p_title != title:
            title = p_title
            update_configuration_warnings()

@export var description = "":
    set(p_description):
        if p_description != description:
            description = p_description
            update_configuration_warnings()


func _get_configuration_warnings():
    var warnings = []

    if title == "":
        warnings.append("Please set `title` to a non-empty value.")

    if description.length() >= 100:
        warnings.append("`description` should be less than 100 characters long.")

    # Returning an empty array means "no warning".
    return warnings

エディタスクリプトで一回限りのスクリプトを実行する

ある時には、エディタから簡単に実行できないような特定の作業を自動化するためにコードを一回だけ実行する必要があるかもしれません。たとえば:

  • プロジェクトを実行せずにGDScriptやC#スクリプトのプレイグラウンドとして使う。 print() 出力をエディタのアプトプットパネルに出力する。

  • 編集中のシーンにある光源ノードを拡大縮小し、ライトを好きな場所に配置した後にレベルが暗すぎたり明るすぎたりしないかを確認する。

  • コピーペーストした複製したノードをシーンインスタンスに置き換え、それらを後で変更しやすいようにする。

Godotでは EditorScript を継承することでこれが可能です。これは個別のスクリプトをエディタプラグインを作ることなくエディタから実行する方法を提供します。

エディタスクリプトを作成するには、ファイルシステムドックのフォルダや空の空間を右クリックし、 ** 新規作成 > スクリプト... ** を選択します。スクリプト作成ダイアログでは、ツリーアイコンを選択して継承元のオブジェクトを選択します。(または EditorScript を直接左のフィールドに入力します。大文字と小文字は区別されます。):

エディタスクリプトを作るスクリプトエディタの作成ダイアログ

エディタスクリプトを作るスクリプトエディタの作成ダイアログ

これで自動的にエディタスクリプトのためのスクリプトテンプレートが選択され、 _run() メソッドが既に入っています:

@tool
extends EditorScript

# Called when the script is executed (using File -> Run in Script Editor).
func _run():
    pass

This _run() method is executed when you use any of the 4 approaches that can be used to run an EditorScript:

  • Use File > Run at the top of the script editor with the EditorScript being the current tab.

  • Press the keyboard shortcut Ctrl + Shift + X while the EditorScript is the current tab. This keyboard shortcut is only effective when focused on the script editor.

  • Right-click the script in the FileSystem dock and choose Run.

  • Add a class_name <name> at the top of the script, bring up the command palette by pressing Ctrl + Shift + P, and enter the class name to run it. The entry will be named according to the class name, with automatic capitalization applied.

Scripts that extend EditorScript must be @tool scripts to function.

注釈

EditorScripts can only be run from the Godot script editor. If you are using an external editor, use one of the last two approaches to run the script.

注釈

C# EditorScripts cannot be run from the script editor as it only supports GDScript. Please refer to the above alternative approaches to run custom C# EditorScripts.

Keep in mind C# tool scripts will only appear in the command palette when denoted by the GlobalClass attribute.

危険

エディタスクリプトには元に戻す/やり直す機能がありません、なのでもしスクリプトがデータを変更するようデザインされているなら ** 実行する前に必ずシーンをセーブしてください ** 。

To access nodes in the currently edited scene, use the EditorInterface.get_edited_scene_root() method which returns the root Node of the currently edited scene. Here's an example that recursively gets all nodes in the currently edited scene and doubles the range of all OmniLight3D nodes:

@tool
# Thanks to the class name, we can run this script by bringing up
# the command palette and searching "Scale Omni Lights".
class_name ScaleOmniLights
extends EditorScript

func _run():
    for node in EditorInterface.get_edited_scene_root().find_children("", "OmniLight3D"):
        # Don't operate on instanced subscene children, as changes are lost
        # when reloading the scene.
        # See the "Instancing scenes" section below for a description of `owner`.
        var is_instanced_subscene_child = node != get_scene() and node.owner != get_scene()
        if not is_instanced_subscene_child:
            node.omni_range *= 2.0
            EditorInterface.mark_scene_as_unsaved()

In the above example, we also call EditorScript.mark_scene_as_unsaved() after any modification that affects the scene's state. This allows the editor to display the scene as "unsaved" (i.e. with an asterisk next to the name). This way, you also get a confirmation when trying to close the scene with unsaved changes.

Tip

You can change the currently edited scene at the top of the editor even while the Script view is open. This will affect the return value of EditorInterface.get_edited_scene_root, so make sure you've selected the scene you intend to iterate upon before running the script.

シーンのインスタンス化

通常はpacked sceneをインスタンス化し、エディタで現在開いているシーンに追加することができます。デフォルトでは、 Node.add_child(node) によって追加されたノードやシーンはシーンツリードックで ** 表示されず ** 、ディスクにも ** 残りません ** 。シーンを保存する時にこのノードやシーンをシーンツリードックで表示したりディスクに保存されるようにするには、子ノードの owner プロパティを編集中のシーンルートに設定する必要があります。

@tool の場合:

func _ready():
    var node = Node3D.new()
    add_child(node) # Parent could be any node in the scene

    # The line below is required to make the node visible in the Scene tree dock
    # and persist changes made by the tool script to the saved scene file.
    node.owner = get_tree().edited_scene_root

If you are using EditorScript:

func _run():
    # `parent` could be any node in the scene.
    var parent = get_scene().get_node("Parent")
    var node = Node3D.new()
    parent.add_child(node)

    # The line below is required to make the node visible in the Scene tree dock
    # and persist changes made by the tool script to the saved scene file.
    node.owner = get_scene()

注釈

Changes made by tool scripts and EditorScript (such as adding nodes or modifying properties) do not automatically mark the scene as unsaved. To show the asterisk (*) and prevent accidental data loss, call EditorInterface.mark_scene_as_unsaved() after modifications, or use EditorUndoRedoManager for undo support.

警告

@tool を不適切に使用すると、多くのエラーが発生する可能性があります。まずはあなたが望むようにコードを書き、それができた後で @tool キーワードを先頭に追加する方法をお勧めします。また、エディタで実行するコードはゲームで実行するコードから分離するようにしてください。これにより、バグを簡単に見つけることができます。