#Scene Loading Issues: Players Falling Through Floor & Camera Freezing

56 messages · Page 1 of 1 (latest)

untold arrow
#

I'm implementing scene loading in my networked Unity game using FishNet, but experiencing a critical issues. The player falls through the floor while the scene is loading. I've tried many workarounds, and I'm getting somewhat close with disabling/re-enabling the character controllers, but the issue is the observer rpc for enabling the players doesn't run on clients.

Example: A client causes the scene change, both client and server freeze for a bit, the server lands and loads as expected but the client isn't re-enabled

[ServerRpc(RequireOwnership = false)]
private void LoadScene(string sceneName)
{
    // Find and disable all players
    Player[] players = GameObject.FindObjectsOfType<Player>();
    DisableAllPlayers();

    SceneLoadData sld = new SceneLoadData()
    {
        SceneLookupDatas = new[] { new SceneLookupData(sceneName) },
        MovedNetworkObjects = new NetworkObject[players.Length],
        ReplaceScenes = ReplaceOption.All,
        Options = new LoadOptions()
        {
            AutomaticallyUnload = true,
            AllowStacking = false,
        }
    };

    // Move all players to the new scene
    for (int i = 0; i < players.Length; i++)
    {
        sld.MovedNetworkObjects[i] = players[i].GetComponent<NetworkObject>();
    }

    InstanceFinder.SceneManager.OnLoadEnd += OnSceneLoadComplete;
    SceneManager.LoadGlobalScenes(sld);
}

private void OnSceneLoadComplete(SceneLoadEndEventArgs args)
{
    InstanceFinder.SceneManager.OnLoadEnd -= OnSceneLoadComplete;
    EnableAllPlayers(); // This works on server but not clients
}

#

here is what my EnableAllPlayers and DisableAllPlayers looks like:

[ObserversRpc(RunLocally = true)]
private void DisableAllPlayers()
{
    Player[] players = GameObject.FindObjectsOfType<Player>();
    foreach (Player player in players)
    {
        PlayerInputHandler inputHandler = player.GetComponent<PlayerInputHandler>();
        if (inputHandler != null)
            inputHandler.enabled = false;

        CharacterController characterController = player.GetComponent<CharacterController>();
        if (characterController != null)
            characterController.enabled = false;
    }
}

[ObserversRpc(RunLocally = true)]
private void EnableAllPlayers()
{
    Player[] players = GameObject.FindObjectsOfType<Player>(); 
    foreach (Player player in players)
    {
        PlayerInputHandler inputHandler = player.GetComponent<PlayerInputHandler>();
        if (inputHandler != null)
        {
            inputHandler.enabled = true;
        }
        CharacterController characterController = player.GetComponent<CharacterController>();
        if (characterController != null)
        {
            characterController.enabled = true;
        }
    }
}
small pumice
#

When you call the ObserversRpc does the object even have any observers yet? I'm thinking that could be the issue

untold arrow
#

what do you mean have observers yet? shouldn't the network objects be carried over through the scenes?

small pumice
#

But you can check the Observers in the NetworkObject

untold arrow
#

I also added the scene condition and it didn't change anything

untold arrow
small pumice
untold arrow
small pumice
#

Yep exactly

#

Before calling the ObserversRpc to see if there are any observing clients that it should be sending it to

untold arrow
#

looks like ur right, when it loads it detects no observers

WindowsPlayer "MSI" Scene load complete. Observers: 0

untold arrow
small pumice
#

You can mark the RPC as BufferLast for all new observers to run the method

#

Or you can call it when the client loads into the scene

untold arrow
small pumice
#

Or you could use one of the Client presence changed events from the SceneManager

untold arrow
untold arrow
#

okay i'll try using this

bright stag
#

Aren’t you only disabling players for the server? Do you have client auth or server auth?

Are the client applying gravity to your players, and they move past the ground before it loads in?

#

Oh I see, that’s an observer RPC

#

I still wonder if there’s some gravity magic happening though?

Janky, but also wonder if you should just reset client positions from the server when the client loads in? Aka a target RPC to set the position of the client?

small pumice
#

You could possibly also re-enable them client side in the OnStartClient call

untold arrow
untold arrow
small pumice
untold arrow
# small pumice When the object is initialized on the client side

