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 腳本潛在濫用的保護. 在操作場景樹時, 尤其是通過 Node.queue_free 時, 要 極其 謹慎, 因為如果你在編輯器運作涉及到節點的邏輯時釋放該節點, 可能會導致當機.

How to use @tool

要把一個腳本變成一個工具, 在你的程式碼頂部新增關鍵字 @tool .

要檢查您目前是否在編輯器中, 請使用 : Engine.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.

備註

編輯器中的修改是永久性的. 例如, 在下面的情況下, 當我們刪除腳本時, 節點將保持其旋轉. 要注意避免做不需要的修改.

備註

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

在場景中新增一個 Sprite 節點, 並將紋理設定為Godot圖示. 新增並打開腳本, 並將其更改為:

@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

保存腳本. 現在, 物件將在編輯器中順時針旋轉, 但如果您運作遊戲, 它將逆時針旋轉.

本地坐標

在腳本中新增並匯出一個變數速度. 在 "setget" 之後的函式set_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

備註

其他節點的程式碼不會在編輯器中運作. 您對其他節點的存取權限被限制了. 您可以存取樹和節點及其預設屬性, 但無法存取使用者變數. 如果要這樣做, 其他節點也必須在編輯器中運作. AutoLoad節點時無法在編輯器中存取的.

程式碼樣式設定

Godot 使用*節點設定警告*系統來警告使用者有關錯誤配置的節點。當節點配置不正確時,場景停靠欄中該節點名稱旁邊會出現一個黃色警告旗標。當您將滑鼠懸停或點擊該圖示時,會彈出警告訊息。您可以在腳本中使用此功能來幫助您和您的團隊在設定場景時避免錯誤。

使用節點配置警告時,當影響或刪除警告的任何值發生變更時,您需要呼叫 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

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 屬性設為目前編輯場景的根節點。

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

警告

不適當地使用 @tool 會產生許多錯誤. 建議首先按照你想要的方式寫程式碼, 然後才在上面新增 @tool 關鍵字. 另外, 要確保把在編輯器中運作的程式碼和在遊戲中運作的程式碼分開. 這樣, 你可以更容易地發現錯誤.