I want to limit the number of instanced scenes. How can I do that?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By System_Error

Hi all, I have a question. In my game, I have a gun that fires as fast as the player can hit a button, which leads to some testers pressing very quickly to rid the screen of incoming enemies. But I don’t want that to be as effective as it feels.

What I’d like is to limit the number of LazerShots instanced into my Level. I might have to re-write the fire() function in my LazerGun script. Any suggestions on how to do that?

LazerGun.gd

extends KinematicBody2D

const LAZER = preload("res://player-side/EnergyShot.tscn")
const ROTATION_SPEED = 0.5

signal fire
var playing = false
var rotation_Direction


func _ready():
	pass

func get_input():
	rotation_Direction = 0.0
	if playing == true:
		if Input.is_action_pressed('test_up'):
			rotation_Direction -= 1.25
		elif Input.is_action_pressed('test_down'):
			rotation_Direction += 1.25
	
		if Input.is_action_just_pressed('test_fire'):
			fire()


func fire():
	var lazershot = LAZER.instance()
	lazershot.start($LazerSpawn.global_position, rotation)
	get_parent().add_child(lazershot)


func _physics_process(delta):
	get_input()
	rotation += rotation_Direction * ROTATION_SPEED * delta
	rotation = clamp(rotation, deg2rad(-60), deg2rad(60))


func _on_game_over():
	playing = false

func _on_HUD_play_game():
	playing = true

Level.gd

extends Node2D

const SCREEN_HEIGHT = 580
const SCREEN_WIDTH = 1010

signal game_over

var timescore
var kills
var finalscore
var EnergyShot = preload ('res://player-side/EnergyShot.tscn')
var eyeworm = preload ('res://Enemy.tscn')
var SpawnWaitTime = 5
var highscore = 0.0

func _ready():
	randomize()

func _on_SpawnTimer_timeout():
	var enemy = eyeworm.instance()
	enemy.position = Vector2(SCREEN_WIDTH + 10, rand_range(0, SCREEN_HEIGHT))
	enemy.connect("kill", self, "_on_kill")
	add_child(enemy)
	SpawnWaitTime -= 0.15
	if (SpawnWaitTime < 0.4):
		SpawnWaitTime += 4.85
	$Node/SpawnTimer.set_wait_time(SpawnWaitTime)

func _on_SurvivalTimer_timeout():
	timescore += 1
	$HUD.score_count(timescore)

func _on_kill():
	kills += 1
	$HUD.kill_count(kills)

func _on_BaseArea_area_entered(area):
	$Node/SpawnTimer.stop()
	$Node/SurvivalTimer.stop()
	finalscore = timescore + kills*5
	if highscore < finalscore:
		highscore = finalscore
	$HUD.player_loss(finalscore, highscore)
	emit_signal('game_over')
	$BaseArea/CollisionShape2D.disabled = true
	get_tree().call_group("EyeWorms", "queue_free")

func _on_HUD_play_game():
	timescore = 0
	kills = 0
	SpawnWaitTime = 5
	finalscore = 0
	$Node/SurvivalTimer.start()
	$Node/SpawnTimer.start() 
	$HUD.show_message("Survive!")
	$BaseArea/CollisionShape2D.disabled = false
:bust_in_silhouette: Reply From: Zylann

One way to limit how fast players can shoot is to introduce a cooldown to the gun. When you fire, store the current time in a member variable (OS.get_ticks_msec()). Then, each time you fire, check the variable difference against the present time. If it’s shorter than the cooldown time, don’t fire. You could also achieve this with a Timer node if you prefer using that (if you don’t know, just try any).

var now = OS.get_ticks_msec()
if now - _last_shot_time > _cooldown:
    _last_shot_time = now
    actually_fire()

On the other hand, if you want to limit the number of bullets currently active in the scene, you could also increment or decrement a variable as they get created, or just count them. One easy way to achieve this is to put your bullets in a group, and query how many nodes are in that group with get_tree().get_nodes_in_group("bullets").

var all_bullets = get_tree().get_nodes_in_group("bullets")
if len(all_bullets) < MAX_BULLETS:
    actually_fire()

That’s neat. I might try those.

System_Error | 2019-01-10 18:40

:bust_in_silhouette: Reply From: Ruckus T-Boom

In addition to Zylann’s answer, there’s another option that combines button spamming with cool down. Basically, shooting heats up the gun, and it can only fire if it’s not too hot, so you can spam, but only briefly.

var max_temp = ...
var cooling_speed = ...
var heating_amount = ...
var current_temp = 0

func _process(delta):
    current_temp -= cooling_speed * delta
    if current_temp < 0:
        current_temp = 0

func fire():
    if current_temp < max_temp:
        actually_fire()

func actually_fire():
    current_temp += heating_amount
    ...

You can also encourage not spamming too much by making it so, if they exceed the heat limit, they have to wait for it to cool down all the way.

var max_temp = ...
var cooling_speed = ...
var heating_amount = ...
var current_temp = 0
var can_fire = true

func _process(delta):
    current_temp -= cooling_speed * delta
    if current_temp < 0:
        current_temp = 0
        can_fire = true

func fire():
    if can_fire and current_temp < max_temp:
        actually_fire()

func actually_fire():
    current_temp += heating_amount
    if current_temp > max_temp:
        can_fire = false
    ...

Thanks!

Oh, and for the fire() and actually_fire() functions, I shortened it a bit.

func fire():
if can_fire and gun_temp < max_temp:
	var lazershot = LAZER.instance()
	gun_temp += heat_per_shot
	if gun_temp > max_temp:
		can_fire = false
	lazershot.start($LazerSpawn.global_position, rotation)
	get_parent().add_child(lazershot)

System_Error | 2019-01-10 18:28