Game Firing Problem?

Godot Version

4.3

Question

Hi all,

I am having a problem with a 2D game I am making. The base game was part of a course where you make bare-bones games which I wanted to modify, so, the game could be reset or play over without resorting back to the main menu. The movement is also something that is really annoying but that is a bridge too far. The TLDR is that everything works with one enemy, everything seems to be functioning correctly the Reset and play buttons all work the shooting everything. However when the enemy is increased from 1 to 2 a problem immediately appears (there is supposed to be 3). If an enemy kills themselves or shot by the player - the player and the remaining enemy can no longer shoot - I can’t figure out why. I’ve gone through numerous permutations and tried to solve that simple problem but to no avail. There is an autoload signal used to destroy bullets because, of another problem I couldn’t overcome like if a bullet was headed toward the player and hit after the gameover had occurred it could say that the player had one and then change it to a loss - not something I wanted to occur - in any case after one enemy is down the bullet autoload seems to be operating on the player why, don’t know. So, I have a problem!

So, here is the rather long game_manager.gd script that is supposed to handle the game:

extends Node2D

var game_started = false;
var blue_tanks	: int 	= 1;
var red_tanks	: int 	= 2;
#var game_oni	: bool 	= true;

@onready var start_screen: CanvasLayer = $StartScreen

var gameboard_redo:bool = false;

@onready var title: Label = $StartScreen/Title
@onready var map: Node2D = $Map
@onready var tile_map_layer: TileMapLayer = $Map/TileMapLayer
@onready var tile_map_layer_2: TileMapLayer = $Map/TileMapLayer2

#-------------------------------------------------------------------------------
#--  Inital locations
# hero
var player_initial_position:Vector2;
var player_initial_rotation: float;
#enemy
var opponent_1_initial_position:Vector2;
var opponent_1_initial_rotation: float;
var opponent_2_initial_position:Vector2;
var opponent_2_initial_rotation: float;
#-----------------------------------------------------------------------------

signal reset;
@onready var player:		CharacterBody2D = $player	
@onready var opponent_1: 	CharacterBody2D = $opponent_1
@onready var opponent_2: 	CharacterBody2D = $opponent_2
#@onready var opponent_3: CharacterBody2D = $opponent_3

var num = 1;

func _ready() -> void:
	game_started = false;
	start_screen.visible=true;
	print("Blue Tanks: "+ str(blue_tanks) + " red tanks: " + str(red_tanks) )
	reset.connect(make_reset); #no brackets required

	#make tank visible now
	player.player_visible = true;
	
	#set the initial positions for all characters
	#player
	player_initial_position 	= player.position;
	#enemy
	opponent_1_initial_position	= opponent_1.position;
	opponent_2_initial_position	= opponent_2.position;
	
	if(gameboard_redo == false):
		set_positions_for_later();
	
	#end


#Update
func _process(_delta: float) -> void:
	if(red_tanks <=0):
		print("red tanks - dead");
		game_over();
	
	if(gameboard_redo == true):
		redo_tank_positions();
			
	if(player.player_destroyed):  #Why?!?
		player.visible = false;
	
	if(opponent_1.enemy_destroyed):
		opponent_1.visible = false;
		Autoload.stop_bullets.emit();
			
	if(opponent_2.enemy_destroyed):
		opponent_2.visible = false;
		Autoload.stop_bullets.emit();
		
	#end func
	


func redo_tank_positions():
	print("redo tank positions");
	print("Redo pos x: " +str(player_initial_position.x) + " redo pos y: " + 
		str(player_initial_position.y));
	
	player.position.x 	= player_initial_position.x;
	player.position.y 	= player_initial_position.y
	player.rotation 	= player_initial_rotation;
	player.tank_visible();
	
	opponent_1.position.x 	= opponent_1_initial_position.x;
	opponent_1.position.y 	= opponent_1_initial_position.y
	opponent_1.rotation 	= opponent_1_initial_rotation;
	print("Redo oppenent 1 position: " + str(opponent_1_initial_position) );
	opponent_1.tank_visible();
	
	opponent_2.position.x 	= opponent_2_initial_position.x;
	opponent_2.position.y 	= opponent_2_initial_position.y
	opponent_2.rotation 	= opponent_2_initial_rotation;
	print("Redo opponent 2 position: " + str(opponent_2_initial_position) );
	opponent_2.tank_visible();

	#stop the redo_tank_positions continually to that position
	gameboard_redo = false;
	#End


	
func set_positions_for_later():
	#set for later
	print("set for later");
	player_initial_position = player.position;
	player_initial_rotation = player.rotation;
	
	opponent_1_initial_position = opponent_1.position;
	print("Enemy_1 pos x: " +str(opponent_1_initial_position.x) +
		" Enemy1 pos y: " +str(opponent_1_initial_position.y));
	opponent_1_initial_rotation = opponent_1.rotation;
	
	opponent_2_initial_position = opponent_2.position;
	print("Enemy_2 pos x: " +str(opponent_2_initial_position.x) +
		" Enemy2 pos y: " +str(opponent_2_initial_position.y));
	opponent_2_initial_rotation = opponent_2.rotation;
	#End


