Advertisement

Lag Compensated Projectiles

Started by September 29, 2017 06:12 AM
3 comments, last by Zhav 7 years, 1 month ago

First of all I just wanted to say thank you for taking the time out to read this.

So I'm having a problem as to how I could create Lag Compensated Projectiles. The system I have is setup to send the frame of the server on which we fire, and it can rewind the hitboxes back up to 60 frames (1 second).

I'm not quite sure how exactly I should go about compensating for the lag.

What I have for my projectile script is this:


public float speed = 10;
 public float gravity = -9;
 public Vector3 oldPos;
 public Vector3 newPos;
 
 Vector3 direction;
 Vector3 velocity;
 Vector3 force;
 float distance;
 
 private void FixedUpdate()
    {
        velocity = speed * transform.forward + force;
        force += new Vector3(0, gravity, 0);

        newPos += velocity * Time.deltaTime;

        direction = newPos - oldPos;
        distance = direction.magnitude;

   		RaycastHit hit;
   		if (Physics.Raycast(oldPos, direction, out hit, distance, ~ignoreLayers))
   		{
     		Destroy(gameObject);
   		}
   		else
   		{
     		oldPos = transform.position;
     		transform.position = newPos;
   		}
    }

All it really does is move a transform and cast a ray from the previous position to the current position and adds gravity.

I'm assuming you want to compensate for collisions/hits from when the client fired, and not do client-side prediction.

The first thing you can try is to just "rewind" the projectile by pulling it back by velocity to test for the collision. Since you have a fixed update you should be able to calculate this fairly easily and just move the projectile back to where it was N time ago.

However, I'd probably opt for a better approach. Projectiles are fairly predictable as they typically have a trajectory that can be represented as some mathematical function (linear for most things, like rockets, and some curved towards gravity, for grenades, etc), so you don't necessarily have to simulate them with the usual physics approach (velocity, etc).

Instead you can use the initial velocity and time, and some trajectory function, to calculate the time and position that the projectile would be at when it reaches the end of its trajectory (i.e. when it hit a wall) and then interpolate through it. In your case it's a simple linear trajectory, so this is as simple as a standard linear interpolation:


p0 + (p1 - p0) * t

where p0 is the start position, p1 is the end position, and t is the 0-1 interpolation factor calculated from the start and end times.

If you do it this way then the prediction becomes a simple matter of using a different interpolation value, rather than trying to run a fully predicted physics sim. This means that if you have the time (when the client fired), you can simply run through the interpolation again. Very easy, very fast.

If you want an example of how to do something like this (albeit in C, not C#), check out the source for Quake 3/idTech3 (BG_EvaluateTrajectory function in bg_misc.c is a good place to start).

Advertisement

It sounds like what you want is:

  • When receiving a new projectile from client.
  • Figure out when it was fired.
  • Rewind simulation to that point.
  • Step simulation forward for projectile until the "current frame" in server, while putting all nearby colliders at whatever positions they had at each of those time steps.
  • If the projectile hits something, apply that damage, which means you re-write history for the targeted entity (it will need a correction.)
  • Once you've rolled the projectile forward to current frame, it will run the same as any other entity until it dies.

The main optimization is "which entities need to be put temporarily back in time while the projectile simulates forward?"

You can typically answer this question by keeping a maximum-velocity value per entity, and calculate the envelope of the projectile for the travel time you're rolling forward, and only actually testing the entities whose current position plus radius plus max-velocity-times-rollback-time intersects with the projectile trajectory envelope (bounds.) Or, alternatively, inflate the projectile envelope by the maximum velocity of any target object, and check that against the current bounds of possible targets (this may be cheaper, depending on your implementation specifics.)

 

enum Bool { True, False, FileNotFound };
Spoiler
8 hours ago, Styves said:

I'm assuming you want to compensate for collisions/hits from when the client fired, and not do client-side prediction.

The first thing you can try is to just "rewind" the projectile by pulling it back by velocity to test for the collision. Since you have a fixed update you should be able to calculate this fairly easily and just move the projectile back to where it was N time ago.

However, I'd probably opt for a better approach. Projectiles are fairly predictable as they typically have a trajectory that can be represented as some mathematical function (linear for most things, like rockets, and some curved towards gravity, for grenades, etc), so you don't necessarily have to simulate them with the usual physics approach (velocity, etc).

Instead you can use the initial velocity and time, and some trajectory function, to calculate the time and position that the projectile would be at when it reaches the end of its trajectory (i.e. when it hit a wall) and then interpolate through it. In your case it's a simple linear trajectory, so this is as simple as a standard linear interpolation:




p0 + (p1 - p0) * t

where p0 is the start position, p1 is the end position, and t is the 0-1 interpolation factor calculated from the start and end times.

If you do it this way then the prediction becomes a simple matter of using a different interpolation value, rather than trying to run a fully predicted physics sim. This means that if you have the time (when the client fired), you can simply run through the interpolation again. Very easy, very fast.

If you want an example of how to do something like this (albeit in C, not C#), check out the source for Quake 3/idTech3 (BG_EvaluateTrajectory function in bg_misc.c is a good place to start).

 

Spoiler
4 hours ago, hplus0603 said:

It sounds like what you want is:

  • When receiving a new projectile from client.
  • Figure out when it was fired.
  • Rewind simulation to that point.
  • Step simulation forward for projectile until the "current frame" in server, while putting all nearby colliders at whatever positions they had at each of those time steps.
  • If the projectile hits something, apply that damage, which means you re-write history for the targeted entity (it will need a correction.)
  • Once you've rolled the projectile forward to current frame, it will run the same as any other entity until it dies.

The main optimization is "which entities need to be put temporarily back in time while the projectile simulates forward?"

You can typically answer this question by keeping a maximum-velocity value per entity, and calculate the envelope of the projectile for the travel time you're rolling forward, and only actually testing the entities whose current position plus radius plus max-velocity-times-rollback-time intersects with the projectile trajectory envelope (bounds.) Or, alternatively, inflate the projectile envelope by the maximum velocity of any target object, and check that against the current bounds of possible targets (this may be cheaper, depending on your implementation specifics.)

 

 

Thank the both of you so much. I understand what I need to do, now it's time to implement it. This problem was bugging me for a while, so I really appreciate the well written and easy to understand explanations.

This topic is closed to new replies.

Advertisement