Bullet spawn (visually) delayed on client

Learning netcode (thanks to @CodeMonkeyYT new netcode tutorial)
and wondering that, are there good ways to avoid this bullet spawn delay?

ServerRPC spawns bullet when client fires, but its quite far from the client player… so it doesn’t feel good.

Initial thought would be to spawn “real” bullet on server, fake “visual” bullet locally (and hide “real” bullet from client), but maybe they are so far apart, that then it collides too far from “visual” local bullet…?

client shooting and moving: (bullet spawns late and player is already far away)
8482499--1127909--spawn_delay.gif

host shooting and moving:
8482499--1127918--spawn-host.gif

*gif is only 7fps, no maybe not so clearly visible, but invaders demo has same issue (moving sideways and shooting forward, delay is too visible…)

The hiding you mentioned is probably the best solution to the problem.

As for hit reg problem you mentioned, you might want to deal damage from client to another client. Of course, this poses problems for an actual released game due to cheating, but at that time, you can either verify the bullet hit the target from the server, or rewrite this completely :slight_smile:

ok, for now i’ll probably just go with client side collision checks (to keep things simple), cheating doesn’t matter in this test project anyways.

checked difference on the fire press spawn position
(when player is moving, and since its moved locally not by server):
Local Fired at: (-6.20, -5.35, 0.00)
ServerRPC: Fired (-6.19, -4.86, 0.00)

Found few interesting threads also, need to look into

  • Spawning locally caused other issues,
    then other players see delayed bullet position for that client… maybe because player position if not interpolated?

** Good enough for now, everyone have close enough positions:

1 Like

tested latest unity demo, GitHub - UnityTechnologies/GalacticKittens: Get started making multiplayer games with Netcode for GameObjects

noticed same issue there, bullets spawn way behind the clients current position: (its using serverrpc to call spawn bullet)

*video example (at 7:25)

Hi, I got same problem but also have issue with collision. For example, spawned bullet detects collision on server-side, call ClientRPC and destroy bullet etc. But on clients, that collision ClientRPC function start working before bullet not collide anything, or not near collided object. Like there is huge distance between bullet position in client and bullet position in server. It’s weird that ClientRPC call works before position data arrived.

I am really beginner on networking stuff, probably did something wrong :smile:

For this type of game, I might try disabling the interpolation on the NetworkTransforms.
Been looking at this since the Dev Bitz and there are some issues that I noticed:

  • The laser canon transform should be positioned out at the tip of the laser in the prefab

  • The NetworkObjectSpawner sort of hides this ability, so until the sample is updated you could write your own or use the method replacement posted below.

  • There is a mix-match between owner(client) and server NetworkTransforms…the ship is a ClientNetworkTransform and the bullets are normal NetworkTransforms. This means the client will always be at least 1 network tick (if not more depending upon latency) ahead of the lasers.

  • I have a branch that I might see if the samples team would like me to submit that converts the laser buller itself into a ClientNetworkTransform and I spawn the laser bullet with the client/player having ownership.

  • This doesn’t fix the issue all together but it does reduce the latent visuals some.

  • There is also the issue of getting visuals to look right when hosting because:

  • The host needs to spawn its local client’s laser bullets “behind” the current local m_cannonPosition for it too look “closer to right” on the connected client(s) side(s).

  • The clients need to send their current position to the host in order for the host to spawn the bullets more “relative to the client’s real position and not interpolated local position”.

With this all said, there are some things that could be updated in this particular sample to help reduce some of the issues but it won’t get rid of the core issue which is the host client is immediately updated with newly spawned objects while clients will always lag by half RTT (possibly a good case for client-side spawning).

But none of this helps you right now today… soo… here are a few options:
Option #1: Switch everything over to server authoritative (i.e. only use NetworkTransform and not ClientNetworkTransform) and keep like a 60 tick rate. This will assure that everything is synchronized by the host/server. The host player would obviously have the advantage here unless you polled the connected clients’ RTT and added an artificial delay to the host side (regarding player input) in order to “level the playing field”.

            foreach(var clientId in NetworkManager.ConnectedClientsIds)
            {
                averageLatency += NetworkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(clientId);
            }
            if (NetworkManager.ConnectedClientsIds.Count > 0)
            {
                averageLatency = averageLatency / NetworkManager.ConnectedClientsIds.Count;
            }

