浏览代码

WIP: Custom Player Data

/main/staging/2021_Upgrade/Async_Refactor
UnityJacob 2 年前
当前提交
3ac97939
共有 9 个文件被更改,包括 792 次插入703 次删除
  1. 143
      Assets/Scripts/GameLobby/Game/GameManager.cs
  2. 72
      Assets/Scripts/GameLobby/Game/LocalLobby.cs
  3. 9
      Assets/Scripts/GameLobby/Game/LocalPlayer.cs
  4. 42
      Assets/Scripts/GameLobby/Lobby/LobbyConverters.cs
  5. 191
      Assets/Scripts/GameLobby/Lobby/LobbyManager.cs
  6. 512
      Assets/Scripts/GameLobby/Relay/RelayUtpClient.cs
  7. 491
      Assets/Scripts/GameLobby/Relay/RelayUtpSetup.cs
  8. 16
      Assets/Scripts/GameLobby/UI/LobbyEntryUI.cs
  9. 19
      Assets/Scripts/GameLobby/UI/LobbyUserListUI.cs

143
Assets/Scripts/GameLobby/Game/GameManager.cs


/// </summary>
public class GameManager : MonoBehaviour
{
public LocalLobby LocalLobby => m_LocalLobby;
public Action<GameState> onGameStateChanged;
public LocalLobbyList LobbyList { get; private set; } = new LocalLobbyList();

LocalPlayer m_LocalUser;
LocalLobby m_LocalLobby;
LobbySynchronizer m_LobbySynchronizer;
RelayUtpSetup m_RelaySetup;
RelayUtpClient m_RelayClient;
vivox.VivoxSetup m_VivoxSetup = new vivox.VivoxSetup();
[SerializeField]

{
m_lobbyColorFilter = (LobbyColor)color;
}
public async Task<LocalPlayer> AwaitLocalUserInitialization()
{

public async void CreateLobby(string name, bool isPrivate, int maxPlayers = 4)
{
var lobby = await LobbyManager.CreateLobbyAsync(
name,
maxPlayers,
isPrivate, m_LocalUser);
if (lobby != null)
try
try
{
LobbyConverters.RemoteToLocal(lobby, m_LocalLobby);
}
catch (Exception exception)
var lobby = await LobbyManager.CreateLobbyAsync(
name,
maxPlayers,
isPrivate, m_LocalUser);
if (lobby == null)
Debug.LogError(exception);
SetGameState(GameState.JoinMenu);
return;
LobbyConverters.RemoteToLocal(lobby, m_LocalLobby);
await LobbyManager.SubscribeToLobbyChanges(lobby.Id, m_LocalLobby);
else
catch (Exception exception)
Debug.LogError(exception);
var lobby = await LobbyManager.JoinLobbyAsync(lobbyID, lobbyCode,
m_LocalUser);
if (lobby != null)
try
var lobby = await LobbyManager.JoinLobbyAsync(lobbyID, lobbyCode,
m_LocalUser);
if (lobby == null)
{
SetGameState(GameState.JoinMenu);
return;
}
await LobbyManager.SubscribeToLobbyChanges(lobby.Id, m_LocalLobby);
else
catch (Exception exception)
Debug.LogError(exception);
public async void QueryLobbies()
{

else
LobbyList.QueryState.Value = LobbyQueryState.Error;
}
public async void QuickJoin()
{

public void SetLocalUserEmote(EmoteType emote)
{
SetUserEmote(m_LocalUser.ID.Value, emote);
SetUserEmote(m_LocalUser.Index.Value, emote);
public void SetUserEmote(string playerID, EmoteType emote)
public void SetUserEmote(int playerID, EmoteType emote)
var player = GetPlayerByID(playerID);
var player = m_LocalLobby.GetLocalPlayer(playerID);
}
}
SetUserStatus(m_LocalUser.ID.Value, status);
SetUserStatus(m_LocalUser.Index.Value, status);
public void SetUserStatus(string playerID, UserStatus status)
public void SetUserStatus(int playerID, UserStatus status)
var player = GetPlayerByID(playerID);
var player = m_LocalLobby.GetLocalPlayer(playerID);
m_LocalUser.UserStatus.Value = status;
player.UserStatus.Value = status;
}
public void CompleteCountDown()

m_setupInGame.StartNetworkedGame(m_LocalLobby, m_LocalUser);
}
#region Setup
async void Awake()

#pragma warning restore IDE0059
Application.wantsToQuit += OnWantToQuit;
m_LocalUser = new LocalPlayer("", 0, false, "LocalPlayer");
m_LocalLobby = new LocalLobby { LocalLobbyState = { Value = LobbyState.Lobby } };
m_LobbySynchronizer = new LobbySynchronizer(LobbyManager);
m_LocalUser = new LocalPlayer("", false, "LocalPlayer");
m_LocalLobby = new LocalLobby { LocalLobbyState = { Value = LobbyState.Lobby } };
await InitializeServices();
AuthenticatePlayer();
StartVivoxLogin();

m_LocalUser.ID.Value = localId;
m_LocalUser.DisplayName.Value = randomName;
m_LocalLobby.AddPlayer(m_LocalUser); // The local LocalPlayer object will be hooked into UI
m_LocalLobby.AddPlayer(0, m_LocalUser); // The local LocalPlayer object will be hooked into UI
LocalPlayer GetPlayerByID(string playerID)
{
if (m_LocalUser.ID.Value == playerID)
return m_LocalUser;
if (!m_LocalLobby.LocalPlayers.ContainsKey(playerID))
{
Debug.LogError($"No player by id : {playerID} in Local Lobby");
return null;
}
return m_LocalLobby.LocalPlayers[playerID];
}
LocalGameState == GameState.Lobby;
LocalGameState == GameState.Lobby;
LocalGameState = state;
Debug.Log($"Switching Game State to : {LocalGameState}");
if (isLeavingLobby)

void JoinLobby()
{
m_LobbySynchronizer.StartSynch(m_LocalLobby, m_LocalUser);
LocalUserToLobby();
StartVivoxJoin();
}

ResetLocalLobby();
m_LobbySynchronizer.EndSynch();
m_VivoxSetup.LeaveLobbyChannel();
if (m_RelaySetup != null)
{
Component.Destroy(m_RelaySetup);
m_RelaySetup = null;
}
if (m_RelayClient != null)
{
m_RelayClient.Dispose();
StartCoroutine(FinishCleanup());
// We need to delay slightly to give the disconnect message sent during Dispose time to reach the host, so that we don't destroy the connection without it being flushed first.
IEnumerator FinishCleanup()
{
yield return null;
Component.Destroy(m_RelayClient);
m_RelayClient = null;
}
}
}
void StartVivoxLogin()

}
}
void StartRelayConnection()
{
if (m_LocalUser.IsHost.Value)
m_RelaySetup = gameObject.AddComponent<RelayUtpSetupHost>();
else
m_RelaySetup = gameObject.AddComponent<RelayUtpSetupClient>();
m_RelaySetup.BeginRelayJoin(m_LocalLobby, m_LocalUser, OnRelayConnected);
void OnRelayConnected(bool didSucceed, RelayUtpClient client)
{
Component.Destroy(m_RelaySetup);
m_RelaySetup = null;
if (!didSucceed)
{
Debug.LogError("Relay connection failed! Retrying in 5s...");
StartCoroutine(RetryConnection(StartRelayConnection, m_LocalLobby.LobbyID.Value));
return;
}
m_RelayClient = client;
SetUserStatus(m_LocalUser.ID.Value, UserStatus.Lobby);
}
}
) // Ensure we didn't leave the lobby during this waiting period.
) // Ensure we didn't leave the lobby during this waiting period.
doConnection?.Invoke();
}

#endregion
}
}
}

72
Assets/Scripts/GameLobby/Game/LocalLobby.cs


