Avoid invoking setget function on starting up - godot

I have a simple script like this:
tool
extends Node2D
export(int) var example_value=0 setget set_example_value
func set_example_value(val):
print("Setting example_value=",val)
#time/memory consuming code segment here
example_value=val
and I set example_value to 3 and then exit the game engine
Now when I startup godot again the set_example_value() gets invoked to set the value,
Is there any way where the example_value will get set to 3 without the setter function being invoked?
why am I doing this?
because I have a time/memory consuming function which generates sprites when the value is changed,
so when I startup godot I don't want to recreate those sprites I only want the value to be changed to what it was before closing godot

Please read my answer for Play animation without invoking setget? first.
The difference is that we will tell Godot to only store one of them. We can do that with _get_property_list. Thus, we are not going to use export with it.
For example we can do this:
var example_value := 0
func _get_property_list() -> Array:
return [
{
name = "example_value",
type = TYPE_INT,
usage = PROPERTY_USAGE_EDITOR
}
]
And the editor will show the variable because it has PROPERTY_USAGE_EDITOR, but it won't be stored because it does not have PROPERTY_USAGE_STORAGE.
If it is not stored, then when loading Godot won't find it, and won't set it (note that it might be already stored before you told Godot to not store it… Saving the resource again will fix it, or use an external editor).
Now the problem is that you are not saving the value at all. So we are going to have a two properties. One is only for the editor, and one is only for storage. And the storage one will not do the expensive process. Like this:
tool
extends Node
var example_value := 0 setget set_example_value
func set_example_value(mod_value:int) -> void:
print("HELLO")
example_value = mod_value
var example_value_storage:int setget set_example_value_storage, get_example_value_storage
func get_example_value_storage() -> int:
return example_value
func set_example_value_storage(mod_value:int) -> void:
example_value = mod_value
func _get_property_list() -> Array:
return [
{
name = "example_value",
type = TYPE_INT,
usage = PROPERTY_USAGE_EDITOR
},
{
name = "example_value_storage",
type = TYPE_INT,
usage = PROPERTY_USAGE_STORAGE
}
]
By the way, while we are at it, checking if the value is being modified in the setter might be a good idea:
func set_example_value(mod_value:int) -> void:
if example_value == mod_value:
return
print("HELLO")
example_value = mod_value

Just incase someone wanted a simpler (but hacky) workaround
tool
extends Node2D
export(int) var example_value=0 setget set_example_value
var startup=true
func set_example_value(val):
print("Setting example_value=",val)
if(startup):
example_value=val
return
#time/memory consuming code segment here
example_value=val
func _process(delta):
startup=false
set_process(false)
func _init():
set_process(true)

Related

Player not looking in the right direction

I have a player that you can move by clicking in a location. They are guided by pathfinding to also move around any potential obstacles in their way. Here is my script:
extends KinematicBody2D
export var speed = 200
var velocity = Vector2.ZERO
onready var navigation_agent = $NavigationAgent2D
func _ready():
navigation_agent.connect("velocity_computed", self, "move")
func _input(event):
if event.is_action_pressed("mouse_right"):
navigation_agent.set_target_location(event.get_global_position())
func _process(_delta):
if $RayCast2D.is_colliding():
if $RayCast2D.get_collider().is_in_group("enemy_ground_troop"):
self.velocity = Vector2.ZERO
ranged_attack()
if navigation_agent.is_navigation_finished():
return
var overlapping_areas = $EnemyDetector.get_overlapping_areas()
for area in overlapping_areas:
if area.is_in_group("enemy_ground_troop"):
navigation_agent.set_target_location(area.get_global_position())
look_at(area.get_global_position())
velocity = global_position.direction_to(navigation_agent.get_next_location()) * speed
look_at(global_position.direction_to(navigation_agent.get_next_location()))
$RayCast2D.global_rotation = self.global_rotation
navigation_agent.set_velocity(velocity)
func move(velocity):
velocity = move_and_slide(velocity)
func ranged_attack():
print_debug("fired ranged attack")
When I run the scene, the player is not looking where I want them too based on the look at commands. How can I fix this?
The look_at method takes a target position, not a direction.
So, instead of this:
look_at(global_position.direction_to(navigation_agent.get_next_location()))
Try this:
look_at(navigation_agent.get_next_location())

