Hi i am a new game dev i got started like from 2 months only and i am making a 2d game and i am having a problem with physics in the game

Godot Version

4.7

Question

Hi everyone! I’m trying to set up clean movement for my enemies in Godot 4, but I’m getting stuck trying to combine a few different behaviors:

​Regular Movement / Lunging: Getting the enemy to move toward the player or lunge at them to attack.

​Knockback: Pushing the enemy backward smoothly when they take damage instead of sliding or glitching.

​Whenever I try to mix these together, they fight each other or don’t feel smooth. How should I structure my code so movement, lunging, and knockback work nicely together?

​Here is what I have tried so far for the player code:

extends CharacterBody2D

signal check_flip_h

var is_attacking = false

signal check_flip_h_1

signal keep_it_stable

signal copy_player_direction_tree_spider_left

signal copy_player_direction_tree_spider_right

signal slash_began

signal slash_finished

signal slash_began1

var cut_jump_hight = 0.01

signal slash_finished1

var roll = 0

var go_left = Input.is_action_just_pressed(“go left”)

signal sound_play

var i_have_the_demon_sword = false

var yafa_slash = false

var SPEED = 250

var JUMP_VELOCITY = -400.0

func _physics_process(delta: float) → void:

if Input.is_action_just_released("jump") and velocity.y < 0:

	velocity.y \*= cut_jump_hight

if yafa_slash == true:

		move_and_slide()

		return

if Input.is_action_just_pressed("jump") and is_on_floor():

	$"2".play()

if not is_on_floor():

	velocity += get_gravity() \* delta

\# Handle jump.

if Input.is_action_just_pressed("jump") and is_on_floor():

	velocity.y = JUMP_VELOCITY

var direction := Input.get_axis("go left", "go right")

if direction:

	velocity.x = direction \* SPEED

else:

	velocity.x = move_toward(velocity.x, 0, SPEED)

move_and_slide()





















if $idle.flip_h == true:

	$"sword hold idle right".visible = false

	$"sword hold idle left".visible = true

if $idle.flip_h == false:

	$"sword hold idle right".visible = true

	$"sword hold idle left".visible = false

if yafa_slash == false:

	if direction == 0:

		$AnimationPlayer.play("idle")

		$run.visible = false

		$idle.visible = true

		$jump.visible = false

		$"yafa sword slash 1".visible = false

		$"yafa sword slash 11".visible = false

	if i_have_the_demon_sword == true:

		if $idle.flip_h == true:

			$"sword hold idle right".visible = false

			$"sword hold idle left".visible = true

		if $idle.flip_h == false:

			$"sword hold idle right".visible = true

			$"sword hold idle left".visible = false

	if i_have_the_demon_sword == false:

		$"sword hold idle right".visible = false

		$"sword hold idle left".visible = false

	if direction != 0:

		$AnimationPlayer.play("run1")

		$idle.visible = false

		$run.visible = true

		$jump.visible = false

		$"yafa sword slash 1".visible = false

		$"yafa sword slash 11".visible = false

	if i_have_the_demon_sword == true:

		if $idle.flip_h == true:

			$"sword hold idle right".visible = false

			$"sword hold idle left".visible = true

		if $idle.flip_h == false:

			$"sword hold idle right".visible = true

			$"sword hold idle left".visible = false

	if i_have_the_demon_sword == false:

		$"sword hold idle right".visible = false

		$"sword hold idle left".visible = false

		

	if not is_on_floor():

		$AnimationPlayer.play("jump")

		$jump.visible = true

		$run.visible = false

		$idle.visible = false

		$"yafa sword slash 1".visible = false

		$"yafa sword slash 11".visible = false

	if i_have_the_demon_sword == true:

		if $idle.flip_h == true:

			$"sword hold idle right".visible = false

			$"sword hold idle left".visible = true

		if $idle.flip_h == false:

			$"sword hold idle right".visible = true

			$"sword hold idle left".visible = false

	if i_have_the_demon_sword == false:

		$"sword hold idle right".visible = false

		$"sword hold idle left".visible = false

if direction <0:

	copy_player_direction_tree_spider_left.emit()

if direction >0:

	copy_player_direction_tree_spider_right.emit()

if direction <0:

	$run.flip_h = true

if direction  >0:

	$run.flip_h = false

	

	

	

if direction <0:

	$idle.flip_h = true

if direction  >0:

	$idle.flip_h = false

if direction <0:

	$jump.flip_h = true

if direction  >0:

	$jump.flip_h = false

if direction <0:

	$"yafa sword slash 1".flip_h = true

if direction  >0:

	$"yafa sword slash 1".flip_h = false

if direction <0:

	$"yafa sword slash 11".flip_h = true

if direction  >0:

	$"yafa sword slash 11".flip_h = false

if i_have_the_demon_sword == true:

	if Input.is_action_just_pressed("yafa slash 1"):

		check_flip_h.emit()

		check_flip_h_1.emit()

func _on_demon_sword_item_yafa_you_tooke_the_demon_sowrd() → void:

i_have_the_demon_sword = true

sound_play.emit()

func _on_yafa_sword_slash_1_yes_it_is() → void:

$"demon swrod attttack sound effect".play()

if is_attacking == false:

	is_attacking = true

	$AnimationPlayer.play("yafa sword slash 1")

	slash_began1.emit()

	yafa_slash = true

	$jump.visible = false

	$run.visible = false

	$idle.visible = false

	$"yafa sword slash 1".visible = true

	$"sword hold idle right".visible = false

	$"sword hold idle left".visible = false

	await $AnimationPlayer.animation_finished

	is_attacking = false

	slash_finished1.emit()

	yafa_slash = false

func _on_yafa_sword_slash_11_yes_it_is_11() → void:

$"demon swrod attttack sound effect".play()

if is_attacking == false:

	is_attacking = true

	$AnimationPlayer.play("yafa sword slash 1.1")

	slash_began.emit()

	yafa_slash = true

	$jump.visible = false

	$run.visible = false

	$idle.visible = false

	$"yafa sword slash 1".visible = false

	$"yafa sword slash 11".visible = true

	$"sword hold idle right".visible = false

	$"sword hold idle left".visible = false

	await $AnimationPlayer.animation_finished

	is_attacking = false

	slash_finished.emit()

	yafa_slash = false

if Input.is_action_just_pressed("go left"):

	pass

if Input.is_action_just_pressed("go right"):

	pass

func _on_left_go_left() → void:

print("i am sup")

And here is waht i have so far in the enymy code:

extends CharacterBody2D

signal disable_collisons

signal disable_the_attack

var cool_down = false

var is_attacking = false

var dead = false

var health = 50

var SPEED = 300.0

var JUMP_VELOCITY = -400.0

var do_noises = true

var knockback_velocity: Vector2 = Vector2.ZERO

func _physics_process(delta: float) → void:

if dead == false:

	if is_attacking == false:

		if velocity.x == 0:

			$"nature taken knight shape".visible = true

			$"nature taken knight shape 2".visible = false

			$"green shoooooooot".visible = false

			$"green shoot other side".visible = false

			$"AnimationPlayer of nature taken knight".play("nature taken knight idle animation")

			$head.visible = false

			$"left hand".visible = false

			$"right hand".visible = false

			$tourso.visible = false

if velocity.x != 0:

	pass

else:

	velocity.x = move_toward(velocity.x, 0, SPEED)

move_and_slide()

func _on_left_attack_get_ddemge() → void:

health -= 10

$"robot geting damged".play()

$"nature taken knight shape".visible = false

$"nature taken knight shape 2".visible = false

$"green shoooooooot".visible = false

$"green shoot other side".visible = false

$head.visible = true

$"left hand".visible = true

$"right hand".visible = true

$tourso.visible = true

$"AnimationPlayer of nature taken knight".play("damged")

if health <= 0:

	dead = true

	print("dead")

	$"nature taken knight shape".visible = false

	$"nature taken knight shape 2".visible = false

	$"green shoooooooot".visible = false

	$"green shoot other side".visible = false

	$head.visible = true

	$"left hand".visible = true

	$"right hand".visible = true

	$tourso.visible = true

	$"AnimationPlayer of nature taken knight".play("deathy after")

	disable_collisons.emit()

	$"nature taken knight noises".stop()

func _on_right_attack_get_damged() → void:

health -= 10

$"robot geting damged".play()

$"nature taken knight shape".visible = false

$"nature taken knight shape 2".visible = false

$head.visible = true

$"left hand".visible = true

$"right hand".visible = true

$tourso.visible = true

$"AnimationPlayer of nature taken knight".play("damged")

