初面网初面网

GDScript 节点与场景操作

访问子节点

通过路径访问

$NodePath

$get_node() 的简写形式,用于获取当前节点的子节点。

$Sprite2D
$AnimationPlayer
$"My Child Node"

路径表示法

语法说明
$Child获取名为 Child 的直接子节点
$Child/SubChild获取 Child 下的 SubChild 节点
%Node获取唯一节点(用 unique_name 设置过的节点)

脚本所在节点相关

get_node(".")           # 获取自身
get_parent()            # 获取父节点
get_tree().root         # 获取根节点(场景树的最高层)
get_tree().current_scene  # 获取当前正在运行的场景

获取多个子节点

get_children()                    # 返回所有子节点的数组
get_child_count()                 # 返回子节点数量
get_node_or_null("Child")         # 安全获取,不存在返回 null
has_node("Child")                  # 检查是否存在指定节点

动态增删节点

添加节点

var new_node = Node2D.new()
add_child(new_node)                # 添加为子节点

删除节点

remove_child(node)                 # 从父节点移除(节点仍在内存中)
node.queue_free()                  # 延迟释放(当前帧结束后释放,安全)
node.free()                        # 立即释放(危险,可能出错)

实例化场景

instance() 是实例化 PackedScene 的标准方法:

var packed_scene = preload("res://path/to/scene.tscn")
var instance = packed_scene.instance()
add_child(instance)

instance() 会返回一个根节点类型的新节点树,自动设置好父子关系。

动态增删节点示例

extends Node2D

var bullet_scene = preload("res://Bullet.tscn")

func spawn_bullet():
    var bullet = bullet_scene.instance()
    bullet.position = Vector2(100, 100)
    add_child(bullet)

func remove_bullet(bullet):
    remove_child(bullet)
    bullet.queue_free()

实例化场景的其他方式

使用 add_child 时设置参数

var instance = packed_scene.instance()
add_child(instance, true)  # 第二参数 true 表示 "inherit_owner"

克隆节点(不推荐)

var copy = duplicate()
add_child(copy)

检测输入

动作定义

Project > Project Settings > Input Map 中定义动作名称和对应的按键。

输入检测函数

is_action_pressed("move_right")   # 按住时返回 true
is_action_just_pressed("jump")     # 刚按下那一帧返回 true
is_action_just_released("jump")   # 刚释放那一帧返回 true

键盘按键直接检测

Input.is_key_pressed(KEY_ESCAPE)
Input.is_mouse_button_pressed(BUTTON_LEFT)

鼠标输入

Input.getMouseMotion()                      # 获取鼠标移动量
Input.get_globalMousePosition()              # 获取鼠标全局位置
get_viewport().get_mouse_position()          # 获取当前视口内的鼠标位置

摇杆/手柄

Input.get_joy_axis(0, JOY_AXIS_0)            # 获取手柄摇杆值
Input.is_joy_button_pressed(0, JOY_BUTTON_A) # 手柄按钮检测

输入示例

func _physics_process(delta):
    var velocity = Vector2.ZERO

    if Input.is_action_pressed("move_right"):
        velocity.x += 1
    if Input.is_action_pressed("move_left"):
        velocity.x -= 1
    if Input.is_action_pressed("jump"):
        velocity.y -= 1

    position += velocity.normalized() * speed * delta

资源路径

常用路径

路径说明
res://项目根目录(项目资源文件夹)
res://path/to/file绝对路径,从项目根开始
user://用户数据目录(可写,用于存档等)

获取路径的方法

get_path()                        # 获取当前节点所在场景中的路径
get_scene_file_path()             # 获取当前场景的 .tscn 路径
ResourceLoader.load("res://...")  # 动态加载资源

路径操作

get_path().get_base_dir()         # 获取所在目录
get_path().get_file()             # 获取文件名(含扩展名)
get_path().get_extension()        # 获取扩展名

动态加载资源

var texture = load("res://assets/image.png")
var scene = load("res://path/to/scene.tscn")

# preload 在脚本解析时执行(编译期)
var icon = preload("res://icon.png")

访问项目设置中的自定义路径

ProjectSettings.get("custom_setting/path")

实用技巧

节点路径缓存(性能优化)

onready var sprite = $Sprite2D

# 或在 _ready() 中缓存
var sprite: Sprite2D
func _ready():
    sprite = get_node("Sprite2D")

安全地检查和获取节点

