Henley Zhang

Technical Details

Table of Contents

Server Authoritative Networking System

The game is networked using a server authoritative system rather than a P2P client-based system. The advantages are that it is much more secure since the client has no authority to change what happens on the server; it only sends requests that the server acts on, preventing many abuses caused by a modified client. However, this style of networking presents many challenges. The client side has to be programmed to preserve responsiveness in movement and shooting, meaning that actions initiated by the player are split into a local instance and a server instance. The server also has to account for lag compensation; otherwise, the validation of a line trace (a fired bullet) has to account for the passage of time between information being sent from the client to the server.

diagram

Custom Lag Compensation Algorithm

Everything seemed alright when I booted up the server on my LAN. The game felt fluid and responsive. However, as soon as I tried to connect to a server my friend hosted 1000 miles away, the networking completely fell apart. None of the bullets I fired hit the target; the bullets that seemed to miss resulted in a kill. I did a lot of research into the problem and discovered the issue of lag compensation. This video explains this topic way better than I could.

However, the problem was that I was programming in Unreal Engine, and such a framework did not exist (actually, it does, but for $350!). I basically gave up on the problem until I came across this research paper here detailing a method for developing a lag compensation algorithm along with testing methodology. I soon ended up with this system.


  1. The server saves a bone map and the location of every single player every tick. The bone map is a hashtable that maps bone names to their local transforms. This location object also maps in a hash table that maps players to their locations. A final double-ended queue maps the player hash tables to the exact server time.
  2. Each client is constantly pinging the server for the ping. (ex 20ms) This ping is updated and saved on the server.
  3. When a client fires a lag-compensated shot, it sends the details of the line trace to the server.
  4. The server uses the saved client's ping along with the current server time to determine exactly what time the client fired the shot on their local instance, which lags behind the server instance in terms of updated information.
  5. This exact time is searched for in the double-ended queue. The time is between two ticks of the server, so the server fetches those two time hashmaps.
  6. The two times hash tables are used to find the positions and transforms of the bones of the player at that time. A final calculated bone transform is determined by linearly interpolating between the two transforms.
  7. The bone transforms are used to reconstruct a hitbox object, which lives on the server.
  8. The line trace is performed in the world and checks for hits against the hitbox object. If the hit connects, the damage is applied.

Gun Spray Algorithm

I designed this algorithm through a lot of playtesting and trial and error. Heres how it works.

  1. There is an internal heat variable, which is stored on the client and the server as two different versions. The client variables are separated from the server to maintain the spray even in poor network conditions.
  2. When a weapon is fired, the heat variable is increased by a certain amount depending on the characteristics of the weapon. For example, SMGs have a higher heat growth multiplier compared to silenced weapons.
  3. There are three components to recoil: screenshake, crosshair movement, and crosshair deviation.
  4. Screenshake is simply implemented by calling a UE4 screenshake locally, and the shake level depends on the force of the gun.
  5. Crosshair movement is implemented by lerping the crosshair to a given amount. This amount is determined by the heat variable as well as the client FPS, so the crosshair movement is consistent across different performing machines.
  6. Crosshair deviation is implemented by adjusting the hitscan vector. This amount is also determined by the heat variable. It causes the bullets to fly above the crosshair in the crux of the spray. A small amount of RNG is applied to crosshair deviation, which is tied to the heat variable.
  7. When the heat variable exceeds a certain amount (Around 30), it cycles back to an intermediate amount, so that the side-to-side motion happens seamlessly.
  8. Any amount of crosshair movement recoil is added to a local variable. When the player stops shooting, the amount of this local variable is used to return the crosshair to the original location. This also takes FPS into account.
  9. The whole time, the heat variable decays, depending on the characteristics of the weapon.

All this complexity allows the spray pattern to follow a consistent characteristic T-shaped pattern found in Valorant and CSGO. The numerous ways spray can be adjusted allow for easy modifications to weapons.

Hitscan Bullet Penetration Algorithm

bullet penetration diagram
  1. The initial line trace is performed (red). When the line trace hits an object, the penetration function is called.
  2. The penetration function performs a depth check line trace (blue). The length of this line trace is determined by the penetration value of the shot.
  3. If the depth check line trace hits an object, it means that the bullet is capable of penetrating to the other side of the object.
  4. The penetration function then performs a final line trace (blue) from the hit location of the depth check line trace to the original line trace hit location.
  5. This final line trace's penetration and damage are reduced based on the length of the depth check line trace, which indicates how much of the bullet's power is reduced.
  6. This line trace does the usual lag-compensated check for a player hit. It also recursively calls the penetration function when this line trace hits an object.
  7. The recursion stops when the penetration value is insufficient to penetrate the following object. There are recursion limits in place to stop infinite loops.