if health <= 0:

	dead = true

	print("dead")

	$"nature taken knight shape".visible = false

	$"nature taken knight shape 2".visible = false

	$head.visible = true

	$"left hand".visible = true

	$"right hand".visible = true

	$tourso.visible = true

	$"AnimationPlayer of nature taken knight".play("deathy after")

	disable_collisons.emit()

	$"nature taken knight noises".stop()

func _on_left_go_left() → void:

is_attacking = true

$head.visible = false

$"left hand".visible = false

$"right hand".visible = false

$tourso.visible = false

$"nature taken knight shape".visible = false

$"nature taken knight shape 2".visible = false

$"green shoooooooot".visible = false

$"green shoot other side".visible = true

$"AnimationPlayer of nature taken knight".play("green shot animation lft other side")

await get_tree().create_timer(0.3).timeout

is_attacking = false

func _on_right_play_right() → void:

is_attacking = true

$head.visible = false

$"left hand".visible = false

$"right hand".visible = false

$tourso.visible = false

$"nature taken knight shape".visible = false

$"nature taken knight shape 2".visible = false

$"green shoooooooot".visible = true

$"green shoot other side".visible = false

$"AnimationPlayer of nature taken knight".play("green shhhot animaiton")

await get_tree().create_timer(0.3).timeout

is_attacking = false

And pleas when you help me explain how these codes work i menat hte codes that you will send me. THANK YOU!

Don’t let move, lunge, and knockback all write velocity.x at once. Use one “mode” at a time.

enum State { MOVE, LUNGE, KNOCKBACK }
var state = State.MOVE
var knockback = Vector2.ZERO

func _physics_process(delta):
	if dead:
		return

	match state:
		State.KNOCKBACK:
			velocity = knockback
			knockback = knockback.move_toward(Vector2.ZERO, 2000 * delta)
			if knockback.length() < 10:
				state = State.MOVE
		State.LUNGE:
			# set lunge velocity here, then go back to MOVE when done
			pass
		State.MOVE:
			# normal chase toward player
			var dir = sign(player.global_position.x - global_position.x)
			velocity.x = dir * SPEED

	move_and_slide()

func apply_knockback(from_pos: Vector2):
	var dir = sign(global_position.x - from_pos.x)
	knockback = Vector2(dir * 400, -100)
	state = State.KNOCKBACK

Call apply_knockback(player.global_position) when the enemy takes damage.

You already have knockback_velocity on the enemy, but nothing ever sets or uses it tho, that’s why knockback isn’t doing anything yet.

Also, put animations in their own bit (or play them when state changes). Mixing tons of .visible = checks inside _physics_process makes this harder to debug.

thank you for the cood but why this happens when i tryed it out here is the video link of what happenes when i try the cood look at the enymy when i reahc him he acts glitchly https://youtu.be/qXWmJ7L02tY and here is the link of how i have wrote the cood is there anything worng with the cood? https://youtu.be/dZoOXw9fjlk

Here’s what’s going wrong, and what each bit does.

  1. Gravity
    –You never add gravity, so after knockback’s upward -100 (or any air time) the enemy can float / slide weird.
if not is_on_floor():
	velocity += get_gravity() * delta

is_on_floor() = standing on ground.
If not, we add gravity so they fall.
Without this, the upward knockback (-100) can leave them floating.

Put it near the top of _physics_process, before the match.

  1. Chase with a stop gap
State.MOVE:
	var dx = $"../yafa".global_position.x - global_position.x
	if abs(dx) > 40:
		velocity.x = sign(dx) * SPEED
	else:
		velocity.x = 0
  • dx = how far the player is on X (negative = player left, positive = right)
  • abs(dx) > 40 = only chase if farther than 40px
  • sign(dx) = -1 or 1 (left/right)
  • velocity.x = 0 when close = stop pushing into the player

–That fixed-distance “follow” was chase + collision fighting. The gap stops the flicker when you jump over them too.

  1. Knockback on hit

In your damage functions (health -= 10), also call:

apply_knockback($"../yafa".global_position)

And keep

func apply_knockback(from_pos: Vector2):
	var dir = sign(global_position.x - from_pos.x)
	knockback = Vector2(dir * 400, -100)
	state = State.KNOCKBACK

from_pos = where the hit came from (player)
dir = push away from that point
dir * 400 = horizontal shove
-100 = little hop up
state = State.KNOCKBACK = for a moment ignore chase, only use knockback

–In KNOCKBACK, knockback.move_toward(Vector2.ZERO, ...) softens it until it’s small, then back to MOVE.

If attack still does nothing, the sword Area2D isn’t touching the enemy’s hurtbox, and that’s separate from movement.

