netfox icon indicating copy to clipboard operation
netfox copied to clipboard

No physics applied to RigidBody3D when TickInterpolator also active

Open krazyjakee opened this issue 1 year ago • 5 comments

new-game-project.zip

Netfox: 1.8.0 Godot 4.3

  • Attached is a minimal test project to demonstrate the issue.
  • Floating ball A has a StateSynchronizer and ball B has a StateSynchronizer AND TickInterpolator.
  • Ball A performs as expected but ball B is frozen in it's initial position.

krazyjakee avatar Aug 31 '24 18:08 krazyjakee

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 avatar Sep 02 '24 13:09 albertok

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 avatar Sep 02 '24 13:09 albertok

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

albertok avatar Sep 02 '24 13:09 albertok

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

homhomhomhomhom avatar Sep 02 '24 18:09 homhomhomhomhom

@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())

krazyjakee avatar Sep 02 '24 23:09 krazyjakee