[System.Serializable]
public class LocalLobby
{
public bool CanSetChanged = true;
public bool CanSetChanged = true;
public Action<Dictionary<string, LocalPlayer>> onUserListChanged;
public Action<Dictionary<int, LocalPlayer>> onUserListChanged;
Dictionary<string, LocalPlayer> m_LocalPlayers = new Dictionary<string, LocalPlayer>();
public Dictionary<string, LocalPlayer> LocalPlayers => m_LocalPlayers;
Dictionary<int, LocalPlayer> m_LocalPlayers = new Dictionary<int, LocalPlayer>();
public LocalLobby()
{
LastUpdated.Value = DateTime.Now.ToFileTimeUtc();
onUserListChanged = (dict) => { SetValueChanged(); };
LobbyID.onChanged = (s) => { SetValueChanged(); };
LobbyCode.onChanged = (s) => { SetValueChanged(); };
RelayCode.onChanged = (s) => { SetValueChanged(); };
RelayNGOCode.onChanged = (s) => { SetValueChanged(); };
RelayServer.onChanged = (s) => { SetValueChanged(); };
LobbyName.onChanged = (s) => { SetValueChanged(); };
LocalLobbyState.onChanged = (s) => { SetValueChanged(); };
Private.onChanged = (s) => { SetValueChanged(); };
AvailableSlots.onChanged = (s) => { SetValueChanged(); };
MaxPlayerCount.onChanged = (s) => { SetValueChanged(); };
LocalLobbyColor.onChanged = (s) => { SetValueChanged(); };
LastUpdated.onChanged = (s) => { SetValueChanged(); };
}
#endregion
#endregion.
public CallbackValue<string> LobbyID = new CallbackValue<string>();
public CallbackValue<string> LobbyCode = new CallbackValue<string>();

public CallbackValue<ServerAddress> RelayServer = new CallbackValue<ServerAddress>();
public CallbackValue<string> LobbyName = new CallbackValue<string>();
public CallbackValue<string> HostID = new CallbackValue<string>();
public CallbackValue<LobbyState> LocalLobbyState = new CallbackValue<LobbyState>();

MaxPlayerCount.Value = 4;
}
public LocalLobby()
{
LastUpdated.Value = DateTime.Now.ToFileTimeUtc();
}
/// <summary>
/// A locking mechanism for registering when something has looked at the Lobby to see if anything has changed
/// </summary>

if (CanSetChanged)
m_ValuesChanged = true;
}
public void AddPlayer(LocalPlayer user)
public LocalPlayer GetLocalPlayer(int index)
if (m_LocalPlayers.ContainsKey(user.ID.Value))
return m_LocalPlayers[index];
}
public void AddPlayer(int index, LocalPlayer user)
{
if (m_LocalPlayers.ContainsKey(index))
Debug.LogError($"Cant add player {user.DisplayName.Value}({user.ID.Value}) to lobby: {LobbyID.Value} twice");
Debug.LogError(
$"Cant add player {user.DisplayName.Value}({user.ID.Value}) to lobby: {LobbyID.Value} twice");
m_LocalPlayers.Add(user.ID.Value, user);
m_LocalPlayers.Add(user.Index.Value, user);
user.Emote.onChanged += EmoteChangedCallback;
user.DisplayName.onChanged += StringChangedCallback;
user.IsHost.onChanged += BoolChangedCallback;

}
public void RemovePlayer(LocalPlayer user)
public void RemovePlayer(int removePlayer)
if (!m_LocalPlayers.ContainsKey(user.ID.Value))
{
Debug.LogWarning($"Player {user.DisplayName.Value}({user.ID.Value}) does not exist in lobby: {LobbyID.Value}");
return;
}
m_LocalPlayers.Remove(user.ID.Value);
user.Emote.onChanged -= EmoteChangedCallback;
user.DisplayName.onChanged -= StringChangedCallback;
user.IsHost.onChanged -= BoolChangedCallback;
user.UserStatus.onChanged -= EmoteChangedCallback;
var player = m_LocalPlayers[removePlayer];
m_LocalPlayers.Remove(removePlayer);
player.Emote.onChanged -= EmoteChangedCallback;
player.DisplayName.onChanged -= StringChangedCallback;
player.IsHost.onChanged -= BoolChangedCallback;
player.UserStatus.onChanged -= EmoteChangedCallback;
void EmoteChangedCallback(EmoteType emote)
{

{
IsLobbyChanged();
}
public override string ToString()
{

return sb.ToString();
}
}
}
}

9
Assets/Scripts/GameLobby/Game/LocalPlayer.cs


public CallbackValue<EmoteType> Emote = new CallbackValue<EmoteType>(EmoteType.None);
public CallbackValue<UserStatus> UserStatus = new CallbackValue<UserStatus>((UserStatus)0);
public CallbackValue<string> ID = new CallbackValue<string>("");
public CallbackValue<int> Index = new CallbackValue<int>(0);
public LocalPlayer(string id, bool isHost, string displayName,
public DateTime LastUpdated
public LocalPlayer(string id, int index, bool isHost, string displayName,
Index.Value = index;
DisplayName.Value = displayName;
Emote.Value = emote;
UserStatus.Value = status;

Emote.Value = EmoteType.None;
UserStatus.Value = LobbyRelaySample.UserStatus.Menu;
}
}
}

42
Assets/Scripts/GameLobby/Lobby/LobbyConverters.cs


Debug.LogError("Remote lobby is null, cannot convert.");
return;
}
localLobby.CanSetChanged = allowSetLobbyChanged;
localLobby.LobbyID.Value = remoteLobby.Id;
localLobby.LobbyName.Value = remoteLobby.Name;

LocalPlayer localPlayer;
//See if we have the remote player locally already
if (localLobby.LocalPlayers.ContainsKey(player.Id))
{
localPlayer = localLobby.LocalPlayers[player.Id];
localPlayer.ID.Value = id;
localPlayer.DisplayName.Value = displayName;
localPlayer.Emote.Value = emote;
localPlayer.UserStatus.Value = userStatus;
}
else
{
localPlayer = new LocalPlayer(id, isHost, displayName, emote, userStatus);
localLobby.AddPlayer(localPlayer);
}
}
var disconnectedUsers = new List<LocalPlayer>();
foreach (var (id, player) in localLobby.LocalPlayers)
{
if (!remotePlayerIDs.Contains(id))
disconnectedUsers.Add(player);
}
foreach (var remove in disconnectedUsers)
{
localLobby.RemovePlayer(remove);
// if (localLobby.LocalPlayers.ContainsKey(player.Id))
// {
// localPlayer = localLobby.LocalPlayers[player.Id];
// localPlayer.ID.Value = id;
// localPlayer.DisplayName.Value = displayName;
// localPlayer.Emote.Value = emote;
// localPlayer.UserStatus.Value = userStatus;
// }
// else
// {
// localPlayer = new LocalPlayer(id, isHost, displayName, emote, userStatus);
// localLobby.AddPlayer(localPlayer);
// }
}
localLobby.CanSetChanged = true;

return data;
}
}
}
}

191
Assets/Scripts/GameLobby/Lobby/LobbyManager.cs


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LobbyRelaySample.ngo;
using Unity.Services.Authentication;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;