–Useful docs related to your topic

  1. Using CharacterBody2D/3D — Godot Engine (stable) documentation in English
  2. Using signals — Godot Engine (stable) documentation in English

i dont whant to make the enymy follow the player ill do other techniques ill use the knockback cood to make the enymy lunch ill do that later but the knockback got fixed but why the enymy keeps sliding after the knockback finishes here is what happens https://youtu.be/Z7sm6Dtc-ro and here is how the cood looks like so far https://youtu.be/0qJtNadAqaY and a small question in the cood

var dir = sign(global_position.x - $“../yafa”.global_position.x)

knockback = Vector2(dir \* -100, -400)

state = State.KNOCKBACK.                             

Do ineed to write the cood

var dir = sign(global_position.x - $“../yafa”.global_position.x)

Everytime i whant to make the enymy move

and in the first massege you sended i didnt understand how do you mean put animations in thier own bit and for you to see why i use .visible look at how they are organiized more than 1 sprite2d is that a wrong thing to do? lok what i mean by more than sprite2d https://youtu.be/QcluDUGkhOQif if it is the wrong way tell me the right way to do it

Knockback looks like it “finishes,” but velocity is still whatever it was. If MOVE doesn’t set velocity.x, they keep sliding forever.

When you leave knockback, clear it:

if knockback.length() < 10:
	velocity.x = 0
	state = State.MOVE

And in MOVE ,since you’re not chasing anymore:

State.MOVE:
	velocity.x = 0

Also this line fights gravity:

velocity = knockback

That overwrites velocity.y every frame. Better version of that:

State.KNOCKBACK:
	if not is_on_floor():
		velocity.y += get_gravity().y * delta
	velocity.x = knockback.x
	knockback = knockback.move_toward(Vector2.ZERO, 800 * delta)
	if knockback.length() < 10:
		velocity.x = 0
		state = State.MOVE

(800 decays faster than 200 so it doesn’t coast forever.)

—Do you need dir every time?
->Only when you start a knockback/lunge. Not every frame while idle.

now the knockback defintly look smoother and works the way it should heres how it look like https://youtu.be/SUTxu8a_yx8 i cant realy tell if the gravity works while the knockback is being applied is it? and here is how the cood looks like so far https://youtu.be/2xZXu6RuFYU the cood is not 100% identical to the cood you sended but it works the way it should as you saw in the video and here is the only problem with that knockback as you saw in the vidoe that shows the codes you saw that i tried to write -400 in the code for the enymy to jump when got knocked back but as you saw in the video he didnt jump why?

Sincs i showd you the cood in the video why when i wrote -400 for the enymy to jump when i knock him back, when i knocked him back he didnt jump like you saw in the video why?

Gravity is running a bit, but the hop never happens because of this line:
velocity.x = knockback.x
That only uses the sideways part. The -400 you put in knockback never gets onto velocity.y, so they stay on the floor and slide.

On hit, give them the upward kick once:


knockback = Vector2(dir * 300, 0)  # sideways only for the decay
velocity.y = -400  # hoppping
state = State.KNOCKBACK

Then in knockback:


State.KNOCKBACK:
	if not is_on_floor():
		velocity.y += get_gravity().y * delta
	velocity.x = knockback.x
	knockback.x = move_toward(knockback.x, 0, 1000 * delta)
	if abs(knockback.x) < 10 and is_on_floor():
		velocity.x = 0
		state = State.MOVE

Also drop the * 0.3 on gravity while testing, that makes the fall really weak. And you don’t need gravity twice (inside the match and again after move_and_slide).

when i tried to put this cood to the player also so he can also get knocked back he have been broken he didnt act normaly like the enymy look how what happened to him after i added the cood thta makes him able to get knocked back but i didnt use it yet i mean i didnt make him get knocked back yet and this already happens look what happened to him in the video and when i fly with the sword i slow down. https://youtu.be/4j8XXfnEqpE. And this is how the cood of of knockback ths ti wrote in the enymym and player look . https://youtu.be/GWbpkFF941g. And with the enymy it works perfectly and smoothly and he can jump i tried it thank you! But help me make it work with the player using what i provided and i aleeady sended you the player script from the first massege

Enemy side looks good. Player broke because that knockback block isn’t isolated - it’s changing normal movement too.

From the code video, two things stick out:

* 0.3 on gravity : that’s why you float forever, then slam when full gravity kicks back in. Use full gravity while testing (get_gravity() * delta, no 0.3).
Gravity after move_and_slide() : add gravity before move_and_slide, once. Doing it after (and only at 0.3) makes air feel wrong, including sword “flight.”
Knockback should only run when state == KNOCKBACK. In MOVE, keep your old player logic (input + jump + full gravity). Don’t leave leftover knockback gravity/decay running every frame when you haven’t been hit.

match state:
	State.KNOCKBACK:
		if not is_on_floor():
			velocity.y += get_gravity().y * delta
		velocity.x = knockback.x
		knockback.x = move_toward(knockback.x, 0, 1000 * delta)
		if abs(knockback.x) < 10 and is_on_floor():
			velocity.x = 0
			state = State.MOVE
	State.MOVE:
		# your normal player move/jump here (full gravity, input)
		pass
move_and_slide()

yafa_slash early return is fine, but that path still needs whatever gravity you want during the slash. Right now it skips the rest and feels slow in the air.

Start state as MOVE, and only call apply_knockback when the player actually gets hit.

–Also, for next posts, paste the player _physics_process / knockback bit as text, in code blocks(like this (Shortcut : CTRL+E)). Video helps for “how it looks,” but digging through a recording for every line is rough; text would way easier to check.

i dont know why but it is not geting fixed look at the cood i wrote https://youtu.be/7n9R4J8EAQc and how the player looks like https://youtu.be/UcuA-Z1LPpw still looks not good and sword flight is not fixed clearly and i think the problem is because the attack has its own function look waht i mean https://youtu.be/Wcfwtsl7e-4 so it gets stuck at the normal speed and gravity outside the knockback so it feels off and cuted of is it hard to fix help me fixing it

Paste your code, not screenshots, and especially not videos of text.


If your player is in the knockback state when attacking then maybe they are hitting themself. Otherwise I’m guessing your attacking state is changing how gravity works.

now i somehow manged to make it work but the knockback it self is not doing the knock back here is the knockback code

match state:

	State.KNOCKBACK:

		if not is_on_floor():

			velocity.y += get_gravity().y \* delta

			velocity.x = knockback.x

			knockback.x = move_toward(knockback.x, 0, 1000 \* delta)

			if abs(knockback.x) < 10 and is_on_floor():

				velocity.x = 0

				state = State.MOVE

	State.MOVE:

		SPEED = 150

		JUMP_VELOCITY = -250

custom_gravity = 500

move_and_slide()

And here is when i tried to applay the knockback func _on_left_go_left() → void:

print("i am sup")

var dir = sign(global_position.x - $"../yafa".global_position.x)

knockback = Vector2(dir \* 300, 0)

state = State.KNOCKBACK

func _on_right_play_right() → void:

var dir = sign(global_position.x - $"../yafa".global_position.x)

knockback = Vector2(dir \* -300, 0)

state = State.KNOCKBACK

How should i fix this?

Yes, looks like knockback isn’t applying because of indentation from the code you pasted here.

Right now almost the whole KNOCKBACK block sits inside if not is_on_floor(). So on the floor (where you usually get hit) you never set velocity.x = knockback.x, and the “exit knockback” check never runs either (is_on_floor() can’t be true inside not is_on_floor()).

Pull horizontal knockback + decay + exit out of that if. Only gravity stays air-only:

match state:
	State.KNOCKBACK:
		if not is_on_floor():
			velocity.y += get_gravity().y * delta
		velocity.x = knockback.x
		knockback.x = move_toward(knockback.x, 0, 1000 * delta)
		if abs(knockback.x) < 10 and is_on_floor():
			velocity.x = 0
			state = State.MOVE
	State.MOVE:
		# normal move / jump / gravity here
		pass
move_and_slide()

Also check the hit directions: left uses dir * 300 and right uses dir * -300 with the same sign(...). One of those will push the wrong way. Pick one rule : e.g. always away from the attacker:

var dir = sign(global_position.x - attacker.global_position.x)
if dir == 0:
	dir = 1
knockback = Vector2(dir * 300, -150) 
state = State.KNOCKBACK

and use that same formula for both hitboxes (or just call one apply_knockback(attacker) from both).


Left/right * 300 vs * -300 is a second bug waiting after the indent fix. -150 on Y is only you want a hop like the enemy.

Thank you soooooo much now everything works perfectly you realy helped me now look how clean the game looks https://youtu.be/8b7ai1UgWhs thank you for your help.