You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Godot切换场景崩溃:敌人实例检测空Player对象

解决Godot场景切换时因敌人引用空Player导致的崩溃问题

核心问题分析

敌人节点在_ready阶段缓存了Player的引用,当Player因死亡被移除或场景切换时,该引用变为nil,但敌人的_physics_process仍在持续尝试访问player.global_position,触发空引用错误直接导致程序崩溃。

具体修复方案

方案1:在敌人逻辑中添加空引用检查

修改敌人代码,每次执行运动逻辑前先判断Player是否存在,不存在则跳过逻辑或直接销毁自身:

extends CharacterBody2D

@onready var player = get_tree().get_first_node_in_group("player")
@onready var death_time = get_tree().get_first_node_in_group("deathtime")

const SPEED = 300.0

func _physics_process(delta):
    # 检查Player是否存在,不存在则终止当前帧逻辑
    if not player:
        # 可选:直接销毁敌人节点
        # queue_free()
        return
    
    rotation = player.global_position.angle_to_point(position)
    self.rotation_degrees += 180
    position += transform.x * SPEED * delta

方案2:场景切换前主动清理所有敌人

修改生成器代码,在切换场景前遍历并销毁所有敌人实例,从根源避免空引用:

extends Node

@onready var player = %Player
@onready var timer = %spawntimer
@onready var death_time = %DeathTime

func _ready():
    print("start")
    timer.start()

func spawn():
    print("spawned")
    var enemy = preload("res://scenes/enemy.tscn").instantiate()
    # 给敌人添加enemy组,方便批量查找
    enemy.add_to_group("enemy")
    add_sibling(enemy)
    enemy.global_position = Vector2(randi_range(-2647,4455),randi_range(2657,-3426))
    timer.start()

func _on_spawntimer_timeout():
    print("timeout")
    spawn()

func _on_death_time_timeout():
    # 遍历并销毁所有敌人
    for enemy in get_tree().get_nodes_in_group("enemy"):
        enemy.queue_free()
    # 执行场景切换
    get_tree().change_scene_to_file("res://scenes/menu.tscn")

方案3:通过信号监听Player销毁事件

在Player节点中定义销毁信号,敌人订阅后自动停止逻辑,实现更优雅的状态同步:

Player代码修改:

extends CharacterBody2D

@export var health = 30
const XSPEED = 400.0
const YSPEED = -400.0
@onready var death_time = %DeathTime

# 定义Player销毁信号
signal player_destroyed

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(_delta):
    var directiony = Input.get_axis("input_up", "input_down")
    if directiony:
            velocity.y = directiony * -YSPEED
    else:
        velocity.y = move_toward(velocity.y, 0, 13)

    var directionx = Input.get_axis("input_left", "input_right")
    if directionx:
        velocity.x = directionx * XSPEED
    else:
        velocity.x = move_toward(velocity.x, 0, 13)
        
    move_and_slide()
    for index in get_slide_collision_count():
        var collision = get_slide_collision(index)
        var body := collision.get_collider()
        print("Collided with: ", body.name)
        health -= 1
        if health <= 0:
            death_time.start()
            # 发出销毁信号
            player_destroyed.emit()
            break

func _exit_tree():
    # 节点被移除时补发信号
    player_destroyed.emit()

敌人代码修改:

extends CharacterBody2D

@onready var player = get_tree().get_first_node_in_group("player")
@onready var death_time = get_tree().get_first_node_in_group("deathtime")

const SPEED = 300.0

func _ready():
    # 订阅Player的销毁信号
    if player:
        player.player_destroyed.connect(_on_player_destroyed)

func _physics_process(delta):
    if not player:
        return
    
    rotation = player.global_position.angle_to_point(position)
    self.rotation_degrees += 180
    position += transform.x * SPEED * delta

func _on_player_destroyed():
    # 清空Player引用,终止后续逻辑
    player = null
    # 可选:直接销毁敌人
    # queue_free()

额外优化建议

  • 避免在_ready阶段缓存跨场景节点引用,可改为每次需要时通过get_tree().get_first_node_in_group动态获取,或使用全局自动加载节点统一管理Player状态。
  • 若敌人节点存在于场景树根节点而非当前场景内,change_scene_to_file不会自动销毁这些节点,必须手动清理。

内容的提问来源于stack exchange,提问作者monkie-dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:13:17