Open lokimckay opened 3 years ago
Duplicate of https://github.com/godotengine/godot-proposals/issues/236 and https://github.com/godotengine/godot-proposals/issues/740.
From what I've seen, it's already possible to implement client-side prediction without manual physics stepping (even though it may be less optimal). cc @jordo
@Calinou
From what I've seen, it's already possible to implement client-side prediction without manual physics stepping
How does one achieve this? Does this require implementing your own physics / movement system?
How does one achieve this? Does this require implementing your own physics / movement system?
I don't know exactly and am not sure if there are any example projects that do this, but I've definitely heard of people doing it.
If all else fails, using client-side physics with server-side checks can be a viable alternative in some cases (on top of using less resources on the server).
@Calinou I know the Godot core engineering team has a lot going on but it would help the community if you share examples of how things can be done beyond just ‘it can be done’. We’ll learn from it and waste your time less.
@Calinou I know the Godot core engineering team has a lot going on but it would help the community if you share examples of how things can be done beyond just ‘it can be done’. We’ll learn from it and waste your time less.
I really have no idea how it can be done, but I can attest that it has been done in the past with varying degrees of success. I don't have links to any examples around, simply because people working on multiplayer games generally don't have time to publish reusable example code.
I bashed my head against my project a bit more and came up with a solution that works in my case
I had incorrectly assumed that multiple calls to move_and_slide
in a single frame was not possible because I was getting inconsistencies between my client position and corrected position - hence why I raised this PR
The actual issue for me was that move_and_slide
automatically ingests delta
(see here). In my case my re-simulated move_and_slide
calls were using the _process
delta, but my current client move_and_slide
calls were ingesting the _physics_process
delta - causing an inconsistency in the 2 positions
I fixed this and matched the two back up by removing the _process delta and applying the _physics delta like so:
move_and_slide(directionVector / get_process_delta_time() * get_physics_process_delta_time())
Depending on the physics API you use, in some cases it's not possible to implement this (in godot physics) without additional support directly in the physics server. If using godot physics, (any godot physics engine), in order to reset to a past known state and fast-forward simulate (running multiple steps in quick succession over the course of a single engine iteration) to a previous forward-predicted time frame, you just need to ability to step a space manually (assuming you've built your own system to replay inputs per step).
This has been discussed previously in a few other issues, but I believe it's on the radar to add the functionality to be able to step a space manually to the physics server api.
In our private fork, we started by simply adding a few additional functions to the physics server that allowed one to explicitly manually step a physics space provided the space's RID and delta time (pretty easy addition to the physics server API). But we've since scraped that, and moved to using a completely different physics engine entirely (box2d), which we've exposed support for manual stepping of the box2d world (physics space), and just control the rewind/reset -> fast-forward simulation directly.
See auto-step checkbox below:
@jordo I’m not that deep into customization yet, how do you add a custom physics engine to Godot? Feel free to post on Discord and Reddit too. It will make killer content
I've also been working on implementing rollback network synchronization, and I'm not sure it's currently possible while using Godot's built-in physics system.
The physics engine appears to not process collisions (ie. a KinematicBody2D
entering an Area2D
and the body_entered
signal getting emitted) until sometime after _physics_process()
(but before the next _physics_process()
). So, if you rollback game state a few frames, and then attempt to manually iterate through those frames again all at once, the collisions won't be processed on each replayed frame - only after all the frames have replayed. That would mean that those frame will happen differently when replayed, than if they had happened without the rollback, breaking the whole model necessary for rollback network synchronization.
I'm going to keep experimenting with this over the next couple week, and if I come up with a way to do this, I'll report back.
I’m not that deep into customization yet, how do you add a custom physics engine to Godot? Feel free to post on Discord and Reddit too. It will make killer content
There's a Box2D C++ module for Godot that jordo likely uses. 🙂
Looks like the implementation is not tied to Godot physics server, but I think it would also be possible to integrate Box2D as a physics 2D backend in Godot (likely with limitations, because Godot's physics server API does not provide the many features Box2D provides).
I've been working on implementing rollback network synchronization for the past few weeks, and I can now say definitively, that it's not possible to do with Godot's physics engine, without exposing a method like Physics.simulate()
that will manually tick the physics simulation forward.
Purely for demonstration purposes, here's a PR that adds a Physics.simulate()
method that works in my project:
https://github.com/godotengine/godot/pull/49976
I'm sure that the implementation is terrible, and I don't ever expect it to be committed. :-) My goal is to argue for this proposal, that we add something like this to Godot's physics API.
For debugging, I've setup my project to rollback 5 frames every frame, just to see what breaks when rolling back. Here's what happens without Physics.simulate()
:
You can see the explosion appears where the bullet was 5 frames ago. This is because the bullet was overlapping the tree according to Godot's physics engine, then we rolled back 5 frames, except Godot's physics engine still considered the bullet as colliding because we were unable to step it forward after changing it's position.
Here's what happens when calling Physics.simulate()
after rolling back, and after re-running each of the subsequent frames:
You can see the explosion appears right on the tree where you'd expect it!
So, in Godot 3.x, the only way to implement rollback is by either:
Physics.simulate()
like I did in my PR, orNow, in Godot 3.x, option nr 2 is the only realistic choice, because this network synchronization technique depends on the physics engine being deterministic, and Godot's physics engine isn't.
However, my understanding is that in Godot 4.x, the plan is that we'll be able to create new physics engines that can be swapped in instead of Godot's default. In that case, someone could implement a deterministic physics engine, and then all we need is this additional bit of API added.
So, I'd like to propose that we re-open this proposal, so that we can discuss the possibility of adding this API to Godot 4.x.
@pouleyKetchoupp What are your thoughts about adding something like this to the Physics API for Godot 4.x?
@pouleyKetchoupp What are your thoughts about adding something like this to the Physics API for Godot 4.x?
Some time ago I asked Camille about the implementation of https://github.com/godotengine/godot-proposals/issues/1373 that aims at the same result, something similar to Physics.Simulate() by exposing some internal variables and methods, I faced the same need to manually process physics steps, in my case this could be useful to bake / predict trajectories for rigid body motion on a parallel physics layer, the answer was that it is on his todo list, likely for Godot 4.1 since there are bugs on both 2d and 3d to be squashed first.
Yes, I can confirm this feature is on the radar of the core devs, and the general idea is already approved. It will just need further discussion to decide how to implement it in a way that makes sense for all the different use cases, so 4.1 seems like a good estimate for now.
In case this is interesting/useful for other folks who find their way here: for my game, I ended up implementing a custom deterministic 2D physics engine as a Godot module.
It's Open Source (MIT license) and you can find the project page here (with some pre-built binaries for Windows, Linux, MacOS and HTML5):
https://gitlab.com/snopek-games/sg-physics-2d/
... and here's a tutorial for getting started with it that I just published today:
I'm building a networked game. Currently I'm wrapping Bepu Physics in C# so that I can step the physics manually (and also so I can create multiple physics worlds), but I'd love to remove the requirement for Bepu and make my networking solution more widely available and applicable.
Regardless of whether you're using a deterministic physics engine, if the client predicts physics (which is true in quite a lot of games, even when the player is a non-rigidbody character controller), it's necessary to be able to step the physics engine manually to handle reconciliation.
Just thought I'd mention that since @dsnopek seemed to imply that predicting physics is only useful when you use a deterministic physics engine (which would, in turn, suggest that being able to step the physics engine manually is only useful if it's also deterministic, which isn't true). As long as you're sending state+input instead of only input and the physics engine comes up with similar results across machines most of the time (which basically all physics engines do), you can call it "close enough", and only make rollback-corrections when the divergence gets too high (or you can accumulate divergence to trigger rollbacks after some time even when divergence isn't continuously increasing).
So this was targeted at 4.1 which is now officially released...does it need to be retargeted then to 4.2 or something?
Still very interested in this to be able to lag compensation!
So this was targeted at 4.1 which is now officially released...does it need to be retargeted then to 4.2 or something?
The milestone was set a long time ago before the new release policy, so it's not relevant anymore. I've moved it to 4.x
as we can't guarantee this will be implemented in 4.2.
FYI It looks like someone implemented a version of this, available in a PR https://github.com/godotengine/godot/pull/76462
Also a similar request made in godot-jolt: https://github.com/godot-jolt/godot-jolt/discussions/623
This has been requested so much for the past 3 years or so. Is there any central way to track it? It makes no sense to not support something when so many hacks have to be discussed.
Is there any central way to track it?
This proposal is the central way to track it :slightly_smiling_face:
Individual PRs are where the implementation is discussed, but the feature itself must be discussed in the proposal.
I want to reference this comment since the original author didn't repost it in this thread.
move_and_slide
should definitely allow passing delta
instead of assuming it from context. It is not uncommon for networked games that use rollback to adjust the physics FPS at runtime on the client when the server is starved for inputs due to packet loss or other bad network conditions. The idea is that the client simulation runs a bit faster so that the server gets to refill the input buffer. The thing is, in order to achieve that, the delta used for calculations must remain constant (as if the FPS hadn't changed). Currently, this technique makes move_and_slide
impossible to use without getting constant desyncing because changing the physics FPS means implicitly changing the delta used for move_and_slide
, which is specifically what we want to avoid here.
The method would still work as intended, since people can pass the delta from the context in which they're calling the method, and if that's too big of a change, we can just make the parameter optional and retain the original behavior if it is not passed
PS: I can personally submit a PR that adds the parameter for both the 2D and 3D versions of the method, if there's interest for it
This might be a bit out of scope, but I guess that's up to everyone here to decide:
Would it be sensible to also expose a way to request force_update_transform
on a list of nodes all at once? Sort of what flush_transform_notifications
does internally but scoped to a list of nodes. I guess you can always iterate on the nodes yourself and call force_update_transform
on each of them, but I assume performance becomes an issue with large amounts of nodes, so native support would be a boon in that case.
My reasoning here is that for a "full" manual simulation of a physics subloop, you would have to do all of these (at least for the nodes you're interested in) in order to replicate the engine's behavior:
PhysicsServer*::flush_queries
SceneTree::flush_transform_notifications
_physics_process
on the nodesSceneTree::flush_transform_notifications
PhysicsServer*::step
: I purposely left out scene tree timers and tweens to simplify the discussion, although maybe a way of manually stepping them should also be included for full control. Then again, that makes me think that maybe this indeed is* out of scope.
On that same vein, should we expose a way of triggering the "internal physics process" callbacks of specific nodes? Otherwise, we wouldn't be able to manually step timer nodes and other such things. Currently, it can be done via notifications, but I assume that a fully fledged "manual stepping" feature shouldn't rely on such things, and also, we don't have control over the delta in that case.
At the very least, documentation surrounding manual stepping should ideally expose all these caveats and how to work around them. A breakdown of how the physics subloop works and how to simulate each step in code is fundamental for a feature like this to make sense IMO. I did some legwork in that regard some days ago. It isn't exhaustive but it I think it's a good start. It's focused on the 2D physics server but it should be very similar (if not the same) for the 3D one: Link
There's another issue that I'm not sure is being discussed elsewhere (please correct me if I'm wrong). The physics server has internal state that is used for things like collision signals (it needs to know which shapes are currently inside an area to know whether a shape just entered or left). That state isn't readable or writable in the public API, which means that even if we can step the physics server manually, rollback can't be properly implemented (or at least you can't rely on collision signals if you do).
Take this example with an area A and shape S. The list of contained shapes of area A according to the physics server on every step will be denoted by square brackets ([ ] denotes empty, [ S ] denotes that S is contained)
As you can see, the second time tick 1 is simulated, the signal won't get emitted even though the shape just entered the area in the previous tick.
@monitz87 Ahhhhh this is a really good point. Hmm, so this PR can't be merged yet then. Maybe I'll take a deeper look at the physics engine integration when I get some time.
No idea if the following helps with the multiplayer issues, but it has allowed me to "step ahead" one physics object in Godot 4.2.
I landed here as I wanted to create a "trajectory indicator" for my Peggle-alike game. The most accurate way to do this is to simply create an invisible duplicate of the ball and step it forward, recording each step, which can then be displayed as a dotted line. Simply running the dummy at normal cycle speed works but is way too slow, my solution is to use a combination of the custom forces integrator and "move_and_collide".
In the _integrate_forces code below I use the same "_apply_constant_forces" function for both the real and dummy balls (to ensure consistency, and because I have yet to add angular force). If the ball is the dummy then I use "move_and_collide" in a loop until a collision occurs. At the point of collision I COULD try and duplicate the bounce as calculated by the physics engine, but the godot source code for that is rather complicated and I'd also have to try and keep up with any changes to the engine. Instead I move the dummy ball to the latest position (otherwise the engine will reset it as it thinks no cycles have occurred) and let the system cycle, so the system deals with the collision for me.
Of course the downside of this is that it can take multiple frames to complete the step ahead if collisions occur, for my game this is not an issue (and actually looks quite good as the trajectory indicator grows in steps).
Hopefully some of the other people here will find this useful:
func _integrate_forces(state : PhysicsDirectBodyState2D) -> void:
# is the ball on the screen?
if not Rect2(-get_canvas_transform().origin, get_viewport_rect().size).has_point(position):
self.queue_free()
return
var _apply_constant_forces : Callable = func () -> void:
# This function applies gravity, damping etc to the ball. Essentially a simplified version of what is in https://github.com/godotengine/godot/blob/master/servers/physics_2d/godot_body_2d.cpp
state.linear_velocity *= (1.0 - state.step * state.total_linear_damp) # Damping is applied first in the physics engine
state.linear_velocity += state.total_gravity * state.step # + state.get_constant_force()
# TODO: Apply angular damping and torque
# if this is a trajectory indicator control the motion directly so we can do multiple cycles
if is_trajectory_indicator:
while true: # loop until the ball is removed or the loop is exited manually
_apply_constant_forces.call()
var col : KinematicCollision2D = move_and_collide(state.linear_velocity * state.step, true) # just test for a collision, don't move the ball
# Alter the trajectory only if the ball collides with something
if col:
# instead of manually calculating the bounce (which is complicated), let the physics engine do it by moving the ball to the collision point and then letting the engine cycle
state.transform = Transform2D(rotation, position)
break
else:
move_and_collide(state.linear_velocity * state.step) # actually move the ball
if not Rect2(-get_canvas_transform().origin, get_viewport_rect().size).has_point(position): # check if the ball is still on the screen
self.queue_free()
return
trajectory.append(position) # record the ball's position for the trajectory indicator
return
else:
# if the ball is not a trajectory indicator, apply the forces as normal
_apply_constant_forces.call()
Describe the project you are working on
Networked player movement with client-side prediction
Describe the problem or limitation you are having in your project
Quick summary of client-side prediction:
In Godot currently, there is no way to re-simulate these pending inputs at a single moment in time
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Allowing Godot users to manually execute / simulate physics steps would make the above method of achieveing client-side prediction possible
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
https://docs.unity3d.com/ScriptReference/Physics.Simulate.html
If this enhancement will not be used often, can it be worked around with a few lines of script?
I am not aware of any workarounds that don't involve writing your own custom physics system within Godot
Is there a reason why this should be core and not an add-on in the asset library?
This functionality would give the user control of when Godot's physics executes - IMO it is very closely related to existing core functionality and wouldn't make much sense to segregate as an addon
Related issues:
https://github.com/godotengine/godot/issues/25068 https://github.com/godotengine/godot/issues/24769