godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.07k stars 68 forks source link

Add property prediction and interpolation, and a way to capture MultiplayerSynchronization events #7280

Open vassembly opened 11 months ago

vassembly commented 11 months ago

Describe the project you are working on

A Fast-Paced MP FPS.

Describe the problem or limitation you are having in your project

There's no easy way to interpolate multiplayer properties, as a workaround, I make a varible that contains current position and share it via MultiplayerSynchronizer node. The reason why i'm using custom varible for position is that MultiplayerSynchronizer automatically replaces the position value with the one it just got from the server.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

It would be nice to be able to write custom logic for these events. Maybe a way to get synchronized properties in a signal and do whatever I really want with them. Custom client logic seems safe as well, if the server is the main authority, position and such values will be correct despite anything that client does with them.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

image

SceneReplicationConfig could have a property which would disable automatic property updates, and MultiplayerSynchronizer synchronized signal could pass a dictionary synchronized values.

If this enhancement will not be used often, can it be worked around with a few lines of script?

I don't think it's easilly possible to modify signals of already existing classes.

Is there a reason why this should be core and not an add-on in the asset library?

Same as above, I think, writing custom logic for synchronized varibles seems like a pretty good idea, which should be included in the engine itself.

vassembly commented 11 months ago

Having a way to at least capture these events might remove the need to add prediction/interpolation in the engine itself

Faless commented 11 months ago

We have been discussing this feature during a networking meeting, and we agree it would come really handy.

We can't change a signal signature, so we would need a new signal in case.

I don't particularly like having all the variables in a signal as that would negatively affect performance.

We could fire a dedicated signal for properties marked as "manually interpolated", but I'm leaning more towards letting the dev set a Callable, so the engine can check and warn when the callable is not set (we could do that check for signals too, but again, in a less performant way).

So something like:

interpolate

extends MultiplayerSynchornizer

func _ready():
    set_interpolate_method(my_interpolate)

func my_interpolate(dict: Dictionary):
    print(dict)  # Will print '{"position": THE_INCOMING_POSITION}' for the above example
TheYellowArchitect commented 11 months ago

I only want to mention that nearly all modern online realtime games use some form of snapshots and rollbacks, because interpolation of the last 2 snapshots isn't enough. While the above solution of a checkbox for interpolation is good, and better than no interpolation at all, I would suggest expanding it a bit further to have new functions inside MultiplayerSynchronizer where you set if the interpolation is to happen from previous snapshot to latest snapshot, or previous-1 snapshot to latest-1 snapshot, or something along those lines, probably bloated. Though adding an intentional delay on the latest snapshot (basically you want 3 snapshots in total to process at all times) as an option, so you interpolate not the latest snapshot but the 2 before it

Honestly the more I write this, the more I feel my suggestion^ is bloated and should be left to the developer. So if he wants advanced interpolation or snapshots or rollback, he can disable interpolation and synced properties' signals to do his own interpolations among the snapshots (synced properties in this case)

vassembly commented 11 months ago

I only want to mention that nearly all modern online realtime games use some form of snapshots and rollbacks, because interpolation of the last 2 snapshots isn't enough. While the above solution of a checkbox for interpolation is good, and better than no interpolation at all, I would suggest expanding it a bit further to have new functions inside MultiplayerSynchronizer where you set if the interpolation is to happen from previous snapshot to latest snapshot, or previous-1 snapshot to latest-1 snapshot, or something along those lines, probably bloated. Though adding an intentional delay on the latest snapshot (basically you want 3 snapshots in total to process at all times) as an option, so you interpolate not the latest snapshot but the 2 before it

Honestly the more I write this, the more I feel my suggestion^ is bloated and should be left to the developer. So if he wants advanced interpolation or snapshots or rollback, he can disable interpolation and synced properties' signals to do his own interpolations among the snapshots (synced properties in this case)

MultiplayerSynchronizer could have new properties like buffer_size and interpolation_offset for interpolation. But I agree, having a way to get synchronized properties and writing custom logic is more than enough, so built-in interpolation is not necessary.

vassembly commented 10 months ago

oh, closed that accidentally

nickpolet commented 9 months ago

It would be great to see something like this implemented. It's really quite crucial for games that require smooth movement over a network (which seems to be most multiplayer games these days).

The MultiplayerSynchronizer node is amazing at the moment, but something like what is described above would really help take it to the next level.

Would be good to see this proposal opened up again.

Calinou commented 9 months ago

Reopening, as @X64X32 said they accidentally closed the proposal. (PS: You could reopen it yourself in that case, since you were the person who closed it :slightly_smiling_face:)

vassembly commented 9 months ago

Reopening, as @X64X32 said they accidentally closed the proposal. (PS: You could reopen it yourself in that case, since you were the person who closed it :slightly_smiling_face:)

Thank you, couldn't find the button in github mobile

cidwel commented 9 months ago

Probably I'm out of touch here, but I was looking for something specific. Not thinking about lag compensation, interpolation or a rollback system that could make it really useful but more about some basic functionality with the MultiplayerSyncroniser.

I expected the component to have signals where you could hook when those variables are changed. As a basic example I could have some variables like hp, defense, mp, experience or whatever I would like to have synced in an PVP arena and hook signals whenever those variables change. So when the on_change of the variable hp is fired I could see the previous value, the new value and do any custom action intended, like updating the UI:

_on_change_hp(var previous, var new):
   update_hp_hud(new)
   print("HP changed from " + previous)

In other systems I've seen this called SyncVars, and they even have some interesting elements like knowing if the variable is changed or even if a collection has added, set, deleted a new element, or collection is cleared.

I found MultiplayerSyncronizer as the ideal component that would be able to create a library of synced vars, where you could also set the refresh rate per variable individually.

As some workaround for this behaviour maybe creating a dictionary of oldVariables, newVariables, then detect in on_process those variable changes and fire a signal whenever old != new might help there

r4ven1245 commented 6 months ago

Is there any progress on this? Didn't see anything in the documentation, and was wondering if this proposal still being considered, or when is it planned to be implemented?

Calinou commented 6 months ago

The proposal is still being considered as per the above comments, but nobody has started an implementation yet so we can't give an ETA.

3da commented 3 months ago

Godot would be the true God of game engines if it has native support of rollback and input prediction. There is nice addon for this purpose https://godotengine.org/asset-library/asset/2450 Made by @dsnopek Would be nice if someone could do something like that, but more integrated into engine and existing scene replication API. I also found nice project for C# https://github.com/lucasteles/Backdash But don't know yet how to integrate that with Godot. I also know that https://teeworlds.com/ has good rollback\prediction system.

ywmaa commented 3 months ago

Godot would be the true God of game engines if it has native support of rollback and input prediction. There is nice addon for this purpose https://godotengine.org/asset-library/asset/2450 Made by @dsnopek Would be nice if someone could do something like that, but more integrated into engine and existing scene replication API. I also found nice project for C# https://github.com/lucasteles/Backdash But don't know yet how to integrate that with Godot. I also know that https://teeworlds.com/ has good rollback\prediction system.

Ok, I am coming from a background where I worked on and helped in developing a rollback netcode fighter game in Godot 3 with dsnopek's addon.

It isn't only about network replication and rollback, it also needs a predictable and deterministic physics engine for both 2D and 3D because we don't know if the game developer will make their fighter game 2D or 3D so before having a rollback netcode base, we will need to have jolt as the 3D engine, as I heard it is deterministic, and make the 2D engine deterministic too or probably use Box2D as I heard it is deterministic (not sure though).

And also rollback netcode in general isn't for all multiplayer games, some games just find normal lag compensation + Client side prediction enough, so rollback netcode is almost a game specific thing.