#Not used anymore
func make_reset():
	print("This is reset time");
	start_screen.visible = true;
	game_started = true;
	#end
	
	
#function called by player - when blue_tank is destroyed
#probably when the reset button is hit as well
func game_over()->void:
	#game_oni= false;
	game_started = false;
	print("\nGame Over code....");
	start_screen.visible=true;
	#reset.emit();
	
	if(blue_tanks <= 0 ):
		title.text = "You Lose :(";
		print("Red Wins");
		#if player is destroyed stop shooting
		#of enemies by saying they are destroyed
		Autoload.stop_bullets.emit();
		blue_tanks = 1;
		
		gameboard_redo = true;
	#end if
	
	if(red_tanks <= 0 ):
		print("\nYou Win!");
		title.text = "You Win!";
		Autoload.stop_bullets.emit();
		red_tanks = 2;
		
		gameboard_redo = true;
	#end if
	
	start_screen.visible=true;
	print("\n");
	#end func
	
	
func new_game():
	print("\nNew game: ");
	game_started = true;
	#make player visible
	#end
	

#-----------------------------------------------------------------
#--  buttons  ----------------------------------------------------
func _on_reset_button_pressed() -> void:
	print("\n............ Reset ATG ....................");
	game_started 			= false;
	start_screen.visible 	= true;
	Autoload.stop_bullets.emit();
	
	if(blue_tanks > 0  && red_tanks >0):
		title.text = "It's a Draw!";
	
	player.commence_fire();
	opponent_1.commence_fire();

	#end


func _on_play_button_pressed() -> void:
	print("Reset - Play Button");
	start_screen.visible 	= false;
	game_started 			= true;
	#game_oni = true;
	
	#--  objects in scene  -------------------------------
	player.visible			= true;
	player.player_destroyed = false;
	
	player.commence_fire();
	
	if(opponent_1.enemy_destroyed):
		opponent_1.visible			= true;
		opponent_1.enemy_destroyed	= false;
	if(opponent_2.enemy_destroyed):
		opponent_2.visible			= true;
		opponent_2.enemy_destroyed	= false;
	#-----------------------------------------------------	

	#end


func _on_quit_button_pressed() -> void:
	print("quit");
	#code to drop back to menu
	get_tree().reload_current_scene();
	#end

If you can solve the problem here is a big thankyou in advance. The only thing I can offer in return is that if we ever meet IRL I will shout you to a cold frosty providing you are over 18.

Thankyou and Regards.

1 Like

what does this “stop_bullets" signal do? After an enemy is destroyed, you appear to be emitting this signal every frame. I presume that it stops bullets, and I don’t see a way this would be able to discriminate between bullets fired by different tanks (granted, you haven’t shared the stop bullets code, so it might be there.)

If the reason you’re trying to stop bullets is to avoid things getting hit after they’ve been destroyed, wouldn’t it make more sense to have each tank toggle their colliders after they detect a bullet collision? That way you don’t need to mess around with figuring out how to make your stop_bullets code only target the bullets from specific tanks.

Something like this:

extends CharacterBody2D

var bullet_collision_layer : int = 1 #set this to the value of whatever collision layer your bullets use

func _physics_process(delta: float) -> void:
	move_and_slide()
	for i in get_slide_collision_count():
		var collision = get_slide_collision(i)
		if collision.get_collider():
			if collision.get_collider().name == "bullet": #change this to the name you use for your bullets, if it's different.
				set_collision_layer_value(bullet_collision_layer, false)
				set_collision_mask_value(bullet_collision_layer, false)
				visible = false
				#insert toggle off for firing bullets here

(be warned, I have not tested this code, I pulled it from this thread and modified it to purpose)

Believe me if there were an easier way I would be doing it. Because the base game is instantiating in another scene and the bullets are instantiating is the game scene I can’t just set the queue_free() to delete the tanks, I have to make them disappear, because theyare taken out of the tree now way to get them back. Without instantiating them into an instantiation of an instantiation - making three layer of instantiation. It’s mind bending how the base_game works.

In the process section it is running every frame, but, it is only checking if the tank is invisible only then does it stop the bullets that are already in the process of firing (well that was the idea). Every other bullets times out or get destroyed after 3 bounces or hits an object. Seems to work. No other bullets can be fired because can_fire bool is set to false.

Stop bullets code is in the Autoloads.gd with just this:

@warning_ignore("unused_signal")
signal stop_bullets;

then in the bullets code defined:

func bullet_stop():
	print("Bullet Stop....");
	queue_free();
	#end