Switch between 2D layers programmatically

NOTE this is about Godot 4.0
In a particular scene, I've a set of layers, of which only one is visible at a time. My code is able to decide how a switch from one layer to other occurs.
I've implemented a class for solutioning this: ViewSwitch. That class has a set of ViewSwitchItem. The layers have to be a subtype of ViewSwitchItem.
The problem I'm facing is that, only once, from a layer first_menu, after I click the "Start Game" button in my first layer and switch to another layer start_game_menu, the game switches back to first_menu even if the player didn't meant so. Like I said, it happens only once. After you click like, say, the second time, you'll be transitioned to start_game_menu without being redirected to first_menu again. All this is done using GDScript. Something is wrong in my logic.
gd_util/ViewSwitch.gd
class_name ViewSwitch
var current_item: ViewSwitchItem = null
var items: Array[ViewSwitchItem] = []
func initialize() -> void:
swap(null)
for sw in items:
sw.parent_switch = self
sw.initialize()
func swap(swap: ViewSwitchItem) -> void:
if current_item == swap:
return
if current_item != null:
current_item.end(swap)
else:
immediate_swap(swap)
func immediate_swap(swap: ViewSwitchItem) -> void:
for sw in items:
sw.node.visible = false
if swap == null:
return
current_item = swap
swap.node.visible = true
swap.start()
gd_util/ViewSwitchItem.gd
class_name ViewSwitchItem
var parent_switch: ViewSwitch = null
var node: Node = null
func initialize():
pass
func start():
pass
func end(swap: ViewSwitchItem):
if parent_switch.current_item == swap:
return
immediate_swap(swap)
func immediate_swap(swap: ViewSwitchItem):
if parent_switch.current_item == swap:
return
parent_switch.immediate_swap(swap)
scenes/mainMenuScene/MainMenuScene.gd
extends Node2D
var view_switch: ViewSwitch = ViewSwitch.new()
var first_menu := MainMenuScene_firstMenu.new()
var start_game_menu := MainMenuScene_startGameMenu.new()
# Called when the node enters the scene tree for the first time.
func _ready():
view_switch.items = [
first_menu,
start_game_menu,
]
first_menu.node = $root/animation/container1
start_game_menu.node = $root/animation/container2_startGame
view_switch.initialize()
view_switch.swap(first_menu)
# first_menu
$root/animation/container1/startGameBtn.pressed.connect(func():
view_switch.swap(start_game_menu))
$root/animation/container1/exitBtn.pressed.connect(func():
get_tree().quit())
# start_game_menu
$root/animation/container2_startGame/panel/PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/returnBtn.pressed.connect(func():
view_switch.swap(first_menu))
$root/animation.animation_finished.connect(func(animation_name):
if animation_name == "Anim":
view_switch.swap(first_menu))
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
# first_menu
if view_switch.current_item == first_menu:
pass
# start_game_menu
elif view_switch.current_item == start_game_menu:
if Input.is_action_just_released("back"):
view_switch.swap(first_menu)
scenes/mainMenuScene/switches/MainMenuScene_firstMenu.gd
class_name MainMenuScene_firstMenu
extends ViewSwitchItem
var start_game_button = null
func initialize():
start_game_button = self.node.get_tree().root.get_child(0).get_node("root/animation/container1/startGameBtn")
func start():
start_game_button.grab_focus()
func end(swap: ViewSwitchItem):
immediate_swap(swap)
scenes/mainMenuScene/switches/MainMenuScene_startGameMenu.gd
class_name MainMenuScene_startGameMenu
extends ViewSwitchItem
var panel1 = null
func initialize():
panel1 = self.node.get_tree().root.get_child(0).get_node("root/animation/container2_startGame/panel")
panel1.after_popup.connect(func():
self.node.get_tree().root.get_child(0).get_node("root/animation/container2_startGame/panel/PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/returnBtn").grab_focus())
panel1.after_collapse.connect(func():
# switch to first_menu
immediate_swap(parent_switch.items[0]))
func start():
panel1.popup()
func end(swap: ViewSwitchItem):
panel1.collapse()
Thanks!

