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

Godot继承场景实例化行为一致问题:击中一敌全敌受击求助

问题描述

我在Godot中创建了名为EnemyBase的父场景(对应EnemiesBase类,继承自CharacterBody2D),并基于它创建了名为Caius的继承子场景作为敌人。现在在关卡场景中实例化多个该子场景实例,但所有敌人的行为完全一致:当我击中其中一个敌人时,其他所有敌人也会触发受击效果。请求帮助修复。

场景示意图

父类(EnemiesBase)代码

class_name EnemiesBase extends CharacterBody2D

#DICT
var ANIMATIONS_ENEMY:Dictionary = { "idle" : "idle", 
                                    "walk" : "walk", 
                                    "attack" : "attack", 
                                    "hit" : "hit",
                                    "dead" : "dead" 
                                    }

#VAR
var _can_attack = false
var _collision_weapon_default_positionX:float
var _raycast_default_rotate_position:float
var _player_ref:Player
var _enemy_direction:Vector2 = Vector2.ZERO 
var _enemy_is_dead:bool = false
var _show_blood:bool = false
var _is_visible_on_screen:bool = false

#ONREADY
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var ray_cast_2d: RayCast2D = $RayCast2D
@onready var collision_shape_2d_attack: CollisionShape2D = $AreaAttackBase/CollisionShape2D
@onready var timer_attack: Timer = $TimerAttack
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D
@onready var timer_to_free: Timer = $TimerToFree
@onready var sound: AudioStreamPlayer2D = $Sound

#EXPORT
@export_category("EnemiesBase Cfg")
@export var speed:float = 50
@export var life_health:int = 20
@export var damage_attack:int = 5
@export var enemy_name:String = "Unnamed"



func _ready() -> void:
    _show_blood = GameSetup.show_blood()
    SignalManager.on_enemy_hit.connect(on_enemy_hit)
    _collision_weapon_default_positionX = collision_shape_2d_attack.position.x
    _player_ref = get_tree().get_first_node_in_group(GameManager.PLAYER_GROUP)  
    _raycast_default_rotate_position = ray_cast_2d.rotation
    set_default_scale_enemy()
    
    
func _physics_process(_delta: float) -> void:
    if(_enemy_is_dead):
        set_physics_process(false)
        return
    _flip_me()
    _enemy_move()
    
    
func _enemy_move() -> void: 
    if(_player_ref.is_player_upraycast_colliding() or _player_ref.is_player_downraycast_colliding()):
        animated_sprite.play(ANIMATIONS_ENEMY.idle)
        set_default_scale_enemy()
        return
    if((_player_ref != null) and 
       (_can_attack == false) and 
       (!ray_cast_2d.is_colliding()) and 
       (_is_visible_on_screen)):            
            var _player_collision:Vector2 = _player_ref.get_target_to_attack_position()
            var x_direction: float = sign(_player_collision.x - global_position.x)
            var y_direction: float = sign(_player_collision.y - global_position.y)
            _enemy_direction = Vector2(x_direction, y_direction)
            velocity = _enemy_direction * speed 
            animated_sprite.play(ANIMATIONS_ENEMY.walk) 
            set_default_scale_enemy()
            move_and_slide()    
        
func _flip_me() -> void:    
    if(_player_ref.global_position.x > global_position.x):
        animated_sprite.flip_h = false
        collision_shape_2d_attack.position.x = _collision_weapon_default_positionX
        ray_cast_2d.rotation = _raycast_default_rotate_position
    elif(_player_ref.global_position.x < global_position.x):
        animated_sprite.flip_h = true
        collision_shape_2d_attack.position.x = -_collision_weapon_default_positionX
        ray_cast_2d.rotation = -_raycast_default_rotate_position
        
func is_enemy_dead(damage:int) -> bool:
    life_health -= damage
    if(life_health <= 0):
        timer_to_free.start()
        call_deferred("disable_collisions") 
        return true
    return false
    
func disable_collisions() -> void:
    collision_shape_2d.disabled = true
    collision_shape_2d_attack.disabled = true
    ray_cast_2d.enabled = false
    
func kill_enemy() -> void:
    queue_free()
    
func on_enemy_hit(damage:int) -> void:
    print("Parent Enemy REceive Damage: ", damage)
    
func set_default_scale_enemy() -> void:
    EnemiesCfg.set_default_scale_animatedsprite2d(animated_sprite)

func _on_timer_to_free_timeout() -> void:
    kill_enemy()


func _on_visible_on_screen_notifier_2d_screen_entered() -> void:
    print("enemy is on scene")
    _is_visible_on_screen = true
    SignalManager.get_enemies_on_screen.emit(self)


func _on_visible_on_screen_notifier_2d_screen_exited() -> void:
    print("enemy out of scene")
    _is_visible_on_screen = false

子类(Caius)代码

class_name Caius extends EnemiesBase