Option #2: Stick with the ClientNetworkTransform approach (a bit trickier) which requires adjustments to the laser bullet. This was my initial approach when trying to see if I could reduce the latent visuals (which I did to a degree…), I started with creating a “bullet collider controller” that I setup to spawn as the server being the owner, I removed the RigidBody from the Bullet prefab and added the new “bullet collider controller” prefab as part of the bullet spawning process. On BulletCollider.OnNetworkSpawn I have the server instantiate and parent this collider under the bullet. This makes the bullets “owner authoritative” when it comes to synchronizing the motion but “server authoritative” when it comes to collision:

    public override void OnNetworkSpawn()
    {
        if (IsServer && ServerCollider != null)
        {
            var objectInstance = Instantiate(ServerCollider);
            m_ServerColliderInstance = objectInstance.GetComponent<NetworkObject>();
            m_ServerColliderInstance.Spawn();
            m_ServerColliderInstance.TrySetParent(NetworkObject, false);
        }
        base.OnNetworkSpawn();
    }

The “BulletColliderController” invokes the same code that was within the BulletController but was migrated out of the BulletController.OnTriggerEnter2D and into a new method “BulletController.OnCollide” that the BulletColliderController invokes:

public class BulletColliderController : NetworkBehaviour
{
    private BulletController m_BulletController;

    public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
    {
        if (parentNetworkObject != null)
        {
            m_BulletController = parentNetworkObject.GetComponent<BulletController>();
        }
        base.OnNetworkObjectParentChanged(parentNetworkObject);
    }

    private void OnTriggerEnter2D(Collider2D collider)
    {
        if (!IsServer || m_BulletController == null)
            return;

        m_BulletController.OnCollide(collider);
    }
}

This assures the server is still synchronizing/controlling the collisions of the bullets, but the bullets themselves are more visually “accurate” on the client side. However, in the end interpolation will always make the bullets latent by at least 1 network tick… so since the bullets are basically “straight lines” you can easily see the latency involved in how NetworkTransform currently handles its updates (once per network tick…the higher the tick value…the smaller the gap… i.e. 1.0f/NetworkConfig.Tick is the frequency).

Here is a screenshot of the host shooting (right side) and how the client (left side) sees the host with the changes discussed along with some latency calculations and other minor tweaks to get an average “spawning position” between the host and clients:

Here is the client shooting (left side) and how the server/host sees it:


(it is hard to grab screens…next time I do a video)
Either case, the laser bullets’ positions are updated by the client (left hand side) which you can see the host (right hand side) is lagging behind. I was “just firing” on the client side (you can see the parented at the tip of the laser cannon point) but it had yet to spawn when I grabbed that screenshot.
As you are looking at the screenshots, remember the first image the host is moving down rapidly…so launching something will always “lag behind” a bit because…well the host ship is moving downwards right after shooting… same with the client side (second screenshot) but it was moving upwards when I got the screenshot.

This reduces the wide gap that you all have been seeing, but it doesn’t resolve the issue completely.
To resolve the issue visually, I am thinking the visuals would need to be separate from the NetworkObject’s with the ClientNetworkTransform/NetworkTransform (depending upon which way you go with it). The “visual Objects” would then adjust themselves based on who was viewing them and would need to adjust based on:
(assuming ClientNetworkTransform)

  • If owner relative then it most likely would only offset slightly based on like half RTT between the client and the server taking the average velocity of the object moving into consideration.

  • If the host then it would slightly need to adjust ahead since the host sends a position that is slightly behind its current in order to make it “look better” on clients.

  • If it is a client that is not an owner then it needs to know the RTT between the server and the owner (something the server would update the non-authoritative sides with) as well as include half of its own RTT into the calculations to come up with an offset for the visuals that would closer match the host’s visuals.

That is the “general idea” but is not a full explanation…if I get a chance to submit this branch (need to clear it with the Samples group) I will post a link to it here.

2 Likes

Any update on the possible solutions mentioned by NoelStephens_Unity above?