Spinning into enemy at high speed randomly damages player instead of enemy
nyanpasu64 opened this issue · comments
Describe the bug
When I spin and enter an enemy (eg. goomba) at a high speed, sometimes Mario takes damage instead of the Goomba.
To Reproduce
- Enter room
tutorial_1_4
, run right, and spin before hitting the goomba.
Unsure if the same bug causes taking damage from Cheep Cheeps during the damaging part of a player spin, or another issue.
Adding logs
How do Mario and goombas interact? In entity_enemy.gd
, the goomba sets up collision checks with the player:
func _connect_signals():
._connect_signals()
_connect_node_signal_if_exists(hurtbox_stomp, "area_entered", self, "_on_HurtboxStomp_area_entered")
# Take damage from Mario
_connect_node_signal_if_exists(hurtbox_strike, "body_entered", self, "_on_HurtboxStrike_body_entered")
# Attack Mario
_connect_node_signal_if_exists(hitbox, "body_entered", self, "_on_Hitbox_body_entered")
To figure out the order that Mario and Goomba's physics ticks, and their collision handlers, execute, I added debug prints to these methods.
Every physics tick, Mario's movement runs before the goomba's movement. How do I know?
- After unpausing, the first print is
player.player_physics()
beforegoomba._physics_step()
. - Player-enemy collision checks always occur after the goomba moves, but before Mario moves.
- When Mario and the enemy run into each other from the side, both
_on_HurtboxStrike_body_entered
and_on_Hitbox_body_entered
always occur on the same (frame? physics tick?), because the goomba's attack hitbox and strike hurtbox are the same width. This may not necessarily be the case for vertical movement.
- When Mario and the enemy run into each other from the side, both
- If I place a breakpoint on each of these methods, camera movement occurs after the goomba moves.
- Interestingly, when I continue the game past Mario's physics tick but break the game when the goomba's physics tick begins, Mario moves relative to the goomba and camera!
- This is bizarre, apparently GDScript breakpoints freeze the physics "thread" but not the rendering code, which plots an inconsistent intermediate state (mid-physics-tick) of the game on-screen?!
Oddly, collision checks apparently occur the frame after the player and enemy move?
- One time I unpaused the game, and the first breakpoint to hit was the collision checks resulting from overlapping player/enemy positions. This means that the player and goomba's last physics step before pausing caused them to overlap (setting Mario up to take damage), but the actual collision damage callbacks were called after unpausing?!
- Similarly, I've managed to get the translucent pause menu to disappear after Mario and the goomba moved, but before they processed their collision from that movement.
Testing the bug
As to why the bug occurs... when I spin into a goomba and kill it, the goomba's _on_HurtboxStrike_body_entered
method is called before its _on_Hitbox_body_entered
method on the same (frame? physics tick? I don't know!):
player.player_physics()
goomba._physics_step()
player.player_physics()
goomba._physics_step()
entity_enemy#_on_HurtboxStrike_body_entered()
player attacking = True
entity_enemy#_on_Hitbox_body_entered()
enemy attacking = False
player.player_physics()
goomba._physics_step()
When the goomba damages Mario, the goomba's _on_Hitbox_body_entered
(attack) method is called first, before Mario's collision with the goomba's hurtbox is processed:
player.player_physics()
goomba._physics_step()
player.player_physics()
goomba._physics_step()
entity_enemy#_on_Hitbox_body_entered()
enemy attacking = True
entity_enemy#_on_HurtboxStrike_body_entered()
player attacking = False
player.player_physics()
goomba._physics_step()
When Mario is spinning and moving slowly compared to the goomba, both methods are run (not just Mario hitting the goomba), but the hurtbox method is usually called first, and Mario usually damages the goomba successfully.
Speculation
One possibility is that if Mario moves into the goomba and collides into the goomba's "position the previous tick", then _on_Hitbox_body_entered
(enemy attacks Mario) gets called first. And if the goomba moves from "not touching Mario's new position" to "colliding with Mario" at the end of the physics tick, then _on_HurtboxStrike_body_entered
(Mario attacks enemy) gets called first.
But I don't know how the order of physics movements could affect the order of callbacks, since I hear callback order is related to node tree order, both connections are made in the same direction, both hurtbox_strike
and hitbox
belong to the goomba and don't change in order (I think? could be wrong), and the player-goomba physics move the goomba as a full unit, rather than its hitboxes individually.
- But perhaps moving a goomba moves/processes its hitboxes in a different order, than moving a player into the goomba's hitbox?
Expected behavior
Spinning into an enemy which can be stunned or defeated by a spin, always damages the enemy without the player taking damage.
I don't know how this could possibly be implemented given Godot's current callback system, unless you can control the order of callbacks, or buffer all player-enemy collision events and process them once a physics tick in a consistent manner (independent of the order of collision events).
Screenshots
Godot_v3.5.2-stable_win64_tHG4tkKfGw.mp4
System Information
- OS/Browser: Windows 10 Ameliorated
- Game version: Git master 5ff3005
- Other specs:
Additional context
Possibly related: