godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.11k stars 69 forks source link

Add ability to simulate physics manually/multiple times at once #2821

Open lokimckay opened 3 years ago

lokimckay commented 3 years ago

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:

  1. Client moves player and stores inputs in an array of pending (un-acknowledged by server) inputs
  2. Simultaneously, client sends those inputs to server
  3. Server uses client inputs to run it's own simulation
  4. Server sends resulting position of player back to the client
  5. Client receives the authoratative position of the player at a time which is now in the past
  6. To calculate where the client should be in the current moment, the client needs to roll back to the moment of the server's position packet, and then re-simulate all pending / un-acknowledged inputs since that time

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

Calinou commented 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

lokimckay commented 3 years ago

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

Calinou commented 3 years ago

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

codecomedytv commented 3 years ago

@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 commented 3 years ago

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

lokimckay commented 3 years ago

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())
jordo commented 3 years ago

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:

Screen Shot 2021-06-03 at 1 22 48 PM
codecomedytv commented 3 years ago

@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

dsnopek commented 3 years ago

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.

Xrayez commented 3 years ago

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

dsnopek commented 3 years ago

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

rollback-broken

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:

rollback-working

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:

  1. Patching Godot to add a Physics.simulate() like I did in my PR, or
  2. Using a different physics engine, like @jordo is doing with Box2D

Now, 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.

dsnopek commented 3 years ago

@pouleyKetchoupp What are your thoughts about adding something like this to the Physics API for Godot 4.x?

JakSparro98 commented 3 years ago

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

pouleyKetchoupp commented 3 years ago

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.

dsnopek commented 2 years ago

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:

https://www.snopekgames.com/tutorial/2021/getting-started-sg-physics-2d-and-deterministic-physics-godot

Clonkex commented 1 year ago

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

singerbj commented 1 year ago

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!

Calinou commented 1 year ago

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.

warent commented 11 months ago

FYI It looks like someone implemented a version of this, available in a PR https://github.com/godotengine/godot/pull/76462

warent commented 11 months ago

Also a similar request made in godot-jolt: https://github.com/godot-jolt/godot-jolt/discussions/623

igamigo commented 10 months ago

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.

Calinou commented 10 months ago

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.

monitz87 commented 10 months ago

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

monitz87 commented 8 months ago

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:

: 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

monitz87 commented 7 months ago

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.

Clonkex commented 7 months ago

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

mcardellg commented 6 months ago

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