|HomeGalleryDownloadHow does it work?UtilitiesImprint/Contact/Privacy|
|Bouncy player-player collision over the net - how does it work?
1. In the toetensen-engine, players are "controlled" by their client machines - that means, each client controls the position and speed and so on of "his" player by it's user interface itself, forwards the data (position, pose, rotation) to the server, which interpolates these periodically received info and broadcasts the whole scene in the same manner to the other clients. the actual latency from one peer to the next is networklatency+transmissioninterval+antijitterdelay, actually a huge value for a swift shooter like toetensen, especially in a German dsl p2p game where usually a good upload capacity can't be provided by anyone.
2. In some 3dfps netcode implementations (like in the Darkplaces Engine(?)) clients only send control packets to the server ~ which keys are pressed (running forward, jumping, ...) and the server decides on the actual movements. The clients predict what the server will probably decide for their new positions and smoothly interpolate between new prediction based on the latest position got from server and their old prediction used so far. While this solution makes it very smooth for spectators it's not so smooth for the actual players and also introduces a lot of challenges and is extremely vulnerable to bugs, so I decided to make it better match my coding capabilities by having the players fully controlled by the clients. However if the client detects that it is "lagging" - it should freeze the player in it's current position until the bidirectional packet flow is up again to prevent "I-pulled-out-the-cable-cheating" and stuff like that.
3. By putting the player's position into the hand of the clients, one problem that appears is how to manage the case he collides with other players. I doubt that there is a perfect solution matching every game for this problem anyway, but in the toetensen-approach I tried to achieve bouncy collisions looking "good" both for the colliding players (who see what the server sees but with their own position a bit ahead) as well as for spectators (who see what the server sees because the timeshift isn't visible without a reference - well actually they see what the server sees but a bit more interpolated, but that's insignificant here).
4. Since the toetensen-players have inertia, I decided to have a collision-event generated by the server which notifies the clients affected of the collision and instructs them to change their current velocity by a given impulse.
5. This event has to be fired BEFORE the collision actually happens in the server's view of the situation, because it's effect appears on the server's view only after the event travelled to the clients and back. To get an optimum result, the server should also notify the clients asynchronously (or add a timestamp for a client-side delay) to allow the clients to "execute" it as simultaneously as possible (simultaneously for the server's view) even in case the rtt differs a lot between them. So what actually happens is that the server is looking "who will creash soon, if all players move on like they do currently?", where "soon" depends on the rtt's of the players inspected. If a crash is detected, the server notifies the clients to change their velocities by a certain amount.
6. One thing that still has to be adjusted is the amount of time the server looks into the future when it decides to create crash-events. First of all it has to calculate the full rtt of the player with the worse connection, that is the time it takes a data packet to travel from the server to the client and back plus the time that is lost by the transmission pause between two sequential packets plus jitterbuffer delays etc. as luckily already provided by the toetensen-protocol. Using this value, the resulting crash of two otherwise unaccelerated players looks perfect for the server (ignoring that smooth accel instead of hard-change issue here, see below), but looks a bit crappy for the colliding players themselves, as they will bounce away only after passing through the other party (depends on the actual velocities, if both are moving towards each other with same speed or if one stands still and only the other approaches him). Fortunately this can be optimised a bit by tweaking the amount of time that the server looks ahead according to the gameplay needs, but - what in my opinion is a much bigger problem - On the screen of the player who had a collision, the other party will be affected with a huge delay by that crash. Also will the other party's instant change of speed look like a smooth acceleration, as a result of the inter-packet-interpolation between the peers.
7. To fix this, a "fake-impulse" could be added to the other players moving path by the client who has to draw the scene (by using oldposition+(oldvelocity+fakeimpulse)*time and constrain that with the physical environment), which quickly (<1s) fades out in favour of the actual new path. Instead of exposing the network latencies to the players by first delaying the other parties impulse and than even soft-accelerating it, it should look more like a small glitch then. A deluxe-implementation could crossfade slower but also transmit control commands like in (2.) to allow a better guess for the fakeimpulse-calculated position of others. Another approach could be to transmit "what my position would be if I hadn't received the crash-impulse" but that's really too complicated for toetensen. ;)
8. Depending on the gameplay needs, a displacement & constrainment system can additionally be added to the client side code. Either "don't accelerate into someone else" - or rather "don't accelerate too much into someone else", or if blocking is more important then bouncing & boosting, "don't move into someone else". I don't care about blocking & stable stacking for toetensen, so the choice is "don't accelerate too much into someone else" for best gameplay. Fragile bouncy stacking like in N.Y. Hardcash is still possible this way! Maybe I later add a gentle (=very slow) position displacement to prevent player-parking-inside-each-other-screenshots, dunno.