#Projectile Latency Compensation

7 messages · Page 1 of 1 (latest)

robust dragon
#

I have a character who shoots an arrow projectile. In order to have it feel more responsive, my strategy is to

  1. Spawn a dummy projectile (basically just a movable model) on the owner client side and store a reference to it.
  2. Have the server spawn a networked projectile, with Network Transform.
  3. Position the server-side networked projectile to compensate for latency. The goal is to position the server-side projectile to be in the same position it is on the owner client, so that future ticks should keep the projectile trajectories in sync.
  4. When the networked projectile spawns on a client, destroy any dummy projectiles. If the server-side projectile was properly synced, the objects should have been overlapping and there shouldn't be a noticeable effect.

Is my strategy right? If so, I'm having trouble with step 3, in that I'm not sure what time deltas to be using to calculate the latency compensation.

For steps 1 and 2, I have something like this on the AbilityUser class:

public class AbilityUser : NetworkBehaviour {
  private NetworkedProjectile m_ProjectilePrefab;
  private List<Projectile> m_DummyProjectiles = new List<Projectile>();

  // called on TimeManager.OnTick
  private void HandleTimeManagerTick() {
    if (IsOwner) {
      BuildAbilityCastData(out AbilityCastData castData); // based on a queued ability input from player
      CastAbility(castData, false);
    }
    
    if (IsServer) {
      CastAbility(default, true);
    }
  }

  private void CastAbility(AbilityCastData castData, bool asServer, bool isReplaying = false) {
    if (!castData.HasQueuedAbility) {
      return;
    }

    if (isReplaying) {
      return;
    }
    
    if (asServer) {
      NetworkedProjectile networkedInstance = Instantiate(m_ProjectilePrefab); // networked projectile also has a projectile component
      Spawn(networkedInstance.gameObject);
      return;
    }

    Projectile dummyProjectile = Instantiate(m_ProjectilePrefab.DummyProjectilePrefab); // projectile is a simple motor component
    m_DummyProjectiles.Add(dummyProjectile); // store these so we can destroy them later
  }

My non-networked projectile component provides a simple motor:

public class Projectile : MonoBehaviour {
  private Vector3 m_InitialPosition;
  private float m_TimeElapsedSeconds;

  public void Launch(Vector3 targetPosition, float travelTimeSeconds) {
    m_InitialPosition = transform.position;
    StartCoroutine(LaunchCo(targetPosition, travelTimeSeconds));
  }

  private IEnumerator LaunchCo(Vector3 targetPosition, float travelTimeSeconds) {
    while (m_TimeElapsedSeconds < travelTimeSeconds) {
      float t = m_TimeElapsedSeconds / travelTimeSeconds;
      transform.position = Vector3.Lerp(m_InitialPosition, targetPosition, t);
      
      m_TimeElapsedSeconds += Time.deltaTime; // this feels like it might not be the right time tick
      yield return null;
    }
  }

  public void AdjustTime(float amt) {
    m_TimeElapsedSeconds += amt; // hopefully this causes the motor coroutine to start at the right position
  }
}

So finally, my attempt at latency compensation:

[RequireComponent(typeof(Projectile))]
public class NetworkedProjectile : NetworkBehaviour {
  public Projectile m_DummyProjectilePrefab;
  
  private Projectile m_Projectile;

  private void Awake() {
    m_Projectile = GetComponent<Projectile>();
  }

  public override void OnStartClient() {
    // send a message to the ability user to destroy dummy projectiles, this works fine
  }

  public override void OnStartServer() {
    // there was some latency between when the client-side player issued the cast command and this gameobject started on the server
    // if we adjust the position on the server, NetworkTransform will sync the client versions, and it should hopefully overlap with the dummy
    // projectile spawned on the owner client
    m_Projectile.AdjustTime((float)TimeManager.TickDelta); // this doesn't feel quite right?
  }
}
fresh niche
#

Seem complicated compared to synchronizing offline projectiles. Otherwise you can do that.
The synchronizing non-networked projectile video is in FGG's mirror playlist for supporters

robust dragon
#

Ah I can check that out

#

Still would like help understanding the right timing vars to use

fresh niche
#

Not much to it other than replace the projectile in OnStartClient

robust dragon
#

after checking out the non-networked projectile video, it looks similar to what i'm trying to do here. it seems like the key is to figure out the latency between when the client sends the command and when the server gets it. in that case, it sounds like the server has to trust the client to put the proper timestamp?

#

in my example, i'm using a [Replicate] function. is there a way to tell the latency in that replication?