I would be happy to have all of these networking solutions built-in in the engine, but I am not sure if this is something that will happen for Godot Engine, since it is always said that it is a General Game Engine, and for a feature to be added, it must be beneficial to a majority of games that will be made by the engine.

3da commented 3 months ago

since it is always said that it is a General Game Engine

Yeah, I agree, that not every game requires these network techniques. Most games even don't have multiplayer. But we have really nice scene replication API. So I believe these guys can do some nice improvements to high-level network API step by step, to provide even better developer experience for network games.

May be after some network\physics core improvements there will appear any epic addon for these purposes. It will be good as well.

Calinou commented 3 months ago

we will need to have jolt as the 3D engine, as I heard it is deterministic

godot-jolt is currently not deterministic: https://github.com/godot-jolt/godot-jolt#what-about-determinism

Even if it was able to be deterministic, this is something you'd have to explicitly opt into, as making guarantees of determinism has several caveats (such as making multithreaded physics simulations impossible by design).

Note that perfect determinism isn't a requirement for client-side prediction: it only has to be deterministic enough so that errors don't accumulate too much over a short period of time (250 ms at most, but usually around 100 ms). godot-jolt should be good enough for this already, and maybe even GodotPhysics (if it exposed what's necessary). You'll need to correct for errors even if the physics engine is 100% deterministic due to latency, packet loss and jitter anyway.

ranger-x-dev commented 3 months ago

So something like:

interpolate

I would love to see this feature implemented. In my server-client implementation, being able to reduce the "server tick rate" by setting the replication interval and delta interval on the MultiplayerSynchronizer node is a great feature to limit bandwidth. Of course that comes with the price of laggy/jerky motion on the client side. Being able to go down and select which properties you want interpolated client-side with a simple check box would be such a convenient solution for that.

vassembly commented 3 months ago

So something like:

interpolate

I would love to see this feature implemented. In my server-client implementation, being able to reduce the "server tick rate" by setting the replication interval and delta interval on the MultiplayerSynchronizer node is a great feature to limit bandwidth. Of course that comes with the price of laggy/jerky motion on the client side. Being able to go down and select which properties you want interpolated client-side with a simple check box would be such a convenient solution for that.

as far as I know, MultiplayerSynchronizer uses delta compression algo, so by implementing interpolation, we will pretty much get a win scenario for multiplayer development.

RexReposit commented 1 month ago

I agree, this function may be extremely necessary.

AThousandShips commented 1 month ago

@RexReposit Please don't bump without contributing significant new information. Use the :+1: reaction button on the first post instead.

majikayogames commented 1 month ago

I believe this is relevant to this discussion:

Took a crack at implementing something like this today. The use case was a creating a single generalized prefab component node I could drop on RigidBody3Ds or other nodes to have their position and rotation synchronized smoothly using interpolation. I've come to the conclusion that this is not possible, or would require some serious hacking to do so. Problems I ran into:

I think I might just give up and hardcode it into the script for every case I need (players, props, cars, planes, projectiles, etc. just repeat the same code everywhere and copy paste it) it in rather than a generalized component I can reuse. If there is a way to do it, I am thinking I might have to write my own synchronization code manually with RPCs, as well as use a MultiplayerSynchronizer for spawn pos/rot synchronization, and split it into two nodes that I put on every RigidBody3D I need synchronized. Edit: Also just tried with a separate MultiplayerSynchronizer/separate replication of properties on spawn only and cannot get that to work either. Looks like it has to be hardcoded for now or a fully custom RPC solution for everything if you want a component based solution like this.

There are many ways this could be implemented, but I think it's clear the current implementation is too restrictive and should leave the developer more options and resources to use in their code. Something as simple as an .update_property_function (like MultiplayerSpawners spawn_function) which can be overridden with a Callable(NodePath, Variant) which allows you to set the property yourself to the new value would open up a lot of possibilities and make this easy to do.