/// Manages one Lobby at a time, Only entry points to a lobby with ID is via JoinAsync, CreateAsync, and QuickJoinAsync
public class LobbyManager : IDisposable
{
const string key_RelayCode = nameof(LocalLobby.RelayCode);
const string key_RelayNGOCode = nameof(LocalLobby.RelayNGOCode);
const string key_LobbyState = nameof(LocalLobby.LocalLobbyState);
const string key_LobbyColor = nameof(LocalLobby.LocalLobbyColor);
const string key_Displayname = nameof(LocalPlayer.DisplayName);
const string key_Userstatus = nameof(LocalPlayer.UserStatus);
const string key_Emote = nameof(LocalPlayer.Emote);
//Once connected to a lobby, cache the local lobby object so we don't query for it for every lobby operation.
// (This assumes that the game will be actively in just one lobby at a time, though they could be in more on the service side.)

const int k_maxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters.
const int
k_maxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters.
public enum RequestType
{
Query = 0,

{
Dictionary<string, PlayerDataObject> data = new Dictionary<string, PlayerDataObject>();
var displayNameObject = new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, user.DisplayName.Value);
var displayNameObject =
new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, user.DisplayName.Value);
public async Task<Lobby> CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, LocalPlayer localUser)
public async Task<Lobby> CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate,
LocalPlayer localUser)
{
if (m_CreateCooldown.IsInCooldown)
{

string uasId = AuthenticationService.Instance.PlayerId;
var playerData = CreateInitialPlayerData(localUser);
if (!string.IsNullOrEmpty(lobbyId))
{
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions

m_CurrentLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);
}
return m_CurrentLobby;
}

//TODO Finish this
public async Task SubscribeToLobbyChanges(string lobbyID, LocalLobby localLobby)
{
m_LobbyEventCallbacks.LobbyChanged += changes =>
m_LobbyEventCallbacks.LobbyChanged += async changes =>
if (changes.LobbyDeleted)
{
await LeaveLobbyAsync();
return;
}
//Lobby Fields
if (changes.HostId.Changed)
localLobby.HostID.Value = changes.HostId.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)
PlayersChanged();
void LobbyChanged()
{
foreach (var change in changes.Data.Value)
{
var changedValue = change.Value;
if (changedValue.Changed)
{
var changedKey = change.Key;
ParseRemoteLobbyData(changedKey, changedValue.Value);
}
}
void ParseRemoteLobbyData(string changedKey, DataObject playerDataObject)
{
if (changedKey == key_RelayCode)
localLobby.RelayCode.Value = playerDataObject.Value;
if (changedKey == key_RelayNGOCode)
localLobby.RelayNGOCode.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.Equals(id);
var displayName = joinedPlayer.Data?.ContainsKey(key_Displayname) == true
? joinedPlayer.Data[key_Displayname].Value
: default;
var emote = joinedPlayer.Data?.ContainsKey(key_Emote) == true
? (EmoteType)int.Parse(joinedPlayer.Data[key_Emote].Value)
: EmoteType.None;
var userStatus = joinedPlayer.Data?.ContainsKey(key_Userstatus) == true
? (UserStatus)int.Parse(joinedPlayer.Data[key_Userstatus].Value)
: UserStatus.Lobby;
var newPlayer = new LocalPlayer(id, index, isHost, displayName, emote, userStatus);
localLobby.AddPlayer(playerChanges.PlayerIndex, newPlayer);
}
}
void PlayersLeft()
{
throw new NotImplementedException("Need to switch to Player lists");
}
void PlayersChanged()
{
foreach (var lobbyPlayerChanges in changes.PlayerData.Value)
{
var playerIndex = lobbyPlayerChanges.Key;
var localPlayer = localLobby.GetLocalPlayer(playerIndex);
var playerChanges = lobbyPlayerChanges.Value;
if (playerChanges.ConnectionInfoChanged.Changed)
{
var connectionInfo = playerChanges.ConnectionInfoChanged.Value;
Debug.Log(
$"ConnectionInfo for {localPlayer.DisplayName.Value} changed to {connectionInfo}");
}
if (playerChanges.LastUpdatedChanged.Changed)
{
var lastUpdated = playerChanges.LastUpdatedChanged.Value;
Debug.Log(
$"ConnectionInfo for {localPlayer.DisplayName.Value} changed to {lastUpdated}");
}
if (playerChanges.ChangedData.Changed)
{
foreach (var playerChange in playerChanges.ChangedData.Value)
{
var changedValue = playerChange.Value;
if (changedValue.Changed)
{
if (changedValue.Removed)
{
Debug.LogWarning("This Sample does not remove Values currently.");
continue;
}
var changedKey = playerChange.Key;
var playerDataObject = changedValue.Value;
ParseLocalPlayerData(changedKey, playerDataObject);
}
}
}
void ParseLocalPlayerData(string dataKey, PlayerDataObject playerDataObject)
{
localPlayer.DisplayName.Value = dataKey == key_Displayname
? playerDataObject.Value
: default;
localPlayer.Emote.Value = dataKey == key_Emote
? (EmoteType)int.Parse(playerDataObject.Value)
: EmoteType.None;
localPlayer.UserStatus.Value = dataKey == key_Userstatus
? (UserStatus)int.Parse(playerDataObject.Value)
: UserStatus.Lobby;
}
}
}
/// <summary>
/// Takes a lobby and a change applicator to update a given lobby in-place.
/// If LobbyDeleted is true, no changes will be applied and a warning will be logged.
/// </summary>
/// <param name="lobby">The lobby model to apply the changes to.</param>
//void ApplyToLobby(Models.Lobby lobby);
};
await LobbyService.Instance.SubscribeToLobbyEventsAsync(lobbyID, m_LobbyEventCallbacks);

{
// 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.
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

void StartHeartBeat()
{
#pragma warning disable 4014
#pragma warning disable 4014
#pragma warning restore 4014
#pragma warning restore 4014
async Task HeartBeatLoop()
{
while (m_CurrentLobby != null)

}
}
}
}
}

512
Assets/Scripts/GameLobby/Relay/RelayUtpClient.cs


