3
\$\begingroup\$

I have been reading the Unity Netcode manual and other related articles, which explains very clearly why it is essential to care about latency. Still, I haven't seen good examples of implementing a solution. Below is a quick video of a bare-bones project simply reproducing the issue. I am using a local host/client model, with a simulated 70ms of delay, and you can see on the client side the bullets always spawn outside the player, which I understand why, but how do I go about fixing it. I just can't find good examples dealing with the issue.

Video here: https://youtu.be/ZuX2f2s3w3o

Screenshot: enter image description here

Here is the code that spawns the bullets:

using Unity.Netcode;
using UnityEngine;

public class PlayerAttacks : NetworkBehaviour
{
    public GameObject projectile;
    public float FireRate;

    float _nextShot;
   
    void Update()
    {
        if (!IsOwner) return;

        if (Input.GetKey(KeyCode.Space))
        {
            if (_nextShot <= Time.time)
            {
                if (IsServer)
                {
                    SpawnProjectile();
                }
                else
                {
                    SpawnServerRpc();
                }

                _nextShot = 1 / FireRate + Time.time;
            }

        } 
    }

    [ServerRpc]
    void SpawnServerRpc()
    {
        SpawnProjectile();
    }

    void SpawnProjectile()
    {
        var p = Instantiate(projectile, transform.position, transform.rotation);
        p.GetComponent<NetworkObject>().Spawn();
        Destroy(p, 4f);
    }
   
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I found a solution that works for me. This is a client-side solution. In the script above I was making an RPC call and waiting for the server to respond with the object instance and location. Fully server managed.

In order to get the visuals and performance I wanted, instead of spawning the projectile on the server, my approach is to instantiate the projectile on all the clients.

So basically, on mouse click, make a RPC call AND create the projectile locally. The RPC call will ask the server to call on all clients to spawn a projectile locally.

  void Update()
{
    if (!IsOwner) return;

    if (Input.GetMouseButton(0) && nextFire <= Time.time)
    {
        nextFire = 1 / fireRate + Time.time;
        FireBulletsServerRpc();
        ExecuteRayCast();

    }
}


[ServerRpc]
public void FireBulletsServerRpc()
{
    FireBulletsClientRpc();
}

[ClientRpc]
public void FireBulletsClientRpc()
{
    if (!IsOwner)
        ExecuteRayCast();
}

void ExecuteRayCast()
{
    Vector3 direction = GetDirection();

    TrailRenderer trail = Instantiate(bulletTrail, LeftGun.transform.position, LeftGun.transform.rotation);
    TrailRenderer trail2 = Instantiate(bulletTrail, RightGun.transform.position, RightGun.transform.rotation);

    StartCoroutine(SpawnTrail(trail));
    StartCoroutine(SpawnTrail(trail2));

    if (Physics.Raycast(LeftGun.transform.position, direction, out RaycastHit hit, float.MaxValue))
    {
        //Now that we hit something calculate the distance
        var distance = Vector3.Distance(transform.position, hit.point);
        print($"Distance: {distance}");
        //Calculate time for the bullet to get there
        var time = distance / (trailSpeed * 2);
        print($"Time to get there: {time}");
        //When time is up, do a physics overlay to see if something still there
        //if so apply damage
        StartCoroutine(CheckForAppliedDamage(time, hit.point));

    }
}

An important detail to keep in mind is in the ClientRpc call. Since the user creating the projectile already created it locally, it should ignore the server call to make it. That's why I checked if I am not the owner of that call. I hope this will help other people out there.

[ClientRpc]
public void FireBulletsClientRpc()
{
    if (!IsOwner)
        ExecuteRayCast();
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .