Strange behavior when joining different scene with AutoSyncScene = true

In my game there is a matchmaking lobby, where players login to Photon and create or join a room. The game (room/level) itself can be joined in at any time. I use AutoSyncScene = true to sens all the clients to the scene controlled by the master client. Things work fine so long as all players are present in the room when the master client "starts the game" (clicking a button that performs the level change).

As the game can be joined in progress at any time though, it is possible for a player to come to the lobby, see the room/game and join it. Unlike a player who was there form the start, the player does not wait in the lobby for the scene to transistion. Upon clicking the room, they join the game and presumably AutoSyncScene does it's magic.

In this case, they DO transition to the correct level, however, and I wish I could be more specific as to the problems popping up, but one can be noted in this post.

https://forum.photonengine.com/discussion/12525/photon-unity-networking-utility-scripts-photonplayer-playerroomindexing#latest

Various other problems pop up, but this one is the most prevalent. Upon entering the new scene, the player returns an index of -1 indicating they are not in the room. It is not consistent, almost as if there is a race condition between the scene loading and Photon connecting them to the room.

What are best practices with Photon with joining a game already in progress with AutoSyncScene = true?

Comments

  • bump
  • To be more clear. Normally one could wait for OnJoinedRoom() to be called to be assured you are in a room. However, it appears with AutoSyncScene = True, you are whisked off to the new scene and can land there and start executing code BEFORE you are in the room (at least as reported by PlayerRoomIndexing() )
  • Perhaps a code snippet will help. Again AutoSyncScene = true.

    1. In the preload/lobby scene there is an empty GameObject PlayerRoomIndexing with the script found at Photon Unity Networking > Utility Scripts > PhotonPlayer > PlayerRoomIndexing attached and only modified by adding DDOL.

    2. My Game Manager does not persist between scenes. In my Game Manager Script's Start() function I have the following:

    //Has to instantiate player in Start not Awake else clients do not see masterclient.
    LocalPlayerInstance = PhotonNetwork.Instantiate("NewPlayer", playerSpawnPointTransform.position, playerSpawnPointTransform.rotation, 0);

    3. The prefab the LocalPlayerInstance is created from contains one script that sets up special features for the local player and colors the local player and each of the networked spawned players in its Start() function.

    photonView = this.gameObject.GetComponent();
    if (photonView.isMine)
    {
    perform code unique to local player
    }

    int playerIndex = PlayerRoomIndexing.Instance.GetRoomIndex(photonView.owner) ;
    print(playerIndex.ToString());

    switch (playerIndex)
    {
    case 0:

    break;
    case 1:

    break;
    case 2:

    break;
    case 3:

    break;
    case 4:

    break;
    case 5:

    break;
    }


    In this last bit, the code block for the local player works. Thus it properly evaluates photonView.isMine of the player object.

    The second case statement however does not work (on the local player) playerindex returns -1 which indicates the player is not in the room. It does however work correctly on the non-local players. They all appear as they should in the correct color.

    Incidentally, since I placed the print statement there I can actually "see" all the player, local or not as they instantiate. The local player as noted doe snot work correctly as it shows an index of -1. The other players though (which all get instantiated after this, show the correct index, 0, 1, etc.

    As I instantiate my local player in a Start() routine, and the room/game is already running, I'd actually expect Photon to instantiate its players BEFORE mine, which is not the case.
  • Hi @Spektre,

    the room index might not be set properly, when trying to access the value. This might result in the -1 you are getting. Instead you can use the RoomIndexingChanged() delegate. It gets called whenever the Room Indexing is changed. To do so you can register a function, like this:
    public void OnEnable()
    {
        PlayerRoomIndexing.instance.OnRoomIndexingChanged += RoomIndexingChanged;
    }
    
    public void OnDisable()
    {
        PlayerRoomIndexing.instance.OnRoomIndexingChanged -= RoomIndexingChanged;
    }
    The function itself looks like this:
    private void RoomIndexingChanged()
    {
        // Do something
        Debug.Log("Room Index: " + PlayerRoomIndexing.instance.GetRoomIndex(PhotonNetwork.player));
    }
    In this example the Index of the local player is logged to the console.
  • Hi @Christian_Simon

    "the room index might not be set properly, when trying to access the value"

    The question is, when will it become correct? The level's code depends on knowing which of the spawned players belongs to whom. None of that can be accomplished until the indexing has resolved.

    Trying to use the delegate callback, as in this function, yields the same issue as trying to use OnJoinedRoom(). For those in the room prior to the scene change, it would be called prior to the scene change. For those joining a game in progress, the timing is variable.
  • When you are using auto sync level, the current scene is put into the properties of the room. On join, this property is read (earlier than "current events") and loading the scene starts. While PUN loads, it will pause the message queue, so no incoming updates are dispatched. So, while loading, nothing changes. This is important as incoming messages will likely target objects that the client is (still) loading...

    The event "join", which is the trigger for "OnJoined" is affected by this.
    So it's by design, that loading a level kicks in before a player-index is assigned.

    Only when loading is done, the indexing gets finished.
    And: When multiple players join at the same time, picking a player-index is done one by one in sequence. So everyone waits for the longest load-time.

    While player-indexing is not finished (e.g., due to loading), some index may be -1. It's finished, when OnRoomIndexingChanged gets fired and all IDs are >= 0.

    There are several solutions here: Your logic may deal with the -1 case and get triggered again, via OnRoomIndexingChanged.
    You may change the logic how PUN syncs the loaded scene: If you don't want it to load right away, disable it and load, when it's more convenient to you. You can do this in our code (this is why it's there, after all) or you drop using auto sync for the level and manage loading it fully yourself. When you do: Consider using IsMessageQueueRunning = false while you load (to avoid executing received events which target objects from the other scene)...
  • Thanks for looking into this. This unfortunately make PlayerIndexing fairly worthless as it is (normally) something that is important at the beginning of a scene to correctly instantiate objects.

    I have temporarily moved to doing this via Player properties, but unfortunately run into concurrency problems with this method. (Two players joining at roughly the same time). I'll keep you updated, but the issue of selecting a unique index for each player (and doing so before the creation of objects begins) must be a fairly common issue.
  • Tobias said:

    When you are using auto sync level, the current scene is put into the properties of the room. On join, this property is read (earlier than "current events") and loading the scene starts. While PUN loads, it will pause the message queue, so no incoming updates are dispatched. So, while loading, nothing changes. This is important as incoming messages will likely target objects that the client is (still) loading...

    The event "join", which is the trigger for "OnJoined" is affected by this.
    So it's by design, that loading a level kicks in before a player-index is assigned.

    Only when loading is done, the indexing gets finished.
    And: When multiple players join at the same time, picking a player-index is done one by one in sequence. So everyone waits for the longest load-time.

    While player-indexing is not finished (e.g., due to loading), some index may be -1. It's finished, when OnRoomIndexingChanged gets fired and all IDs are >= 0.

    There are several solutions here: Your logic may deal with the -1 case and get triggered again, via OnRoomIndexingChanged.
    You may change the logic how PUN syncs the loaded scene: If you don't want it to load right away, disable it and load, when it's more convenient to you. You can do this in our code (this is why it's there, after all) or you drop using auto sync for the level and manage loading it fully yourself. When you do: Consider using IsMessageQueueRunning = false while you load (to avoid executing received events which target objects from the other scene)...

    So, I have tried a different approach and still am running into timing issues.

    I am using Player Custom Properties to try an accomplish this indexing.

    In the lobby, I start with automaticallySyncScene = false.

    In the OnJoinedRoom() routine I set isJoined = true;

    The Update() routine looks as follows:

    private void Update()
    {
    if (isJoined)
    {
    //Setup Player Index
    //Initialize Player Custom Property
    Hashtable indexProps = new Hashtable() { { "in", -1 } };
    //PhotonNetwork.player.SetCustomProperties(indexProps);

    //Iterate through all Players to get their index
    bool[] taken = { false, false, false, false, false, false };

    foreach (PhotonPlayer photonPlayer in PhotonNetwork.otherPlayers)
    {
    int index = 0;
    Nullable nullIndex = photonPlayer.CustomProperties["in"] as Nullable;
    if (nullIndex != null)
    {
    index = (int)nullIndex;
    taken[index] = true;
    print(index.ToString());
    if (index > -1)
    {
    taken[index] = true;
    }
    }
    else
    {
    print("Null found. Join time too close to another. Retry later.");
    return;
    }
    }

    //Step through indices until empty spot is found and assign it to player
    int i2 = 0;
    while (taken[i2] == true)
    {
    i2++;
    }

    indexProps = new Hashtable() { { "in", i2 } };
    PhotonNetwork.player.SetCustomProperties(indexProps);
    PlayerNetwork.Instance.Index = i2;
    print(i2.ToString());

    PhotonNetwork.automaticallySyncScene = true;
    isJoined = false;
    }
    }

    The problem here is when the player does establish an index and autoSyncScene is set to true. the player is taken to the scene, but none of the other player's are shown. Namely it appears since the player entered the room but did not join the scene fast enough after joining, the other photon instantiated objects are unable to be sent to the late joining player.

    Received OnSerialization for view ID 2001. We have no such PhotonView! Ignored this if you're leaving a room. State: Joined

    Is the error seen.

    This really seems to be a catch-22.

    Enter the scene too fast and you have no index.
    Enter the scene too slow and photon will not instantiate the objects to the late joining player.

    Any ideas?





  • bump.

    This must be a commonly needed functionality from an networking API.

    I would like to join a room via a lobby scene and have a correct unique player index before transitioning into the next scene (which may or may not be in progress) such that objects may be instantiated in that scene correctly.

    Is Photon incapable of this?
  • Crosspost from:
    https://forum.unity.com/threads/photon-unity-joining-a-room-with-game-in-progress.564043/#post-3773323

    The PhotonPlayer.ID is a unique ID (in a room), given by the server when someone joins a room. In PUN 2, this is the Player.ActorNumber. It's never reused and automatically used all messages.
    The player numbering / indexing does not provide a unique ID: When player number 2 leaves (e.g.) someone else may become player 2, joining a moment later.
    This is why you should use the player numbering in a purely cosmetic way (display, etc) and instead rely on the PhotonPlayer.ID for anything that identifies a client/actor in the room.
    Player Numbering needs some negotiation, which is why it's not always available on join.
    We could make everyone wait until it's done, of course but this wasn't requested often, before.

    So, if you want to build your logic based on non-unique numbers which are reused when players leave and others join, then you should simply do your own level loading and when the player numbering for new players is "done", you load the new scene (leaving the "lobby scene"). Deactivate PUN's "Automatically Sync Scene" feature, put the "map" info into a Custom Room Property and use that to load the play scene whenever you're ready.

    In PUN 2, the Asteroids Demo is doing something like it: It has a screen where every player has to check "ready", before we start the game. This is similar. You can do this in PUN Classic, too, of course.
  • Hi,

    Are you using the callback from playerNumbering script ( OnPlayerNumberingChanged), this should be how your players logic gets to know about its index.

    Then if the index changes or is not final during these cases you mention, your scripts will always be made aware of this.

    Can you confirm you are using this callback or not?

    Bye,

    Jean
  • Hi Jean,

    This is the callback I would like to use, unfortunately, it does not fire prior to Unity changing scenes.