Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

在编辑器中运行代码

@tool 是什么?

@tool 是一行强大的代码,添加到脚本的顶部后,脚本就会在编辑器中执行。你还可以决定脚本的哪些部分在编辑器中执行、哪些部分在游戏中执行、哪部分在两者中均执行。

你可以使用它来做很多事情, 它在层次设计中非常有用, 可以直观地呈现难以预测的事物. 以下是一些用例:

  • 如果你有一门发射受物理学(重力)影响的炮弹的大炮, 你可以在编辑器中画出炮弹的轨迹, 使关卡设计容易得多.

  • 如果你有不同跳跃高度的跳线, 你可以绘制游戏角色能跳过的最大跳跃高度, 也可以让关卡设计变得更容易.

  • 如果你的游戏角色不使用精灵, 却使用代码来绘制, 你可以在编辑器中执行该绘图代码以查看你的游戏角色.

危险

@tool scripts run inside the editor, and let you access the scene tree of the currently edited scene. This is a powerful feature which also comes with caveats, as the editor does not include protections for potential misuse of @tool scripts. Be extremely cautious when manipulating the scene tree, especially via Node.queue_free, as it can cause crashes if you free a node while the editor runs logic involving it.

How to use @tool

To turn a script into a tool, add the @tool annotation at the top of your code.

To check if you are currently in the editor, use: 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.

Pieces of code that do not have either of the 2 conditions above will run both in-editor and in-game.

以下是 _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.

备注

Modifications in the editor are permanent. For example, in the following case, when we remove the script, the node will keep its rotation. Be careful to avoid making unwanted modifications.

备注

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.

Try @tool out

Add a Sprite2D node to your scene and set the texture to Godot icon. Attach and open a script, and change it to this:

@tool
extends Sprite2D

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

保存脚本并返回编辑器. 现在你应该看到你的对象在旋转. 如果你运行游戏, 它也会旋转.

../../_images/rotating_in_editor.gif

备注

如果你没有看到变化, 请重新加载场景(关闭它并再次打开).

现在让我们选择何时运行代码. 将 _process() 函数修改为:

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

保存脚本. 现在, 对象将在编辑器中顺时针旋转, 但如果你运行游戏, 它将逆时针旋转.

编辑变量

Add and export a variable speed to the script. To update the speed and also reset the rotation angle add a setter set(new_speed) which is executed with the input from the inspector. Modify _process() to include the rotation speed.

@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. Autoload nodes cannot be accessed in the editor at all.

报告节点配置警告

Godot uses a node configuration warning system to warn users about incorrectly configured nodes. When a node isn't configured correctly, a yellow warning sign appears next to the node's name in the Scene dock. When you hover or click on the icon, a warning message pops up. You can use this feature in your scripts to help you and your team avoid mistakes when setting up scenes.

When using node configuration warnings, when any value that should affect or remove the warning changes, you need to call update_configuration_warnings . By default, the warning only updates when closing and reopening the scene.

# 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

Running one-off scripts using EditorScript

Sometimes, you need to run code just one time to automate a certain task that is not available in the editor out of the box. Some examples might be:

  • Use as a playground for GDScript or C# scripting without having to run a project. print() output is displayed in the editor Output panel.

  • Scale all light nodes in the currently edited scene, as you noticed your level ends up looking too dark or too bright after placing lights where desired.

  • Replace nodes that were copy-pasted with scene instances to make them easier to modify later.

This is available in Godot by extending EditorScript in a script. This provides a way to run individual scripts in the editor without having to create an editor plugin.

To create an EditorScript, right-click a folder or empty space in the FileSystem dock then choose New > Script.... In the script creation dialog, click the tree icon to choose an object to extend from (or enter EditorScript directly in the field on the left, though note this is case-sensitive):

Creating an editor script in the script editor creation dialog

Creating an editor script in the script editor creation dialog

This will automatically select a script template that is suited for EditorScripts, with a _run() method already inserted:

@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 File > Run or the keyboard shortcut Ctrl + Shift + X while the EditorScript is the currently open script in the script editor. This keyboard shortcut is only effective when currently focused on the script editor.

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

警告

EditorScripts have no undo/redo functionality, so make sure to save your scene before running one if the script is designed to modify any data.

To access nodes in the currently edited scene, use the EditorScript.get_scene 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
extends EditorScript

func _run():
    for node in get_all_children(get_scene()):
        if node is 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

# This function is recursive: it calls itself to get lower levels of child nodes as needed.
# `children_acc` is the accumulator parameter that allows this function to work.
# It should be left to its default value when you call this function directly.
func get_all_children(in_node, children_acc = []):
    children_acc.push_back(in_node)
    for child in in_node.get_children():
        children_acc = get_all_children(child, children_acc)

    return children_acc

小技巧

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 EditorScript.get_scene, so make sure you've selected the scene you intend to iterate upon before running the script.

实例化场景

在编辑器中,你可以正常实例化打包场景,并将它们添加到当前打开的场景中。默认情况下,使用 Node.add_child(node) 添加的节点或场景在“场景”树面板中是不可见的,也不会持久化到磁盘上。如果你希望节点和场景在场景树面板中可见,并在保存场景时持久化到磁盘上,则需要将这些子节点的 owner 属性设为当前编辑场景的根节点。

If you are using @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

如果你使用 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()

警告

Using @tool improperly can yield many errors. It is advised to first write the code how you want it, and only then add the @tool annotation to the top. Also, make sure to separate code that runs in-editor from code that runs in-game. This way, you can find bugs more easily.