Multiplayer knockback (3D)

Godot Version

4.3

Question

I’m having trouble applying knockback on another player when he is hit by another player, the damage works, the knockback doesnt

here is my player script:

extends CharacterBody3D


@export var JUMP_VELOCITY = 4.5

const MOUSE_SENSITIVITY = 0.1


var SPEED = 5.0
@export var sprint_speed = 7.5
@export var crouch_speed = 3.5
@export var walk_speed = 5.0

@export var BASE_HEALTH = 250
var health = BASE_HEALTH
var knockback=Vector3.ZERO

@export var baseheight = 0.9
var HEIGHT = 0.9
@export var crouching_depth = -0.75

var base_pos
var base_velo

var wall_normal

var pull
var target_pos

var timer
var slide_indulgence_timer = 0.5


#movement_possible
@export var can_wall_run = true
@export var can_slide = true


#states
var on_wall = false
var walking = false
var sprinting = false
var crouching = false
var sliding = false

var slide_timer= 0.0
var slide_timer_max= 1.3
var slide_vector= Vector2.ZERO
var slide_speed = 10

var BOB_FREQ = 2.0
var BOB_AMP = 0.05
var t_bob=0.0


var BASE_FOV= 85.0
var FOV_CHANGE = 3.5


#anim

var PUNCH=1

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = 9.8
@onready var standing_model=$standing_model
@onready var crouching_model=$crouching_model
@onready var crouching_collision_shape= $crouching_collision_shape
@onready var standing_collision_shape= $standing_collision_shape
@onready var camera = $CamRoot/Camera3D
@onready var ray_cast = $RayCast3D
@onready var ray_cast_l = $RayCast3D_left
@onready var ray_cast_r = $RayCast3D_right
@onready var tp_cast = $CamRoot/Camera3D/RayCast3D
@onready var hit_cast = $CamRoot/Camera3D/Hit_cast
@onready var hand_anim = $CamRoot/Camera3D/fps_rig/shotgun/PSX_First_Person_Arms/AnimationPlayer

func _enter_tree():
	set_multiplayer_authority(name.to_int())
	
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	camera.current = is_multiplayer_authority()
	




func _process(delta):
	window_activity()
	
	
func window_activity():
	if Input.is_action_just_pressed("ui_cancel"):
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		else:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _input(event):
	if is_multiplayer_authority():
		if event is InputEventMouseMotion:
			# Rotates the view vertically
			$CamRoot.rotate_x(deg_to_rad(event.relative.y * MOUSE_SENSITIVITY * -1))
			$CamRoot.rotation_degrees.x = clamp($CamRoot.rotation_degrees.x, -75, 75)
			
			# Rotates the view horizontally
			self.rotate_y(deg_to_rad(event.relative.x * MOUSE_SENSITIVITY * -1))