Godot smooth transition between players

I have a "parent" player scene, and I inherit scenes for each player. The parent player scene has a camera. When the game switches between players, one player turns off its camera, and the other player turns its camera on:
if state != State.ACTIVE:
# If this player is becoming active, also
# set camera current
state = State.ACTIVE
camera.current = true
else:
# If player is not becoming active,
# disable this players camera
camera.current = false
But players can be in different positions, so the camera "jumps" from one to the other. Can we do something more sophisticated, like set the new camera to the current position so the smooth setting can be used to handle the transition?
One idea is to do get_viewport().get_camera() to find the current position of the camera to try and sync the position of the current camera with the new camera that is about to turn on, but appears to not work for 2D scenes. CF: https://github.com/godotengine/godot/pull/38317
Sadly, as you found out, there is no way to get the current Camera2D in Godot 3.x. And you found the pull request that adds the feature to Godot 4.0.
What I'm going to suggest is to have one sole Camera2D, so that one is always the current one. And you can define Position2D inside your scenes that can serve as interpolation targets to move the Camera2D.
I have an script that I think will be useful for you (I made it to be RemoteTransform2D but backwards, it does push a transform, it pulls it), I call it anchor_transform_2d.gd:
tool
class_name AnchorTransform2D
extends Node2D
export var anchor_path:NodePath setget set_anchor_path
export var reference_path:NodePath setget set_reference_path
export var set_local_transform:bool
export(int, FLAGS, "x", "y") var translation_mode:int
export(int, FLAGS, "x", "y") var scale_mode:int
export var rotation_mode:bool
var _anchor:Node2D
var _reference:Node2D
func _physics_process(_delta: float) -> void:
if not is_instance_valid(_anchor) or Engine.editor_hint:
set_physics_process(false)
return
#INPUT
var input := _anchor.global_transform
if is_instance_valid(_reference):
input = _reference.global_transform.affine_inverse() * input
#TRANSLATION
var origin := Vector2 (
input.origin.x if translation_mode & 1 else 0.0,
input.origin.y if translation_mode & 2 else 0.0
)
#ROTATION
var angle := 0.0
if rotation_mode:
angle = input.get_rotation()
#SCALE
var source_scale = input.get_scale()
var scaling := Vector2 (
source_scale.x if scale_mode & 16 else 1.0,
source_scale.y if scale_mode & 32 else 1.0
)
#RESULT
_set_target_transform(
Transform2D(angle, origin) * Transform2D.IDENTITY.scaled(scaling)
)
func set_anchor_path(new_value:NodePath) -> void:
anchor_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_anchor = get_node_or_null(anchor_path) as Node2D
set_physics_process(is_instance_valid(_anchor) and not Engine.editor_hint)
if Engine.editor_hint:
update_configuration_warning()
func set_reference_path(new_value:NodePath) -> void:
reference_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_reference = get_node_or_null(reference_path) as Node2D
func _set_target_transform(new_value:Transform2D) -> void:
if set_local_transform:
transform = new_value
return
global_transform = new_value
func _get_configuration_warning() -> String:
if _anchor == null:
return "Anchor not found"
return ""
Add this attached to a Node2D in anchor_path set the target from which you want to pull the transform (anchor_path is a NodePath, you can set to it something like $Position2D.get_path()). And set what do you want to copy (you can choose any combination of position x, position y, scaling x, scaling y, and rotation). Then put the Camera2D as a child of the AnchorTransform2D, and set smoothing_enabled to true.
Rundown of the properties:
anchor_path: A NodePath pointing to the Node2D you want to pull the transform from.
reference_path: A NodePath pointing to a Node2D used to make the transform relative (you will be taking the transform of what you put in anchor_path relative to what you put in reference_path).
set_local_transform: Set to true if you want to pull the transform as local (relative to the parent of AnchorTransform2D), leave to false to set the global transform instead.
translation_mode: Specifies if you are going to copy the x position, y position, both or neither.
scale_mode: Specifies if you are going to copy the x scale, y scale, both or neither.
rotation_mode: Specifies if you are going to copy the rotation or not.
The only reason the script is a tool script is to give you a warning in the editor if you forgot to set the anchor_path.