using System.Collections.Generic;
using Unity.Networking.Transport;
using UnityEngine;
/*
public enum Approval { OK = 0, GameAlreadyStarted }
/// <summary>
/// This observes the local player and updates remote players over Relay when there are local changes, demonstrating basic data transfer over the Unity Transport (UTP).
/// Created after the connection to Relay has been confirmed.
/// If you are using the Unity Networking Package, you can use their Relay instead of building your own packets.
/// </summary>
public class RelayUtpClient : MonoBehaviour, IDisposable // This is a MonoBehaviour merely to have access to Update.
{
protected LocalPlayer m_localUser;
protected LocalLobby m_localLobby;
protected NetworkDriver m_networkDriver;
protected List<NetworkConnection> m_connections; // For clients, this has just one member, but for hosts it will have more.
protected bool m_IsRelayConnected { get { return m_localLobby.RelayServer != null; } }
public enum Approval { OK = 0, GameAlreadyStarted }
protected bool m_hasSentInitialMessage = false;
const float k_heartbeatPeriod = 5;
bool m_hasDisposed = false;
/// <summary>
/// This observes the local player and updates remote players over Relay when there are local changes, demonstrating basic data transfer over the Unity Transport (UTP).
/// Created after the connection to Relay has been confirmed.
/// If you are using the Unity Networking Package, you can use their Relay instead of building your own packets.
/// </summary>
public class RelayUtpClient : MonoBehaviour, IDisposable // This is a MonoBehaviour merely to have access to Update.
{
protected LocalPlayer m_localUser;
protected LocalLobby m_localLobby;
protected NetworkDriver m_networkDriver;
protected List<NetworkConnection> m_connections; // For clients, this has just one member, but for hosts it will have more.
protected bool m_IsRelayConnected { get { return m_localLobby.RelayServer != null; } }
protected enum MsgType { Ping = 0, NewPlayer, PlayerApprovalState, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }
protected bool m_hasSentInitialMessage = false;
const float k_heartbeatPeriod = 5;
bool m_hasDisposed = false;
public virtual void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LocalPlayer localUser, LocalLobby localLobby)
{
m_localUser = localUser;
m_localLobby = localLobby;
//m_localUser.onChanged += OnLocalChange;
m_networkDriver = networkDriver;
m_connections = connections;
Locator.Get.UpdateSlow.Subscribe(UpdateSlow, k_heartbeatPeriod);
}
protected virtual void Uninitialize()
{
//m_localUser.onChanged -= OnLocalChange;
Leave();
Locator.Get.UpdateSlow.Unsubscribe(UpdateSlow);
// Don't clean up the NetworkDriver here, or else our disconnect message won't get through to the host. The host will handle cleaning up the connection.
}
protected enum MsgType { Ping = 0, NewPlayer, PlayerApprovalState, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }
public void Dispose()
{
if (!m_hasDisposed)
{
Uninitialize();
m_hasDisposed = true;
}
}
public virtual void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LocalPlayer localUser, LocalLobby localLobby)
{
m_localUser = localUser;
m_localLobby = localLobby;
//m_localUser.onChanged += OnLocalChange;
m_networkDriver = networkDriver;
m_connections = connections;
Locator.Get.UpdateSlow.Subscribe(UpdateSlow, k_heartbeatPeriod);
}
protected virtual void Uninitialize()
{
//m_localUser.onChanged -= OnLocalChange;
Leave();
Locator.Get.UpdateSlow.Unsubscribe(UpdateSlow);
// Don't clean up the NetworkDriver here, or else our disconnect message won't get through to the host. The host will handle cleaning up the connection.
}
public void OnDestroy()
{
Dispose();
}
public void Dispose()
{
if (!m_hasDisposed)
{
Uninitialize();
m_hasDisposed = true;
}
}
private void OnLocalChange(LocalPlayer localUser)
{
if (m_connections.Count == 0) // This could be the case for the host alone in the lobby.
return;
foreach (NetworkConnection conn in m_connections)
DoUserUpdate(m_networkDriver, conn, m_localUser);
}
public void OnDestroy()
{
Dispose();
}
private void Update()
{
OnUpdate();
}
private void OnLocalChange(LocalPlayer localUser)
{
if (m_connections.Count == 0) // This could be the case for the host alone in the lobby.
return;
foreach (NetworkConnection conn in m_connections)
DoUserUpdate(m_networkDriver, conn, m_localUser);
}
/// <summary>
/// Clients need to send any data over UTP periodically, or else Relay will remove them from the allocation.
/// </summary>
private void UpdateSlow(float dt)
{
if (!m_IsRelayConnected) // If disconnected from Relay for some reason, we *want* this client to timeout.
return;
foreach (NetworkConnection connection in m_connections)
WriteByte(m_networkDriver, connection, "0", MsgType.Ping, 0); // The ID doesn't matter here, so send a minimal number of bytes.
}
private void Update()
{
OnUpdate();
}
protected virtual void OnUpdate()
{
if (!m_hasSentInitialMessage)
ReceiveNetworkEvents(m_networkDriver); // Just on the first execution, make sure to handle any events that accumulated while completing the connection.
m_networkDriver.ScheduleUpdate().Complete(); // This pumps all messages, which pings the Relay allocation and keeps it alive. It should be called no more often than ReceiveNetworkEvents.
ReceiveNetworkEvents(m_networkDriver); // This reads the message queue which was just updated.
if (!m_hasSentInitialMessage)
SendInitialMessage(m_networkDriver, m_connections[0]); // On a client, the 0th (and only) connection is to the host.
}
/// <summary>
/// Clients need to send any data over UTP periodically, or else Relay will remove them from the allocation.
/// </summary>
private void UpdateSlow(float dt)
{
if (!m_IsRelayConnected) // If disconnected from Relay for some reason, we *want* this client to timeout.
return;
foreach (NetworkConnection connection in m_connections)
WriteByte(m_networkDriver, connection, "0", MsgType.Ping, 0); // The ID doesn't matter here, so send a minimal number of bytes.
}
private void ReceiveNetworkEvents(NetworkDriver driver)
{
NetworkConnection conn;
DataStreamReader strm;
NetworkEvent.Type cmd;
while ((cmd = driver.PopEvent(out conn, out strm)) != NetworkEvent.Type.Empty) // NetworkConnection also has PopEvent, but NetworkDriver.PopEvent automatically includes new connections.
{
ProcessNetworkEvent(conn, strm, cmd);
}
}
protected virtual void OnUpdate()
{
if (!m_hasSentInitialMessage)
ReceiveNetworkEvents(m_networkDriver); // Just on the first execution, make sure to handle any events that accumulated while completing the connection.
m_networkDriver.ScheduleUpdate().Complete(); // This pumps all messages, which pings the Relay allocation and keeps it alive. It should be called no more often than ReceiveNetworkEvents.
ReceiveNetworkEvents(m_networkDriver); // This reads the message queue which was just updated.
if (!m_hasSentInitialMessage)
SendInitialMessage(m_networkDriver, m_connections[0]); // On a client, the 0th (and only) connection is to the host.
}
// See the Write* methods for the expected event format.
private void ProcessNetworkEvent(NetworkConnection conn, DataStreamReader strm, NetworkEvent.Type cmd)
{
if (cmd == NetworkEvent.Type.Data)
{
List<byte> msgContents = new List<byte>(ReadMessageContents(ref strm));
if (msgContents.Count < 3) // We require at a minimum - Message type, the length of the user ID, and the user ID.
return;
private void ReceiveNetworkEvents(NetworkDriver driver)
{
NetworkConnection conn;
DataStreamReader strm;
NetworkEvent.Type cmd;
while ((cmd = driver.PopEvent(out conn, out strm)) != NetworkEvent.Type.Empty) // NetworkConnection also has PopEvent, but NetworkDriver.PopEvent automatically includes new connections.
{
ProcessNetworkEvent(conn, strm, cmd);
}
}
MsgType msgType = (MsgType)msgContents[0];
int idLength = msgContents[1];
if (msgContents.Count < idLength + 2)
{ UnityEngine.Debug.LogWarning($"Relay client processed message of length {idLength}, but contents were of length {msgContents.Count}.");
return;
}
// See the Write* methods for the expected event format.
private void ProcessNetworkEvent(NetworkConnection conn, DataStreamReader strm, NetworkEvent.Type cmd)
{
if (cmd == NetworkEvent.Type.Data)
{
List<byte> msgContents = new List<byte>(ReadMessageContents(ref strm));
if (msgContents.Count < 3) // We require at a minimum - Message type, the length of the user ID, and the user ID.
return;
string id = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(2, idLength).ToArray());
if (!CanProcessDataEventFor(conn, msgType, id))
return;
msgContents.RemoveRange(0, 2 + idLength);
MsgType msgType = (MsgType)msgContents[0];
int idLength = msgContents[1];
if (msgContents.Count < idLength + 2)
{ UnityEngine.Debug.LogWarning($"Relay client processed message of length {idLength}, but contents were of length {msgContents.Count}.");
return;
}
if (msgType == MsgType.PlayerApprovalState)
{
Approval approval = (Approval)msgContents[0];
string id = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(2, idLength).ToArray());
if (!CanProcessDataEventFor(conn, msgType, id))
return;
msgContents.RemoveRange(0, 2 + idLength);
if (msgType == MsgType.PlayerApprovalState)
{
Approval approval = (Approval)msgContents[0];
}
else if (msgType == MsgType.PlayerName)
{
int nameLength = msgContents[0];
string name = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(1, nameLength).ToArray());
m_localLobby.LocalPlayers[id].DisplayName.Value = name;
}
else if (msgType == MsgType.Emote)
{
EmoteType emote = (EmoteType)msgContents[0];
m_localLobby.LocalPlayers[id].Emote.Value = emote;
}
else if (msgType == MsgType.ReadyState)
{
UserStatus status = (UserStatus)msgContents[0];
m_localLobby.LocalPlayers[id].UserStatus.Value = status;
}
else if (msgType == MsgType.StartCountdown)
GameManager.Instance.BeginCountdown();
else if (msgType == MsgType.CancelCountdown)
GameManager.Instance.CancelCountDown();
else if (msgType == MsgType.ConfirmInGame)
GameManager.Instance.ConfirmIngameState();
else if (msgType == MsgType.EndInGame)
GameManager.Instance.EndGame();
}
else if (msgType == MsgType.PlayerName)
{
int nameLength = msgContents[0];
string name = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(1, nameLength).ToArray());
m_localLobby.LocalPlayers[id].DisplayName.Value = name;
}
else if (msgType == MsgType.Emote)
{
EmoteType emote = (EmoteType)msgContents[0];
m_localLobby.LocalPlayers[id].Emote.Value = emote;
}
else if (msgType == MsgType.ReadyState)
{
UserStatus status = (UserStatus)msgContents[0];
m_localLobby.LocalPlayers[id].UserStatus.Value = status;
}
else if (msgType == MsgType.StartCountdown)
GameManager.Instance.BeginCountdown();
else if (msgType == MsgType.CancelCountdown)
GameManager.Instance.CancelCountDown();
else if (msgType == MsgType.ConfirmInGame)
GameManager.Instance.ConfirmIngameState();
else if (msgType == MsgType.EndInGame)
GameManager.Instance.EndGame();
ProcessNetworkEventDataAdditional(conn, msgType, id);
}
else if (cmd == NetworkEvent.Type.Disconnect)
ProcessDisconnectEvent(conn, strm);
}
ProcessNetworkEventDataAdditional(conn, msgType, id);
}
else if (cmd == NetworkEvent.Type.Disconnect)
ProcessDisconnectEvent(conn, strm);
}
protected virtual bool CanProcessDataEventFor(NetworkConnection conn, MsgType type, string id)
{
// Don't react to our own messages. Also, don't need to hold onto messages if the ID is absent; clients should be initialized and in the lobby before they send events.
// (Note that this enforces lobby membership before processing any events besides an approval request, so a client is unable to fully use Relay unless they're in the lobby.)
return true;// != m_localUser.ID && (m_localUser.IsApproved && m_localLobby.LocalPlayers.ContainsKey(id) || type == MsgType.PlayerApprovalState);
}
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id) { }
protected virtual void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)
{
// The host disconnected, and Relay does not support host migration. So, all clients should disconnect.
string msg;
if (m_IsRelayConnected)
msg = "The host disconnected! Leaving the lobby.";
else
msg = "Connection to host was lost. Leaving the lobby.";
protected virtual bool CanProcessDataEventFor(NetworkConnection conn, MsgType type, string id)
{
// Don't react to our own messages. Also, don't need to hold onto messages if the ID is absent; clients should be initialized and in the lobby before they send events.
// (Note that this enforces lobby membership before processing any events besides an approval request, so a client is unable to fully use Relay unless they're in the lobby.)
return true;// != m_localUser.ID && (m_localUser.IsApproved && m_localLobby.LocalPlayers.ContainsKey(id) || type == MsgType.PlayerApprovalState);
}
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id) { }
protected virtual void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)
{
// The host disconnected, and Relay does not support host migration. So, all clients should disconnect.
string msg;
if (m_IsRelayConnected)
msg = "The host disconnected! Leaving the lobby.";
else
msg = "Connection to host was lost. Leaving the lobby.";
Debug.LogError(msg);
LogHandlerSettings.Instance.SpawnErrorPopup( msg);
Leave();
GameManager.Instance.ChangeMenuState(GameState.JoinMenu);
}
Debug.LogError(msg);
LogHandlerSettings.Instance.SpawnErrorPopup( msg);
Leave();
GameManager.Instance.ChangeMenuState(GameState.JoinMenu);
}
/// <summary>
/// UTP uses raw pointers for efficiency (i.e. C-style byte* instead of byte[]).
/// ReadMessageContents converts them back to byte arrays, assuming the stream contains 1 byte for array length followed by contents.
/// Any actual pointer manipulation and so forth happens service-side, so we simply need to convert back to a byte array here.
/// </summary>
unsafe private byte[] ReadMessageContents(ref DataStreamReader strm) // unsafe is required to access the pointer.
{
int length = strm.Length;
byte[] bytes = new byte[length];
fixed (byte* ptr = bytes)
{
strm.ReadBytes(ptr, length);
}
return bytes;
}
/// <summary>
/// UTP uses raw pointers for efficiency (i.e. C-style byte* instead of byte[]).
/// ReadMessageContents converts them back to byte arrays, assuming the stream contains 1 byte for array length followed by contents.
/// Any actual pointer manipulation and so forth happens service-side, so we simply need to convert back to a byte array here.
/// </summary>
unsafe private byte[] ReadMessageContents(ref DataStreamReader strm) // unsafe is required to access the pointer.
{
int length = strm.Length;
byte[] bytes = new byte[length];
fixed (byte* ptr = bytes)
{
strm.ReadBytes(ptr, length);
}
return bytes;
}
/// <summary>
/// Once a client is connected, send a message out alerting the host.
/// </summary>
private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection)
{
WriteByte(driver, connection, m_localUser.ID.Value, MsgType.NewPlayer, 0);
m_hasSentInitialMessage = true;
}
private void OnApproved(NetworkDriver driver, NetworkConnection connection)
{
// m_localUser.IsApproved = true;
ForceFullUserUpdate(driver, connection, m_localUser);
}
/// <summary>
/// Once a client is connected, send a message out alerting the host.
/// </summary>
private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection)
{
WriteByte(driver, connection, m_localUser.ID.Value, MsgType.NewPlayer, 0);
m_hasSentInitialMessage = true;
}
private void OnApproved(NetworkDriver driver, NetworkConnection connection)
{
// m_localUser.IsApproved = true;
ForceFullUserUpdate(driver, connection, m_localUser);
}
/// <summary>
/// When player data is updated, send out events for just the data that actually changed.
/// </summary>
private void DoUserUpdate(NetworkDriver driver, NetworkConnection connection, LocalPlayer user)
{
/// <summary>
/// When player data is updated, send out events for just the data that actually changed.
/// </summary>
private void DoUserUpdate(NetworkDriver driver, NetworkConnection connection, LocalPlayer user)
{
}
/// <summary>
/// Sometimes (e.g. when a new player joins), we need to send out the full current state of this player.
/// </summary>
protected void ForceFullUserUpdate(NetworkDriver driver, NetworkConnection connection, LocalPlayer user)
{
// Note that it would be better to send a single message with the full state, but for the sake of shorter code we'll leave that out here.
WriteString(driver, connection, user.ID.Value, MsgType.PlayerName, user.DisplayName.Value);
WriteByte(driver, connection, user.ID.Value, MsgType.Emote, (byte)user.Emote.Value);
WriteByte(driver, connection, user.ID.Value, MsgType.ReadyState, (byte)user.UserStatus.Value);
}
}
/// <summary>
/// Sometimes (e.g. when a new player joins), we need to send out the full current state of this player.
/// </summary>
protected void ForceFullUserUpdate(NetworkDriver driver, NetworkConnection connection, LocalPlayer user)
{
// Note that it would be better to send a single message with the full state, but for the sake of shorter code we'll leave that out here.
WriteString(driver, connection, user.ID.Value, MsgType.PlayerName, user.DisplayName.Value);
WriteByte(driver, connection, user.ID.Value, MsgType.Emote, (byte)user.Emote.Value);
WriteByte(driver, connection, user.ID.Value, MsgType.ReadyState, (byte)user.UserStatus.Value);
}
/// <summary>
/// Write string data as: [1 byte: msgType] [1 byte: id length N] [N bytes: id] [1 byte: string length M] [M bytes: string]
/// </summary>
protected void WriteString(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, string str)
{
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(id);
byte[] strBytes = System.Text.Encoding.UTF8.GetBytes(str);
/// <summary>
/// Write string data as: [1 byte: msgType] [1 byte: id length N] [N bytes: id] [1 byte: string length M] [M bytes: string]
/// </summary>
protected void WriteString(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, string str)
{
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(id);
byte[] strBytes = System.Text.Encoding.UTF8.GetBytes(str);
List<byte> message = new List<byte>(idBytes.Length + strBytes.Length + 3); // Extra 3 bytes for the msgType plus the ID and message lengths.
message.Add((byte)msgType);
message.Add((byte)idBytes.Length);
message.AddRange(idBytes);
message.Add((byte)strBytes.Length);
message.AddRange(strBytes);
SendMessageData(driver, connection, message);
}
List<byte> message = new List<byte>(idBytes.Length + strBytes.Length + 3); // Extra 3 bytes for the msgType plus the ID and message lengths.
message.Add((byte)msgType);
message.Add((byte)idBytes.Length);
message.AddRange(idBytes);
message.Add((byte)strBytes.Length);
message.AddRange(strBytes);
SendMessageData(driver, connection, message);
}
/// <summary>
/// Write byte data as: [1 byte: msgType] [1 byte: id length N] [N bytes: id] [1 byte: data]
/// </summary>
protected void WriteByte(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, byte value)
{
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(id);
List<byte> message = new List<byte>(idBytes.Length + 3); // Extra 3 bytes for the msgType, ID length, and the byte value.
message.Add((byte)msgType);
message.Add((byte)idBytes.Length);
message.AddRange(idBytes);
message.Add(value);
SendMessageData(driver, connection, message);
}
/// <summary>
/// Write byte data as: [1 byte: msgType] [1 byte: id length N] [N bytes: id] [1 byte: data]
/// </summary>
protected void WriteByte(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, byte value)
{
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(id);
List<byte> message = new List<byte>(idBytes.Length + 3); // Extra 3 bytes for the msgType, ID length, and the byte value.
message.Add((byte)msgType);
message.Add((byte)idBytes.Length);
message.AddRange(idBytes);
message.Add(value);
SendMessageData(driver, connection, message);
}
private void SendMessageData(NetworkDriver driver, NetworkConnection connection, List<byte> message)
{
if (driver.BeginSend(connection, out var dataStream) == 0)
{
byte[] bytes = message.ToArray();
unsafe // Similarly to ReadMessageContents, our data must be converted to a pointer before being sent.
{
fixed (byte* bytesPtr = bytes)
{
dataStream.WriteBytes(bytesPtr, message.Count);
driver.EndSend(dataStream);
}
}
}
}
private void SendMessageData(NetworkDriver driver, NetworkConnection connection, List<byte> message)
{
if (driver.BeginSend(connection, out var dataStream) == 0)
{
byte[] bytes = message.ToArray();
unsafe // Similarly to ReadMessageContents, our data must be converted to a pointer before being sent.
{
fixed (byte* bytesPtr = bytes)
{
dataStream.WriteBytes(bytesPtr, message.Count);
driver.EndSend(dataStream);
}
}
}
}
/// <summary>
/// Disconnect from Relay, usually while leaving the lobby. (You can also call this elsewhere to see how Lobby will detect a Relay disconnect automatically.)
/// </summary>
public virtual void Leave()
{
foreach (NetworkConnection connection in m_connections)
// If the client calls Disconnect, the host might not become aware right away (depending on when the PubSub messages get pumped), so send a message over UTP instead.
WriteByte(m_networkDriver, connection, m_localUser.ID.Value, MsgType.PlayerDisconnect, 0);
m_localLobby.RelayServer = null;
}
}
/// <summary>
/// Disconnect from Relay, usually while leaving the lobby. (You can also call this elsewhere to see how Lobby will detect a Relay disconnect automatically.)
/// </summary>
public virtual void Leave()
{
foreach (NetworkConnection connection in m_connections)
// If the client calls Disconnect, the host might not become aware right away (depending on when the PubSub messages get pumped), so send a message over UTP instead.
WriteByte(m_networkDriver, connection, m_localUser.ID.Value, MsgType.PlayerDisconnect, 0);
m_localLobby.RelayServer = null;
}
}
*/

