Hello! I have recently started godot and was testing some enemies. I wanted to add a knock back whenever the enemy hits the player. My current knock back system is very inconsistent. Sometimes it works and sometimes it doesn’t. During various interaction the knock back speed also changes.
extends Area2D
@onready var timer: Timer = $Timer
var damage: int
var knockbackDirection: int
@export var knockbackSpeed: int = 200
func _on_body_entered(body: Node2D) -> void:
var playerHP = body.playerHurt(damage)
if body.is_on_floor():
body.velocity.y = -100
body.velocity.x = knockbackDirection * knockbackSpeed
print("New HP: " + str(playerHP))
SoundManager.play_sound("hurt_sound")
if playerHP <= 0:
body.playerDied()
Engine.time_scale = 0.5
timer.start()
func _on_timer_timeout() -> void:
Engine.time_scale = 1
get_tree().reload_current_scene()
the same hitbox is attached to various weapons like arrows and swords. The direction these weapons are facing is decided during runtime so the direction is assigned during runtime itself. I can show a snippet for more clarification
if animated_sprite_2d.flip_h:
hurtzone.knockbackDirection = -1
if hurtzone.get_node("CollisionShape2D").position.x > 0:
hurtzone.get_node("CollisionShape2D").position.x *= -1
else:
hurtzone.knockbackDirection = 1
if hurtzone.get_node("CollisionShape2D").position.x < 0:
hurtzone.get_node("CollisionShape2D").position.x *= -1
here the direction is set based on the direction the enemy is facing at the moment.
First, I’d recommend taking almost everything in your knockback Area2D and move it to the player. I see how it would seem like it makes sense, but objects should be in charge of themselves, and the enemy should not need to know all about the player’s internal functions.
Second, I’d recommend showing us the full code of the Area2D and the Player, or you’re going to continue to get piecemeal answers and questions.
yeah i think this is one of the things thats happening since if i stay still and let the enemy hit me, the knock back works fine. But if i am moving, my character only jumps. Also i think the momentum from the projectiles is somehow being carried over since it just blasts me off
i switched the knowckback to the player script. Also i am attaching all the relevant scripts.
Hitbox/Hurtzone:
extends Area2D
@onready var timer: Timer = $Timer
var damage: int
var knockbackDirection: int
@export var knockbackSpeed: int = 200
func _on_body_entered(body: Node2D) -> void:
var playerHP = body.playerHurt(damage, knockbackDirection)
print("New HP: " + str(playerHP))
SoundManager.play_sound("hurt_sound")
if playerHP <= 0:
body.playerDied()
Engine.time_scale = 0.5
timer.start()
func _on_timer_timeout() -> void:
Engine.time_scale = 1
get_tree().reload_current_scene()
Player:
extends CharacterBody2D
var playerHP = 10
@export var SPEED = 100.0
@export var deacceleration = 0.1
@export var JUMP_VELOCITY = -300.0
@export var rollSpeed = 70
var dead: bool = false
var jumping: bool = false
var jumpCounter: int = 0
var rolling: bool = false
var rollDir = 0
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var buffer_timer: Timer = $RollBufferTimer
@onready var roll_wait_timer: Timer = $RollWaitTimer
@onready var jump_buffer_timer: Timer = $JumpBufferTimer
var canMove = true
func playerHurt(damage: int, direction: int):
print("Old player HP: " + str(playerHP))
playerHP -= damage
if is_on_floor():
velocity.y = -100
velocity.x = direction * 200
return playerHP
func playerDied():
dead = true
func roll(direction):
rolling = true
canMove = false
rollDir = sign(direction)
if direction < 0:
animated_sprite_2d.flip_h = true
elif direction > 0:
animated_sprite_2d.flip_h = false
roll_wait_timer.start()
buffer_timer.stop()
@warning_ignore("shadowed_variable_base_class")
func customGravity(velocity: Vector2):
return get_gravity() if velocity.y < 0.0 else (get_gravity()*1.5)
func _physics_process(delta: float) -> void:
if dead:
animated_sprite_2d.play("hurt")
else:
# Add the gravity.
if not is_on_floor():
velocity += customGravity(velocity) * delta
#Reset the jump counter
if is_on_floor() and jumpCounter != 0:
jumpCounter = 0
# Handle jump.
if Input.is_action_just_released("jump") and velocity.y < 0.0:
velocity.y = JUMP_VELOCITY/3
if Input.is_action_just_pressed("jump") and not rolling:
jumping = true
jump_buffer_timer.start()
if jumping and not jump_buffer_timer.is_stopped() and jumpCounter < 2:
SoundManager.play_sound("jump_sound")
velocity.y = JUMP_VELOCITY
jumpCounter += 1
jumping = false
if Input.is_action_just_pressed("roll") and is_on_floor() and not rolling:
buffer_timer.start()
if is_on_floor() and not buffer_timer.is_stopped():
var rollDirection = Input.get_axis("move_left", "move_right")
if rollDirection != 0:
roll(rollDirection)
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
#Get direction: -1, 0, 1
var direction := Input.get_axis("move_left", "move_right")
if direction > 0:
animated_sprite_2d.flip_h = false
elif direction < 0:
animated_sprite_2d.flip_h = true
if rolling:
animated_sprite_2d.play("roll")
elif is_on_floor():
if direction == 0:
animated_sprite_2d.play("idle")
else:
animated_sprite_2d.play("run")
else:
animated_sprite_2d.play("jump")
if direction and canMove and not rolling:
velocity.x = direction * SPEED
elif rolling and canMove:
velocity.x = rollDir * rollSpeed
elif not direction and is_on_floor():
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
func _on_animated_sprite_2d_animation_finished() -> void:
rolling = false
func _on_roll_wait_timer_timeout() -> void:
canMove = true
Arrow:
extends Area2D
@export var arrowSpeed = 110
@onready var hurtzone: Area2D = $Sprite2D/Hurtzone
@onready var sprite_2d: Sprite2D = $Sprite2D
var direction: int
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
hurtzone.damage = 1
hurtzone.knockbackDirection = direction
position.x += arrowSpeed * delta
func _on_visible_on_screen_notifier_2d_screen_exited() -> void:
queue_free()
func _on_hurtzone_body_entered(body: Node2D) -> void:
sprite_2d.visible = false
set_process(false)
hurtzone.set_deferred("monitoring", false)
Soldier/Enemy
extends Area2D
var direction = 1
var action = false
var rng = RandomNumberGenerator.new()
@onready var ray_cast_left: RayCast2D = $CollisionShape2D/RayCastLeft
@onready var ray_cast_right: RayCast2D = $CollisionShape2D/RayCastRight
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var arrow_placement: CollisionShape2D = $ArrowPlacement
@onready var Arrow = preload("res://scenes/arrow.tscn")
@onready var hurtzone: Area2D = $Hurtzone
func slashPlayer(playerDirection):
action = true
direction = playerDirection
if direction < 0:
animated_sprite_2d.flip_h = true
else:
animated_sprite_2d.flip_h = false
var randomSlashPattern = rng.randf()
if randomSlashPattern > 0.5:
animated_sprite_2d.play("slash1")
hurtzone.damage = 1
else:
animated_sprite_2d.play("slash2")
hurtzone.damage = 2
await get_tree().create_timer(0.4).timeout
hurtzone.monitoring = true
if animated_sprite_2d.flip_h:
hurtzone.knockbackDirection = -1
if hurtzone.get_node("CollisionShape2D").position.x > 0:
hurtzone.get_node("CollisionShape2D").position.x *= -1
else:
hurtzone.knockbackDirection = 1
if hurtzone.get_node("CollisionShape2D").position.x < 0:
hurtzone.get_node("CollisionShape2D").position.x *= -1
await get_tree().create_timer(0.1).timeout
hurtzone.monitoring = false
await animated_sprite_2d.animation_finished
action = false
animated_sprite_2d.play("idle")
func shootPlayer(playerDirection):
action = true
direction = playerDirection
if direction < 0:
animated_sprite_2d.flip_h = true
else:
animated_sprite_2d.flip_h = false
animated_sprite_2d.play("shoot")
await get_tree().create_timer(0.7).timeout
var arrow = Arrow.instantiate()
var arrowSprite = arrow.get_node("Sprite2D")
if animated_sprite_2d.flip_h:
arrowSprite.flip_h = true
arrow.direction = -1
arrow.arrowSpeed = arrow.arrowSpeed * -1
if arrow_placement.position.x > 0:
arrow_placement.position.x *= -1
else:
arrowSprite.flip_h = false
arrow.arrowSpeed = arrow.arrowSpeed * 1
arrow.direction = 1
if arrow_placement.position.x < 0:
arrow_placement.position.x *= -1
arrow.global_position = arrow_placement.global_position
add_sibling(arrow)
await animated_sprite_2d.animation_finished
action = false
animated_sprite_2d.play("idle")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
if not action:
if ray_cast_left.is_colliding():
if (position.x - ray_cast_left.get_collider().position.x) < 34:
slashPlayer(-1)
else:
shootPlayer(-1)
elif ray_cast_right.is_colliding():
if (ray_cast_right.get_collider().position.x - position.x) < 13:
slashPlayer(1)
else:
shootPlayer(1)
if direction and canMove and not rolling:
velocity.x = direction * SPEED
elif rolling and canMove:
velocity.x = rollDir * rollSpeed
elif not direction and is_on_floor():
velocity.x = move_toward(velocity.x, 0, SPEED)
I believe that this code will override the knockback velocity, since there is no check if the player is being knocked back. Maybe you could set canMove to false when the player is knocked back, or you can set up a state machine for the player.
i tried doing canMove to falsebut since both of them are being changed in the same function, it doesn’t work well. I have no idea what a state machine is so I will learn about it now.
Apart from all this, I am trying some other stuff to let the player system know that they are currently being knockbacked, so that the normal velocity doesn’t override the knockback velocity. I will also definitely checkout state machines.