godotgodot4

Why do Inherited Scenes have the same behavior when instantiated?


I've created a Parent Scene as EnemyBase and after that I created Inherited Scene from that as Child that is Enemy. Now, I am creating instances of child on the Level Scene, but all children have the same behavior. When I do hit in one enemy the others enemies has receiving hit too. I don't know why it happens. How can I fix it.

Parent

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

Child

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)

enter image description here


Solution

  • Instead of making each enemy report whenever they are hit, you are waiting for the player to hit anything:

    SignalManager.on_enemy_hit.connect(on_enemy_hit)
    

    However, you aren't discriminating which enemy got hit, only the amount of damage taken; since your enemies connect to a global signal, all of them are taking damage regardless.

    Each enemy should be in charge of taking damage for themselves, and not a global singleton for them. From a design perspective, you should distribute the collision detection logic among your game objects: Player deals damage to whatever they hit, and whatever they hit reacts to it if it's supposed to, and every participating actor finally triggers global signals. OTOH, if you want a quick solution, extend your current detection logic to emit a signal which also carries a reference to the enemy node hit, not only the damage dealt.