491
Assets/Scripts/GameLobby/Relay/RelayUtpSetup.cs


using Unity.Networking.Transport.Relay;
using Unity.Services.Relay.Models;
using UnityEngine;
/*
/// <summary>
/// Responsible for setting up a connection with Relay using Unity Transport (UTP). A Relay Allocation is created by the host, and then all players
/// bind UTP to that Allocation in order to send data to each other.
/// Must be a MonoBehaviour since the binding process doesn't have asynchronous callback options.
/// </summary>
public abstract class RelayUtpSetup : MonoBehaviour
{
protected bool m_isRelayConnected = false;
protected NetworkDriver m_networkDriver;
protected List<NetworkConnection> m_connections;
protected NetworkEndPoint m_endpointForServer;
protected LocalLobby m_localLobby;
protected LocalPlayer m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
/// <summary>
/// Responsible for setting up a connection with Relay using Unity Transport (UTP). A Relay Allocation is created by the host, and then all players
/// bind UTP to that Allocation in order to send data to each other.
/// Must be a MonoBehaviour since the binding process doesn't have asynchronous callback options.
/// </summary>
/*public abstract class RelayUtpSetup : MonoBehaviour
{
public static string AddressFromEndpoint(NetworkEndPoint endpoint)
{
return endpoint.Address.Split(':')[0];
}
protected bool m_isRelayConnected = false;
protected NetworkDriver m_networkDriver;
protected List<NetworkConnection> m_connections;
protected NetworkEndPoint m_endpointForServer;
protected LocalLobby m_localLobby;
protected LocalPlayer m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
public void BeginRelayJoin(
LocalLobby localLobby,
LocalPlayer localUser,
Action<bool, RelayUtpClient> onJoinComplete)
{
m_localLobby = localLobby;
m_localUser = localUser;
m_onJoinComplete = onJoinComplete;
JoinRelay();
}
public static string AddressFromEndpoint(NetworkEndPoint endpoint)
{
return endpoint.Address.Split(':')[0];
}
public void BeginRelayJoin(
LocalLobby localLobby,
LocalPlayer localUser,
Action<bool, RelayUtpClient> onJoinComplete)
{
m_localLobby = localLobby;
m_localUser = localUser;
m_onJoinComplete = onJoinComplete;
JoinRelay();
}
protected abstract void JoinRelay();
protected abstract void JoinRelay();
/// <summary>
/// Determine the server endpoint for connecting to the Relay server, for either an Allocation or a JoinAllocation.
/// If DTLS encryption is available, and there's a secure server endpoint available, use that as a secure connection. Otherwise, just connect to the Relay IP unsecured.
/// </summary>
public static NetworkEndPoint GetEndpointForAllocation(
List<RelayServerEndpoint> endpoints,
string ip,
int port,
out bool isSecure)
{
/// <summary>
/// Determine the server endpoint for connecting to the Relay server, for either an Allocation or a JoinAllocation.
/// If DTLS encryption is available, and there's a secure server endpoint available, use that as a secure connection. Otherwise, just connect to the Relay IP unsecured.
/// </summary>
public static NetworkEndPoint GetEndpointForAllocation(
List<RelayServerEndpoint> endpoints,
string ip,
int port,
out bool isSecure)
{
foreach (RelayServerEndpoint endpoint in endpoints)
{
if (endpoint.Secure && endpoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
{
isSecure = true;
return NetworkEndPoint.Parse(endpoint.Host, (ushort) endpoint.Port);
}
}
foreach (RelayServerEndpoint endpoint in endpoints)
{
if (endpoint.Secure && endpoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
{
isSecure = true;
return NetworkEndPoint.Parse(endpoint.Host, (ushort) endpoint.Port);
}
}
isSecure = false;
return NetworkEndPoint.Parse(ip, (ushort) port);
}
isSecure = false;
return NetworkEndPoint.Parse(ip, (ushort) port);
}
/// <summary>
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
/// </summary>
protected void BindToAllocation(
NetworkEndPoint serverEndpoint,
byte[] allocationIdBytes,
byte[] connectionDataBytes,
byte[] hostConnectionDataBytes,
byte[] hmacKeyBytes,
int connectionCapacity,
bool isSecure)
{
RelayAllocationId allocationId = ConvertAllocationIdBytes(allocationIdBytes);
RelayConnectionData connectionData = ConvertConnectionDataBytes(connectionDataBytes);
RelayConnectionData hostConnectionData = ConvertConnectionDataBytes(hostConnectionDataBytes);
RelayHMACKey key = ConvertHMACKeyBytes(hmacKeyBytes);
/// <summary>
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
/// </summary>
protected void BindToAllocation(
NetworkEndPoint serverEndpoint,
byte[] allocationIdBytes,
byte[] connectionDataBytes,
byte[] hostConnectionDataBytes,
byte[] hmacKeyBytes,
int connectionCapacity,
bool isSecure)
{
RelayAllocationId allocationId = ConvertAllocationIdBytes(allocationIdBytes);
RelayConnectionData connectionData = ConvertConnectionDataBytes(connectionDataBytes);
RelayConnectionData hostConnectionData = ConvertConnectionDataBytes(hostConnectionDataBytes);
RelayHMACKey key = ConvertHMACKeyBytes(hmacKeyBytes);
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData,
ref hostConnectionData, ref key, isSecure);
relayServerData
.ComputeNewNonce(); // For security, the nonce value sent when authenticating the allocation must be increased.
var networkSettings = new NetworkSettings();
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData,
ref hostConnectionData, ref key, isSecure);
relayServerData
.ComputeNewNonce(); // For security, the nonce value sent when authenticating the allocation must be increased.
var networkSettings = new NetworkSettings();
m_networkDriver = NetworkDriver.Create(networkSettings.WithRelayParameters(ref relayServerData));
m_connections = new List<NetworkConnection>(connectionCapacity);
m_networkDriver = NetworkDriver.Create(networkSettings.WithRelayParameters(ref relayServerData));
m_connections = new List<NetworkConnection>(connectionCapacity);
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0)
Debug.LogError("Failed to bind to Relay allocation.");
else
StartCoroutine(WaitForBindComplete());
}
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0)
Debug.LogError("Failed to bind to Relay allocation.");
else
StartCoroutine(WaitForBindComplete());
}
private IEnumerator WaitForBindComplete()
{
while (!m_networkDriver.Bound)
{
m_networkDriver.ScheduleUpdate().Complete();
yield return null;
}
private IEnumerator WaitForBindComplete()
{
while (!m_networkDriver.Bound)
{
m_networkDriver.ScheduleUpdate().Complete();
yield return null;
}
OnBindingComplete();
}
OnBindingComplete();
}
protected abstract void OnBindingComplete();
protected abstract void OnBindingComplete();
#region UTP uses pointers instead of managed arrays for performance reasons, so we use these helper functions to convert them.
#region UTP uses pointers instead of managed arrays for performance reasons, so we use these helper functions to convert them.
unsafe private static RelayAllocationId ConvertAllocationIdBytes(byte[] allocationIdBytes)
{
fixed (byte* ptr = allocationIdBytes)
{
return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length);
}
}
unsafe private static RelayAllocationId ConvertAllocationIdBytes(byte[] allocationIdBytes)
{
fixed (byte* ptr = allocationIdBytes)
{
return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length);
}
}
unsafe private static RelayConnectionData ConvertConnectionDataBytes(byte[] connectionData)
{
fixed (byte* ptr = connectionData)
{
return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length);
}
}
unsafe private static RelayConnectionData ConvertConnectionDataBytes(byte[] connectionData)
{
fixed (byte* ptr = connectionData)
{
return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length);
}
}
unsafe private static RelayHMACKey ConvertHMACKeyBytes(byte[] hmac)
{
fixed (byte* ptr = hmac)
{
return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length);
}
}
unsafe private static RelayHMACKey ConvertHMACKeyBytes(byte[] hmac)
{
fixed (byte* ptr = hmac)
{
return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length);
}
}
#endregion
#endregion
private void OnDestroy()
{
if (!m_isRelayConnected && m_networkDriver.IsCreated)
m_networkDriver.Dispose();
}
}
private void OnDestroy()
{
if (!m_isRelayConnected && m_networkDriver.IsCreated)
m_networkDriver.Dispose();
}
}
/// <summary>
/// Host logic: Request a new Allocation, and then both bind to it and request a join code. Once those are both complete, supply data back to the lobby.
/// </summary>
public class RelayUtpSetupHost : RelayUtpSetup
{
[Flags]
private enum JoinState
{
None = 0,
Bound = 1,
Joined = 2
}
/// <summary>
/// Host logic: Request a new Allocation, and then both bind to it and request a join code. Once those are both complete, supply data back to the lobby.
/// </summary>
public class RelayUtpSetupHost : RelayUtpSetup
{
[Flags]
private enum JoinState
{
None = 0,
Bound = 1,
Joined = 2
}
private JoinState m_joinState = JoinState.None;
private Allocation m_allocation;
private JoinState m_joinState = JoinState.None;
private Allocation m_allocation;
protected override void JoinRelay()
{
RelayAPIInterface.AllocateAsync(m_localLobby.MaxPlayerCount.Value, OnAllocation);
}
protected override void JoinRelay()
{
RelayAPIInterface.AllocateAsync(m_localLobby.MaxPlayerCount.Value, OnAllocation);
}
private void OnAllocation(Allocation allocation)
{
m_allocation = allocation;
RelayAPIInterface.GetJoinCodeAsync(allocation.AllocationId, OnRelayCode);
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(allocation.ServerEndpoints, allocation.RelayServer.IpV4,
allocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, allocation.AllocationIdBytes, allocation.ConnectionData,
allocation.ConnectionData, allocation.Key, 16, isSecure);
}
private void OnAllocation(Allocation allocation)
{
m_allocation = allocation;
RelayAPIInterface.GetJoinCodeAsync(allocation.AllocationId, OnRelayCode);
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(allocation.ServerEndpoints, allocation.RelayServer.IpV4,
allocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, allocation.AllocationIdBytes, allocation.ConnectionData,
allocation.ConnectionData, allocation.Key, 16, isSecure);
}
private void OnRelayCode(string relayCode)
{
m_localLobby.RelayCode.Value = relayCode;
m_localLobby.RelayServer.Value =
new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
m_joinState |= JoinState.Joined;
private void OnRelayCode(string relayCode)
{
m_localLobby.RelayCode.Value = relayCode;
m_localLobby.RelayServer.Value =
new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
m_joinState |= JoinState.Joined;
CheckForComplete();
CheckForComplete();
}
}
protected override void OnBindingComplete()
{
if (m_networkDriver.Listen() != 0)
{
Debug.LogError("RelayUtpSetupHost failed to bind to the Relay Allocation.");
m_onJoinComplete(false, null);
}
else
{
Debug.Log("Relay host is bound.");
m_joinState |= JoinState.Bound;
protected override void OnBindingComplete()
{
if (m_networkDriver.Listen() != 0)
{
Debug.LogError("RelayUtpSetupHost failed to bind to the Relay Allocation.");
m_onJoinComplete(false, null);
}
else
{
Debug.Log("Relay host is bound.");
m_joinState |= JoinState.Bound;
CheckForComplete();
CheckForComplete();
}
}
}
}
private void CheckForComplete()
{
if (m_joinState == (JoinState.Joined | JoinState.Bound) && this != null
) // this will equal null (i.e. this component has been destroyed) if the host left the lobby during the Relay connection sequence.
{
m_isRelayConnected = true;
RelayUtpHost host = gameObject.AddComponent<RelayUtpHost>();
host.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, host);
var connectionInfo = $"{m_allocation.RelayServer.IpV4}:{m_allocation.RelayServer.Port}";
// await LobbyManager.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode, connectionInfo);
}
}
}
private void CheckForComplete()
{
if (m_joinState == (JoinState.Joined | JoinState.Bound) && this != null
) // this will equal null (i.e. this component has been destroyed) if the host left the lobby during the Relay connection sequence.
{
m_isRelayConnected = true;
RelayUtpHost host = gameObject.AddComponent<RelayUtpHost>();
host.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, host);
var connectionInfo = $"{m_allocation.RelayServer.IpV4}:{m_allocation.RelayServer.Port}";
// await LobbyManager.Instance.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode, connectionInfo);
}
}
}
/// <summary>
/// Client logic: Wait until the Relay join code is retrieved from the lobby's shared data. Then, use that code to get the Allocation to bind to, and
/// then create a connection to the host.
/// </summary>
public class RelayUtpSetupClient : RelayUtpSetup
{
private JoinAllocation m_allocation;
/// <summary>
/// Client logic: Wait until the Relay join code is retrieved from the lobby's shared data. Then, use that code to get the Allocation to bind to, and
/// then create a connection to the host.
/// </summary>
public class RelayUtpSetupClient : RelayUtpSetup
{
private JoinAllocation m_allocation;
protected override void JoinRelay()
{
m_localLobby.RelayCode.onChanged += OnRelayChanged;
}
protected override void JoinRelay()
{
m_localLobby.RelayCode.onChanged += OnRelayChanged;
}
private void OnRelayChanged(string relayCode)
{
if (string.IsNullOrEmpty(relayCode))
return;
private void OnRelayChanged(string relayCode)
{
if (string.IsNullOrEmpty(relayCode))
return;
RelayAPIInterface.JoinAsync(m_localLobby.RelayCode.Value, OnJoin);
m_localLobby.RelayCode.onChanged -= OnRelayChanged;
}
RelayAPIInterface.JoinAsync(m_localLobby.RelayCode.Value, OnJoin);
m_localLobby.RelayCode.onChanged -= OnRelayChanged;
}
private void OnJoin(JoinAllocation joinAllocation)
{
if (joinAllocation == null || this == null
) // The returned JoinAllocation is null if allocation failed. this would be destroyed already if you quit the lobby while Relay is connecting.
return;
m_allocation = joinAllocation;
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(joinAllocation.ServerEndpoints,
joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, joinAllocation.AllocationIdBytes, joinAllocation.ConnectionData,
joinAllocation.HostConnectionData, joinAllocation.Key, 1, isSecure);
m_localLobby.RelayServer.Value =
new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
}
private void OnJoin(JoinAllocation joinAllocation)
{
if (joinAllocation == null || this == null
) // The returned JoinAllocation is null if allocation failed. this would be destroyed already if you quit the lobby while Relay is connecting.
return;
m_allocation = joinAllocation;
bool isSecure = false;
m_endpointForServer = GetEndpointForAllocation(joinAllocation.ServerEndpoints,
joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port, out isSecure);
BindToAllocation(m_endpointForServer, joinAllocation.AllocationIdBytes, joinAllocation.ConnectionData,
joinAllocation.HostConnectionData, joinAllocation.Key, 1, isSecure);
m_localLobby.RelayServer.Value =
new ServerAddress(AddressFromEndpoint(m_endpointForServer), m_endpointForServer.Port);
}
protected override void OnBindingComplete()
{
protected override void OnBindingComplete()
{
ConnectToServer();
ConnectToServer();
}
}
private async Task ConnectToServer()
{
// Once the client is bound to the Relay server, send a connection request.
m_connections.Add(m_networkDriver.Connect(m_endpointForServer));
while (m_networkDriver.GetConnectionState(m_connections[0]) == NetworkConnection.State.Connecting)
{
m_networkDriver.ScheduleUpdate().Complete();
await Task.Delay(100);
}
private async Task ConnectToServer()
{
// Once the client is bound to the Relay server, send a connection request.
m_connections.Add(m_networkDriver.Connect(m_endpointForServer));
while (m_networkDriver.GetConnectionState(m_connections[0]) == NetworkConnection.State.Connecting)
{
m_networkDriver.ScheduleUpdate().Complete();
await Task.Delay(100);
}
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
{
Debug.LogError("RelayUtpSetupClient could not connect to the host.");
m_onJoinComplete(false, null);
}
else if (this != null)
{
m_isRelayConnected = true;
RelayUtpClient client = gameObject.AddComponent<RelayUtpClient>();
client.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, client);
var connectionInfo = $"{m_allocation.RelayServer.IpV4}:{m_allocation.RelayServer.Port}";
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
{
Debug.LogError("RelayUtpSetupClient could not connect to the host.");
m_onJoinComplete(false, null);
}
else if (this != null)
{
m_isRelayConnected = true;
RelayUtpClient client = gameObject.AddComponent<RelayUtpClient>();
client.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby);
m_onJoinComplete(true, client);
var connectionInfo = $"{m_allocation.RelayServer.IpV4}:{m_allocation.RelayServer.Port}";
await GameManager.Instance.LobbyManager.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode.Value,connectionInfo);
}
}
}
}
await GameManager.Instance.LobbyManager.UpdatePlayerRelayInfoAsync(m_allocation.AllocationId.ToString(), m_localLobby.RelayCode.Value,connectionInfo);
}
}
}
}*/