func _physics_process(delta):
	
	if is_multiplayer_authority():
		
		var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
		var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
		
		
		
		if !sliding and !on_wall:
			$CamRoot.rotation_degrees.z = lerp($CamRoot.rotation_degrees.z, -4.0*sign(input_dir.x),delta* 5)
		else:
			$CamRoot.rotation_degrees.z = lerp($CamRoot.rotation_degrees.z, 0.0,delta* 4)
		
		#click logic
		if Input.is_mouse_button_pressed( 1 ) and (hand_anim.current_animation != "Combat_punch_left" and hand_anim.current_animation != "Combat_punch_right"):
			if hit_cast.is_colliding() :
				if hit_cast.get_collider().is_in_group("player"):
					var knock_direction = $CamRoot.position.direction_to(hit_cast.get_collider().position)
					var knock_force = 7.0
					hit_cast.get_collider().knockback= knock_direction*knock_force
					hit_cast.get_collider().take_damage(5)
					
			
			if PUNCH==1:
				hand_anim.play("Combat_punch_left") 
				PUNCH=0
			else:
				hand_anim.play("Combat_punch_right")
				PUNCH=1

		else:
			
			hand_anim.queue("Combat_idle")
		#click logic end
		
		
		#pull logic start
		if Input.is_action_pressed("E_spec") and Global.E_spec_cooldown<=0 and position.distance_to(tp_cast.get_collision_point())>5:
			
			if tp_cast.is_colliding():
				Global.E_spec_cooldown = 5.0
				hand_anim.play("Collect_something",-1)
				hand_anim.queue("Combat_idle_start")
				pull=true
				base_pos=position
				base_velo= velocity
				timer = 0
				target_pos= lerp(position,tp_cast.get_collision_point(),1-(1/(position.distance_to(tp_cast.get_collision_point()))))
				
				
		Global.E_spec_cooldown-=delta
		
		
		if pull == true:
			if position.distance_to(target_pos)<1.5 or timer>1 :
				pull = false
				velocity.y=clamp(9*$CamRoot.rotation.x,0.0,10)
				
				
			else:
				if (target_pos-base_pos).length() < 10:
					
					velocity = (target_pos-base_pos).normalized()*10 + (0.1*velocity)
					
				else:
					velocity = target_pos-base_pos + (0.1*velocity)
				timer+=delta
		#pull logic end
				
		if Input.is_action_pressed("ui_crouch") and !on_wall:
			SPEED = crouch_speed
			
			
			standing_collision_shape.disabled = true
			crouching_collision_shape.disabled = false
			
			standing_model.visible = false
			crouching_model.visible = true
			
			HEIGHT= lerp(HEIGHT,baseheight + crouching_depth,delta*7)
			
			
			#slide begin logic
			
			if can_slide:
				if (slide_indulgence_timer<0.5 or velocity.length()>5.0 or velocity.y < 0) and input_dir != Vector2.ZERO and !sliding and !is_on_wall():
					sliding = true
				
					slide_timer = slide_timer_max
					slide_vector= input_dir

			walking = false
			sprinting = false
			crouching = true

		elif !ray_cast.is_colliding():
			
			
			HEIGHT= lerp(HEIGHT, baseheight ,delta*7)
			
			standing_collision_shape.disabled = false
			crouching_collision_shape.disabled = true
			
			standing_model.visible = true
			crouching_model.visible = false
			
			if Input.is_action_pressed("ui_sprint"):
				SPEED = sprint_speed
				walking = false
				sprinting = true
				slide_indulgence_timer=0.0
				crouching = false
			else:
				SPEED = walk_speed
				walking = true
				sprinting = false
				crouching = false
		
		if can_slide:
			slide_indulgence_timer+=delta
			# handle sliding
			if sliding:
				slide_timer-=delta
				
				$CamRoot.rotation.z= lerp($CamRoot.rotation.z, deg_to_rad(6.0),delta* 7)
				HEIGHT= lerp(HEIGHT,baseheight + crouching_depth,delta*7)
				
				if slide_timer<=0:
					
					sliding=false
					$CamRoot.rotation.z = lerp($CamRoot.rotation.z, 0.0, delta * 4.5)
					HEIGHT= lerp(HEIGHT,baseheight ,delta* 1)
					
		if can_wall_run:		
			if	((is_on_wall() and !is_on_floor())) or on_wall:
				
				if !on_wall:
					on_wall = true
					wall_normal = get_slide_collision(0)
					velocity.z*=2 
					
					velocity.y = 2
				gravity = 2
				
				if is_on_wall():
					direction += -(wall_normal.get_normal(0)/1.5)
				if ray_cast_l.is_colliding():
					$CamRoot.rotation.z= lerp($CamRoot.rotation.z, deg_to_rad(20.0),delta* 4.5)
				elif ray_cast_r.is_colliding():
					$CamRoot.rotation.z= lerp($CamRoot.rotation.z, deg_to_rad(-20.0),delta* 4.5)
				
				
				
				
			if (!(ray_cast_l.is_colliding() or ray_cast_r.is_colliding()) and !is_on_wall()) or is_on_floor() :
				gravity = 9.8
				$CamRoot.rotation.z = lerp($CamRoot.rotation.z, 0.0, delta * 4.5)
				on_wall=false
			# Add the gravity.
		
		if not is_on_floor():
			velocity.y -= gravity * delta

		# Handle Jump.
		if Input.is_action_just_pressed("ui_accept") and (is_on_floor() or on_wall) and !ray_cast.is_colliding():
			
			if on_wall and is_on_wall():

				velocity += wall_normal.get_normal(0) * JUMP_VELOCITY
			
			if pull:
				pull = false
				velocity.y=clamp(9*$CamRoot.rotation.x,0.0,10)
				
			
			if sliding:
				sliding=false
				$CamRoot.rotation.z = lerp($CamRoot.rotation.z, 0.0, delta * 7)
				HEIGHT= lerp(HEIGHT,baseheight ,delta* 1)
			
			
			velocity.y = JUMP_VELOCITY*1.5
			
			
			sliding = false
			slide_timer=0.0

		# Get the input direction and handle the movement/deceleration.
		# As good practice, you should replace UI actions with custom gameplay actions.
		
		
		
		if sliding:
			direction = (transform.basis * Vector3(slide_vector.x,0,slide_vector.y)).normalized()
			
		if is_on_floor():
			if !pull:
				if direction:
					velocity.x = direction.x * SPEED
					velocity.z = direction.z * SPEED
					
					if sliding:
						velocity.x = direction.x * (slide_timer+0.15) * slide_speed
						velocity.z = direction.z * (slide_timer+0.15) * slide_speed
				else:
					velocity.x = lerp(velocity.x, direction.x * SPEED, delta * 7.0)
					velocity.z = lerp(velocity.z, direction.z * SPEED, delta * 7.0)
		else:
			velocity.x = lerp(velocity.x, direction.x * SPEED, delta * 3.0) 
			velocity.z = lerp(velocity.z, direction.z * SPEED, delta * 3.0)
			
	
		
		velocity += knockback

		knockback = lerp(knockback, Vector3.ZERO, 3)
			
		t_bob += delta * velocity.length() * float(is_on_floor())
		$CamRoot.transform.origin= _headbob(t_bob)
		
		var velocity_clamped = clamp(velocity.length(), 0.5, 12)

		var target_fov = BASE_FOV + FOV_CHANGE * velocity_clamped
		camera.fov = lerp(camera.fov, target_fov, delta * 8.0)
		
		move_and_slide()
		
		
	
