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

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

Open
vassembly opened this issue Jul 14, 2023 · 21 comments

Comments

@vassembly
Copy link

vassembly commented Jul 14, 2023

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.

@Calinou Calinou changed the title Property prediction and interpolation, and a way to capture MultiplayerSynchronization events Add property prediction and interpolation, and a way to capture MultiplayerSynchronization events Jul 14, 2023
@vassembly
Copy link
Author

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

@Faless
Copy link

Faless commented Jul 29, 2023

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

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

vassembly commented Jul 30, 2023

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

This comment was marked as outdated.

@nickpolet
Copy link

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

Calinou commented Sep 7, 2023

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

@Calinou Calinou reopened this Sep 7, 2023
@vassembly
Copy link
Author

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

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

@cidwel
Copy link

cidwel commented Sep 19, 2023

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

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

Calinou commented Dec 22, 2023

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

3da commented Mar 27, 2024

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

ywmaa commented Mar 27, 2024

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

3da commented Mar 27, 2024

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

Calinou commented Mar 27, 2024

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

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

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

This comment was marked as off-topic.

@AThousandShips
Copy link
Member

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

@majikayogames
Copy link

majikayogames commented May 3, 2024

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:

  • If you want to do something like connect to MultiplayerSynchronizer's synchronized and delta_synchronized signals to do so, this is not really possible. There is no way of knowing which properties are updated. You can try to detect which properties are updated manually by keeping a running list of target properties, and current interpolated property value. And roll back to the interpolated value, updating the target value after every sync. But there is an edge case here, what if a property is updated to the current 'interpolated' value and you get a false positive, no change is detected, and it would continue interpolating to the stale value. Might work but hacky and not really reliable.
  • If you don't use MultiplayerSynchronizer directly, and try to do a buffer class. Like a small node which has its own properties synchronized and copies them over to RigidBody3D, it is very hard to get this to work and also synchronize the properties on spawn. I tried a bunch of combinations. It might be possible, but I would have to read the Godot source of these things to figure out the exact order to call things in so the properties get synchronized on spawn. (I tried setters/getters to map the component properties to the RigidBody3D properties. No idea when/how MultiplayerSpawner decides to send over the properties who have replicate on spawn checked. It might also be because I can't access .position when it's not in tree yet.)

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.

@Rybiscom
Copy link

Rybiscom commented Oct 17, 2024

Hey guys 👋
I'm writing my multiplayer game on Godot.
And since I haven't found a suitable solution for interpolating motion on the client side, I wrote my own script.
This script interpolates the position, rotation and scale of an object.

How to use it?

  1. Add a new Node3D node to your character instead of the MultiplayerSynchronizer node.
  2. Add my script to this node.

Since the script does not synchronize the spawning of the object, you need to add a MultiplayerSpawner node to the main scene and configure it to spawn your character.

Script variables:
TrackThisObject - Add the main node of the player or character whose parameters will be synchronized
SyncPosition, SyncRotation, SyncScale - Which parameters should be synchronized.
InterpolationOffsetMin, InterpolationOffsetMax - Since the script automatically adjusts the delay required for interpolation, these values allow you to set the maximum and minimum delay (you can leave them at default 1-500).

As I have already written the script automatically adjusts the delay on each object depending on the speed of your server and the latency of the Internet connection with the player.
This is necessary for smooth interpolation
Also the data is sent only if it has been changed, if the object does not move - the data is not updated.

The script is not perfect, for example in the current version the script tracks any change (position, rotation, scale) and at any change of even 1 parameter it synchronizes all other parameters too
If you want, you can finalize it yourself
But if you need a quick solution, I think this script will be perfect for you.

(Also added the c# version of the code)

Script

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

No branches or pull requests