16
Assets/Scripts/GameLobby/UI/LobbyEntryUI.cs


/// </summary>
public void OnLobbyClicked()
{
onLobbyPressed.Invoke(m_Lobby);
onLobbyPressed.Invoke(m_Lobby);
}
public void SetLobby(LocalLobby lobby)

SetLobbyCount(m_Lobby.LocalPlayers);
SetLobbyCount(m_Lobby.PlayerCount);
m_Lobby.onUserListChanged += SetLobbyCount;
m_Lobby.onUserListChanged += (dict) =>
{
SetLobbyCount(dict.Count);
};
}
void SetLobbyname(string lobbyName)

void SetLobbyCount(Dictionary<string, LocalPlayer> userList)
void SetLobbyCount(int count)
lobbyCountText.SetText($"{userList.Count}/{m_Lobby.MaxPlayerCount.Value}");
lobbyCountText.SetText($"{count}/{m_Lobby.MaxPlayerCount.Value}");
}
}

19
Assets/Scripts/GameLobby/UI/LobbyUserListUI.cs


{
[SerializeField]
List<InLobbyUserUI> m_UserUIObjects = new List<InLobbyUserUI>();
List<string>
m_CurrentUsers =
new List<string>(); // Just for keeping track more easily of which users are already displayed.
public override void Start()
{

}
void OnUsersChanged(Dictionary<string, LocalPlayer> newUserDict)
void OnUsersChanged(Dictionary<int, LocalPlayer> newUserDict)
for (int id = m_CurrentUsers.Count - 1;
for (int id = m_UserUIObjects.Count - 1;
string userId = m_CurrentUsers[id];
string userId = m_UserUIObjects[id];
if (!newUserDict.ContainsKey(userId))
{
foreach (var ui in m_UserUIObjects)

}
}
foreach (var lobbyUserKvp in newUserDict) // If there are new players, we need to hook them into the UI.
// If there are new players, we need to hook them into the UI.
foreach (var lobbyUserKvp in newUserDict)
{
if (m_CurrentUsers.Contains(lobbyUserKvp.Key))
continue;

}
}
void OnUserLeft(string userID)
void OnUserLeft(int userID)
if (!m_CurrentUsers.Contains(userID))
return;
m_CurrentUsers.Remove(userID);
m_UserUIObjects.RemoveAt(userID);
}
}
}
正在加载...
取消
保存