Hitbox moves too fast, hits register late & wrong direction

Godot Version

4.5.1

Hey everyone,

I’m running into an issue with my hit detection and I’m hoping someone has an idea on how to fix it without making things too heavy.

Problem

When my hitbox moves very fast, the collision is detected too late and the impact direction ends up being wrong.
It looks like the physics step misses the moment when the hitbox was actually in front of the enemy, so by the time the hit triggers, the hitbox is already behind them.

This results in the knockback direction being completely reversed.

I’ll add an image here showing the issue:

Current Implementation

Here’s how my hit logic currently works:

1. Hurtbox detects the hit and calls take_damage with the hitbox as the source

# health_component.gd
func _on_hurtbox_area_entered(area: Area2D) -> void:
    if area.has_method("get_damage"):
        var dmg = area.get_damage()
        take_damage(dmg, area)


func take_damage(amount: int, from: Node = null) -> void:
    if invincible:
        return
    current_health -= amount
    emit_signal("damaged", amount, from)

2. Enemy listens to the damaged signal and uses the hitbox position to calculate knockback

# enemy.gd
func _on_health_component_damaged(_amount: int, from: Node) -> void:
    if from is Node2D:
        var source_pos = from.global_position
        var dir = (global_position - source_pos).normalized()
        _knockback_velocity = dir * knockback_strength

Issue with this approach

Because from.global_position is sampled at the time the hit is detected, a very fast-moving hitbox is often already behind the enemy.
That causes:

  • reversed knockback
  • wrong damage direction logic
  • inconsistent behavior depending on speed and framerate

What I’ve Considered (but haven’t tried yet)

I’ve seen a few possible approaches—like using shape-casts/raycasts for “swept” hit detection, enlarging the hitbox, or switching from Area2D signals to manual physics queries—but I haven’t implemented them yet because they seem more time-consuming, may introduce extra performance overhead, or could complicate my current hitbox/hurtbox architecture without guaranteeing a fix. I’d like to understand which approach is actually worth the effort before refactoring the system.

Question

Has anyone dealt with this before? What are some good approaches to:

  • Detect hits earlier even at high velocities
  • Get the true point or direction of impact
  • Keep the solution lightweight / performant

Any ideas, patterns, or alternative approaches would be super helpful!

Thanks in advance!

1 Like

Funny that, while i was trying solutions and writing this post, someone else had a very similar issue! Area3D's as Hitboxes Registering Late - #5 by outofnothing_art

I tried applying their solution to my 2D project and here are the results:

Swept Hitbox (Godot 2D)

TL;DR

Sweep the actual hitbox shape between frames using PhysicsShapeQueryParameters2D + direct_space_state.intersect_shape and pass a contact_point to your damage handler.

Why this works

  • Sweep tests the entire motion volume of the hitbox (prev_pos → curr_pos) instead of a single-point overlap or ray. You get an immediate contact point and can compute knockback from that point.

Minimal code snippets

  1. HealthComponent
signal damaged(amount:int, from:Node, contact_point)
func take_damage(amount:int, from:Node = null, contact_point = null) -> void:
    # update health and emit
    emit_signal("damaged", amount, from, contact_point)
  1. Hitbox swept-shape
# assume cs is your CollisionShape2D node, prev_pos and curr_pos are Vector2
var params = PhysicsShapeQueryParameters2D.new()
params.shape = cs.shape
params.transform = Transform2D(0.0, prev_pos + cs.position)
params.motion = curr_pos - prev_pos
params.collide_with_areas = true
var hits = get_world_2d().direct_space_state.intersect_shape(params, 16)
for hit in hits:
    var area = hit.collider
    if area and area.name.to_lower().find("hurtbox") != -1:
        var hc = area.get_parent().get_node("HealthComponent")
        hc.take_damage(damage, self, hit.get("point", null))
  1. Call from mover/attacker (each frame when hitbox is active):
hitbox.sweep_and_hit(prev_pos, curr_pos)

Runtime cost

Low for a few hitboxes; grows with number of simultaneous sweeps and results returned. This can be most likely be improved on!

I’m leaving this here in case someone needs it. I have some improvement ideas but right now it seems fine.

If you have something to add to this, for example if you find my implementation repulsive, feel free to correct me! :slight_smile:

2 Likes

You could also use a ShapeCast2D.

1 Like

Thanks! Yeah, ShapeCast2D probably would’ve been easier to implement from the start. If my current solution ends up causing issues down the line, I’ll definitely give ShapeCast2D a try. Appreciate the suggestion!

1 Like

Hey! Glad to see someone else making a fighter in Godot!

Happy my thread could help :slight_smile:

Perhaps 2D Shapecasts are better but in my experience (spawning them and rotating them manually with code) they were even worse than Areas. They didn’t even rotate to the right orientation until a frame after their spawn unlike Area3Ds. Godot is primarily a 2D engine, though! Good luck on your journey, and hopefully we’ll run into each other again.

That is a common misconception based on older versions of the engine. It’s a pretty a very functional 3D engine that’s getting better every release.