Multiplayer: Apply_impulse is only working on Host

Godot Version

v4.6.stable.official

Question

I have a P2P multiplayer game where players can pick up and throw objects. So far, pick up is working fine for all players but throwing would only work properly with the host. Any clients would simply drop the item. Sometimes the item would spin, but I’m unsure if that is the apply_central_impulse.

extends Area3D

@onready var interaction_ctrl = $"."
var interaction_component = RigidBody3D
@onready var remote_transform_3d: RemoteTransform3D = $HoldSpaceRMT
@onready var _camera: Camera3D = %Camera3D

var viable_item = null
var held_item = null

func _process(_delta) -> void:
	if !is_multiplayer_authority(): return 
	if Input.is_action_just_pressed("Interact") and viable_item != null:
		remote_transform_3d.remote_path = viable_item.get_path()
		held_item = viable_item 
	elif Input.is_action_just_pressed("Interact") and held_item != null:
		throw()
	
	var forward := _camera.global_basis.z 
	var target_angle := Vector3.FORWARD.signed_angle_to(forward, Vector3.UP)
	interaction_ctrl.global_rotation.y = lerp_angle(interaction_ctrl.rotation.y, target_angle, 1)

func _on_body_entered(_body):
	if _body.has_method("pickup"):
		viable_item = _body

func _on_body_exited(_body):
	if _body.has_method("pickup"):
		viable_item = null

func throw():
	remote_transform_3d.remote_path = "."
	held_item.apply_central_impulse(-_camera.global_basis.z * 50)
	held_item = null
	print_debug("throw works")

Here’s the setup for the player.

I can get the function to work for everyone if I desynchronize object positions, but I don’t want to do that. Also with desyncing the objects, clients can see when the host throws an object but a client throwing an object would look like they dropped it from the perspective of any other player.

I’ve attempted to make the throwing function into an RPC but I keep on getting the same result of the clients only dropping the item or getting the following error

throw: Invalid call. Nonexistent function ‘apply_central_impulse’ in base ‘Nil’.

What confuses me most though is that in my player movement script, it has similar code for pushing rigidbodies that works for ALL players.

func _physics_process(delta: float) -> void:
	for i in get_slide_collision_count():
		var c = get_slide_collision(i)
		if c.get_collider() is RigidBody3D:
			c.get_collider().apply_central_impulse(-c.get_normal() * push_force)

I feel like I’m missing something fundamental and would appreciate any help.

The input singleton is local only. You do not have any code synchronizing player input to “throw” on every client.

Your push code works by happenstance since all clients run the same code without authority checks on a perfect localhost network(i assume), but you should not rely on that to stay synchronized on a real network scenario AND not all computers run the same floating point hardware!

I would avoid “pvp”, just make it central authority. It will make the source of truth much easier. And because of how networking generally works in Godot all packets route through the host anyway requiring more bandwidth and more prone to network desync.

Separate your network code from the logic and focus on data only. E.g.

# player psuedo code
var jump = false, var position
func _unhandled_input(event):
  If event is jump:
    jump = true

func _process():
  if jump:
    do_jump()
    jump = false
  update_position()

then you point one MultiplayerSynchroniser at the jump var and authorize it to the player. Then a second multiplayersynchrizer authed to the host to update position.

Position can be updated by both sides, this is called state synchronization. Otherwise you can do snapshot synchronization. But for players specifically you want responsiveness so you can work in client side prediction allowing the local code to update position but check to see how far they are away from the server instance. (This requires scripting a custom multiplayerSynchronizer but the same idea here hopefully you get it)

Thank you for the response!! Central authority does sound much better, I’ll try to do this as soon as possible, do you have any recommended tutorials or documentation I should look at?