Hit reaction location

Godot Version

4.6.3/4.7

Question

I’ve been having this dilemma on where to apply the camera shake, hit-stop, and knockback. Stuck between putting it directly in the hurt-box and have the hit-box determine the values of each or tie it to the hurt animation.

any suggestions?

I have a Camera2DShake component in my Camera 2D Plugin that handles it for 2D games. It handles screen and controller shake. I can add multiple if I want, and each one can be called for various things like knockback, drowning, etc.

Water Hazard:

extends Area2D

@onready var camera_2d_shake: Camera2DShake = $"../CharacterBody2D/Camera2D/Camera2DShake"


func _on_body_entered(body: Node2D) -> void:
	print_rich("[wave amp=50.0 freq=4.0 connected=1][color=light_sky_blue]Water Hazard Triggered[/color][/wave]")
	await get_tree().create_timer(0.1).timeout
	camera_2d_shake.start()
	body.hurt()


func _on_body_exited(_body: Node2D) -> void:
	camera_2d_shake.stop()

I got the camera shake and hadn’t got to the point of needing the other camera stuff yet

but here’s the one have from a tutorial:

the only difference was the stuff in the process and base_offset being set outside the shake function

The problem with that implementation is that your Camera2D has to be a ScreenShake object to work. My version allows you to make any Camera2D into a screen shake camera. Especially helpful if you want to use something like PhantomCamera.

what does the plugin do?

Also the current setup if you’re curious

hit-box:

class_name Hitbox extends Area2D

@export var damage : int = 1
@export var knockback_force : float = 100.0
@export var knockback_up : float = 50.0
@export var shake_force : float 
@export var shake_duration : float 
@export_range(0.05, 1.0) var slow_duration : float = 1

hurt-box:

class_name Hurtbox extends Area2D

@export var health: HealthComponent
@export var hit_reaction: HitReactionComponent
@export var parent : PhysicsBody2D

var hitbox: Hitbox

func _ready() -> void:
	monitorable = false
	area_entered.connect(_on_area_entered)
	area_exited.connect(_on_area_exited)
	set_physics_process(false)

func _physics_process(_delta: float) -> void:
	if health.can_be_damaged:
		health.damage(hitbox.damage)
	
	if hit_reaction != null and hit_reaction.can_be_knocked_back:
		var knockback_direction = hitbox.global_position.direction_to(parent.global_position)
		hit_reaction.apply_knockback(knockback_direction, hitbox.knockback_force)
		
		if health.has_been_damaged:
			ScreenShake.instance.shake(hitbox.shake_duration, hitbox.shake_force)
			Global.freeze(hitbox.slow_duration)
			

func _on_area_entered(area: Area2D) -> void:
	hitbox = area
	set_physics_process(true)

func _on_area_exited(_area: Area2D) -> void:
	hitbox = null
	set_physics_process(false)

I’ve been thinking about changing the setup because it feels wrong to me (reason why I asked the question)

If you click the link, the Readme will tell you what the plugin does.

so i’ve split the camera shake from the camera and made it just a node and it works

Screenshot 2026-06-19 8.28.00 AM

class_name ScreenShake
extends Node2D

@export var camera : Camera2D

static var instance: ScreenShake
var shake_tween: Tween = null
@onready var base_offset : Vector2 = camera.offset

func _ready() -> void:
	instance = self

func shake(_duration : float = 0.0, _strength : float = 0.0) -> void:
	if shake_tween: shake_tween.kill()
	shake_tween = get_tree().create_tween()
	shake_tween.tween_method(func (delay: float):
		var movement = Vector2(
			randf_range(-1.0, 1.0), randf_range(-1.0, 1.0)) * _strength * delay
		camera.offset = base_offset + movement, 1.0, 0.0, _duration)

func _process(delta: float) -> void:
	if not shake_tween:
		camera.offset = lerp(camera.offset, base_offset, 1)

But my original question still stands, where should it trigger?

I’d start by tying the Hurtbox to the screen shake. When you get hit, request a shake.

got it

func _physics_process(delta: float) -> void:
	if health.can_be_damaged:
		health.damage(hitbox.damage)
		print("Damage delt: %s" %hitbox.damage)
	
	if hitbox.can_shake_camera == true and health.has_been_damaged:
		ScreenShake.instance.shake(hitbox.shake_duration, hitbox.shake_force)
		print("Shake")
		
	
	if hitbox.can_freeze_screen == true and health.has_been_damaged:
		print("Freeze")
		Global.freeze(hitbox.freeze_duration)


func _on_area_entered(area : Area2D) -> void:
	hitbox = area
	set_physics_process(true)

func _on_area_exited(area : Area2D) -> void:
	hitbox = null
	set_physics_process(false)