foxssake / netfox

Addons for building multiplayer games with Godot
https://foxssake.github.io/netfox/
MIT License
396 stars 16 forks source link

No physics applied to RigidBody3D when TickInterpolator also active #265

Open krazyjakee opened 2 months ago

krazyjakee commented 2 months ago

new-game-project.zip

Netfox: 1.8.0 Godot 4.3

albertok commented 2 months ago

RidigBodies don't directly support being moved the same way most other nodes do. They're expected to be driven almost exclusively by the physics engine.

TickInterpolator which is interpolating position between ticks is probably not the right tool to use.

The closest we can probably get is to sync physic states from the authoritative server and copy and apply them to the clients

This probably needs it's own dedicated Node. RigidBodySynchronizer?

It won't be able to participate in rollback either since there's no current way to run more than a single physics simulation per frame in Godot.

albertok commented 2 months ago

There's an implementation here of what a physics synchronizer can look like:

Copying the code to here as things tend to disappear on the Internet over time.

extends MultiplayerSynchronizer
class_name PhysicsSynchronizer
@export var sync_bstate_array : Array = \
    [0, Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO]

@onready var sync_object : RigidBody3D = get_node(root_path)
@onready var body_state : PhysicsDirectBodyState3D = \
    PhysicsServer3D.body_get_direct_state( sync_object.get_rid() )

var frame : int = 0
var last_frame : int = 0

enum { 
    FRAME,
    ORIGIN,
    QUAT, # the quaternion is used for an optimized rotation state
    LIN_VEL,
    ANG_VEL,
}

#copy state to array
func get_state( state, array ):
    array[ORIGIN] = state.transform.origin
    array[QUAT] = state.transform.basis.get_rotation_quaternion()
    array[LIN_VEL] = state.linear_velocity
    array[ANG_VEL] = state.angular_velocity

#copy array to state
func set_state( array, state ):
    state.transform.origin = array[ORIGIN]
    state.transform.basis = Basis( array[QUAT] )
    state.linear_velocity = array[LIN_VEL]
    state.angular_velocity = array[ANG_VEL]

func get_physics_body_info():
    # server copy for sync
    get_state( body_state, sync_bstate_array )

func set_physics_body_info():
    # client rpc set from server
    set_state( sync_bstate_array, body_state )

func _physics_process(_delta):
    if is_multiplayer_authority() and sync_object.visible:
        frame += 1
        sync_bstate_array[FRAME] = frame
        get_physics_body_info()

# make sure to wire the "synchronized" signal to this function
func _on_synchronized():
    correct_error()
    # is this necessary?
    if is_previouse_frame():
        return
    set_physics_body_info()

#  very basic network jitter reduction
func correct_error():
    var diff :Vector3= body_state.transform.origin - sync_bstate_array[ORIGIN]
#   print(name,": diff origin ", diff.length())
    # correct minor error, but snap to incoming state if too far from reality
    if diff.length() < 3.0:
        sync_bstate_array[ORIGIN] = body_state.transform.origin.lerp(sync_bstate_array[ORIGIN],0.05)

func is_previouse_frame() -> bool:
    if sync_bstate_array[FRAME] <= last_frame:
        return true
    else:
        last_frame = sync_bstate_array[FRAME]
        return false
albertok commented 2 months ago

Actually looking at the correct_error section in the code above maybe TickInterpolator can work if its set to transform.origin

A few comments in other places mention its a hack that lets you mess with the position of a RigidBody

homhomhomhomhom commented 2 months ago

Actually looking at the correct_error section in the code above maybe TickInterpolator can work if its set to transform.origin

A few comments in other places mention its a hack that lets you mess with the position of a RigidBody

what would be the property path of transform.origin, because my netfox gives warnings that it's an invalid path

also if I put that code above on a player rigidbody it desyncs even on localhost and honestly I'm kinda at a loss as to why

krazyjakee commented 2 months ago

@homhomhomhomhom

netfox gives warnings that it's an invalid path

Are you typing it like :transform.origin? It works for me.

@albertok

maybe TickInterpolator can work if its set to transform.origin

This technically fixes the issue and while there's a definite improvement over no interpolation, it is still quite jumpy and unstable.

https://github.com/user-attachments/assets/0a04cdb2-2d69-4ddf-9a42-daeb79aa3e76

There's an implementation here

Using this implementation, the physics are silky smooth on the client.

https://github.com/user-attachments/assets/6f2c8f1b-6cd3-4f0a-b30a-f05e77acb9e7

Adding this _ready function to the code also removes any need for configuration...

func _ready():
    connect("synchronized", _on_synchronized)
    replication_config.add_property("%s:sync_bstate_array" % self.get_path())