본 내용은 정확하지 않을 수 있으며 문제가 생길시 수정하도록 하겠습니다.
스터디를 복습하기 위해 적어둔 글임을 강조합니다.
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를 만드는 이유
로비에 연결된 후, 모든 로비 동작에 대해 로컬 로비 객체를 캐시하여 반복적인 쿼리를 방지합니다.
즉 불필요한 데이터 전송을 막기 위함입니다.