Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No physics applied to RigidBody3D when TickInterpolator also active #265

Open
krazyjakee opened this issue Aug 31, 2024 · 5 comments
Open

Comments

@krazyjakee
Copy link

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.
@albertok
Copy link
Contributor

albertok commented Sep 2, 2024

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
Copy link
Contributor

albertok commented Sep 2, 2024

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
Copy link
Contributor

albertok commented Sep 2, 2024

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
Copy link

homhomhomhomhom commented Sep 2, 2024

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
Copy link
Author

krazyjakee commented Sep 2, 2024

@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.

1.mp4

There's an implementation here

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

2.mp4

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants