I did the reverse of the bird for the collision layer and mask numbers, which is what I thought I remember I should do so it doesn’t collide with the rabbit - however I will try doing it the same exact numbers as the rabbit and see what happens.
Okay, after trying that now it works perfectly! Hallelujah!
So now I will work on adding the shield effect in the way you specified, and then I should have a masterpiece of a game after I have added the score and life system, as well as the pause screen, main menu, game over screen and a more polished animation for death than just popping out of existence.
Okay, so now I am following part 6, the second to last part, of the Dodge the Creeps tutorial for adding an HUD to my game so I can display score, a game over message, and a life system for more than just one life.
Everything works smoothly mostly, except I get a crash with "invalid get index ‘position’ " despite me using the same exact syntax as something that is known to work, and you told me to type it that way word for word whenever I need to reference an object’s property that way, and I know as well the object is a real object in the scene, it’s the rabbit the bird is carrying that I want to change back to its starting position when you die.
The latest upload is at the top, per usual, demonstrating exactly which line crashes when you run the program.
Could you attach a screenshot and/or paste the code where it crashes. Would be easier for me and any passerbys
Okay.
In gamenode.gd, this is the full code:
extends Node2D
@export var mob_scene: PackedScene
@export var snailpowerup_scene: PackedScene
@export var shieldpowerup_scene: PackedScene
@onready var shield: Shield = $Shield
@onready var player: Player = $Player
@onready var player_rabbit: Player_Rabbit = $Player_Rabbit
var score: int
var leftorright: int
var flipped: float
# Called when the node enters the scene tree for the first time.
func _ready():
get_tree().call_group("mobs", "queue_free")
leftorright = Globals.leftorright
$Shield.hide()
$MobSpawnTimer.set_wait_time(randf_range(6.0, 8.0)) # Set mob spawn timer to random value between 1 and 4.
$ProgressTimer.set_wait_time(5.0)
$SnailTimer.set_wait_time(randf_range(4.0, 10.0))
$ShieldTimer.set_wait_time(randf_range(10.0, 20.0))
$MobSpawnTimer.start()
$ProgressTimer.start()
$SnailTimer.start()
$ShieldTimer.start()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
$HUD.update_score(score)
if Globals.progress_stage == 5:
$MobSpawnTimer.set_wait_time(randf_range(4.0, 5.0))
$SnailTimer.set_wait_time(randf_range(5.0, 8.0))
$ShieldTimer.set_wait_time(randf_range(10.0, 15.0))
if Globals.progress_stage == 10:
$MobSpawnTimer.set_wait_time(randf_range(2.0, 4.0))
$SnailTimer.set_wait_time(randf_range(3.0, 5.0))
$ShieldTimer.set_wait_time(randf_range(9.0, 12.0))
if Globals.progress_stage == 15:
$MobSpawnTimer.set_wait_time(1.0)
$SnailTimer.set_wait_time(randf_range(1.0, 3.0))
$ShieldTimer.set_wait_time(randf_range(6.0, 10.0))
if Globals.playerhasshield == true:
$Shield.show()
shield.position = player.position
if Globals.playerhasshield == false:
$Shield.hide()
if Globals.lives == 3:
$HUD.get_node("Life1").show()
$HUD.get_node("Life2").show()
$HUD.get_node("Life3").show()
if Globals.lives == 2:
$HUD.get_node("Life1").show()
$HUD.get_node("Life2").show()
$HUD.get_node("Life3").hide()
if Globals.lives == 1:
$HUD.get_node("Life1").show()
$HUD.get_node("Life2").hide()
$HUD.get_node("Life3").hide()
if Globals.lives == 0:
$HUD.get_node("Life1").hide()
$HUD.get_node("Life2").hide()
$HUD.get_node("Life3").hide()
player.queue_free()
player_rabbit.queue_free()
game_over()
func game_over():
$HUD.show_game_over()
func _on_mob_spawn_timer_timeout():
if Globals.leftorright == 1:
flipped = randf_range(0.0, 2.0)
var mob = mob_scene.instantiate()
mob.position.x = 1110
if flipped < 1.0:
mob.rotation = 0.0
mob.position.y = randf_range(560, 600)
if flipped >= 1.0:
mob.rotation = 270.0
mob.position.y = randf_range(15, 45)
add_child(mob)
func _on_progress_timer_timeout():
if Globals.leftorright == 1:
Globals.progress_stage += 1
func _on_snail_timer_timeout():
if Globals.leftorright == 1:
var snailpowerup = snailpowerup_scene.instantiate()
snailpowerup.position.x = 1110
snailpowerup.position.y = randf_range(50, 540)
add_child(snailpowerup)
func _on_slow_down_timer_timeout():
Globals.slowedtime = 0
func _on_shield_timer_timeout():
if Globals.leftorright == 1:
var shieldpowerup = shieldpowerup_scene.instantiate()
shieldpowerup.position.x = 1110
shieldpowerup.position.y = randf_range(20, 500)
add_child(shieldpowerup)
func _on_player_area_entered(body) -> void:
if body is Mob:
get_tree().call_group("mobs", "queue_free")
Globals.lives -= 1
player.position.x = 160
player.position.y = 288
player_rabbit.position.x = 196
player_rabbit.position.y = 318
The crash is at the second to last line, “player_rabbit.position.x = 196”, even though I used the same exact logic as for acessing the player’s position coordinates and that didn’t give errors.
What is the error exactly? If it says something like “on null instance” then maybe your player or rabbit has been renamed. If it says “previously freed” then it’s because the object has been deleted, and deleted objects cannot be moved.
It says “previously freed”, but the object is still in the scene if you download the game and look for yourself, so even though that’s what the error normally means in this context it doesn’t actually make sense.
It’s in the editor, but in the game you had it delete the player when it touches the mob right? So it’s been deleted in-game and then trying to move it. Of course restarting the game, or checking the editor it will still be there.
Since you have lives you need to re-evaluate when to delete the player vs when to move their position
Oh, that makes sense - it’s not deleted in the editor but it’s deleted at that point in the game
Okay, now it doesn’t crash after that helpful fix, thank you as always,
but now it doesn’t go back to the starting position every time it hits an obstacle flawlessly, but rather sometimes it lets you subtract one life before restarting on the second hit, or sometimes it won’t restart the obstacles at all even though told explicitly to do so, it’s really weird.
The latest version at the top demonstrates everything per usual.
I figured out part of the problem, I was subtracting 1 from your life on both the Main game logic end and the Player end, but now after I do it in only the main game logic to make the code more organized, the player doesn’t go back to the restarting position even though the obstacles get deleted correctly.
Demonstration at the top per usual.
More organized is great! Could you paste that respawn function?
What are you talking about? I don’t see a respawn function in the latest version I uploaded, unless it’s not the word “respawn” used verbatim and more like “restart”
Ah that’s just what I would call it, restart is apt too. Speicifally your _on_player_area_entered
function is what does “respawning”
That makes sense - I was expecting it to be there word for word and didn’t think of what would happen if I couldn’t find the exact words.
However, I do believe that particular piece of code is as organized and narrowed down as it can get, unless you see something I don’t?
Maybe it will help fix the latest issue about why doesn’t the player go back to the starting position even though it is told to very specifically in that particular section of code, by telling the player and the player rabbit to go to a particular x and y location?
I was just saying good job on organizing! It’s good to have one function for one feature. Then I asked you to paste the new _on_player_area_entered
function that is causing issues with them not going back to the starting position.
Okay, here it is, note the directions are very particular about telling it to go to the starting position:
func _on_player_area_entered(body) -> void:
if body is Mob:
get_tree().call_group("mobs", "queue_free")
Globals.lives -= 1
player.position.x = 160
player.position.y = 288
player_rabbit.position.x = 196
player_rabbit.position.y = 318
I have one for the rabbit entered as well, but it’s the same exact thing just with a different function name autogenerated for the different object being tested, but the body is 100% identical.
Could be that the jump tween is playing, does it reset your position if you are falling? If that’s the case you would have to kill the tween, but first it will have to be made global on the player script.
@export var speed: float # etc...
var jump_tween: Tween
if (Input.is_action_pressed("jump") and jumptimertimedout):
jump_tween = create_tween()
jump_tween.tween_property(self, "position", jumpposition, 1.0).set_trans(Tween.TRANS_QUAD)
jumptimertimedout = false
await jump_tween.finished
jumptimertimedout = true
Now your respawn function can kill the tween for each player.
func _on_player_area_entered(body) -> void:
if body is Mob:
get_tree().call_group("mobs", "queue_free")
Globals.lives -= 1
if player.jump_tween.is_valid():
player.jump_tween.kill()
player.position.x = 160
player.position.y = 288
if player_rabbit.jump_tween.is_valid():
player_rabbit.jump_tween.kill()
player_rabbit.position.x = 196
player_rabbit.position.y = 318
I will try this code now and see if it works - great suggestions as always!
Okay, unfortunately I get told “cannot call is_valid on a null value” even though I followed your directions exactly for making the tween variable global, so just type “var” without the @export beforehand because the “class_name Player” should mean everything in it is accesible when called as a class, like you had me do earlier in the main gamenode script.
Here is the code in full:
extends Node2D
@export var mob_scene: PackedScene
@export var snailpowerup_scene: PackedScene
@export var shieldpowerup_scene: PackedScene
@onready var shield: Shield = $Shield
@onready var player: Player = $Player
@onready var player_rabbit: Player_Rabbit = $Player_Rabbit
var score: int
var leftorright: int
var flipped: float
# Called when the node enters the scene tree for the first time.
func _ready():
get_tree().call_group("mobs", "queue_free")
leftorright = Globals.leftorright
$Shield.hide()
$MobSpawnTimer.set_wait_time(randf_range(6.0, 8.0)) # Set mob spawn timer to random value between 1 and 4.
$ProgressTimer.set_wait_time(5.0)
$SnailTimer.set_wait_time(randf_range(4.0, 10.0))
$ShieldTimer.set_wait_time(randf_range(10.0, 20.0))
$MobSpawnTimer.start()
$ProgressTimer.start()
$SnailTimer.start()
$ShieldTimer.start()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
$HUD.update_score(score)
if Globals.progress_stage == 5:
$MobSpawnTimer.set_wait_time(randf_range(4.0, 5.0))
$SnailTimer.set_wait_time(randf_range(5.0, 8.0))
$ShieldTimer.set_wait_time(randf_range(10.0, 15.0))
if Globals.progress_stage == 10:
$MobSpawnTimer.set_wait_time(randf_range(2.0, 4.0))
$SnailTimer.set_wait_time(randf_range(3.0, 5.0))
$ShieldTimer.set_wait_time(randf_range(9.0, 12.0))
if Globals.progress_stage == 15:
$MobSpawnTimer.set_wait_time(1.0)
$SnailTimer.set_wait_time(randf_range(1.0, 3.0))
$ShieldTimer.set_wait_time(randf_range(6.0, 10.0))
if Globals.playerhasshield == true:
$Shield.show()
shield.position = player.position
if Globals.playerhasshield == false:
$Shield.hide()
if Globals.lives == 3:
$HUD.get_node("Life1").show()
$HUD.get_node("Life2").show()
$HUD.get_node("Life3").show()
if Globals.lives == 2:
$HUD.get_node("Life1").show()
$HUD.get_node("Life2").show()
$HUD.get_node("Life3").hide()
if Globals.lives == 1:
$HUD.get_node("Life1").show()
$HUD.get_node("Life2").hide()
$HUD.get_node("Life3").hide()
if Globals.lives == 0:
$HUD.get_node("Life1").hide()
$HUD.get_node("Life2").hide()
$HUD.get_node("Life3").hide()
player.queue_free()
player_rabbit.queue_free()
game_over()
func game_over():
$HUD.show_game_over()
func _on_mob_spawn_timer_timeout():
if Globals.leftorright == 1:
flipped = randf_range(0.0, 2.0)
var mob = mob_scene.instantiate()
mob.position.x = 1110
if flipped < 1.0:
mob.rotation = 0.0
mob.position.y = randf_range(560, 600)
if flipped >= 1.0:
mob.rotation = 270.0
mob.position.y = randf_range(15, 45)
add_child(mob)
func _on_progress_timer_timeout():
if Globals.leftorright == 1:
Globals.progress_stage += 1
func _on_snail_timer_timeout():
if Globals.leftorright == 1:
var snailpowerup = snailpowerup_scene.instantiate()
snailpowerup.position.x = 1110
snailpowerup.position.y = randf_range(50, 540)
add_child(snailpowerup)
func _on_slow_down_timer_timeout():
Globals.slowedtime = 0
func _on_shield_timer_timeout():
if Globals.leftorright == 1:
var shieldpowerup = shieldpowerup_scene.instantiate()
shieldpowerup.position.x = 1110
shieldpowerup.position.y = randf_range(20, 500)
add_child(shieldpowerup)
func _on_player_area_entered(body) -> void:
if body is Mob:
get_tree().call_group("mobs", "queue_free")
Globals.lives -= 1
if player.jump_tween.is_valid():
player.jump_tween.kill()
player.position.x = 160
player.position.y = 288
if player_rabbit.jump_tween.is_valid():
player_rabbit.jump_tween.kill()
player_rabbit.position.x = 196
player_rabbit.position.y = 318
func _on_player_rabbit_area_entered(body) -> void:
if body is Mob:
get_tree().call_group("mobs", "queue_free")
Globals.lives -= 1
if player.jump_tween.is_valid():
player.jump_tween.kill()
player.position.x = 160
player.position.y = 288
if player_rabbit.jump_tween.is_valid():
player_rabbit.jump_tween.kill()
player_rabbit.position.x = 196
player_rabbit.position.y = 318