func _headbob(time) -> Vector3:
	var pos = Vector3.ZERO
	pos.y = sin(time * BOB_FREQ) * BOB_AMP + HEIGHT
	pos.x = cos(time * BOB_FREQ / 2) * BOB_AMP
	return pos

func take_damage(amt):
	health -= amt
	$GPUParticles3D.restart()


	

lerp’s third argument should be from 0.0 to 1.0, where 0.5 would be the midpoint between the two arguments. You have it set to 3 which will instantly and always set it to ZERO. Usually people give a really small number for the third argument, though this will be frame dependant.

This talk goes over this whole lerp-as-smooth-decay issue, which seems to happen a lot in your script.

For just the code part of this talk try using this funciton.

func exp_decay(a: Vector3, b: Vector3, decay: float, delta: float) -> Vector3
    return b + (a - b) * exp(-decay * delta)

# try out different decay numbers, freya recommends 1 to 25
knockback = exp_decay(knockback, Vector3.ZERO, 16, delta)
# and use this for the rest of your lerp'd values.

Another really good solution would be to use Tweens with a quadratic or cubic transition set by set_trans(Tween.TRANS_QUAD)

Thank you for your help , now the knock back only applies to myself (due to a bug I havent fixed yet since I use it to debug right now, when i crouch i can punch my crouching model for a split second) but when punching another player the player doesnt take any knockback, I tought it was due to the physics process only running on the multiplayer authority but after disabling the authority Check for testing , the same issue still arises (maybe the multiplayer synchroniser ?)

This is my first ever game so im still not very experienced, and Even tho I know making a multiplayer game as your first game is not the best way to go about it, i’d still like to accomplish this.

I don’t see any functions marked @rpc, and the knockback variable would have to be @export to work with a MultiplayerSynchronizer I believe. Seems like a multiplayer/authority issue, but that’s another topic.