PhotonTeamsManager.GetTeamMembersCount("TeamName") is giving a Null Reference Exception

Options
Hello everyone,
I have a very big problem. This whole thing was working before and I didn't change anything since then. But now I get a Null Reference Exception at this line:
byte teamBluePlayers = (byte)teamsManager.GetTeamMembersCount("Blue");

And yes, I have a Team with the name "Blue" and I also checked the spelling. I also tried with the Team Code (that's 1) in my case. It doesn't work at all! And yes, my teamsManager variable is referenced in the Inspector and it also doesn't change at Runtime. That's not the problem. Like I said, I checked everything! And it worked before. I didn't change anything since then.

I would really like to get some help. I don't know what to do anymore.

Thank you very very much already in advance.

Comments

  • JohnTube
    JohnTube ✭✭✭✭✭
    Options
    Hi @Duck,

    what PUN version are you using?
    could you get us the full stacktrace of the exception log?
    how to reproduce this?
  • Duck
    Duck
    edited June 2020
    Options
    Hello @JohnTube .
    I am using the newest PUN version. That is PUN 2.18.1. It didn't work in PUN 2.18 either.
    This is the full error:
    NullReferenceException: Object reference not set to an instance of an object
    Photon.Pun.UtilityScripts.PhotonTeamsManager.TryGetTeamByCode (System.Byte code, Photon.Pun.UtilityScripts.PhotonTeam& team) (at Assets/Photon/PhotonUnityNetworking/UtilityScripts/PhotonPlayer/PhotonTeamsManager.cs:280)
    Photon.Pun.UtilityScripts.PhotonTeamsManager.GetTeamMembersCount (System.Byte code) (at Assets/Photon/PhotonUnityNetworking/UtilityScripts/PhotonPlayer/PhotonTeamsManager.cs:415)
    LevelManager+<AssignTeamsCoroutine>d__162.MoveNext () (at Assets/Scripts/LevelManager.cs:1378)
    UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <f38c71c86aa64e299d4cea9fb7c715e1>:0)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    LevelManager:Awake() (at Assets/Scripts/LevelManager.cs:176)
    

    And this is basically my coroutine where the responsible code is, nothing more. This coroutine is called in the Awake() function of my script:
    IEnumerator AssignTeamsCoroutine()
        {
            foreach (Player p in PhotonNetwork.PlayerList)
            {
                byte teamBluePlayers = (byte)teamsManager.GetTeamMembersCount(1);
                byte teamRotPlayers = (byte)teamsManager.GetTeamMembersCount(2); 
    
                if (teamBluePlayers == teamRotPlayers)
                {
                    byte whichTeam = (byte)Random.Range(1, 3);
                    p.JoinTeam(whichTeam);
                }
                else if (teamBluePlayers > teamRotPlayers)
                {
                    p.JoinTeam("Red");
                }
                else
                {
                    p.JoinTeam("Blue");
                }
                yield return new WaitForSeconds(0.5f);
            }
        }
    
  • JohnTube
    JohnTube ✭✭✭✭✭
    Options
    Sorry for the inconvenience.
    The issue is that you called PhotonTeamsManager too early (before its Awake methods is called).
    Anyways, we need to support this and handle it in a better way.
    Could you replace PhotonTeamsManager with the code below and try again?
    // --------------------------------------------------------------------------------------------------------------------
    // <copyright file="PhotonTeamsManager.cs" company="Exit Games GmbH">
    //   Part of: Photon Unity Utilities, 
    // </copyright>
    // <summary>
    // Implements teams in a room/game with help of player properties.
    // </summary>
    // <remarks>
    // Teams are defined by name and code. Change this to get more / different teams.
    // There are no rules when / if you can join a team. You could add this in JoinTeam or something.
    // </remarks>                                                                                           
    // <author>developer@exitgames.com</author>
    // --------------------------------------------------------------------------------------------------------------------
    
    using System;
    using System.Collections.Generic;
    using UnityEngine;
    
    using Photon.Realtime;
    using Hashtable = ExitGames.Client.Photon.Hashtable;
    
    namespace Photon.Pun.UtilityScripts
    {
        [Serializable]
        public class PhotonTeam
        {
            public string Name;
            public byte Code;
    
            public override string ToString()
            {
                return string.Format("{0} [{1}]", this.Name, this.Code);
            }
        }
    
        /// <summary>
        /// Implements teams in a room/game with help of player properties. Access them by Player.GetTeam extension.
        /// </summary>
        /// <remarks>
        /// Teams are defined by enum Team. Change this to get more / different teams.
        /// There are no rules when / if you can join a team. You could add this in JoinTeam or something.
        /// </remarks>
        [DisallowMultipleComponent]
        public class PhotonTeamsManager : MonoBehaviour, IMatchmakingCallbacks, IInRoomCallbacks
        {
            #if UNITY_EDITOR
            #pragma warning disable 0414
            [SerializeField]
            private bool listFoldIsOpen = true;
            #pragma warning restore 0414
            #endif
    
            [SerializeField]
            private List<PhotonTeam> teamsList = new List<PhotonTeam>
            {
                new PhotonTeam { Name = "Blue", Code = 1 },
                new PhotonTeam { Name = "Red", Code = 2 }
            };
    
            private Dictionary<byte, PhotonTeam> teamsByCode;
            private Dictionary<string, PhotonTeam> teamsByName;
            
            /// <summary>The main list of teams with their player-lists. Automatically kept up to date.</summary>
            private Dictionary<byte, HashSet<Player>> playersPerTeam;
    
            /// <summary>Defines the player custom property name to use for team affinity of "this" player.</summary>
            public const string TeamPlayerProp = "_pt";
    
            public static event Action<Player, PhotonTeam> PlayerJoinedTeam;
            public static event Action<Player, PhotonTeam> PlayerLeftTeam;
    
            private static PhotonTeamsManager instance;
            public static PhotonTeamsManager Instance
            {
                get
                {
                    if (instance == null)
                    {
                        instance = FindObjectOfType<PhotonTeamsManager>();
                        if (instance == null)
                        {
                            GameObject obj = new GameObject();
                            obj.name = "PhotonTeamsManager";
                            instance = obj.AddComponent<PhotonTeamsManager>();
                        }
                        instance.Init();
                    }
    
                    return instance;
                }
            }
    
            #region MonoBehaviour
    
            private void Awake()
            {
                if (instance == null || ReferenceEquals(this, instance))
                {
                    this.Init();
                    instance = this;
                }
                else
                {
                    Destroy(this);
                }
            }
    
            private void OnEnable()
            {
                PhotonNetwork.AddCallbackTarget(this);
            }
    
            private void OnDisable()
            {
                PhotonNetwork.RemoveCallbackTarget(this);
                this.ClearTeams();
            }
    
            private void Init()
            {
                teamsByCode = new Dictionary<byte, PhotonTeam>(teamsList.Count);
                teamsByName = new Dictionary<string, PhotonTeam>(teamsList.Count);
                playersPerTeam = new Dictionary<byte, HashSet<Player>>(teamsList.Count);
                for (int i = 0; i < teamsList.Count; i++)
                {
                    teamsByCode[teamsList[i].Code] = teamsList[i];
                    teamsByName[teamsList[i].Name] = teamsList[i];
                    playersPerTeam[teamsList[i].Code] = new HashSet<Player>();
                }
            }
    
            #endregion
    
            #region IMatchmakingCallbacks
    
            void IMatchmakingCallbacks.OnJoinedRoom()
            {
                this.UpdateTeams();
            }
    
            void IMatchmakingCallbacks.OnLeftRoom()
            {
                this.ClearTeams();
            }
    
            void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
            {
                object temp;
                if (changedProps.TryGetValue(TeamPlayerProp, out temp))
                {
                    if (temp == null)
                    {
                        foreach (byte code in playersPerTeam.Keys)
                        {
                            if (playersPerTeam[code].Remove(targetPlayer))
                            {
                                if (PlayerLeftTeam != null)
                                {
                                    PlayerLeftTeam(targetPlayer, teamsByCode[code]);
                                }
                                break;
                            }
                        }
                    } 
                    else if (temp is byte)
                    {
                        byte teamCode = (byte) temp;
                        // check if player switched teams, remove from previous team 
                        foreach (byte code in playersPerTeam.Keys)
                        {
                            if (code == teamCode)
                            {
                                continue;
                            }
                            if (playersPerTeam[code].Remove(targetPlayer))
                            {
                                if (PlayerLeftTeam != null)
                                {
                                    PlayerLeftTeam(targetPlayer, teamsByCode[code]);
                                }
                                break;
                            }
                        }
                        PhotonTeam team = teamsByCode[teamCode];
                        if (!playersPerTeam[teamCode].Add(targetPlayer))
                        {
                            Debug.LogWarningFormat("Unexpected situation while setting team {0} for player {1}, updating teams for all", team, targetPlayer);
                            this.UpdateTeams();
                        }
                        if (PlayerJoinedTeam != null)
                        {
                            PlayerJoinedTeam(targetPlayer, team);
                        }
                    }
                    else
                    {
                        Debug.LogErrorFormat("Unexpected: custom property key {0} should have of type byte, instead we got {1} of type {2}. Player: {3}", 
                            TeamPlayerProp, temp, temp.GetType(), targetPlayer);
                    }
                }
            }
    
            void IInRoomCallbacks.OnPlayerLeftRoom(Player otherPlayer)
            {
                if (otherPlayer.IsInactive)
                {
                    return;
                }
                PhotonTeam team = otherPlayer.GetPhotonTeam();
                if (team != null && !playersPerTeam[team.Code].Remove(otherPlayer))
                {
                    Debug.LogWarningFormat("Unexpected situation while removing player {0} who left from team {1}, updating teams for all", otherPlayer, team);
                    // revert to 'brute force' in case of unexpected situation
                    this.UpdateTeams();
                }
            }
    
            void IInRoomCallbacks.OnPlayerEnteredRoom(Player newPlayer)
            {
                PhotonTeam team = newPlayer.GetPhotonTeam();
                if (team == null)
                {
                    return;
                }
                if (playersPerTeam[team.Code].Contains(newPlayer))
                {
                    // player rejoined w/ same team
                    return;
                }
                // check if player rejoined w/ different team, remove from previous team 
                foreach (var key in teamsByCode.Keys)
                {
                    if (playersPerTeam[key].Remove(newPlayer))
                    {
                        break;
                    }
                }
                if (!playersPerTeam[team.Code].Add(newPlayer))
                {
                    Debug.LogWarningFormat("Unexpected situation while adding player {0} who joined to team {1}, updating teams for all", newPlayer, team);
                    // revert to 'brute force' in case of unexpected situation
                    this.UpdateTeams();
                }
            }
    
            #endregion
    
            #region Private methods
    
            private void UpdateTeams()
            {
                this.ClearTeams();
                for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
                {
                    Player player = PhotonNetwork.PlayerList[i];
                    PhotonTeam playerTeam = player.GetPhotonTeam();
                    if (playerTeam != null)
                    {
                        playersPerTeam[playerTeam.Code].Add(player);
                    }
                }
            }
    
            private void ClearTeams()
            {
                foreach (var key in playersPerTeam.Keys)
                {
                    playersPerTeam[key].Clear();
                }
            }
    
            #endregion
    
            #region Public API
    
            /// <summary>
            /// Find a PhotonTeam using a team code.
            /// </summary>
            /// <param name="code">The team code.</param>
            /// <param name="team">The team to be assigned if found.</param>
            /// <returns>If successful or not.</returns>
            public bool TryGetTeamByCode(byte code, out PhotonTeam team)
            {
                return teamsByCode.TryGetValue(code, out team);
            }
    
            /// <summary>
            /// Find a PhotonTeam using a team name.
            /// </summary>
            /// <param name="teamName">The team name.</param>
            /// <param name="team">The team to be assigned if found.</param>
            /// <returns>If successful or not.</returns>
            public bool TryGetTeamByName(string teamName, out PhotonTeam team)
            {
                return teamsByName.TryGetValue(teamName, out team);
            }
    
            /// <summary>
            /// Gets all teams available.
            /// </summary>
            /// <returns>Returns all teams available.</returns>
            public PhotonTeam[] GetAvailableTeams()
            {
                if (teamsList != null)
                {
                    return teamsList.ToArray();
                }
                return null;
            }
    
            /// <summary>
            /// Gets all players joined to a team using a team code.
            /// </summary>
            /// <param name="code">The code of the team.</param>
            /// <param name="members">The array of players to be filled.</param>
            /// <returns>If successful or not.</returns>
            public bool TryGetTeamMembers(byte code, out Player[] members)
            {
                members = null;
                HashSet<Player> players;
                if (this.playersPerTeam.TryGetValue(code, out players))
                {
                    members = new Player[players.Count];
                    int i = 0;
                    foreach (var player in players)
                    {
                        members[i] = player;
                        i++;
                    }
                    return true;
                }
                return false;
            }
    
            /// <summary>
            /// Gets all players joined to a team using a team name.
            /// </summary>
            /// <param name="teamName">The name of the team.</param>
            /// <param name="members">The array of players to be filled.</param>
            /// <returns>If successful or not.</returns>
            public bool TryGetTeamMembers(string teamName, out Player[] members)
            {
                members = null;
                PhotonTeam team;
                if (this.TryGetTeamByName(teamName, out team))
                {
                    return this.TryGetTeamMembers(team.Code, out members);
                }
                return false;
            }
    
            /// <summary>
            /// Gets all players joined to a team.
            /// </summary>
            /// <param name="team">The team which will be used to find players.</param>
            /// <param name="members">The array of players to be filled.</param>
            /// <returns>If successful or not.</returns>
            public bool TryGetTeamMembers(PhotonTeam team, out Player[] members)
            {
                members = null;
                if (team != null)
                {
                    return this.TryGetTeamMembers(team.Code, out members);
                }
                return false;
            }
    
            /// <summary>
            /// Gets all team mates of a player.
            /// </summary>
            /// <param name="player">The player whose team mates will be searched.</param>
            /// <param name="teamMates">The array of players to be filled.</param>
            /// <returns>If successful or not.</returns>
            public bool TryGetTeamMatesOfPlayer(Player player, out Player[] teamMates)
            {
                teamMates = null;
                if (player == null)
                {
                    return false;
                }
                PhotonTeam team = player.GetPhotonTeam();
                if (team == null)
                {
                    return false;
                }
                HashSet<Player> players;
                if (this.playersPerTeam.TryGetValue(team.Code, out players))
                {
                    if (!players.Contains(player))
                    {
                        Debug.LogWarningFormat("Unexpected situation while getting team mates of player {0} who is joined to team {1}, updating teams for all", player, team);
                        // revert to 'brute force' in case of unexpected situation
                        this.UpdateTeams();
                    }
                    teamMates = new Player[players.Count - 1];
                    int i = 0;
                    foreach (var p in players)
                    {
                        if (p.Equals(player))
                        {
                            continue;
                        }
                        teamMates[i] = p;
                        i++;
                    }
                    return true;
                }
                return false;
            }
    
            /// <summary>
            /// Gets the number of players in a team by team code.
            /// </summary>
            /// <param name="code">Unique code of the team</param>
            /// <returns>Number of players joined to the team.</returns>
            public int GetTeamMembersCount(byte code)
            {
                PhotonTeam team;
                if (this.TryGetTeamByCode(code, out team))
                {
                    return this.GetTeamMembersCount(team);
                }
                return 0;
            }
    
            /// <summary>
            /// Gets the number of players in a team by team name.
            /// </summary>
            /// <param name="name">Unique name of the team</param>
            /// <returns>Number of players joined to the team.</returns>
            public int GetTeamMembersCount(string name)
            {
                PhotonTeam team;
                if (this.TryGetTeamByName(name, out team))
                {
                    return this.GetTeamMembersCount(team);
                }
                return 0;
            }
    
            /// <summary>
            /// Gets the number of players in a team.
            /// </summary>
            /// <param name="team">The team you want to know the size of</param>
            /// <returns>Number of players joined to the team.</returns>
            public int GetTeamMembersCount(PhotonTeam team)
            {
                HashSet<Player> players;
                if (team != null && this.playersPerTeam.TryGetValue(team.Code, out players) && players != null)
                {
                    return players.Count;
                }
                return 0;
            }
    
            #endregion
    
            #region Unused methods
    
            void IMatchmakingCallbacks.OnFriendListUpdate(List<FriendInfo> friendList)
            {
            }
    
            void IMatchmakingCallbacks.OnCreatedRoom()
            {
            }
    
            void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
            {
            }
    
            void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message)
            {
            }
    
            void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
            {
            }
    
            void IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
            {
            }
    
            void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient)
            {
            }
    
            #endregion
        }
    
        /// <summary>Extension used for PunTeams and Player class. Wraps access to the player's custom property.</summary>
        public static class PhotonTeamExtensions
        {
            /// <summary>Gets the team the player is currently joined to. Null if none.</summary>
            /// <returns>The team the player is currently joined to. Null if none.</returns>
            public static PhotonTeam GetPhotonTeam(this Player player)
            {
                object teamId;
                PhotonTeam team;
                if (player.CustomProperties.TryGetValue(PhotonTeamsManager.TeamPlayerProp, out teamId) && PhotonTeamsManager.Instance.TryGetTeamByCode((byte)teamId, out team))
                {
                    return team;
                }
                return null;
            }
    
            /// <summary>
            /// Join a team.
            /// </summary>
            /// <param name="player">The player who will join a team.</param>
            /// <param name="team">The team to be joined.</param>
            /// <returns></returns>
            public static bool JoinTeam(this Player player, PhotonTeam team)
            {
                if (team == null)
                {
                    Debug.LogWarning("JoinTeam failed: PhotonTeam provided is null");
                    return false;
                }
                PhotonTeam currentTeam = player.GetPhotonTeam();
                if (currentTeam != null)
                {
                    Debug.LogWarningFormat("JoinTeam failed: player ({0}) is already joined to a team ({1}), call SwitchTeam instead", player, team);
                    return false;
                }
                return player.SetCustomProperties(new Hashtable { { PhotonTeamsManager.TeamPlayerProp, team.Code } });
            }
    
            /// <summary>
            /// Join a team using team code.
            /// </summary>
            /// <param name="player">The player who will join the team.</param>
            /// <param name="teamCode">The code fo the team to be joined.</param>
            /// <returns></returns>
            public static bool JoinTeam(this Player player, byte teamCode)
            {
                PhotonTeam team;
                return PhotonTeamsManager.Instance.TryGetTeamByCode(teamCode, out team) && player.JoinTeam(team);
            }
    
            /// <summary>
            /// Join a team using team name.
            /// </summary>
            /// <param name="player">The player who will join the team.</param>
            /// <param name="teamName">The name of the team to be joined.</param>
            /// <returns></returns>
            public static bool JoinTeam(this Player player, string teamName)
            {
                PhotonTeam team;
                return PhotonTeamsManager.Instance.TryGetTeamByName(teamName, out team) && player.JoinTeam(team);
            }
    
            /// <summary>Switch that player's team to the one you assign.</summary>
            /// <remarks>Internally checks if this player is in that team already or not. Only team switches are actually sent.</remarks>
            /// <param name="player"></param>
            /// <param name="team"></param>
            public static bool SwitchTeam(this Player player, PhotonTeam team)
            {
                if (team == null)
                {
                    Debug.LogWarning("SwitchTeam failed: PhotonTeam provided is null");
                    return false;
                }
                PhotonTeam currentTeam = player.GetPhotonTeam();
                if (currentTeam == null)
                {
                    Debug.LogWarningFormat("SwitchTeam failed: player ({0}) was not joined to any team, call JoinTeam instead", player);
                    return false;
                }
                if (currentTeam.Code == team.Code)
                {
                    Debug.LogWarningFormat("SwitchTeam failed: player ({0}) is already joined to the same team {1}", player, team);
                    return false;
                }
                return player.SetCustomProperties(new Hashtable { { PhotonTeamsManager.TeamPlayerProp, team.Code } },
                    new Hashtable { { PhotonTeamsManager.TeamPlayerProp, currentTeam.Code }});
            }
    
            /// <summary>Switch the player's team using a team code.</summary>
            /// <remarks>Internally checks if this player is in that team already or not.</remarks>
            /// <param name="player">The player that will switch teams.</param>
            /// <param name="teamCode">The code of the team to switch to.</param>
            /// <returns>If the team switch request is queued to be sent to the server or done in case offline or not joined to a room yet.</returns>
            public static bool SwitchTeam(this Player player, byte teamCode)
            {
                PhotonTeam team;
                return PhotonTeamsManager.Instance.TryGetTeamByCode(teamCode, out team) && player.SwitchTeam(team);
            }
    
            /// <summary>Switch the player's team using a team name.</summary>
            /// <remarks>Internally checks if this player is in that team already or not.</remarks>
            /// <param name="player">The player that will switch teams.</param>
            /// <param name="teamName">The name of the team to switch to.</param>
            /// <returns>If the team switch request is queued to be sent to the server or done in case offline or not joined to a room yet.</returns>
            public static bool SwitchTeam(this Player player, string teamName)
            {
                PhotonTeam team;
                return PhotonTeamsManager.Instance.TryGetTeamByName(teamName, out team) && player.SwitchTeam(team);
            }
    
            /// <summary>
            /// Leave the current team if any.
            /// </summary>
            /// <param name="player"></param>
            /// <returns>If the leaving team request is queued to be sent to the server or done in case offline or not joined to a room yet.</returns>
            public static bool LeaveCurrentTeam(this Player player)
            {
                PhotonTeam currentTeam = player.GetPhotonTeam();
                if (currentTeam == null)
                {
                    Debug.LogWarningFormat("LeaveCurrentTeam failed: player ({0}) was not joined to any team", player);
                    return false;
                }
                return player.SetCustomProperties(new Hashtable {{PhotonTeamsManager.TeamPlayerProp, null}}, new Hashtable {{PhotonTeamsManager.TeamPlayerProp, currentTeam.Code}});
            }
    
            /// <summary>
            /// Try to get the team mates.
            /// </summary>
            /// <param name="player">The player to get the team mates of.</param>
            /// <param name="teamMates">The team mates array to fill.</param>
            /// <returns>If successful or not.</returns>
            public static bool TryGetTeamMates(this Player player, out Player[] teamMates)
            {
                return PhotonTeamsManager.Instance.TryGetTeamMatesOfPlayer(player, out teamMates);
            }
        }
    }
    
  • Duck
    Options
    @JohnTube Oh, alright. That makes sense. I haven't remembered that. I think I will leave the PhotonTeamsManager as he is. I will just change the order of my method (I'll call my coroutine in Start() ). I will rather change my script instead of modifying the PhotonTeamsManager class.

    Thank you very very much for your help! It helped a lot.