유니티 LobbyManager, GameManager etc 스크립트 복습

본 내용은 정확하지 않을 수 있으며 문제가 생길시 수정하도록 하겠습니다.

 

스터디를 복습하기 위해 적어둔 글임을 강조합니다.


LobbyManager

로비를 만드는 중추 역할을 합니다.


CreateInitialPlayerData

Dictionary<string, PlayerDataObject> CreateInitialPlayerData(LocalPlayer user)
{
    Dictionary<string, PlayerDataObject> data = new Dictionary<string, PlayerDataObject>();

    var displayNameObject =
        new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, user.DisplayName.Value);
    data.Add("DisplayName", displayNameObject);
    return data;
}

Player의 VisibilityOption과 Player의 Name옵션을 Dictinary에 담아서 return합니다.


CreateLobbyAsync

public async Task<Lobby> CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate,
    LocalPlayer localUser)
{
    if (m_CreateCooldown.IsCoolingDown)
    {
        Debug.LogWarning("Create Lobby hit the rate limit.");
        return null;
    }

    await m_CreateCooldown.QueueUntilCooldown();

    try
    {
        string userId = AuthenticationService.Instance.PlayerId;

        CreateLobbyOptions createOptions = new CreateLobbyOptions
        {
            IsPrivate = isPrivate,
            Player = new Player(id: userId, data: CreateInitialPlayerData(localUser))
        };
        _CurrentLobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions);
        StartHeartBeat();

        return _CurrentLobby;
    }
    catch (Exception ex)
    {
        Debug.LogError($"Lobby Create failed:\n{ex}");
        return null;
    }
}

GameManager로부터 lobbyname, maxPlayer, isPrivate, localPlayer(해당 클라이언트)값을 받아서 방을 만듦. 방을 만들 때에는 옵션을 설정해주어야 하는데 CreateLobbyOptions으로 설정할 수 있으며 isPrivate과 Player(방을 만드는 클라이언트의 정보)을 설정해주어야합니다.

그 후 현재 로비에 LobbyService.Instance.CreateLobbyAsync를 통해 서버에 만들어달라고 요청을 하고 이때도 마찬가지로 lobbyname, maxPlayer, 그리고 아까 만든 방의 옵션을 담아서 만든 후 return해줍니다.


JoinLobbyAsync

public async Task<Lobby> JoinLobbyAsync(string lobbyId, string lobbyCode, LocalPlayer localUser)
{
    if (m_JoinCooldown.IsCoolingDown ||
        (lobbyId == null && lobbyCode == null))
    {
        return null;
    }

    await m_JoinCooldown.QueueUntilCooldown();

    string userId = AuthenticationService.Instance.PlayerId;
    var playerData = CreateInitialPlayerData(localUser);

    if (!string.IsNullOrEmpty(lobbyId))
    {
        JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions
            { Player = new Player(id: userId, data: playerData) };
        _CurrentLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions);
    }
    else
    {
        JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions
            { Player = new Player(id: userId, data: playerData) };
        _CurrentLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);
    }

    return _CurrentLobby;
}

들어가고자 하는 lobbyId와 lobbyCode, 들어가려는 LocalPlayer(클라이언트)를 받습니다.


그 후 클라이언트의 인증Id를 받아오고 CreateInitialPlayerData(LocalPlayer)를 이용하여 PlayerData를 만듭니다.

lobbyId나 lobbyCode 둘 중 하나를 이용하여 방을 탐색하는데 if (!string.IsNullOrEmpty(lobbyId))를 이용하여 lobbyId가 있는 경우 

JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions
{ 
	Player = new Player(id: userId, data: playerData) 
};
_CurrentLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions)

을 이용하며 그 반대는

 JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions
{ 
	Player = new Player(id: userId, data: playerData) 
};
_CurrentLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);

를 이용합니다.


QuickJoinLobbyAsync

Lobby에서 나갈 때 호출되는 함수이며 나가고자 하는 LocalPlayer를 받는다. 그 후 똑같이 인증된 PlayreId를 받아오고

var joinRequest = new QuickJoinLobbyOptions
{
      Filter = filters,
      Player = new Player(id: uasId, data: CreateInitialPlayerData(localUser))
};

를 이용하여 

return _CurrentLobby = await LobbyService.Instance.QuickJoinLobbyAsync(joinRequest)

를 반환합니다.


UpdateLobbyDataAsync

LocalLobby의 Data변화를 감지하여 변경해주는 역할을 합니다.