I'm trying to use the hooks and it's just absolutely not working:

    [ServerRpc(RequireOwnership = false)]
    private void LoadScene(string sceneName)
    {
        // Find all Player objects
        Player[] players = GameObject.FindObjectsOfType<Player>();

        // Call disable on server, which will also call it on all clients
        DisableAllPlayers();

        // get the main camera
        Camera mainCamera = Camera.main;

        // disable the main camera
        mainCamera.enabled = false;

        SceneLoadData sld = new SceneLoadData()
        {
            SceneLookupDatas = new[] { new SceneLookupData(sceneName) },
            MovedNetworkObjects = new NetworkObject[players.Length],
            ReplaceScenes = ReplaceOption.All,
            Options = new LoadOptions()
            {
                AutomaticallyUnload = true,
                AllowStacking = false,
            }
        };

        // Add all players to the moving objects
        for (int i = 0; i < players.Length; i++)
        {
            sld.MovedNetworkObjects[i] = players[i].GetComponent<NetworkObject>();
        }

        // Register for client presence change event instead of load end
        InstanceFinder.SceneManager.OnClientPresenceChangeStart += OnClientPresenceChangeStart;
        InstanceFinder.SceneManager.OnClientPresenceChangeEnd += OnClientPresenceChangeEnd;
        
        SceneManager.LoadGlobalScenes(sld);

        // enable the main camera
        mainCamera.enabled = true;
    }


    private void OnClientPresenceChangeStart(ClientPresenceChangeEventArgs args)
    {
        DisableAllPlayers();
    }

    private void OnClientPresenceChangeEnd(ClientPresenceChangeEventArgs args)
    {
        EnableAllPlayers();

        // teleport all players to the new scene
        TeleportAllPlayersInternal(teleportPosition);
    }
#

disabling the players only works for a moment, but while the new scene is loading everything is re-enabled, even when I remove the OnClientPresenceChangeStart

small pumice
#

Are the things you disable set as enabled in the object prefabs?

untold arrow
#

I managed to get the server to work again at least, but same problem where the client is staying inactive/having weird behavior after being moved over

small pumice
#

Most likely they are all enabled in the prefab, so when the objects are despawned and then respawned on the client the default settings are what you are seeing

untold arrow
# small pumice And this?

No, so I want to disable only the Character Controller and PlayerInputHandler Script. Both client and server disable correctly, and then they should re-enable after loading. THe server re-enables correctly, but the client NEVER re-enables again (I can fix this by manually going to the prefab and enabling it after loading, but it's still not being moved to the correct position)

small pumice
small pumice
untold arrow
#

I made the enable one BufferLast and I don't see a change:

    [ObserversRpc(RunLocally = true, BufferLast = true)]
    private void EnableAllPlayersObserver()
    {
        EnableAllPlayersHelper();
    }
small pumice
#

What about trying to send a TragetRpc to the individual clients?

untold arrow
#

I can try that, but when would I send it?

#

Like after the scene loads again?

small pumice
bright stag
#

Is it running EnableAllPlayers on the client(s)? How many player game objects is it finding?

untold arrow
# small pumice From this event most likely. Where you checking the event was for the correct cl...

targetRpc didn't change anything, I even tried with a delay:

    private void OnClientPresenceChangeEnd(ClientPresenceChangeEventArgs args)
    {
        if (args.Scene.name == NewSceneName)
        {
            // Only enable players when a client is being added to the scene (not removed)
            Debug.Log($"Client {args.Connection.ClientId} has fully joined scene {args.Scene.name}");

            // Enable all players using existing method
            EnableAllPlayers();
            
            // Also send a targeted enable to the specific client that just joined
            EnablePlayerForClient(args.Connection);

            // teleport all players to the new scene
            TeleportAllPlayersInternal(teleportPosition);
        }
    }

    [TargetRpc]
    private void EnablePlayerForClient(NetworkConnection conn)
    {
        Debug.Log($"Target enabling player for client {conn.ClientId}");
        StartCoroutine(EnablePlayerWithDelay(2.0f));
    }

    private IEnumerator EnablePlayerWithDelay(float delay)
    {
        Debug.Log($"Waiting {delay} seconds before enabling player...");
        yield return new WaitForSeconds(delay);
        Debug.Log("Delay complete, enabling player now");
        EnableAllPlayersHelper();
    }
untold arrow
#

I've even tried calling a serverRpc to then call a target rpc from the client

#

I have no idea why it's not working

#

I mean I imagine that loading 3d scenes must be a pretty common use case? there must be something i'm missing in what im doing

untold arrow
#

I found what's going on (but not a solution yet), the characterController AND the PlayerInputHandler IS enabled for the server, it's just not enabled for the client. So for example

Server view:
player 1 has both components enabled
player 2 has both components enabled

Client view:
player 1 has both components enabled
player 2 has NEITHER component enabled

I thought network objects should be synced?