@tool
2026/4/14大约 4 分钟
最后更新日期:2026-04-16
最后同步日期:2026-04-15 | Godot 官方原文 — @tool
@tool
定义
@tool 是 GDScript 中的一个注解(annotation),用来告诉 Godot 这个脚本不仅在游戏运行时执行,在编辑器中也要执行。正常情况下,脚本只有在游戏运行时才会跑起来。但加了 @tool 之后,即使你只是在编辑器里摆弄场景,脚本中的代码也会立刻生效。
简单说,@tool 让你的脚本变成"实时预览"模式。比如你做了一个自定义的网格对齐工具,加了 @tool 之后,你在编辑器里拖动节点就能实时看到对齐效果,不需要每次都运行游戏。
在 C# 中,对应的写法是在类上方添加 [Tool] 特性。
语法
C#
[Tool]
public partial class GridSnap : Node3D
{
[Export] public int ExGridSize = 32;
public override void _Process(double delta)
{
// 在编辑器中也会执行
}
}GDScript
@tool
extends Node3D
@export var ex_grid_size: int = 32
func _process(delta):
# 在编辑器中也会执行
pass参数说明
@tool 不需要任何参数——它直接写在脚本的最顶部(在 extends 之前或之后都可以,但通常写在最前面)。
| 说明项 | 详情 |
|---|---|
| 放置位置 | 必须放在脚本的最顶部(第一行) |
| 影响范围 | 整个脚本都会在编辑器中执行 |
| 注意事项 | 需要用 Engine.IsEditorHint() 区分编辑器和运行时 |
返回值
@tool 是一个注解,没有返回值。它的作用是让整个脚本在编辑器环境中也能运行。
代码示例
C#
using Godot;
[Tool]
public partial class GridSnap : Node3D
{
// ===== 基础用法:编辑器中实时预览 =====
[Export] public int ExGridSize = 32;
public override void _Process(double delta)
{
if (Engine.IsEditorHint())
{
// 只在编辑器中执行:将位置对齐到网格
Vector3 pos = Position;
Position = new Vector3(
Mathf.RoundToInt(pos.X / ExGridSize) * ExGridSize,
Mathf.RoundToInt(pos.Y / ExGridSize) * ExGridSize,
Mathf.RoundToInt(pos.Z / ExGridSize) * ExGridSize
);
}
}
// 编辑器中拖动节点时,位置自动对齐到 32 的整数倍
// ===== 实际场景:自定义编辑器可视化 =====
[Export(PropertyHint.Range, "0,100,1")]
public int ExDetectionRadius = 50;
private MeshInstance3D _debugSphere;
public override void _Ready()
{
if (Engine.IsEditorHint())
{
_debugSphere = new MeshInstance3D();
AddChild(_debugSphere);
UpdateDebugVisual();
}
}
public override void _Process(double delta)
{
if (Engine.IsEditorHint())
{
UpdateDebugVisual();
}
}
private void UpdateDebugVisual()
{
// 在编辑器中显示检测范围的可视化球体
GD.Print($"检测范围更新: 半径={ExDetectionRadius}");
}
// 编辑器中调整 ExDetectionRadius 时实时更新
// ===== 进阶用法:编辑器工具脚本 =====
[Export] public bool ExAutoRename = true;
[Export] public string ExNamePrefix = "Enemy_";
public override void _Process(double delta)
{
if (!Engine.IsEditorHint()) return;
if (!ExAutoRename) return;
// 在编辑器中自动给子节点加上前缀
int index = 0;
foreach (var child in GetChildren())
{
if (child is Node3D node3D)
{
string expectedName = $"{ExNamePrefix}{index:D3}";
if (node3D.Name != expectedName)
{
node3D.Name = expectedName;
GD.Print($"重命名: -> {expectedName}");
}
index++;
}
}
}
// 编辑器中自动将子节点重命名为 Enemy_000, Enemy_001...
}GDScript
@tool
extends Node3D
# ===== 基础用法:编辑器中实时对齐网格 =====
@export var ex_grid_size: int = 32
func _process(delta):
if Engine.is_editor_hint():
# 只在编辑器中执行:将位置对齐到网格
position = Vector3(
roundi(position.x / ex_grid_size) * ex_grid_size,
roundi(position.y / ex_grid_size) * ex_grid_size,
roundi(position.z / ex_grid_size) * ex_grid_size
)
# 编辑器中拖动节点时,位置自动对齐到 32 的整数倍
# ===== 实际场景:自定义编辑器可视化 =====
@export_range(0, 100, 1) var ex_detection_radius: int = 50
func _ready():
if Engine.is_editor_hint():
update_debug_visual()
func _process(delta):
if Engine.is_editor_hint():
update_debug_visual()
func update_debug_visual():
# 在编辑器中显示检测范围
print("检测范围更新: 半径=%d" % ex_detection_radius)
# 编辑器中调整 ex_detection_radius 时实时更新
# ===== 进阶用法:编辑器工具脚本 =====
@export var ex_auto_rename: bool = true
@export var ex_name_prefix: String = "Enemy_"
func _process(delta):
if not Engine.is_editor_hint():
return
if not ex_auto_rename:
return
# 在编辑器中自动给子节点加上前缀
var index := 0
for child in get_children():
if child is Node3D:
var expected_name := "%s%03d" % [ex_name_prefix, index]
if child.name != expected_name:
child.name = expected_name
print("重命名: -> %s" % expected_name)
index += 1
# 编辑器中自动将子节点重命名为 Enemy_000, Enemy_001...注意事项
@tool必须放在脚本的最顶部(第一行有效代码)。- 加了
@tool后,脚本中的_process、_physics_process等函数在编辑器中也会执行,可能影响编辑器性能。请确保代码足够轻量。 - 务必使用
Engine.IsEditorHint()(C#)或Engine.is_editor_hint()(GDScript) 来区分代码是在编辑器中运行还是游戏中运行,避免编辑器中的代码在发布后继续执行。 @tool脚本中的_ready()会在编辑器中场景加载时执行,不仅在游戏运行时。- 在
@tool脚本中不要做危险操作(如文件删除、网络请求等),因为这些操作在编辑器中就会执行。 - C# 中使用
[Tool]特性,放在类声明上方。 @tool常用于制作编辑器插件、自定义 Gizmo、实时预览工具等高级场景。
