Search Unity

Third Party Photon2 User Lists.

Discussion in 'Multiplayer' started by Shabbalaka, Apr 4, 2020.

  1. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Hi Guys

    I am starting to learn Photon2 but am struggling with this issue for some time.

    I have a LobbyScene and a GameScene, The LobbyScene has a button to allow users to join a room when someone has created one which then does PhotonNetwork.LoadScene("GameScene"), This is working fine.

    I am having an issue where a room owner can get all the users of a room but when a client joins the room I cannot get the users in the current room I am sure this is an easy fix but after 4 hrs of trying to work this out I gave up and decided to ask the pro,s.

    PlayerListingMenu.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. using Photon.Realtime;
    6.  
    7. public class PlayerListingMenu : MonoBehaviourPunCallbacks
    8. {
    9. [SerializeField]
    10.     private PlayerListing _playerListing;
    11.    
    12.  
    13.     [SerializeField]
    14.     private Transform _content;
    15.  
    16.     [Header("Create Fake Rooms")]
    17.     public int _numFakePlayers;
    18.    
    19.     public bool _fakePlayers;
    20.  
    21.     public DebugConsole _console;
    22.  
    23.  
    24.     public List<PlayerListing> listings = new List<PlayerListing>();
    25.     private void Start()
    26.     {
    27.  
    28.         if(_fakePlayers)
    29.         {
    30.             for(int x = 0; x < _numFakePlayers; x++)
    31.             {
    32.            
    33.             PlayerListing listing = Instantiate(_playerListing,_content);
    34.             if(listing != null)
    35.                 listing.SetFakePlayerInfo(x.ToString(), Random.Range(100, 100000));
    36.                 listings.Add(listing);
    37.  
    38.                
    39.             }
    40.  
    41.         }
    42.  
    43.     }
    44.  
    45.     public override void OnPlayerEnteredRoom(Player newPlayer)
    46.     {
    47.    
    48.         AddPlayerListing(newPlayer);
    49.     }
    50.  
    51.     public override void OnPlayerLeftRoom(Player otherPlayer)
    52.     {
    53.            int index = listings.FindIndex(x => x.Player == otherPlayer);
    54.            Debug.Log("Player left the room");
    55.  
    56.                 if(index != -1)
    57.                 {
    58.                     //Destroy(listings[index]);
    59.                     listings[index].gameObject.SetActive(false);
    60.                        
    61.                     //Clean up the inactive rooms.
    62.                     Destroy(listings[index].gameObject);
    63.                     listings.RemoveAt(index);
    64.                    
    65.                 }
    66.  
    67.     }
    68.  
    69.     private void Awake() {
    70.        
    71.  
    72.         GetCurrentRoomPlayers();
    73.  
    74.  
    75.     }
    76.  
    77.     private void AddPlayerListing(Player player)
    78.     {
    79.             //player.NickName = player.NickName;
    80.          
    81.             PlayerListing listing = Instantiate(_playerListing,_content);
    82.                 if(listing != null)
    83.                     listing.SetPlayerInfo(player);
    84.                     listings.Add(listing);
    85.     }
    86.  
    87.     private void GetCurrentRoomPlayers()
    88.     {
    89.         if(PhotonNetwork.IsConnected)
    90.         {
    91.             _console.AddText("IS CONNECTED.");
    92.         }else{
    93.             _console.AddText("IS NOT CONNECTED");
    94.         }
    95.  
    96.         if(PhotonNetwork.InRoom)
    97.         {
    98.            
    99.             foreach(KeyValuePair < int, Player> playerInfo in PhotonNetwork.CurrentRoom.Players)
    100.             {
    101.                 AddPlayerListing(playerInfo.Value);
    102.    
    103.             }
    104.         }else{
    105.             _console.AddText("Player is not in a room....");
    106.         }
    107.     }
    108.  
    109.  
    110. }
    111.  
    The code here reports "Player is not in a room" but ONLY for the clients and not the owner.

    So how can I get the clients to retrieve all the currentroom.players please?

    Thanks!
     
  2. Foestar

    Foestar

    Joined:
    Aug 12, 2013
    Posts:
    350
    Hi! So first off if you're trying to do something when a player enters a room you'll want to use the callback listed below.
    Code (CSharp):
    1. public override void OnJoinedRoom()
    This happens to check when we enter a room.

    Once we enter this callback we can then maybe attach it to a private function that gets the players list. In this case you have "GetCurrentRoomPlayers" I see. Under this private function you can retrieve every player in the room by making a var and linking it to the "PhotonNetwork.PlayersList" like so.
    Code (CSharp):
    1. foreach (var playersName in PhotonNetwork.PlayerList)
    2.         {
    3.             //tell use each player who is in the room
    4.             Debug.Log(playersName + " is in the room");
    5.         }
    What the above code is doing is essentially drawing out each player in the PlayerList and telling us under a debug log that said player is in the room. This log will of course go off for each (as stated with the "foreach") person in said list.

    Hope this helps!

    Edited: Also, you can make it so everyone will automatically sync to the same scene as the host by using the below code.
    Code (CSharp):
    1. PhotonNetwork.AutomaticallySyncScene = true;
    Remember that this must be set before you make the connection and you must connect using:
    Code (CSharp):
    1. PhotonNetwork.ConnectUsingSettings();
     
    Last edited: Apr 6, 2020
  3. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Many thanks for your reply!

    I did keep trying and this is what I came up with in the end which seems to work as it creates a list of players in the room and then removes a person from the list if they disconnect.

    I was following a tutorial but i think they kind of overcomplicated the code side of it which is not very good for starting out.
    ConnectionManager.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5.  
    6. public class ConnectionScript : MonoBehaviourPunCallbacks
    7. {
    8.  
    9.     public string namePrefix;
    10.  
    11.     void Start()
    12.     {
    13.         PhotonNetwork.ConnectUsingSettings();
    14.     }
    15.  
    16.     public override void OnConnectedToMaster()
    17.     {
    18.         Debug.Log("Joined Master....Going to lobby");
    19.         PhotonNetwork.JoinLobby();
    20.     }
    21.  
    22.     public override void OnJoinedLobby()
    23.     {
    24.         Debug.Log("We have joined the lobby getting list of rooms....");
    25.         //Append a random number to a name so we can identify the user in the room scene.
    26.         PhotonNetwork.NickName = namePrefix+Random.Range(0000,99999);
    27.         Debug.Log("Welcome " + PhotonNetwork.NickName);
    28.     }
    29. }
    30.  
    LobbyManager.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using Photon.Pun;
    6. using Photon.Realtime;
    7. using System;
    8. using UnityEngine.EventSystems;
    9.  
    10. public class LobbyManager : MonoBehaviourPunCallbacks
    11. {
    12.         public Transform userPanel;
    13.         public Transform listingsPanel;
    14.         public GameObject listingPrefab;
    15.  
    16.         [Header("Room Creation Settings")]
    17.         public Text roomName;
    18.  
    19.         public List<String> currentRoomList = new List<string>();
    20.  
    21.     public void OnClickCreateRoom()
    22.     {
    23.         // byte players = Convert.ToByte(playerAmt.value+1);
    24.         PhotonNetwork.CreateRoom(roomName.text, new RoomOptions{EmptyRoomTtl = 3000, MaxPlayers = Convert.ToByte(UnityEngine.Random.Range(2,20)) });
    25.         //Automatically Sync the scene across users so all users are on the same "page" and seeing t he same thing.
    26.         PhotonNetwork.AutomaticallySyncScene = true;
    27.         PhotonNetwork.LoadLevel("GameScene2");
    28.      
    29.     }
    30.  
    31.     public override void OnCreateRoomFailed(short returnCode, string message)
    32.     {
    33.         Debug.Log("Room Creation Failed " + message);
    34.     }
    35.  
    36.     public override void OnCreatedRoom()
    37.     {
    38.         Debug.Log("ROOM Created OK");
    39.     }
    40.  
    41.     public void OnClickJoinRoom(string roomname)
    42.     {      
    43.         Debug.Log(roomname);
    44.         PhotonNetwork.JoinRoom(roomname);
    45.         PhotonNetwork.LoadLevel("GameScene2");    
    46.     }
    47.  
    48.  
    49.     public override void OnRoomListUpdate(List<RoomInfo> roomList)
    50.     {
    51.         //Did the OnRoomListUpdate actually occur??
    52.         Debug.Log("FIRED");
    53.         foreach(RoomInfo info in roomList)
    54.         {
    55.             //If the room does not exist we create a button for it.
    56.             if(!currentRoomList.Contains(info.Name))
    57.             {
    58.                 CreateNewRoomListItem(info);
    59.             }else if(currentRoomList.Contains(info.Name))
    60.             {
    61.                 UpdateRoomListItem(info);
    62.             }      
    63.         }
    64.     }
    65.  
    66.     public void UpdateRoomListItem(RoomInfo info)
    67.     {
    68.         GameObject listing = GameObject.Find(info.Name);
    69.         if(listing != null)
    70.         {
    71.             Text txt = listing.GetComponentInChildren<Text>();
    72.             txt.text = info.Name + " - "+ info.PlayerCount + " / " + info.MaxPlayers;
    73.         }else{
    74.             Debug.Log("Cannot find a gameobject with the name of " + info.Name);
    75.         }
    76.     }
    77.  
    78.     public void CreateNewRoomListItem(RoomInfo info)
    79.     {
    80.         //Double check that the room does not exist.
    81.         if(!currentRoomList.Contains(info.Name))
    82.         {
    83.             GameObject listing = Instantiate(listingPrefab, listingsPanel.transform);
    84.             Text btnText = listing.GetComponentInChildren<Text>();
    85.             listing.GetComponent<Button>().onClick.AddListener(() => { OnClickJoinRoom(info.Name); });
    86.             btnText.text = info.Name + " - "+ info.PlayerCount + " / " + info.MaxPlayers;
    87.             listing.transform.SetParent(listingsPanel.transform);
    88.             Debug.Log("ROOM NAME"  + info.Name);
    89.             currentRoomList.Add(info.Name);
    90.         }
    91.     }
    92. }
    93.  

    RoomManager.cs
    Code (CSharp):
    1. public class _RoomManager : MonoBehaviourPunCallbacks
    2. {
    3.  
    4.  
    5.         public Transform userPanel;
    6.         public GameObject userPrefab;
    7.         private int numPlayers;
    8.         public List<int> currentRoomPlayers =new List<int>();
    9.  
    10.         public override void OnJoinedRoom()
    11.         {
    12.             Debug.Log("User " + PhotonNetwork.NickName + " has joined the room...");
    13.             StartCoroutine(RunUserListUpdate());
    14.             numPlayers  = PhotonNetwork.CurrentRoom.PlayerCount;    
    15.         }
    16.  
    17.    
    18.         IEnumerator RunUserListUpdate()
    19.         {
    20.             while(true)
    21.             {
    22.                 if(PhotonNetwork.CurrentRoom.PlayerCount != numPlayers)
    23.                 {
    24.                     Dictionary<int, Photon.Realtime.Player> pList = Photon.Pun.PhotonNetwork.CurrentRoom.Players;
    25.                     foreach (KeyValuePair<int, Photon.Realtime.Player> p in pList)
    26.                     {
    27.                         if(!currentRoomPlayers.Contains(p.Value.ActorNumber))
    28.                         {
    29.                             GameObject listing = Instantiate(userPrefab, userPanel.transform);
    30.                             listing.name = p.Value.ActorNumber.ToString();
    31.                             Text btnText = listing.GetComponentInChildren<Text>();
    32.                             btnText.text = p.Value.NickName;
    33.                             listing.transform.SetParent(userPanel.transform);
    34.                              //Add to the list of current players in the room.
    35.                             currentRoomPlayers.Add(p.Value.ActorNumber);
    36.                             numPlayers = PhotonNetwork.CurrentRoom.PlayerCount;
    37.                         }
    38.                     }                            
    39.                 }
    40.                 yield return new WaitForSeconds(2f);
    41.             }
    42.         }
    43.  
    44.    
    45.     public override void OnDisconnected(DisconnectCause cause){
    46.         Debug.Log(PhotonNetwork.NickName + " has disconnected. " + cause.ToString());
    47.     }
    48.  
    49.     public override void OnPlayerLeftRoom(Player otherPlayer)
    50.     {
    51.         Debug.Log(otherPlayer.NickName + " has left, disconnected or closed the game");
    52.         //Stop the routine because we are modifying the list.
    53.         StopCoroutine(RunUserListUpdate());
    54.         foreach(int x in currentRoomPlayers)
    55.         {
    56.             if(x == otherPlayer.ActorNumber)
    57.             {
    58.                 currentRoomPlayers.Remove(x);
    59.                 GameObject playerListItem = GameObject.Find(otherPlayer.ActorNumber.ToString());
    60.                 Destroy(playerListItem);
    61.             }
    62.         }
    63.         //restart the routine
    64.         StartCoroutine(RunUserListUpdate());
    65.     }
    66.  
    67.  
    68. }
    69.  
    This appears to work good although I am not sure if it,s a correct way to do it.

    but if so might be time to move on to the next 7hr headache of trying to sync a gameobject across clients.

    PS : Thought I would post my entire code in case anyone else is having issues with getting user lists in Photon2.

    Really appreciate the reply!

    Quick Question :
    If I want to sync a gameobject across clients I have to use a PhotonView on said object right?
     
    Last edited: Apr 6, 2020
  4. Foestar

    Foestar

    Joined:
    Aug 12, 2013
    Posts:
    350
    I'm guessing you were following First Gear Games tutorials with PUN 2 on youtube. Which is actually where I send people who are starting out with Photon. His tutorial is a great starting place! Though I do agree with you that he did over complicate things a bit. But he gives you the base of what you need, and you only grow from there. You start seeing how his code works and how he uses things only to branch off and write your own. Either that or you were watching InfoGamer's who does a better job explaining but has too much content imo atm. So spread out, which I guess can be a good thing with learning.

    For example, I handle my entire player list in 1 file in under 60 lines of code. My entire code for this is below.
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections.Generic;
    4. using Photon.Pun;
    5. using Photon.Realtime;
    6.  
    7. public class PlayerListing : MonoBehaviourPunCallbacks
    8. {
    9.     [SerializeField]
    10.     private GameObject playerListingPrefab;
    11.     [SerializeField]
    12.     private Transform playerListContent;
    13.  
    14.     #region PUN 2 Callbacks
    15.     public override void OnEnable()
    16.     {
    17.         base.OnEnable();
    18.         //first we need to clear the list of any prior player listings
    19.         //from previous games
    20.         foreach (Transform child in playerListContent)
    21.         {
    22.             GameObject.Destroy(child.gameObject);
    23.         }
    24.         //then we trigger our refresh list function
    25.         refreshPlayerListing();
    26.     }
    27.  
    28.     public override void OnPlayerEnteredRoom(Player newPlayer)
    29.     {
    30.         //create the players name object
    31.         var newPlayerListing = Instantiate(playerListingPrefab, playerListContent);
    32.         //and set the players name
    33.         newPlayerListing.gameObject.transform.GetChild(0).GetComponent<Text>().text = newPlayer.NickName;
    34.     }
    35.  
    36.     public override void OnPlayerLeftRoom(Player otherPlayer)
    37.     {
    38.         foreach (Transform child in playerListContent)
    39.         {
    40.             GameObject.Destroy(child.gameObject);
    41.         }
    42.         //then we trigger our refresh list function
    43.         refreshPlayerListing();
    44.     }
    45.     #endregion
    46.  
    47.     private void refreshPlayerListing()
    48.     {
    49.         //this adds a player listing prefab for every player in the list
    50.         foreach (var playersName in PhotonNetwork.PlayerList)
    51.         {
    52.             //create the players name object
    53.             var newPlayerListing = Instantiate(playerListingPrefab, playerListContent);
    54.             //and set the players name
    55.             newPlayerListing.gameObject.transform.GetChild(0).GetComponent<Text>().text = playersName.NickName;
    56.         }
    57.     }
    58. }
    I was gonna go through and explain the concept of this all, but I feel like it will just create a wall of text. So I'll make a quick video explaining how to do this and what it does in a few.

    As for the sync an object question. The PhotonView is essentially an ID across the network and helps with control (ie. who owns that object). You can send data or update information in said view objects using RPC's. These are packages sent over the network like say, a player opening a door. We also have PhotonTransformView which is a way of tracking/syncing the objects actual transform or more physical attributes. And of course the more touchy PhotonAnimatorView which I've had less success on limiting network lag sync.

    It is however good to note that PUN2 now has a new sync method to try out which I still haven't had a chance to play with despite my excitement for it. You can find it HERE on their thread. All good stuff!

    EDIT: Here is the video explaining the above code.
     
    Last edited: Apr 6, 2020
  5. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Im ashamed to say Im not too familiar with installing things from Github (Never used it before), Do you know of a decent PhotonView tutorial which just provides the basics.

    I am trying to create a game which is similar to Skribbl.io where users draw words so I need to make a brush gameobject which is synced across all players

    1 Person draws the others have to guess.

    I thought it sounded like a pretty simple thing to do and good as a starter project for multiplayer

    Many thanks for the reply ! :)
     
    Last edited: Apr 6, 2020
  6. Foestar

    Foestar

    Joined:
    Aug 12, 2013
    Posts:
    350
    Ohhh, that's a good little concept. So I'm not the most knowledgeable when it comes to PUN 2 or how you would go about something that needs to be updated that frequently. My first thought would be to update said drawing palette every few strokes maybe. Since only one person is drawing your RPC's aren't too crazy. Of course you'd have to have a chat field for players to guess.

    @tobiass How would you go about updating something like this? As it's a constant update involving mouse input maybe he could create a custom cursor that follows the mouse, then track its PhotonTransformView and use RPC's to determine if the person is drawing or not?

    Now that I think about it that may actually work. Track the position of the cursor object, then have a variable playerDrawing and set it using RPC's if that person clicks or lets go.

    Oooh, I gotta stop before I get drawn from my own project. I love trying new things!!! :):D:rolleyes:
     
  7. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    I did once recreate MSN Messenger in a program called Windev which was Socket based so users could send messages and files, I had to code a server side program and client side program which seemed a lot easier than this. xD

    I will keep you updated with my progress!.
    Here is a few images of my work in progress.



    BlankClients.JPG Clean Users
    RoomCreated.JPG Create Room
    JoinedRoom.JPG Joined Room
    PaintUser.JPG User Painting

    So you can see why I am trying to sync the brush across users : )
     
    Last edited: Apr 7, 2020
    Foestar likes this.
  8. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,072
    Hmmm.
    You'd track the pointer position in some interval. This defines how precise the recording is. If needs be, you could optimize it and drop positions that were not at least some distance from the previous position.
    In an update interval, you'd send those positions as list / array. Send them reliable and you can be sure that everyone active in the room gets the update.
    To hide that you are not sending each little move, you can playback the received position list slowly, so that painting the path will use the time between consecutive "sends".

    Last but not least: If you want to support late joining users, you could do one of two things:
    a) The painting player can send the complete list of points to a user who joins (on join). If that's rare.
    b) Replace sending each update with setting a new key/value property. Properties are sent to users who join, so everyone will know the complete path immediately.

    Careful when using custom properties, they accumulate, unless you reuse the keys or empty them.
    It's easy to make up a scheme of keys which can be reused. Example: Use key p1, p2 and so on for partial updates. When setting one pX value, also set a property "count" to X. So new players check "count" and use p1 to pX to draw past updates, then follow along.
    When you use properties, there is a callback to signal changes.
     
    Foestar likes this.
  9. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    Many thanks for the reply Tobiass, I am currently working through the tutorial here starting from the very basics even if I do know how to do those already and hoping that the "syncing" users part will shed some light on how to sync a gameobject across players.

    https://doc.photonengine.com/en-us/pun/current/demos-and-tutorials/pun-basics-tutorial/game-scenes (The tutorial I am following at the moment)

    As for late joiners I think I will "lock" the room once enough players have joined for a session to start so players cant join midway, If people get disconnected I will most likely save a list of Players and then give the player a chance to reconnect to the session in the room they were in previously but that,s something for later.

    For now I aim to just try to get the "simple" connect to room, get X amount of players and then start game, If you disconnect you lose your spot in the room and the others can close the room to start a new game or finish their current one.

    Just wondering why do they get you to create 3-4 scenes, "Room for 1", " Room for 2", "Room for 3" etc.... Thought the idea was 1 room for many players ??
     
  10. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,072
    The "Room for 1" is just the scene's name (and confusing, probably). It's not another room in terms of the server.
    We wanted to show loading scenes and it got "linked" to the number of players. Probably not the best showcase. On the other hand, some games do load a new scene, when enough players got into the room to start playing, so it's not unusual as such.
     
  11. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    About to do Transform Synchronization tomorrow on that tutorial wish me luck! XD
     
  12. Shabbalaka

    Shabbalaka

    Joined:
    Jan 15, 2018
    Posts:
    154
    SpawnLocaiton.JPG

    Ok followed the tutorial to the end and now have these errors..

    "There are 3 audio listeners in the scene, Please ensure there is always exactly one in the scene".
    (This message is self explanatory but I,m not sure why when it loads Room For 1 with 1 player and then loads Room For 2 when a new player joins or it appears to be that way I think.

    Also when the robot is spawned it,s spawned a million miles away from the room? (See image)

    I also noticed that my RobotKyle has no UI associated with him when he is loaded but the player inputs do work on him.

    I,ve attached the errorlog from the PhotonNetwork client application as I cannot make too much sense of it.

    If you can shed any light on this it would be appreciated! :)

    Just a small thing also

    I found this small thing which needs updating in the tutorial .

    Player Instantiation
    Section 3
    Debug.LogFormat("We are Instantiating LocalPlayer from {0}", Application.loadedLevelName);
    Should be
    Debug.LogFormat("we are instantiating localplayer from {0}", SceneManager.GetActiveScene().name);
    (I think)


    Many thnaks!

    Update:
    I managed to fix the spawning thing not sure what I done but I had some missing references in the RobotKyle prefab.

    I still have issues with the UI not showing up and the camera problem though, The camera follows the player but has some kind of weird animation it,s not like "over the shoulder" as it kind of slides around behind the player and sometimes is slower than the player.
    Video Link of weird camera


    I have double instances of players happening now though.
    DoubleInstance.JPG
     

    Attached Files:

    Last edited: Apr 9, 2020