public async Task UpdateLobbyDataAsync(Dictionary<string, string> data)
{
    if (!InLobby())
        return;

    Dictionary<string, DataObject> dataCurr = _CurrentLobby.Data ?? new Dictionary<string, DataObject>();

    var shouldLock = false;
    foreach (var dataNew in data)
    {
        // Special case: We want to be able to filter on our color data, so we need to supply an arbitrary index to retrieve later. Uses N# for numerics, instead of S# for strings.
        DataObject.IndexOptions index = dataNew.Key == "LocalLobbyColor" ? DataObject.IndexOptions.N1 : 0;
        DataObject dataObj = new DataObject(DataObject.VisibilityOptions.Public, dataNew.Value,
            index); // Public so that when we request the list of lobbies, we can get info about them for filtering.
        if (dataCurr.ContainsKey(dataNew.Key))
            dataCurr[dataNew.Key] = dataObj;
        else
            dataCurr.Add(dataNew.Key, dataObj);

        //Special Use: Get the state of the Local lobby so we can lock it from appearing in queries if it's not in the "Lobby" LocalLobbyState
        if (dataNew.Key == "LocalLobbyState")
        {
            Enum.TryParse(dataNew.Value, out LobbyState lobbyState);
            shouldLock = lobbyState != LobbyState.Lobby;
        }
    }

    //We can still update the latest data to send to the service, but we will not send multiple UpdateLobbySyncCalls
    if (m_UpdateLobbyCooldown.TaskQueued)
        return;
    await m_UpdateLobbyCooldown.QueueUntilCooldown();

    UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = dataCurr, IsLocked = shouldLock };
    _CurrentLobby = await LobbyService.Instance.UpdateLobbyAsync(_CurrentLobby.Id, updateOptions);
}
async void SendLocalLobbyData()
{
     await LobbyManager.UpdateLobbyDataAsync(LobbyConverters.LocalToRemoteLobbyData(_LocalLobby));
}

GameManager에서 위와같이 호출합니다.

 

그럼 LobbyManager에서의 UpdateLobbyDataAsync함수는 LocalLobby(data)의 정보를 받고

 

public async Task UpdateLobbyDataAsync(Dictionary<string, string> data)

현재 CurrentLobby가 존재한다면 CurrentLobby.Data를 그대로 가져오고 아니면 새로 딕셔너리를 하나 만듭니다.

Dictionary<string, DataObject> dataCurr = _CurrentLobby.Data ?? new Dictionary<string, DataObject>();

새로운 데이터(DataNew)를 가공하여 DataObject로 만든 다음 dataCurr에 집어넣습니다.

DataObject dataObj = new DataObject(DataObject.VisibilityOptions.Public, dataNew.Value,
    index);
if (dataCurr.ContainsKey(dataNew.Key))
    dataCurr[dataNew.Key] = dataObj;

 dataNew는

foreach (var dataNew in data)

를 통해 얻어온 데이터입니다.
 
현재 CurrentLobby가 없다면

dataCurr.Add(dataNew.Key, dataObj);

새로 만듭니다.


UpdatePlayerDataAsync

localPlayer의 변경을 적용해주는 역할입니다.

public async Task UpdatePlayerDataAsync(Dictionary<string, string> data)
{
    if (!InLobby())
        return;

    string playerId = AuthenticationService.Instance.PlayerId;
    Dictionary<string, PlayerDataObject> dataCurr = new Dictionary<string, PlayerDataObject>();
    foreach (var dataNew in data)
    {
        PlayerDataObject dataObj = new PlayerDataObject(visibility: PlayerDataObject.VisibilityOptions.Member,
            value: dataNew.Value);
        if (dataCurr.ContainsKey(dataNew.Key))
            dataCurr[dataNew.Key] = dataObj;
        else
            dataCurr.Add(dataNew.Key, dataObj);
    }

    if (m_UpdatePlayerCooldown.TaskQueued)
        return;
    await m_UpdatePlayerCooldown.QueueUntilCooldown();

    UpdatePlayerOptions updateOptions = new UpdatePlayerOptions
    {
        Data = dataCurr,
        AllocationId = null,
        ConnectionInfo = null
    };
    _CurrentLobby = await LobbyService.Instance.UpdatePlayerAsync(_CurrentLobby.Id, playerId, updateOptions);
}

BindLocalLobbyToRemote

기존 LocalLobby에 변화를 감지하여 새로운 옵션들을 서버에 연결시켜주는 역할입니다.