if has_node("Child"):
    var child = get_node("Child")
    # 安全使用 child

var node = get_node_or_null("OptionalChild")
if node:
    # node 存在

跨场景访问

get_tree().root.get_node("Main/Player")
get_tree().current_scene

get_tree() 场景树操作

get_tree() 返回 SceneTree 对象,是场景管理的核心。

常用属性

属性/方法说明
root获取根视口,是所有场景的最上层节点
current_scene获取当前正在运行的主场景
paused获取/设置游戏暂停状态

场景切换

get_tree().reload_current_scene()     # 重新加载当前场景
get_tree().change_scene("res://path/to/scene.tscn")  # 切换到指定场景

场景切换示例

func go_to_game():
    get_tree().change_scene("res://Game.tscn")

func restart_level():
    get_tree().reload_current_scene()

异步场景切换

func _ready():
    var result = get_tree().change_scene("res://Game.tscn")
    if result == OK:
        print("场景切换成功")

暂停与恢复

get_tree().paused = true   # 暂停游戏
get_tree().paused = false  # 恢复游戏

遍历场景树

get_tree().get_nodesInGroup("enemies")  # 获取所有在 "enemies" 分组的节点

get_tree().call_group("enemies", "die")  # 调用分组中所有节点的 "die" 方法

get_tree().notify_child_processed(node)  # 通知子节点已处理

分组管理

# 添加节点到分组
add_to_group("players")
add_to_group("enemies")

# 从分组移除
remove_from_group("players")

# 检查节点是否在分组中
is_in_group("players")

全局节点访问

get_tree().root.get_node("Main/Player")  # 通过绝对路径访问
get_tree().getFirstNode()                 # 获取第一个匹配节点

发出信号

get_tree().emit_signal("game_over")      # 发出全局信号
get_tree().broadcast("game_over")        # 向所有节点广播

获取根视口

get_tree().root               # 获取根视口
get_viewport()                # 获取当前脚本所在节点的视口

帧同步

get_tree().processFrame       # 等待下一帧(idle process)
get_tree().physicsProcessFrame  # 等待下一物理帧(physics process)

quit 退出游戏

get_tree().quit()             # 退出游戏

注意事项

节点生命周期

  • 不要在 _init() 中访问节点:节点尚未加入场景树,$get_node() 可能返回 null
  • _ready() 是最早可安全访问子节点的时机:此时节点已完全加入场景树
  • queue_free() 是安全的删除方式:在当前帧结束后释放,避免在物理处理中立即删除导致的崩溃

节点路径访问

  • $get_node() 只能在场景树内使用:独立运行的脚本(如 main() 函数)无法使用
  • 路径是相对于脚本所在节点:使用相对路径时注意当前节点的位置
  • 节点名称区分大小写$Sprite2D$sprite2d 是不同的节点
  • % 唯一名称只在当前场景内唯一:跨场景使用可能导致冲突

实例化与删除

  • 实例化后必须 add_child():仅 instance() 不会显示节点
  • 删除节点前先检查存在性:已删除的节点调用 queue_free() 不会有问题,但 free() 会报错
  • 父子关系影响生命周期:子节点会随父节点一起删除或隐藏
  • 避免循环引用:大量节点动态创建/删除时注意内存管理

场景切换

  • change_scene() 是同步的:场景加载完成前游戏会卡住,大型场景建议用 change_scene_to() 异步加载
  • 切换场景不会自动清理旧场景:需确保旧场景的节点已正确释放,否则可能导致内存泄漏
  • reload_current_scene() 会丢失未保存的状态:做好数据持久化

输入检测

  • _input() 比 Input.is_action_pressed 更早触发:但 _input() 每帧可能多次调用
  • _physics_process() 固定帧率:不受 delta 影响的物理计算应放在这里
  • paused = true 会阻塞 _physics_process():但 _input()_process() 默认仍会响应

资源加载

  • preload() 在解析脚本时执行:路径必须是硬编码的常量,不能动态拼接
  • load() 在首次调用时缓存:后续调用返回同一对象,需注意状态共享
  • 资源路径使用正斜杠 /:Godot 内部会自动转换,但跨平台时需注意

分组与全局访问

  • 分组名称建议统一命名规范:如 "enemies""players""ui"
  • call_group() 会遍历所有节点:大量节点时注意性能,可使用 call_group_flags()
  • 避免过度依赖 get_tree().root 跨层级访问:会增加耦合度

更新于 2026/5/30