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

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)