public async Task BindLocalLobbyToRemote(string lobbyID, LocalLobby localLobby)
{
    _LobbyEventCallbacks.LobbyChanged += async changes =>
    {
        if (changes.LobbyDeleted)
        {
            await LeaveLobbyAsync();
            return;
        }

        //Lobby Fields
        if (changes.Name.Changed)
            localLobby.LobbyName.Value = changes.Name.Value;
        if (changes.HostId.Changed)
            localLobby.HostID.Value = changes.HostId.Value;
        if (changes.IsPrivate.Changed)
            localLobby.Private.Value = changes.IsPrivate.Value;
        if (changes.IsLocked.Changed)
            localLobby.Locked.Value = changes.IsLocked.Value;
        if (changes.AvailableSlots.Changed)
            localLobby.AvailableSlots.Value = changes.AvailableSlots.Value;
        if (changes.MaxPlayers.Changed)
            localLobby.MaxPlayerCount.Value = changes.MaxPlayers.Value;

        if (changes.LastUpdated.Changed)
            localLobby.LastUpdated.Value = changes.LastUpdated.Value.ToFileTimeUtc();

        //Custom Lobby Fields
        if (changes.Data.Changed)
            LobbyChanged();

        if (changes.PlayerJoined.Changed)
            PlayersJoined();

        if (changes.PlayerLeft.Changed)
            PlayersLeft();

        if (changes.PlayerData.Changed)
            PlayerDataChanged();

        void LobbyChanged()
        {
            foreach (var change in changes.Data.Value)
            {
                var changedValue = change.Value;
                var changedKey = change.Key;

                if (changedValue.Removed)
                {
                    RemoveCustomLobbyData(changedKey);
                }

                if (changedValue.Changed)
                {
                    ParseCustomLobbyData(changedKey, changedValue.Value);
                }
            }

            void RemoveCustomLobbyData(string changedKey)
            {
                if (changedKey == key_RelayCode)
                    localLobby.RelayCode.Value = "";
            }

            void ParseCustomLobbyData(string changedKey, DataObject playerDataObject)
            {
                if (changedKey == key_RelayCode)
                    localLobby.RelayCode.Value = playerDataObject.Value;

                if (changedKey == key_LobbyState)
                    localLobby.LocalLobbyState.Value = (LobbyState)int.Parse(playerDataObject.Value);

                if (changedKey == key_LobbyColor)
                    localLobby.LocalLobbyColor.Value = (LobbyColor)int.Parse(playerDataObject.Value);
            }
        }

        void PlayersJoined()
        {
            foreach (var playerChanges in changes.PlayerJoined.Value)
            {
                Player joinedPlayer = playerChanges.Player;

                var id = joinedPlayer.Id;
                var index = playerChanges.PlayerIndex;
                var isHost = localLobby.HostID.Value == id;

                var newPlayer = new LocalPlayer(id, index, isHost);

                foreach (var dataEntry in joinedPlayer.Data)
                {
                    var dataObject = dataEntry.Value;
                    ParseCustomPlayerData(newPlayer, dataEntry.Key, dataObject.Value);
                }

                localLobby.AddPlayer(index, newPlayer);
            }
        }

        void PlayersLeft()
        {
            foreach (var leftPlayerIndex in changes.PlayerLeft.Value)
            {
                localLobby.RemovePlayer(leftPlayerIndex);
            }
        }

        void PlayerDataChanged()
        {
            foreach (var lobbyPlayerChanges in changes.PlayerData.Value)
            {
                var playerIndex = lobbyPlayerChanges.Key;
                var localPlayer = localLobby.GetLocalPlayer(playerIndex);
                if (localPlayer == null)
                    continue;
                var playerChanges = lobbyPlayerChanges.Value;
                if (playerChanges.ConnectionInfoChanged.Changed)
                {
                    var connectionInfo = playerChanges.ConnectionInfoChanged.Value;
                    Debug.Log(
                        $"ConnectionInfo for player {playerIndex} changed to {connectionInfo}");
                }

                if (playerChanges.LastUpdatedChanged.Changed) { }

                //There are changes on the Player
                if (playerChanges.ChangedData.Changed)
                {
                    foreach (var playerChange in playerChanges.ChangedData.Value)
                    {
                        var changedValue = playerChange.Value;

                        //There are changes on some of the changes in the player list of changes

                        if (changedValue.Changed)
                        {
                            if (changedValue.Removed)
                            {
                                Debug.LogWarning("This Sample does not remove Player Values currently.");
                                continue;
                            }

                            var playerDataObject = changedValue.Value;
                            ParseCustomPlayerData(localPlayer, playerChange.Key, playerDataObject.Value);
                        }
                    }
                }
            }
        }
    };
    await LobbyService.Instance.SubscribeToLobbyEventsAsync(lobbyID, _LobbyEventCallbacks);
}

LocalLobby를 만드는 이유

로비에 연결된 후, 모든 로비 동작에 대해 로컬 로비 객체를 캐시하여 반복적인 쿼리를 방지합니다.

즉 불필요한 데이터 전송을 막기 위함입니다.