Weird question.
I have a class object, a door, that is inheriting its base functions from its parent class. Knowing inheritance, I am only running ready once: in the main class, with a function in all children separate that triggers when ready is called.

I have a problem with an instance that keeps instancing at this door object. It should stop instancing when the objects is no longer present (ran a debug, the variables are all flipping) but for some reason this function, which is only in ready, continues to be called.

Any reasons a "ready" would get called twice? I am not using any loops or yields. (Update: solved, with answer below)

What happened: My monster kept instancing after removing itself and then was stuck in a loop of instancing and immediately removing itself every time its timer went off. Essentially, the function I called at ready kept repeating, because every time an enemy instance was removed, the nodes would fall back to ready. That's my theory anyways, please correct me if i am wrong so I can know how to work around this better.

If you're talking about _ready(), it gets called automatically when you add the object to the scene tree. However, all parent classes' _ready() methods also get called. Children's _ready() are called first, then parent's. Godot 4.x will change that a bit, so that you have more control.

I usually use specific methods for anything that I want called from _ready(), unless it's something trivial. Then I can make sure they only get called once, though technically you shouldn't duplicate code in an inherited class, so it shouldn't ever be an issue.

    duane Yeah i read about that, pretty sure in this forum at some point. 😃

    I am using a separate function in the children and calling that only in the parent's ready code so that doesn't seem to be it.

    By chance, does ready call a second time whenever an object "leaves" the tree? My enemy object keeps respawning/disappearing in a loop and the only instancing i have in the scene is coming from said door. (and the player object is having no problems).

    I will suck it up and post the code here though I don't like making people dig through this for me.

    This is the BASE event class for the door.

    extends Area2D
    
    
    class_name DoorEvent
    
    ### all doors have an area 2D. duh. attach sprites in editor.
    onready var _sprite = $AnimatedSprite
    onready var _box = $CollisionShape2D
    onready var _timer = $Timer
    
    var _enterBuffer = true ## this bool only flips on exits
    var _monsterBuffer = true ## same but for monster
    var _myDest : PackedScene ## holds scene destination, export in inherits\
    var _player = preload("res://actors/Lydia.tscn")
    var _enemy = preload("res://actors/Patches.tscn")
    var _doorProcessed = false
    
    
    ## determine door place and where to go
    var _amIDoorLeft = false ## update global with data
    var _pos = Vector2(0,0) ## zero velocity shorthand
    
    
    ## connect door functions/signals, play base animation
    func _ready():
    	var _d_enter = self.connect("body_entered",self,"_door_enter")
    	var _p_enter = self.connect("body_entered",self,"_enemy_enter")
    	var _d_exit = self.connect("body_exited",self,"_door_exit")
    	var _p_Jar = _timer.connect("timeout", self, "_pursuit_pull")
    	
    	## open animation if from correct side
    	if StageHand._lastDoorLeft == _amIDoorLeft:
    		_sprite.play('shut')
    	else:
    		_sprite.play('idle')
    	
    	## door processing
    	if _monsterBuffer == true or _enterBuffer == true:
    		_door_process()
    	if _monsterBuffer == false or _enterBuffer == false:
    		pass
    
    func _door_process():
    	pass ### placeholder overriden at instances
    
    
    ### ON ENTER, move player, remove enemy if buffer is off
    func _door_enter(body):
    		if body.name == "Lydia":
    			if _enterBuffer == false:
    				_z_edit()
    				StageHand._lastDoorLeft = _amIDoorLeft
    				_sprite.play("open")
    				yield(_sprite,"animation_finished")
    				var _nextRoom = get_tree().change_scene_to(_myDest)
    		
    		### parse distance to time in pursuit mode
    		if body.name == "Lydia" and Director._inPursuit == true:
    			_pursuit_parse_x()
    				
    				
    ### enemy door functions 
    func _enemy_enter(body):
    		if body.name == "Patches":
    			if _monsterBuffer == false:
    				_monsterBuffer = true
    				_z_edit()
    				_sprite.play("open")
    				body._velocity = Vector2.ZERO
    				Director._enemyHere = false
    				Director._inPursuit = false
    				_sprite.play('shut')
    				body.queue_free()
    
    
    ## process check, at end of shut, return to idle animation
    func _process(_delta):
    	if _sprite.frame == 3 and _sprite.animation == 'shut':
    		_door_idle()
    
    
    ## disable buffer when out of door frame
    func _door_exit(body):
    	if body.name == "Lydia":
    		_enterBuffer = false
    	if body.name == "Patches":
    		_monsterBuffer = false		
    
    
    ## idle animation frames
    func _door_idle():
    	_sprite.play('idle')
    
    
    ## assign door and destination based on place
    func _door_assign(_dest):
    	_myDest = _dest ## this will update in inheritors
    
    
    ## spawn bodies
    func _create_body(body):
    	_sprite.play('shut')
    	var _body_inst = body.instance()
    	self.add_child(_body_inst)
    	_body_inst.global_position.x = self.position.x + _pos.x
    	_enterBuffer = true
    
    
    
    ### enemy call when chasing between rooms, timeout from buffer
    func _pursuit_pull():
    	if Director._enemyHere == false:
    		_create_body(_enemy)
    	else:
    		return
    
    
    ### translate distance to time for room change
    func _pursuit_parse_x():
    	Director._pursuitJar = (abs(Director._playerVector2.x) - abs(Director._enemyVector2.x))/20
    	
    
    ### respawn player/enemy at catch location if they escape
    func _escape_process():
    	_pos = Director._playerVector2
    	_create_body(_player)
    	_pos = Director._enemyVector2
    	_create_body(_enemy)
    	Director._stunFlag = true
    	Director._escapeFlag = false
    	_buffers_off()
    	
    
    ### turn enter buffers off
    func _buffers_off():
    	_enterBuffer = false
    	_monsterBuffer = false
    	
    	
    ### z correct for left side doors
    func _z_edit():
    	if _amIDoorLeft:
    		self.z_index = self.z_index+1
    	else:
    		pass

    Then this is the inheritor, with its function, door_process, which was blank in the parent then is called here. (The inheritor overrides because it is called first, yes? There is no ready as you see in the inheritor.)

    extends "res://doors/DoorEventClass.gd"
    
    export var _myDestination : PackedScene
    
    func _door_process():
    	### give instance position, leftdoor flag and get resource
    	_pos = Vector2(100,0)
    	_amIDoorLeft = true
    	_door_assign(_myDestination)
    	
    	### instance player if coming from right
    	if StageHand._lastDoorLeft == false: 
    		if Director._escapeFlag == false:
    			_create_body(_player)
    		else:
    			pass
    
    		if Director._inPursuit == true:
    			if Director._enemyHere == false:
    				_timer.start(Director._pursuitJar)
    			if _monsterBuffer == false:
    				pass
    				
    		## when escaping, turn buffers off
    		if Director._escapeFlag == true:
    			_escape_process()
    		else:
    			pass
    	
    	## also buffers off if not spawning
    	if StageHand._lastDoorLeft == true:
    		_buffers_off()

    UPDATED: Solved. My pursuit flag was not a condition on spawn, so it kept repeating because it wasn't coded to the right bools. Here's the fix. One addition to a line in my _pursuitPull (enemy instancing) fixed it.

    func _pursuit_pull():
    	if Director._enemyHere == false and Director._inPursuit == true:
    		_create_body(_enemy)
    		_enterBuffer = false
    	else:
    		pass