How to create a getter function without a setter function?

I have multiple exported variables in my script and anytime a single one is changed I want to invoke a common getter and let the values be set automatically
tool
export(float) var sample1 setget ,smthn_changed;
export(float) var sample2 setget ,smthn_changed;
export(float) var sample3 setget ,smthn_changed;
func smthn_changed():
print("something changed!")
but this doesn't work and I have to create a setter for every single variable
is there any solution around this?
Please notice that you are defining smthn_changed as getter for those properties. And the getters are called when you try to read them, not when you try to assign them.
Alright, let us say you do want to know when the variables are being assigned. For that you would usually use setters, like this:
export var property:bool setget set_property
func set_property(new_value:bool) -> void:
if property == new_value:
return
property = new_value
print("value changed") # or emit a signal or whatever
The setter will be called at any time the variable is asignad externally (or internally with self.property = value, if you don't use self you can assign the variable directly without trigering the setter).
However, since you need to write the actual variable from the setter, this implies making a setter for each variable (if you used the same setter for multiple variable, you would not know which to set).
There is something else you can try: _set. The issue with _set is that will only be called for variables that are not declared in the script.
So, here is the plan:
We are going to declare backing variables with a different name, not export them.
We are going to use _set and _set to handle them.
And we are going to use _get_property_list to export them.
Let us see the case of just one variable:
tool
extends Spatial
var _x:String setget _no_set
func _set(property: String, value) -> bool:
if property == "x":
_x = value
smth_changed()
return true
return false
func _get(property: String):
if property == "x":
return _x
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
return [
{
name = "x",
type = TYPE_STRING,
usage = PROPERTY_USAGE_DEFAULT
}
]
func _no_set(_new_value) -> void:
pass
func smth_changed() -> void:
print("something changed!")
That is not worth the effort compared to a simple setter.
The setter _no_set is a setter that does nothing (not even set the variable). I have added it to prevent bypassing the mechanism externally by setting to the backing variable directly. You could add a warning there, as that is not something you code should be doing. On the flip the fact that your code should not be doing it could also be taken as an argument against having _no_set.
But let us see how it scales to multiple variables:
tool
extends Spatial
var _x:String setget _no_set
var _y:String setget _no_set
func _set(property: String, value) -> bool:
match property:
"x":
_x = value
"y":
_y = value
_:
return false
smth_changed()
return true
func _get(property: String):
match property:
"x":
return _x
"y":
return _y
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
return [
{
name = "x",
type = TYPE_STRING,
usage = PROPERTY_USAGE_DEFAULT
},
{
name = "y",
type = TYPE_STRING,
usage = PROPERTY_USAGE_DEFAULT
}
]
func _no_set(_new_value) -> void:
pass
func smth_changed() -> void:
print("something changed!")
Still not great, since we are having to repeat the variables multiple times. I would still prefer to have multiple setters, even if they all have the same code.
A generic case for an arbitrary set of properties is tricky, because calling get from _get, or set from _set, or get_property_list form _get_property_list in such way that it causes a stack overflow will crash Godot (and continue crashing it upon opening the project). So be careful when writing this code.
What I'm going to do to avoid calling get_property_list from _get_property_list is to put the properties we want in a dictionary:
tool
extends Spatial
var _properties := {
"x": "",
"y": ""
} setget _no_set, _no_get
func _set(property: String, value) -> bool:
if _properties.has(property):
_properties[property] = value
smth_changed()
return true
return false
func _get(property: String):
if _properties.has(property):
return _properties[property]
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
var result := []
for property_name in _properties.keys():
result.append(
{
name = property_name,
type = typeof(_properties[property_name]),
usage = PROPERTY_USAGE_DEFAULT
}
)
return result
func _no_set(_new_value) -> void:
pass
func _no_get():
return null
func smth_changed() -> void:
print("something changed!")
Notice also that I'm reporting the type based on the value with typeof.
I'll leave it to you to decide if this approach is worth the effort. It might be, if the set of variables can change, for example. And I remind you that you can call property_list_changed_notify so that Godot calls _get_property_list and updates the inspector panel with the new set of properties.
Despite the _no_set, the dictionary could still be read and manipulated externally. So I added a getter _no_get that returns null to prevent that. If you like a warning in your _no_set, you may want a warning in your _no_get too.
Addendum: Here is a variation that uses an array for the names of the properties you want to export. This way you can still have regular variables instead of dealing with a dictionary. It is up to you to keep the array up to date.
tool
extends Spatial
var _property_names := ["x", "y"] setget _no_set, _no_get
var _x:String
var _y:String
func _set(property: String, value) -> bool:
if _property_names.has(property):
set("_" + property, value)
smth_changed()
return true
return false
func _get(property: String):
if _property_names.has(property):
return get("_" + property)
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
var result := []
for property_name in _property_names:
if not "_" + property_name in self:
push_warning("Not existing variable: " + property_name)
continue
result.append(
{
name = property_name,
type = typeof(get("_" + property_name)),
usage = PROPERTY_USAGE_DEFAULT
}
)
return result
func _no_set(_new_value) -> void:
pass
func _no_get():
return null
func smth_changed() -> void:
print("something changed!")
Note that I have added a check to prevent exporting without a backing variable, which also pushes a warning. It is not catastrophic to expose them as they would just be handled as null.
Also note that I had to remove the _no_set from the variables in this version. The reason being that I set them with set, which results in calling the setter, and since the _no_set didn't set the variable the result was it wasn't saving the values.
Addendum on resetting the value
If you want to add that arrow to reset the value you need to implement a couple of (yikes) undocumented methods:
func property_can_revert(property:String) -> bool:
if property in self:
return true
return false
func property_get_revert(property:String):
match typeof(get(property)):
TYPE_NIL:
return null
TYPE_BOOL:
return false
TYPE_INT:
return 0
TYPE_REAL:
return 0.0
TYPE_STRING:
return ""
TYPE_VECTOR2:
return Vector2()
TYPE_RECT2:
return Rect2()
TYPE_VECTOR3:
return Vector3()
TYPE_TRANSFORM2D:
return Transform2D()
TYPE_PLANE:
return Plane()
TYPE_QUAT:
return Quat()
TYPE_AABB:
return AABB()
TYPE_BASIS:
return Basis()
TYPE_TRANSFORM:
return Transform()
TYPE_COLOR:
return Color()
TYPE_NODE_PATH:
return NodePath()
TYPE_RID:
return RID(Object())
TYPE_OBJECT:
return Object()
TYPE_DICTIONARY:
return {}
TYPE_ARRAY:
return []
TYPE_RAW_ARRAY:
return PoolByteArray()
TYPE_INT_ARRAY:
return PoolIntArray()
TYPE_REAL_ARRAY:
return PoolRealArray()
TYPE_STRING_ARRAY:
return PoolStringArray()
TYPE_VECTOR2_ARRAY:
return PoolVector2Array()
TYPE_VECTOR3_ARRAY:
return PoolVector3Array()
TYPE_COLOR_ARRAY:
return PoolColorArray()
return null
The idea is that property_can_revert will return true for any property that will have the reset arrow. And property_get_revert will give the value that will be set when you click said arrow. This had to be found in the source code since it is not documented.

Godot Engine Multiple Objects Instanced

I have an issue instantiating objects.
On the one hand, I have the following script for one projectile in one scene:
extends KinematicBody2D
var speed = 200
var life_time = 2
var life_spawn = 0
func _physics_process(delta):
var collision = move_and_collide(Vector2.UP * delta * speed)
life_spawn += delta
if life_spawn > life_time:
queue_free()
pass
and on the other hand I have the player in other scene with the following script:
extends KinematicBody2D
func _physics_process(delta):
if Input.is_action_pressed("ui_accept"):
createLaser()
pass
func createLaser():
var laser = preload("res://scenes/space_ship/laser/Laser.tscn")
var left_fired_laser = laser.instance()
var right_fired_laser = laser.instance()
var left_cannon = get_node("Cannons/left_cannon").get_global_position()
var right_cannon = get_node("Cannons/right_cannon").get_global_position()
left_fired_laser.position = Vector2(left_cannon.x, left_cannon.y)
get_parent().call_deferred("add_child", left_fired_laser)
right_fired_laser.position = Vector2(right_cannon.x, right_cannon.y)
get_parent().call_deferred("add_child", right_fired_laser)
pass
The problem is that the object is instantiated a lot of times. Even if I put a yield() function. If I put that function the object waits to be instantiated but it is instantiated a lot of times anyway.
How many and how often do you want to instance?
You say:
The problem is that the object is instantiated a lot of times.
But how many times is a lot?
I'll answer the cases that come to mind. Also, some of these can be combined.
If this answer does not cover what you want… You will need to be more specific. Baring that, hopefully you can figure it out considering these tools:
Picking between is_action_pressed vs is_action_just_pressed.
The use of Timers (the actual object or makeshift with delta).
Signals (including but not limited to the timeout signals of actual Timers).
Speaking of signals, all the approaches below require no special knowledge of the instance (except that it is some Node2D). You might add custom signals to the instances and connect to them to know when you can create more instances.
Another thing you can do is hold references to the instances to interrogate them (you may for example use is_instance_valid). However, I present below an approach that uses a tree_exited signal, which I consider more general.
You could also take advantage of AnimationPlayer with a call method track. For example if you need to add the instances on a particular frame of the animation.
Once per input
This code instances every physics frame as long as the input is pressed:
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept"):
createLaser()
If you only want to instance the first physic frame when the input was pressed, use is_action_just_pressed:
func _physics_process(delta:float) -> void:
if Input.is_action_just_pressed("ui_accept"):
createLaser()
Once every so often
We can use the same makeshift timer strategy you use for bullet life time:
var instance_period:float = 10.0
var instance_elapsed:float = 0.0
func _physics_process(delta:float) -> void:
instance_elapsed += delta
if Input.is_action_pressed("ui_accept") and instance_elapsed > instance_period:
createLaser()
instance_elapsed = 0.0
Only one time ever
If you only want one, we can hold a boolean variable to know if we already instanced:
var did_instance:bool = false
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and not did_instance:
createLaser()
did_instance = true
Only a fixed number of times ever
You can use an integer countdown:
export var yet_to_instance:int = 10
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and yet_to_instance > 0:
createLaser()
yet_to_instance -= 1
I made it an export variable so you can edit it from the Inspector panel.
*This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Also you can consider "Only one time ever" an special case when the number is 1.
Only a recharging number of times
This is a particular way to combines the makeshift timer with the idea of a fixed number of times to instance:
var recharge_period:float = 10.0
var recharge_elapsed:float = 0.0
export var max_to_instance:int = 10
onready var yet_to_instance:int = max_to_instance
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and yet_to_instance > 0:
createLaser()
yet_to_instance -= 1
# recharge_elapsed = 0.0
recharge_elapsed += delta
if recharge_elapsed > recharge_period:
if yet_to_instance < max_to_instance:
yet_to_instance += 1
recharge_elapsed = 0.0
This way the number of instances you can create increases overtime up to a maximum. If you can un-comment # instance_elapsed = 0.0 if you want to prevent that number to increase when there is input. You can think of it as autoreloading.
This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Or alternatively with "Once every so often" (i.e. makeshift time) to limit the instance rate.
At most an amount alive
We are going to connect to the tree_exited signal to update our count:
const laser = preload("res://scenes/space_ship/laser/Laser.tscn")
export var max_to_instance:int = 20
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and max_to_instance > 0:
createLaser()
func createLaser() -> void:
createLaserFrom($Cannons/left_cannon)
createLaserFrom($Cannons/right_cannon)
func createLaserFrom(cannon:Node2D) -> void:
var fired_laser = laser.instance()
max_to_instance -= 1
fire_laser.connect("tree_exited", self, "laser_exited")
get_parent().add_child(fired_laser)
fired_laser.global_position = cannon.global_position
func laser_exited() -> void:
max_to_instance += 1
This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Or alternatively with "Once every so often" (i.e. makeshift time) to limit the instance rate.

Resources