@onready var blood: Node2D = $Blood

func _physics_process(delta: float) -> void:
    if(_enemy_is_dead):
        animated_sprite.play(ANIMATIONS_ENEMY.dead)
        return
    super._physics_process(delta)
    enemy_attack()

func enemy_attack() -> void:
    if(ray_cast_2d.is_colliding() and _can_attack == false and !_enemy_is_dead):        
        _can_attack = true
        animated_sprite.play(ANIMATIONS_ENEMY.attack)
        timer_attack.start()
        _player_ref.on_player_receive_hit(damage_attack)
        
func set_idle_animation() -> void:
    if(_enemy_is_dead):
        return
    animated_sprite.play(ANIMATIONS_ENEMY.idle)

func _on_timer_attack_timeout() -> void:
    _can_attack = false
    
#enemy hit dead
func on_enemy_hit(damage:int) -> void:  
    if(_player_ref.get_key_state_selected() != "ARMS"):
        display_blood()
    if(!is_enemy_dead(damage)):
        animated_sprite.play(ANIMATIONS_ENEMY.hit)
        if(animated_sprite.flip_h == true):
            global_position.x += global_position.x * 0.1
        elif(animated_sprite.flip_h == false):
            global_position.x -= global_position.x * 0.1
        animated_sprite.play(ANIMATIONS_ENEMY.dead)
        if(_player_ref.get_key_state_selected() == "ARMS"):
            SoundManager.execute_sound_punch(sound)
    if(is_enemy_dead(damage)):
        _enemy_is_dead = true
        animated_sprite.play(ANIMATIONS_ENEMY.dead)
        SoundManager.execute_sound_argh(sound)
        super.set_physics_process(false)
        
func display_blood() -> void:
    if(_show_blood):            
        for b in blood.get_children():
            SignalManager.on_execute_blood.emit(b)
                    
func _on_animated_sprite_2d_animation_finished() -> void:
    if(_can_attack):
        animated_sprite.play(ANIMATIONS_ENEMY.idle)

问题根源与修复方案

问题1:全局信号广播导致所有敌人响应

所有敌人实例都连接了SignalManager.on_enemy_hit全局信号,信号触发时未指定目标,导致所有订阅该信号的敌人都会执行受击逻辑,出现集体受击现象。

修复方式(二选一):

方案一:修改信号传递逻辑,携带目标敌人实例

  1. 更新SignalManager中的信号定义:
    signal on_enemy_hit(damage: int, target_enemy: Node2D)
    
  2. 在玩家攻击检测代码中,触发信号时传递被击中的敌人实例:
    # 假设检测到击中enemy实例
    SignalManager.on_enemy_hit.emit(attack_damage, enemy)
    
  3. 在EnemiesBase和Caius的on_enemy_hit方法中添加判断,仅当前实例为目标时才处理:
    # EnemiesBase中修改
    func on_enemy_hit(damage:int, target_enemy:Node2D) -> void:
        if target_enemy != self:
            return
        print("Parent Enemy REceive Damage: ", damage)
    
    # Caius中修改
    func on_enemy_hit(damage:int, target_enemy:Node2D) -> void:  
        if target_enemy != self:
            return
        # 原有逻辑保留
        if(_player_ref.get_key_state_selected() != "ARMS"):
            display_blood()
        # ... 后续代码
    

方案二:取消全局信号,直接调用目标敌人方法
删除EnemiesBase的_ready中SignalManager.on_enemy_hit.connect(on_enemy_hit)代码,在玩家攻击检测到具体敌人时,直接调用其on_enemy_hit方法:

# 玩家攻击检测代码中
if enemy != null:
    enemy.on_enemy_hit(attack_damage)

问题2:子类受击逻辑重复扣血

Caius的on_enemy_hit方法中两次调用is_enemy_dead(damage),导致敌人血量被重复扣除。

修复方式:

将is_enemy_dead的调用结果保存为变量,避免重复调用:

func on_enemy_hit(damage:int) -> void:  
    if(_player_ref.get_key_state_selected() != "ARMS"):
        display_blood()
    var is_dead = is_enemy_dead(damage)
    if(!is_dead):
        animated_sprite.play(ANIMATIONS_ENEMY.hit)
        # 修复位移逻辑:原代码位移方向错误,改为向远离玩家的方向推
        var push_dir = global_position.direction_to(_player_ref.global_position).opposite()
        global_position += push_dir * 8 # 可根据需求调整数值
        if(_player_ref.get_key_state_selected() == "ARMS"):
            SoundManager.execute_sound_punch(sound)
    else:
        _enemy_is_dead = true
        animated_sprite.play(ANIMATIONS_ENEMY.dead)
        SoundManager.execute_sound_argh(sound)
        set_physics_process(false)

内容的提问来源于stack exchange,提问作者FernandoPaiva

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 09:08:11