question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Client-authoritative NetworkTransform spawns in wrong position

See original GitHub issue

Description

When spawning a NetworkObject using SpawnAsPlayerObject method, it always appear in 0,0,0, ignoring the position it had when it was instantiated.

Reproduce Steps

  1. Create a simple ClientNetworkTransform component that overrides OnIsServerAuthoritative method to return false
  2. Assign this component to a prefab
  3. Spawn prefab on server in some place, other than origin

Actual Outcome

Spawned prefab teleports immediately to scene’s origin

Expected Outcome

Spawned prefab should remain the same position it had before spawning over the network.

Screenshots

If applicable, add screenshots to help explain your problem.

Environment

  • OS: Windows 11
  • Unity Version: 2023.2
  • Netcode Version: 1.3.1 (dev branch)

Additional Context

Please take a look at this video.

https://user-images.githubusercontent.com/5504685/234705013-b02f6e37-c108-40d0-bfdd-57b9fecc20e7.mp4

The way I instantiate and spawn the prefab:

public class SceneManager : NetworkBehaviour {

    public GameObject playerPrefab;
    public Transform spawnPosition;

    public void Start() {
        NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
    }
    void OnClientConnected(ulong clientId) {
        if (IsHost) {
            var newPlayer = Instantiate(playerPrefab, spawnPosition.position + spawnOffset, Quaternion.identity);
            newPlayer.GetComponent<NetworkObject>().SpawnAsPlayerObject(clientId);
        }
    }
}

Thanks a lot!

Issue Analytics

  • State:closed
  • Created 5 months ago
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
Maclay74commented, Apr 27, 2023

Got it, now it’s crystal clear! Thanks again for your support! I’ll try this approach!

0reactions
NoelStephensUnitycommented, Apr 27, 2023

@Maclay74 That is pretty much correct.

  • You have a host with Client-A already connected.
  • Client-B joins
  • Based on how you are spawning players:
    • Client-B will first synchronize with all scenes required and then all NetworkObjects (including already spawned players)
    • Upon finishing synchronization, the server’s OnClientConnected handler is triggered and the server spawns Client-B’s player.
      • At this time Client-B (if no position is set on the host) is at the default transform settings.
    • Client-A and Client-B both receive this spawn message
      • Client-A spawns the new player and the Client-B’s player is at the default transform settings.
      • Client-B spawns the new player, applies updates position and rotation.
        • When a NetworkTransform is spawned (OnNetworkSpawn) on the authority side it will send a “teleporting” state.
          • This is why I made sure to set the position and rotation before invoking the base.OnNetworkSpawn
        • Within 1/tickrate period (most likely about half of that) the updated state is sent to the host
    • The host receives Client-B’s player’s updated state and forwards that updated state to other clients
      • Total time from spawn on Client-B to host is about 1/2 RTT + remaining tick period on Client-B after spawn
    • The remaining clients receive the updated state for Client-B’s player’s transform
      • Total time from spawn on Client-B to other clients is RTT + remaining tick period on Client-B after spawn

That is pretty much the timing layout. What you can do to have precise “upon spawn” placement (if you want to keep your method of spawning) is the following:

  • On the host side, register for the NetworkSceneManager.OnSynchronize event
    • This is triggered right after a server has sent the synchronization data to a newly connected client
    • Spawn the newly connected client’s player object at this time with the server being the owner.
    • Keep the ClientNetworkTransform just as it is (i.e. position and rotation are set before base.OnNetworkSpawn)
  • On the host side, register for the NetworkSceneManager.OnSynchronizeComplete (when you register for OnSynchronize)
    • This is triggered when the client is has completely synchronized and the server is notified of this
    • At this time, change the ownership of the newly spawned Player to the player that just finished synchronizing

This should allow you to keep pretty much the exact same approach with just the adjustments in who is the owner. In the ClientNetworkTransform, you would need to override the “OnGainedOwnership” and enable the CharacterController locally when that is invoked. You would also need to have the special case for the host as to when you enable the CharacterController…sort of like this:

public class ClientNetworkTransform : NetworkTransform
{
    protected override bool OnIsServerAuthoritative() => false;

    private bool HostHasPlayerObject()
    {
        if (!IsHost)
        {
            return false;
        }

        if (NetworkManager.LocalClient != null && NetworkManager.LocalClient.PlayerObject != null)
        {
            var hostCharacterController = NetworkManager.LocalClient.PlayerObject.GetComponent<CharacterController>();
            if (hostCharacterController != null && hostCharacterController.enabled)
            {
                return true;
            }
        }
        return false;
    }

    public override void OnNetworkSpawn()
    {
        if (IsOwner)
        {
            transform.position = SceneManager.Instance.GetSpawnPoint();
            transform.rotation = SceneManager.Instance.GetFacing();
            if (!HostHasPlayerObject())
            {
                GetComponent<CharacterController>().enabled = true;
            }
        }
        base.OnNetworkSpawn();
    }

    public override void OnGainedOwnership()
    {
        if (!IsHost && IsOwner)
        {
            GetComponent<CharacterController>().enabled = true;
        }
        base.OnGainedOwnership();
    }
}

(did not test the above code…it should work…but if it doesn’t let me know) This way the host only enables the CharacterController once for its local player and for all other client players it leaves them disabled. Then when the host transfers ownership to the client, the client will enable it locally.

Something along these lines would assure the spawn position and rotation is set when spawned and that there is no latency between the client owner, the host, and the other connected clients.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Spawning as PlayerObject with ClientNetworkTransform
I have a player prefab which has a ClientNetworkTransform component. The server instantiates the player and spawns it as PlayerObject. var ...
Read more >
Unity network transform problem - Client spawning objects ...
I'll try and pinpoint the code so I can place it here but I imagine it could be a number of things. Local...
Read more >
ClientDriven Sample
In this sample, our spawn points list is server side (to have a single source of truth). ClientNetworkTransforms can be updated by owners...
Read more >
Unity - Bullet spawns in wrong position in client
The bullet tends to spawn in a location where the player was a few miliseconds ago, making that the bullet sometimes spawns inside...
Read more >
Server Authoritative Movement, Unity Multiplayer with Fish ...
Asset Store: https://assetstore.unity.com/packages/tools/network/fish-net-networking-evolved-207815 Github: ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found