Godot Version
4.4.1
Question
I found a solution to user @suero 's p2p networking issue, but I am a new user so made a newpost to respond to him
So I am very new to all of this grander godot documentaion and community stuff so I dont know if this is good form but I was so excited after finally working out a solution to @suero 's issue, and finding a solution to one of my own. Hopefully this gets to him and @pennyloafers for all his help.
For more background info look to this post
So I have been poking around with what you have made here and I think I have found the solution
The synchronizer that penny provided works great for plain rigidbodies like balls or boxes, but for rigidbody character controllers it gets more complicated, but still works.
Add this to the penny synchronizer. The server should be the authority on all of these rigidbody synchronisers. If the client attempts to set positions things get wacky
func _enter_tree() → void:
set_multiplayer_authority(1)
Then on your player add another synchroniser and add export vars of all your player inputs. we will then synchronize those inputs. This synchroniser should also have the player be the authority not the server. to make sure this is the case add this to your player script if it isnt already.
func _enter_tree() → void:
set_multiplayer_authority(name.to_int())
This should set up all children as the same authority except for the loafer synchronizer we set earlier.
Next wherever you have your player control stuff (mine is in integrate_forces if that ends up being important), add an if else like this
if is_multiplayer_authority():
input = (Controls) ## Grabs and sets the synced inputs to controls
synced_input = input ##sets the sync input for the synchroniser
character_controller_logic(input) ##Apply character control logic
else:
input = synced_input ##sets the input to what the synchroniser is setting
character_controller_logic(input) ##Apply character control logic
Now I dont really know what im doing, so this could potentially be causing huge issues I cant really see at the moment, but im tired and have been beating my head against the desk for hours learning multiplayer stuff for the first time, and physics is what I love so I NEEDED buttery network physics, and making these changes fixed it.
My hypothesis is this structure causes the server to do all the “Real” physics calculations while allowing the client to also run its physics separately, and in combination with the ring buffer that penny created, this really smooths out the physics at least in the little local host testing I have done. I made a similar synchroniser to his but it had really bad jitter. The ring buffer though did the trick so I cant thank you enough penny I would never have figured out all that without you. ![]()
heres a modified version for Rigidbody2Ds as well if anyone needs it. Only differences are typing things for rigidbody2d’s, the enter tree func, and changing the exported vars into a single array I got annoyed with setting 5 vars to sync over and over
class_name RigidBody2DSynchronizer
extends MultiplayerSynchronizer##Make sure to connect the synchronized signal to on_synchronized()
@onready var sync_object : PhysicsBody2D = get_node(root_path)
@onready var body_state : PhysicsDirectBodyState2D =
PhysicsServer2D.body_get_direct_state( sync_object.get_rid() )##Make sure to sync these properties
@export_group(“Needs to be synced”)
enum {SYNC_POS,SYNC_LVEL,SYNC_ROT,SYNC_AVEL,SYNC_FRAME}
@export var sync_data : Array = [Vector2.ZERO, Vector2.ZERO, 0.0, 0.0, 0]var ring_buffer : RingBuffer2D = RingBuffer2D.new()
var last_frame = -1
var set_num = 0##For the default rigidbody synch the server should be the one calling the shots
func _enter_tree() → void:
set_multiplayer_authority(1)func _exit_tree():
ring_buffer.free()#copy state to array
func get_state(state : PhysicsDirectBodyState2D ):
sync_data[SYNC_POS] = state.transform.origin
sync_data[SYNC_LVEL] = state.linear_velocity
sync_data[SYNC_ROT] = state.transform.get_rotation()
sync_data[SYNC_AVEL] = state.angular_velocity#copy array to state
func set_state(state : PhysicsDirectBodyState2D, data:Array ):
state.transform.origin = data[SYNC_POS]
state.linear_velocity = data[SYNC_LVEL]
state.transform.x = Vector2.RIGHT.rotated(data[SYNC_ROT])
state.transform.y = Vector2.DOWN.rotated(data[SYNC_ROT])
state.angular_velocity = data[SYNC_AVEL]func get_physics_body_info():
server copy for sync
get_state( body_state )
func set_physics_body_info():
client rpc set from server
var data :Array = ring_buffer.remove()
while data.is_empty():
return
set_state( body_state, data )func _physics_process(_delta):
if is_multiplayer_authority():
sync_data[SYNC_FRAME] += 1
get_physics_body_info()
else:
set_physics_body_info()make sure to wire the “synchronized” signal to this function
func _on_synchronized():
if is_previouse_frame():
return
ring_buffer.add([
sync_data[SYNC_POS],
sync_data[SYNC_LVEL],
sync_data[SYNC_ROT],
sync_data[SYNC_AVEL],
])func is_previouse_frame() → bool:
if sync_data[SYNC_FRAME] <= last_frame:
#print(“previous frame %d %d” % [sync_frame, last_frame] )
return true
else:
last_frame = sync_data[SYNC_FRAME]
return falseclass RingBuffer2D extends Object:
const SAFETY:int = 1
const CAPACITY:int = 4 + SAFETY
var buf:Array[Array]
var head:int = 0
var tail:int = 0func _init():
buf.resize(CAPACITY)func add(frame:Array):
if _increment(head) == tail: # full
_comsume_extra()
if is_low():
_produce_extra(frame)
buf[head]=frame
head = _increment(head)func _comsume_extra():
#print( “RingBuffer: consume_extra”)
var next_index = _increment(tail)
buf[next_index] = _interpolate(buf[tail], buf[next_index],0.5)
tail = next_indexfunc _produce_extra(frame:Array):
#print(“RingBuffer: produce_extra”)
var first_frame = _interpolate(buf[tail],frame, 0.33) # assume only one frame exists tail should point at it
var second_frame = _interpolate(buf[tail],frame, 0.66) # assume only one frame exists tail should point at it
buf[head]=first_frame
head = _increment(head)
buf[head] = second_frame
head = _increment(head)func _interpolate(from:Array, to:Array, percentage:float) → Array:
var frame:Array = [
from[SYNC_POS].lerp(to[SYNC_POS], percentage),
from[SYNC_LVEL].lerp(to[SYNC_LVEL], percentage),
lerp_angle(from[SYNC_ROT], to[SYNC_ROT], percentage),
lerp_angle(from[SYNC_AVEL],to[SYNC_AVEL],percentage)
]
return framefunc _increment(index:int)->int:
index += 1
if index == CAPACITY: # avoid modulus
index = 0
return indexfunc remove() → Array:
var frame : Array = buf[tail]
if is_empty() or is_low():
frame =
else:
tail = _increment(tail)
return framefunc is_empty() → bool:
return tail == headfunc is_low() → bool:
return _increment(tail) == head