Client predicted projectile @ 300 ms of latency, followed by the server side shot (white is client prediction, black is server-confirmed reality, though seeing it is subject to the latency as well).
Out of the 7 projectiles, the first 4 have been created on the server as far as this client knows. The 4th projectile has a line demonstrating its relationship with its counterpart. The last 3 white dots are projectiles that have also been fired on the client but due to simulated lag have yet to be created on the server and confirmed again on the client (which is the point at which the black dots appear).
Getting these things really deterministic (and therefore consistent) with date/time related math seemed inherently flawed, so I’ve favored a fixed timestep + deterministic simulation approach instead.
Example/process:
When the client is determining if it can fire a projectile, it does so by comparing the weapon cooldown with an accumulator variable. e.g. is our accumulator greater or equal to our cooldown of 0.500 seconds? Each frame it adds 1/60 of a second to the accumulator.
The server also uses the same logic to determine if it should created a projectile -- at a rate of 1/60 being accumulated for each frame it receives from the client.
The result being that the number of frames experienced by the client is the number of frames processed on the server... and the small aggregate of 1/60s being added up will pass 0.500 on the identical frame for both computers (deterministic).
The server can also decide if the frames it receives from the client are valid... did they contain actions that are feasible for that player? Are they coming in at a believable framerate?
From there each computer (client and server) can simulate their own highly-similar result for the projectile. The server might determine that the projectile hit another player, and deduct hitpoints. Meanwhile the client, which for cheating reasons does not get to say anything about anyone’s hitpoints, might instead determine that the projectile hit another player and draw a purely aesthetic blood effect.
After some polish and lag compensation, I’ll publish a nengi-demo of a client+server combo that can predict and compensate high speed shots at a variety of different latencies while maintaining very reactive controls (16 ms input delay). An early prototype of this demo already exists for hitscan weapons, but has some bugs with its timers (Date.now() math...).
Some visual tools for continuous collision detection, broadphase sweeps, and a raycaster. In this case the green rectangles are showing areas where a spatial structure was accessed in the recent past.
There’s something naturally unintuitive about a game which takes into account both the immutable past (for lag compensation) and the hypothetical future (for clientside prediction). Each tick of the engine occurs in the present... but the state of the present depends on a past of the server as well as the newly discovered pasts of players (things players allegedly did a while ago, but the server hears about just now). The state of the present for the client depends on the past of the server, plus a hypothetical future that the client simulates just ahead of confirmation from the server. So nothing is simple, nor particularly relate-able to the physical world (some overlap w/ astronomy and looking at distant objects, maybe).
While I’ve been tinkering with these things for quite a long time, I’m near the point where prediction and compensation become part of nengi’s formal api. The challenge isn't in getting them to work, nengi has thought in these terms since its inception -- the challenge comes in writing the actual game code in a way that these various temporal states become valuable tools and not an overwhelming mess. (We’ll see about that...) After all people are often looking to translate game ideas from singleplayer that exist in a single temporal state to multiplayer and then they want the bells and whistles of CSP and lag comp... which exist by constantly reconciling multiple temporal states.
(lag compensation of shots) The advanced features have come along nicely! Now if only I knew how to *explain* them. All I’ve really managed to do is make them.
I’ll add this to the downloads section after some much needed clean up. Possibly as late as Monday (9/25). There’s currently a prediction-related demo available for download, it only does the first half of what is shown in this video. I will warn that both prediction+compensation are highly variable depending on the type of game.. there’s no automagic solving of the problem by nengi, just tools for being able to add these features. Only network programmers (or heavy readers on the topic) who have already made a small prototype in nengi will be able to make sense of this particular demo. Apologies for the complexity and lack of supplemental material.
Clientside prediction of weapon switching and reloading
I ran into an interesting problem while building my most recent 2D shooter game. This game exists for two purposes: 1) refining nengi.js, my networking library (aiming for over 100 simultaneous players) and 2) capturing my insatiable need for actiony multiplayer gameplay. As such, I need everything to ‘feel’ a certain way, fast and responsive... and I need it all multiplayer.
Enter the classic FPS-style switching between weapons. You’ve got a few ‘slots’ with a weapon in each, and you can switch between them all. The weapons don’t come out instantly (or else you would just mash 1 2 3 4 5 while clicking your mouse as fast as you can and every weapon would fire in a split second). The timings of everything matter. Pull out your <whatever> at the wrong time as someone peeks the corner? Well that 1/4 of a second it takes to switch back is starting to feel real long.
To implement this system, I began by making weapons that had ‘drawTime’ and ‘reloadTime’ referring respectively to how long it takes to pull out the weapon and how long it takes to reload the weapon. Maybe the pistol comes out inhumanely quick in 50 milliseconds, but the sniper rifle takes 800 milliseconds making you wonder why you even bother. I then wrote a fairly robust ‘WeaponController’ that handled switching between weapons and reloading them, basing the timing math off of javascript’s setTimeout and closures that would finish the weapon draw or finish the weapon reload.
As I finished the WeaponController, I wondered to myself if today was the day that I would get away with leaving setTimeout in one of my apps. I mean for once, it wasn’t just a hack.. I really did have code whose job was to wait awhile and then call a function. I even wrote some clever ‘cancelReload’ functionality which used clearTimeout to stop the reload when the player switched weapons or started to reload something else. It was like async programming... in a program with a game loop that had no need for such a thing.. but alas i was hopeful anyways.
Let’s talk about networked games, and prediction, so that the mistake may unfold.
Here’s a naive networked game:
every frame on your client, collect input (keystrokes, commands, etc) and send them to the server
every frame on your server, process input, and send the game state (positions of entities, etc) to the clients
Here’s a networked game with the possibility of prediction:
every frame on your client increment a clientFrameNumber variable, collect input (keystrokes, commands, etc) and send them to the server stamping them with the clientFrameNumber
every frame on your server, process input, and send the game state (positions of entities, etc) to the clients noting the last processed clientFrameNumber
Now when the client receives the game state from the server, it also knows how many frames a head of the server it is. If the server just responded saying the last processed clientFrameNumber is 52, but the client just finished sending 59, then the client knows it has 7 frames worth of inputs sent to the server that are not yet represented in the most recently received game state. Now its time for prediction! Staying very general, prediction code looks like this:
1) set the state of things on the client to the last confirmed server state
2) all at once, apply all the inputs that have yet to be confirmed by the server
Do this every frame of the game, and the end result is the game responds to you instantaneously, as if you’re playing a single player game. Also, if ever there is a disagreement between the server and the client, the client will be corrected (harshly), thus preserving the server’s authority over the game.
Back to my issues with drawing weapons and reloading. Prediction involves applying numerous past frames worth of inputs to your game every new frame. These inputs are being repeated every frame! If you’re 7 frames behind on frame 52, then you have multiple frames of input that need applied right away to catch your client up to what the player has already done. Getting your game into the right state always consists of the same process: setting the state to the last known state from the server, and then advancing that state by 7 frames of yet-to-be-confirmed inputs. And how long do you have to run these 7 frames? No time really. It all has to happen instantly. You have to write the type of game code that can handle being stepped forward 7 frames all at once. That means you gotta have a ‘deltaTime’ on your update functions, and the code must be relatively frame-rate independent. Running someComponent.update(10) ten times in a row and running someComponent.update(100) should produce the same state in someComponent. And that’s prerequisite to a lot of multiplayer functionality (well, if it is going to be nicely networked).
Figuring that out initially might be tricky, but making it actually happen isn’t too bad at all. How does setTimeout(action, 800) convert to something compatible with prediction? Like this:
var timeElapsed = 0 function update(deltaTime) { timeElapsed += deltaTime if (timeElapsed >= 800) { action() } }
This deltaTime idea doesn’t even originate from multiplayer programming... this idea originates from the desire to have games play similarly on different speed CPUs.
Code like this allows you to advance the simulation of the game by any amount of time. Advance it by 500 ms? Well then action won’t have fired yet. Advance it 7 times by 200 ms? Then action is going to fire (on call 4 out of 7). In my case, action() is the code which decides whether that sniper rifle is in your hand, or still in your bag. It decides if your ammo is 0/30, or a freshly reloaded 30/30... and the best part is that combined with prediction it allows most of the game interface respond to you as if your ping is zero.
Inspiration/Idea: Source Multiplayer Networking: Lag Compensation
A top down Action RPG is a bit different than any Source Engine game, and I can't yet program like anyone at valve, but nonetheless I've implemented a version of lag compensation specific to this game that appears to work.
The player can attack an NPC on his/her own screen by moving close and taking a slash. Unfortunately, by the time the player's attack is heard by the server the NPC has moved a bit. On LAN or with < 50 ping this is no problem. However depending on the speed of the NPC and the latency of the player's connection it may appear to the server that the player has missed the NPC despite standing right ontop of it. This problem is worse on slow connections or with fast NPCs.
More Specifics
In the case of my prototype the lag becomes evident when trying to chase an NPC at ~300 ping. Despite sticking right behind the target, the player's character won't take a swing until practically standing on the target. And in my fancy "throttle the updates" cases the perceived position of characters gets very far from their server positions and the player won't attack at all.
Attempted Compensation, w/ pseudocode
(image: purple box collides with the red box denoting valid attack range)
The key to lag compensation is for the server to validate the attack (or other interaction) based on what the player was looking at -- something slightly in the server's past. How far in the past exactly? Well it is at least the length of a ping. In the case of this prototype it is approximately the length of a ping plus however much throttling is occurring. If the ping is 300 ms, and updates are sent lazily every 400 ms (hypothetical bad lag scenario), then after entity interpolation the player is seeing objects at least 700 ms behind their server position. Here's how I make and apply that estimation***
// how far behind the player is in milliseconds var totalDelay = player.ping + player.networkTickLength // convert totalDelay from milliseconds to serverTicks var ticksAgo = floor(totalDelay / serverTickLength) // the remainder of the above division, as // a fraction (e.g. 0.87 ticks) var portion = (totalDelay % serverTickLength) / serverTickLength // calculate position of the NPC in the past, by // interpolating between two past positions // position of the NPC @ ticksAgo - 1 var positionA = gameState.getHistory( currentTick - ticksAgo - 1, npc.id ) // position of the NPC @ ticksAgo var positionB = gameState.getHistory( currentTick - ticksAgo, npc.id ) var xy = lerp(positionA, positionB, portion)
Now xy is a guess of where the NPC was right when the player attacked/interacted/etc. The real version of the code then goes on to assess whether the player's attackHitBox intersects the NPC's collisionHitBox, and you can imagine the rest.
*** take with a mountain of salt, I'm making this up as I go... but so far it does appear to extend the range of latencies with which the game feels playable
Hit LIKE if "lag compensation" is a problem for you!
➤ Reblog with your funny / annoying Call of Duty problem and it could get featured in our next edition.