connected with the following also in the bullet.gd _ready:

	Autoload.stop_bullets.connect(bullet_stop); #no brackets required

Then using the autoready emits as necessary.

Does that make a difference? Because I can’t quite follow what you are doing with your code especially with the bullet_collision_layer: int = 1. Like where would I put that in the game_manager code, the Bullet_code, enemy_code or player_code and why does it equal one.

Any help you can give would be appreciated.

1 Like

Believe me if there were an easier way I would be doing it. Because the base game is instantiating in another scene and the bullets are instantiating is the game scene I can’t just set the queue_free() to delete the tanks, I have to make them disappear, because theyare taken out of the tree now way to get them back. Without instantiating them into an instantiation of an instantiation - making three layer of instantiation. It’s mind bending how the base_game works.

Okay, this seems manageable. I’m not sure why you can’t use queue_free() to delete the tanks if they’re contained in a nested scene, you can reset the base scene by deleting it with queue_free() itself, and reinstantiating it from an autoload/global script.

I noticed you were saving the locations of tanks, and I wasn’t sure why before. If you’re doing it to reset their positions when resetting the game, no need. Just queue_free() and instantiate a new tank, or an entire new scene, if you need it.

Stop bullets code is in the Autoloads.gd with just this:

@warning_ignore("unused_signal")
signal stop_bullets;

then in the bullets code defined:

func bullet_stop():
	print("Bullet Stop....");
	queue_free();
	#end

connected with the following also in the bullet.gd _ready:

	Autoload.stop_bullets.connect(bullet_stop); #no brackets required

Then using the autoready emits as necessary.

I’m still not seeing how the code is supposed to know which bullets to get rid of, and I’m not seeing anything that controls how often the signal stop_bullets is emitted.

The code below checks if the tank is visible every frame, and if it’s not then it emits stop_bullets. If you have this in _process it will run this check every frame until you turn off the game.

	if(opponent_1.enemy_destroyed):
		opponent_1.visible = false;
		Autoload.stop_bullets.emit();

This here is your problem; the code isn’t checking if the enemy is visible once, it’s checking if the enemy is visible 30 times a second, forever. If you’re sure this is the method you want to use to stop the bullets, you need to find a way to call this only once. My suggestion is to handle this by having the enemy tank detect when it’s hit, and run your code when it detects a collision with a bullet.

Does that make a difference? Because I can’t quite follow what you are doing with your code especially with the bullet_collision_layer: int = 1. Like where would I put that in the game_manager code, the Bullet_code, enemy_code or player_code and why does it equal one.

Okay, I’ll walk you through my code.

First of all, the script goes in the CharacterBody2D of your enemy tanks. If you’re not using CharacterBody2D for your tanks, then I’m sorry for making the assumption, it’s just what is normally used for this kind of thing. I can modify this code to work with other bodies if you need.

The point of the code is to detect collisions on a particular collision layer, and if one of those collisions is a bullet it will run some code.

var bullet_collision_layer : int = 1 #set this to the value of whatever collision layer your bullets use

This will be used later to establish which collision layer the code should disable after detecting a bullet. I’ve set it to “1” as a default, but the number should be changed to match the collision layer your bullets use if it’s not “collision layer 1”.

func _physics_process(delta: float) -> void:
	move_and_slide()

This is the function you need to put the code in for it to work. We’re using _physics_process() because we want this to run every time the engine does a “physics tick” and updates the physics in the game.

move_and_slide() toggles on a basic form of collision for CharacterBody2Ds. We can then use this to detect exactly what’s colliding with the tank with the code below:

	for i in get_slide_collision_count():
		var collision = get_slide_collision(i)
		if collision.get_collider():
			if collision.get_collider().name == "bullet": #change this to the name you use for your bullets, if it's different.
				

This is a loop that does the following:

  1. Checks how many objects are currently colliding with the tank, and loops that many times
  2. Defines a variable (called "collision") that’s equal to the colliding body
    1. (this means that all that variable’s data is equal to the data of the thing that collided with it.
  3. Checks if the colliding body is colliding (might be redundant, I left this in because the original code did this)
  4. Checks if the colliding body is named “bullet”.
  5. Runs code outlined below:
set_collision_layer_value(bullet_collision_layer, false)
set_collision_mask_value(bullet_collision_layer, false)
visible = false
#insert toggle off for firing bullets here

This code is pretty simple. It disables collision detection on a specific layer and mask, then makes the tank invisible the same way you did it in your code. You’d also need to put in code to stop the tank from firing bullets while invisible here. You mentioned doing this with can_fire = false, and that should work fine.

Edit: Worth noting that if you’re trying my method for disabling tanks, it will be incompatible with how you’re currently handling the disabling of tanks. So if you’re trying my code, you should comment out your tank-disabling code.

1 Like