浏览代码

Adding inclusion of initial player data to lobby creation/join. Adding logic for Relay hosts to ferry network events from client to client. A bit of progress on changing the flow of user state to account for Relay, but that's still mostly in-progress. Adding client heartbeat to keep the UTP connection alive.

/main/staging
nathaniel.buck@unity3d.com 3 年前
当前提交
fbb0cb37
共有 11 个文件被更改,包括 229 次插入194 次删除
  1. 33
      Assets/Prefabs/UI/PlayerInteractionPanel.prefab
  2. 13
      Assets/Scripts/Entities/GameStateManager.cs
  3. 13
      Assets/Scripts/Entities/LobbyUser.cs
  4. 12
      Assets/Scripts/Lobby/LobbyAPIInterface.cs
  5. 18
      Assets/Scripts/Lobby/LobbyAsyncRequests.cs
  6. 39
      Assets/Scripts/Relay/RelayUtpClient.cs
  7. 38
      Assets/Scripts/Relay/RelayUtpHost.cs
  8. 2
      Assets/Scripts/Relay/RelayUtpSetup.cs
  9. 233
      Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs
  10. 10
      Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs
  11. 12
      Assets/Scripts/UI/InLobbyUserUI.cs

33
Assets/Prefabs/UI/PlayerInteractionPanel.prefab


m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8979361099148208042}
- {fileID: 1135759803389522016}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3210254045315593125}
m_RootOrder: 0

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 506300669760714432}
m_Father: {fileID: 759037105273515699}

m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7173726187766461959}
m_Father: {fileID: 1611213509401803489}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1309712272618711075}
m_RootOrder: 0

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1135759803389522016}
m_RootOrder: 0

m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1611213509401803489}
m_RootOrder: 0

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1770059644204541294}
m_Father: {fileID: 6919891912281880971}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 372360833073125696}
m_Father: {fileID: 2056817220376623591}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6004833744538984969}
m_RootOrder: 0

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3214500912641965185}
m_RootOrder: 0

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3981267961258646419}
m_Father: {fileID: 2056817220376623591}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2056817220376623591}
- {fileID: 4558362294547660329}

m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6004833744538984969}
m_Father: {fileID: 4558362294547660329}

m_PersistentCalls:
m_Calls: []
showing: 1
ShowThisWhen: 1
Permissions: 2147483647
ShowThisWhen: 2
Permissions: -1
--- !u!1 &5340674743026918435
GameObject:
m_ObjectHideFlags: 0

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 759037105273515699}
- {fileID: 1108938163892239274}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7775606550556000280}
m_Father: {fileID: 2056817220376623591}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1919168897190896396}
m_RootOrder: 0

m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1611213509401803489}
- {fileID: 7303921398628037483}

m_PersistentCalls:
m_Calls: []
showing: 0
ShowThisWhen: 12
Permissions: 2147483647
ShowThisWhen: 4
Permissions: -1
--- !u!1 &6502491834776149889
GameObject:
m_ObjectHideFlags: 0

m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3210254045315593125}
- {fileID: 3214500912641965185}

m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1309712272618711075}
m_Father: {fileID: 4558362294547660329}

m_PersistentCalls:
m_Calls: []
showing: 0
ShowThisWhen: 16
ShowThisWhen: 8
Permissions: 2
--- !u!114 &900710893788416922
MonoBehaviour:

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8905267601204628791}
m_Father: {fileID: 0}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1754486100769227438}
m_Father: {fileID: 2056817220376623591}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6664205945102926799}
m_RootOrder: 0

13
Assets/Scripts/Entities/GameStateManager.cs


else if (type == MessageType.CreateLobbyRequest)
{
var createLobbyData = (LocalLobby)msg;
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, (r) =>
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, m_localUser, (r) =>
{
lobby.ToLocalLobby.Convert(r, m_localLobby, m_localUser);
OnCreatedLobby();

{
LobbyInfo lobbyInfo = (LobbyInfo)msg;
LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode, (r) =>
LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode, m_localUser, (r) =>
{
lobby.ToLocalLobby.Convert(r, m_localLobby, m_localUser);
OnJoinedLobby();

LobbyAsyncRequests.Instance.BeginTracking(m_localLobby.LobbyID);
m_lobbyContentHeartbeat.BeginTracking(m_localLobby, m_localUser);
SetUserLobbyState();
Dictionary<string, string> displayNameData = new Dictionary<string, string>();
displayNameData.Add("DisplayName", m_localUser.DisplayName);
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(displayNameData, null);
StartRelayConnection();
}

m_localLobby.CountDownTime = m_localLobby.TargetEndTime.Subtract(DateTime.Now).Seconds;
}
m_localUser.UserStatus = UserStatus.Connecting;
m_localUser.UserStatus = UserStatus.InGame;
m_localLobby.State = LobbyState.InGame;
// TODO TRANSPORT: Move Relay Join to Pre-Countdown, and do connection and health checks before counting down for the game start.

void ToLobby()
void ToLobby() // TODO: What to make of this?
m_localLobby.RelayServer = null;
m_localLobby.RelayCode = null;
SetUserLobbyState();
}

13
Assets/Scripts/Entities/LobbyUser.cs


[Flags]
public enum UserStatus
{
Lobby = 1, // Connected to lobby, not ready yet
Ready = 4, // User clicked ready (Note that 2 is missing; some flags have been removed over time, but we want any serialized values to be unaffected.)
Connecting = 8, // User sent join request through Relay
Connected = 16, // User connected through Relay
Menu = 32, // User is in a menu, external to the lobby
None = 0,
Connecting = 1, // User has joined a lobby but has not yet connected to Relay.
Lobby = 2, // User is in a lobby and connected to Relay.
Ready = 4, // User has selected the ready button, to ready for the "game" to start.
InGame = 8, // User is part of a "game" that has started.
Menu = 16 // User is not in a lobby, in one of the main menus.
}
/// <summary>

/// </summary>
[Flags]
public enum UserMembers { IsHost = 1, DisplayName = 2, Emote = 4, ID = 8, UserStatus = 16 }
private UserMembers m_lastChanged;
private UserMembers m_lastChanged;// TODO: Is the following necessary to prompt an initial update, or do I need to adjust RelayUtpClient.DoUserUpdate to force all messages on the first go? (Or maybe just have some separate call to send full state as one message to start with? Although it should only be name...) = (UserMembers)(-1); // All values are set as changed to begin with, for initial updates.
public UserMembers LastChanged => m_lastChanged;
bool m_isHost;

12
Assets/Scripts/Lobby/LobbyAPIInterface.cs


private const int k_maxLobbiesToShow = 64;
public static void CreateLobbyAsync(string requesterUASId, string lobbyName, int maxPlayers, bool isPrivate, Action<Response<Lobby>> onComplete)
public static void CreateLobbyAsync(string requesterUASId, string lobbyName, int maxPlayers, bool isPrivate, Dictionary<string, PlayerDataObject> localUserData, Action<Response<Lobby>> onComplete)
player: new Player(requesterUASId),
player: new Player(id: requesterUASId, data: localUserData),
maxPlayers: maxPlayers,
isPrivate: isPrivate
));

new InProgressRequest<Response>(task, onComplete);
}
public static void JoinLobbyAsync_ByCode(string requesterUASId, string lobbyCode, Action<Response<Lobby>> onComplete)
public static void JoinLobbyAsync_ByCode(string requesterUASId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData, Action<Response<Lobby>> onComplete)
JoinLobbyByCodeRequest joinRequest = new JoinLobbyByCodeRequest(new JoinByCodeRequest(lobbyCode, new Player(requesterUASId)));
JoinLobbyByCodeRequest joinRequest = new JoinLobbyByCodeRequest(new JoinByCodeRequest(lobbyCode, new Player(id: requesterUASId, data: localUserData)));
public static void JoinLobbyAsync_ById(string requesterUASId, string lobbyId, Action<Response<Lobby>> onComplete)
public static void JoinLobbyAsync_ById(string requesterUASId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData, Action<Response<Lobby>> onComplete)
JoinLobbyByIdRequest joinRequest = new JoinLobbyByIdRequest(lobbyId, new Player(requesterUASId));
JoinLobbyByIdRequest joinRequest = new JoinLobbyByIdRequest(lobbyId, new Player(id: requesterUASId, data: localUserData));
var task = LobbyService.LobbyApiClient.JoinLobbyByIdAsync(joinRequest);
new InProgressRequest<Response<Lobby>>(task, onComplete);
}

18
Assets/Scripts/Lobby/LobbyAsyncRequests.cs


#endregion
private static Dictionary<string, PlayerDataObject> CreateInitialPlayerData(LobbyUser player)
{
Dictionary<string, PlayerDataObject> data = new Dictionary<string, PlayerDataObject>();
PlayerDataObject dataObjName = new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, player.DisplayName);
data.Add("DisplayName", dataObjName);
return data;
}
public void CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, Action<Lobby> onSuccess, Action onFailure)
public void CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, LobbyUser localUser, Action<Lobby> onSuccess, Action onFailure)
LobbyAPIInterface.CreateLobbyAsync(uasId, lobbyName, maxPlayers, isPrivate, OnLobbyCreated);
LobbyAPIInterface.CreateLobbyAsync(uasId, lobbyName, maxPlayers, isPrivate, CreateInitialPlayerData(localUser), OnLobbyCreated);
void OnLobbyCreated(Response<Lobby> response)
{

}
/// <summary>Attempt to join an existing lobby. Either ID xor code can be null.</summary>
public void JoinLobbyAsync(string lobbyId, string lobbyCode, Action<Lobby> onSuccess, Action onFailure)
public void JoinLobbyAsync(string lobbyId, string lobbyCode, LobbyUser localUser, Action<Lobby> onSuccess, Action onFailure)
LobbyAPIInterface.JoinLobbyAsync_ById(uasId, lobbyId, OnLobbyJoined);
LobbyAPIInterface.JoinLobbyAsync_ById(uasId, lobbyId, CreateInitialPlayerData(localUser), OnLobbyJoined);
LobbyAPIInterface.JoinLobbyAsync_ByCode(uasId, lobbyCode, OnLobbyJoined);
LobbyAPIInterface.JoinLobbyAsync_ByCode(uasId, lobbyCode, CreateInitialPlayerData(localUser), OnLobbyJoined);
void OnLobbyJoined(Response<Lobby> response)
{

39
Assets/Scripts/Relay/RelayUtpClient.cs


m_localUser.onChanged += OnLocalChange;
m_networkDriver = networkDriver;
m_connections = connections;
Locator.Get.UpdateSlow.Subscribe(UpdateSlow);
if (this is RelayUtpHost) // The host will be alone in the lobby at first, so they need not send any messages right away.
m_hasSentInitialMessage = true;

m_localUser.onChanged -= OnLocalChange;
Locator.Get.UpdateSlow.Unsubscribe(UpdateSlow);
}
private void OnLocalChange(LobbyUser localUser)

OnUpdate();
}
private void UpdateSlow(float dt)
{
// Clients need to send any data over UTP periodically, or else the connection will timeout.
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.
}
protected virtual void OnUpdate()
{
m_networkDriver.ScheduleUpdate().Complete(); // This pumps all messages, which pings the Relay allocation and keeps it alive.

{
while ((cmd = connection.PopEvent(driver, out strm)) != NetworkEvent.Type.Empty)
{
ProcessNetworkEvent(strm, cmd);
ProcessNetworkEvent(connection, strm, cmd);
private void ProcessNetworkEvent(DataStreamReader strm, NetworkEvent.Type cmd)
private void ProcessNetworkEvent(NetworkConnection conn, DataStreamReader strm, NetworkEvent.Type cmd)
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id))
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id)) // TODO: Do we want to hold onto the message if the user isn't present *now* in case they're pending?
return;
if (msgType == MsgType.PlayerName)

Debug.LogError("User id " + id + " is named " + name);
Debug.LogError("User id " + id + " has emote " + emote.ToString());
Debug.LogError("User id " + id + " has state " + status.ToString());
ProcessNetworkEventDataAdditional(strm, cmd, msgType, id);
ProcessNetworkEventDataAdditional(conn, strm, msgType, id);
ProcessNetworkEventAdditional(strm, cmd);
protected virtual void ProcessNetworkEventDataAdditional(DataStreamReader strm, NetworkEvent.Type cmd, MsgType msgType, string id) { }
protected virtual void ProcessNetworkEventAdditional(DataStreamReader strm, NetworkEvent.Type cmd) { }
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, DataStreamReader strm, MsgType msgType, string id) { }
unsafe private string ReadLengthAndString(ref DataStreamReader strm)
{

{
// Only update with actual changes. (If multiple change at once, we send messages for each separately, but that shouldn't happen often.)
if (0 < (m_localUser.LastChanged & LobbyUser.UserMembers.DisplayName))
WriteString(driver, connection, MsgType.PlayerName, m_localUser.DisplayName);
WriteString(driver, connection, m_localUser.ID, MsgType.PlayerName, m_localUser.DisplayName);
WriteByte(driver, connection, MsgType.Emote, (byte)m_localUser.Emote);
WriteByte(driver, connection, m_localUser.ID, MsgType.Emote, (byte)m_localUser.Emote);
WriteByte(driver, connection, MsgType.ReadyState, (byte)m_localUser.UserStatus);
WriteByte(driver, connection, m_localUser.ID, MsgType.ReadyState, (byte)m_localUser.UserStatus);
private void WriteString(NetworkDriver driver, NetworkConnection connection, MsgType msgType, string str)
protected void WriteString(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, string str)
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(m_localUser.ID);
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);

/// <summary>
/// Write byte data as: [1 byte: msgType][1 byte: id length N][N bytes: id][1 byte: data]
/// </summary>
private void WriteByte(NetworkDriver driver, NetworkConnection connection, MsgType msgType, byte value)
protected void WriteByte(NetworkDriver driver, NetworkConnection connection, string id, MsgType msgType, byte value)
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(m_localUser.ID);
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(id);
List<byte> message = new List<byte>(idBytes.Length + 3);
message.Add((byte)msgType);
message.Add((byte)idBytes.Length);

38
Assets/Scripts/Relay/RelayUtpHost.cs


namespace LobbyRelaySample.Relay
{
/// <summary>
/// In addition to maintaining a heartbeat with the Relay server to keep it from timing out, the host player must pass network events
/// from clients to all other clients, since they don't connect to each other.
/// </summary>
public class RelayUtpHost : RelayUtpClient
{
protected override void OnUpdate()

}
protected override void ProcessNetworkEventDataAdditional(DataStreamReader strm, NetworkEvent.Type cmd, MsgType msgType, string id)
/// <summary>
/// When a new client connects, they need to be given all up-to-date info.
/// </summary>
protected override void ProcessNetworkEventAdditional(DataStreamReader strm, NetworkEvent.Type cmd)
{
if (cmd == NetworkEvent.Type.Connect)
{
}
}
protected override void ProcessNetworkEventDataAdditional(NetworkConnection conn, DataStreamReader strm, MsgType msgType, string id)
if (msgType == MsgType.PlayerName)
{
string name = m_localLobby.LobbyUsers[id].DisplayName;
foreach (NetworkConnection otherConn in m_connections)
{
if (otherConn == conn)
continue;
WriteString(m_networkDriver, otherConn, id, msgType, name);
}
}
else if (msgType == MsgType.Emote || msgType == MsgType.ReadyState)
{
byte value = msgType == MsgType.Emote ? (byte)m_localLobby.LobbyUsers[id].Emote : (byte)m_localLobby.LobbyUsers[id].UserStatus;
foreach (NetworkConnection otherConn in m_connections)
{
if (otherConn == conn)
continue;
WriteByte(m_networkDriver, otherConn, id, msgType, value);
}
}
// Note that the strm contents might have already been consumed, depending on the msgType.
if (msgType == MsgType.ReadyState)
{

2
Assets/Scripts/Relay/RelayUtpSetup.cs


protected LobbyUser m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
public enum MsgType { NewPlayer = 0, ReadyState = 2, PlayerName = 3, Emote = 4 }
public enum MsgType { NewPlayer = 0, Ping = 1, ReadyState = 2, PlayerName = 3, Emote = 4 }
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser, Action<bool, RelayUtpClient> onJoinComplete)
{

233
Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs


using LobbyRelaySample;
using NUnit.Framework;
using System.Collections;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using UnityEngine;
using UnityEngine.TestTools;
using LobbyAPIInterface = LobbyRelaySample.lobby.LobbyAPIInterface;
// TODO: Obsolete. Replace with something accurate to Relay with transport.
namespace Test
{
public class LobbyReadyCheckTests
{
private string m_workingLobbyId;
private LobbyRelaySample.Auth.Identity m_auth;
private bool m_didSigninComplete = false;
private GameObject m_updateSlowObj;
//using LobbyRelaySample;
//using NUnit.Framework;
//using System.Collections;
//using Unity.Services.Lobbies;
//using Unity.Services.Lobbies.Models;
//using UnityEngine;
//using UnityEngine.TestTools;
//using LobbyAPIInterface = LobbyRelaySample.lobby.LobbyAPIInterface;
//namespace Test
//{
// public class LobbyReadyCheckTests
// {
// private string m_workingLobbyId;
// private LobbyRelaySample.Auth.Identity m_auth;
// private bool m_didSigninComplete = false;
// private GameObject m_updateSlowObj;
[OneTimeSetUp]
public void Setup()
{
m_auth = new LobbyRelaySample.Auth.Identity(() => { m_didSigninComplete = true; });
Locator.Get.Provide(m_auth);
m_updateSlowObj = new GameObject("UpdateSlowTest");
m_updateSlowObj.AddComponent<UpdateSlow>();
}
// [OneTimeSetUp]
// public void Setup()
// {
// m_auth = new LobbyRelaySample.Auth.Identity(() => { m_didSigninComplete = true; });
// Locator.Get.Provide(m_auth);
// m_updateSlowObj = new GameObject("UpdateSlowTest");
// m_updateSlowObj.AddComponent<UpdateSlow>();
// }
[UnityTearDown]
public IEnumerator PerTestTeardown()
{
if (m_workingLobbyId != null)
{ LobbyAPIInterface.DeleteLobbyAsync(m_workingLobbyId, null);
m_workingLobbyId = null;
}
yield return new WaitForSeconds(0.5f); // We need a yield anyway, so wait long enough to probably delete the lobby. There currently (6/22/2021) aren't other tests that would have issues if this took longer.
}
// [UnityTearDown]
// public IEnumerator PerTestTeardown()
// {
// if (m_workingLobbyId != null)
// { LobbyAPIInterface.DeleteLobbyAsync(m_workingLobbyId, null);
// m_workingLobbyId = null;
// }
// yield return new WaitForSeconds(0.5f); // We need a yield anyway, so wait long enough to probably delete the lobby. There currently (6/22/2021) aren't other tests that would have issues if this took longer.
// }
[OneTimeTearDown]
public void Teardown()
{
Locator.Get.Provide(new LobbyRelaySample.Auth.IdentityNoop());
m_auth.Dispose();
LogAssert.ignoreFailingMessages = false;
LobbyAsyncRequests.Instance.EndTracking();
GameObject.Destroy(m_updateSlowObj);
}
// [OneTimeTearDown]
// public void Teardown()
// {
// Locator.Get.Provide(new LobbyRelaySample.Auth.IdentityNoop());
// m_auth.Dispose();
// LogAssert.ignoreFailingMessages = false;
// LobbyAsyncRequests.Instance.EndTracking();
// GameObject.Destroy(m_updateSlowObj);
// }
private IEnumerator WaitForSignin()
{
// Wait a reasonable amount of time for sign-in to complete.
if (!m_didSigninComplete)
yield return new WaitForSeconds(3);
if (!m_didSigninComplete)
Assert.Fail("Did not sign in.");
}
// private IEnumerator WaitForSignin()
// {
// // Wait a reasonable amount of time for sign-in to complete.
// if (!m_didSigninComplete)
// yield return new WaitForSeconds(3);
// if (!m_didSigninComplete)
// Assert.Fail("Did not sign in.");
// }
private IEnumerator CreateLobby(string lobbyName, string userId)
{
Response<Lobby> createResponse = null;
float timeout = 5;
LobbyAPIInterface.CreateLobbyAsync(userId, lobbyName, 4, false, (r) => { createResponse = r; });
while (createResponse == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout check (lobby creation).");
m_workingLobbyId = createResponse.Result.Id;
}
// private IEnumerator CreateLobby(string lobbyName, string userId)
// {
// Response<Lobby> createResponse = null;
// float timeout = 5;
// LobbyAPIInterface.CreateLobbyAsync(userId, lobbyName, 4, false, (r) => { createResponse = r; });
// while (createResponse == null && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (lobby creation).");
// m_workingLobbyId = createResponse.Result.Id;
// }
private IEnumerator PushPlayerData(LobbyUser player)
{
bool hasPushedPlayerData = false;
float timeout = 5;
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(LobbyRelaySample.lobby.ToLocalLobby.RetrieveUserData(player), () => { hasPushedPlayerData = true; }); // LobbyContentHeartbeat normally does this.
while (!hasPushedPlayerData && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout check (push player data).");
}
// private IEnumerator PushPlayerData(LobbyUser player)
// {
// bool hasPushedPlayerData = false;
// float timeout = 5;
// LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(LobbyRelaySample.lobby.ToLocalLobby.RetrieveUserData(player), () => { hasPushedPlayerData = true; }); // LobbyContentHeartbeat normally does this.
// while (!hasPushedPlayerData && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (push player data).");
// }
/// <summary>
/// After creating a lobby and a player, signal that the player is Ready. This should lead to a countdown time being set for all players.
/// </summary>
[UnityTest]
public IEnumerator SetCountdownTimeSinglePlayer()
{
LogAssert.ignoreFailingMessages = true; // Not sure why, but when auth logs in, it sometimes generates an error: "A Native Collection has not been disposed[...]." We don't want this to cause test failures, since in practice it *seems* to not negatively impact behavior.
ReadyCheck readyCheck = new ReadyCheck(5); // This ready time is used for the countdown target end, not for any of the timing of actually detecting readies.
yield return WaitForSignin();
// /// <summary>
// /// After creating a lobby and a player, signal that the player is Ready. This should lead to a countdown time being set for all players.
// /// </summary>
// [UnityTest]
// public IEnumerator SetCountdownTimeSinglePlayer()
// {
// LogAssert.ignoreFailingMessages = true; // Not sure why, but when auth logs in, it sometimes generates an error: "A Native Collection has not been disposed[...]." We don't want this to cause test failures, since in practice it *seems* to not negatively impact behavior.
// ReadyCheck readyCheck = new ReadyCheck(5); // This ready time is used for the countdown target end, not for any of the timing of actually detecting readies.
// yield return WaitForSignin();
string userId = m_auth.GetSubIdentity(LobbyRelaySample.Auth.IIdentityType.Auth).GetContent("id");
yield return CreateLobby("TestReadyLobby1", userId);
// string userId = m_auth.GetSubIdentity(LobbyRelaySample.Auth.IIdentityType.Auth).GetContent("id");
// yield return CreateLobby("TestReadyLobby1", userId);
LobbyAsyncRequests.Instance.BeginTracking(m_workingLobbyId);
yield return new WaitForSeconds(2); // Allow the initial lobby retrieval.
// LobbyAsyncRequests.Instance.BeginTracking(m_workingLobbyId);
// yield return new WaitForSeconds(2); // Allow the initial lobby retrieval.
LobbyUser user = new LobbyUser();
user.ID = userId;
user.UserStatus = UserStatus.Ready;
yield return PushPlayerData(user);
// LobbyUser user = new LobbyUser();
// user.ID = userId;
// user.UserStatus = UserStatus.Ready;
// yield return PushPlayerData(user);
readyCheck.BeginCheckingForReady();
float timeout = 5; // Long enough for two slow updates
yield return new WaitForSeconds(timeout);
// readyCheck.BeginCheckingForReady();
// float timeout = 5; // Long enough for two slow updates
// yield return new WaitForSeconds(timeout);
readyCheck.Dispose();
LobbyAsyncRequests.Instance.EndTracking();
// readyCheck.Dispose();
// LobbyAsyncRequests.Instance.EndTracking();
yield return new WaitForSeconds(2); // Buffer to prevent a 429 on the upcoming Get, since there's a Get request on the slow upate loop when that's active.
Response<Lobby> getResponse = null;
timeout = 5;
LobbyAPIInterface.GetLobbyAsync(m_workingLobbyId, (r) => { getResponse = r; });
while (getResponse == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;
}
Assert.Greater(timeout, 0, "Timeout check (get lobby).");
Assert.NotNull(getResponse.Result, "Retrieved lobby successfully.");
Assert.NotNull(getResponse.Result.Data, "Lobby should have data.");
// yield return new WaitForSeconds(2); // Buffer to prevent a 429 on the upcoming Get, since there's a Get request on the slow upate loop when that's active.
// Response<Lobby> getResponse = null;
// timeout = 5;
// LobbyAPIInterface.GetLobbyAsync(m_workingLobbyId, (r) => { getResponse = r; });
// while (getResponse == null && timeout > 0)
// { yield return new WaitForSeconds(0.25f);
// timeout -= 0.25f;
// }
// Assert.Greater(timeout, 0, "Timeout check (get lobby).");
// Assert.NotNull(getResponse.Result, "Retrieved lobby successfully.");
// Assert.NotNull(getResponse.Result.Data, "Lobby should have data.");
Assert.True(getResponse.Result.Data.ContainsKey("AllPlayersReady"), "Check for AllPlayersReady key.");
string readyString = getResponse.Result.Data["AllPlayersReady"]?.Value;
Assert.NotNull(readyString, "Check for non-null AllPlayersReady.");
Assert.True(long.TryParse(readyString, out long ticks), "Check for ticks value in AllPlayersReady."); // This will be based on the current time, so we won't check for a specific value.
}
// Assert.True(getResponse.Result.Data.ContainsKey("AllPlayersReady"), "Check for AllPlayersReady key.");
// string readyString = getResponse.Result.Data["AllPlayersReady"]?.Value;
// Assert.NotNull(readyString, "Check for non-null AllPlayersReady.");
// Assert.True(long.TryParse(readyString, out long ticks), "Check for ticks value in AllPlayersReady."); // This will be based on the current time, so we won't check for a specific value.
// }
// Can't test with multiple players on one machine, since anonymous UAS credentials can't be manually supplied.
}
}
// // Can't test with multiple players on one machine, since anonymous UAS credentials can't be manually supplied.
// }
//}

10
Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs


using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;

{
/// <summary>
/// Hits the Authentication and Lobbies services in order to ensure lobbies can be created and deleted.
/// The actual code accessing lobbies should go through LobbyAsyncRequests.
/// The actual code accessing lobbies should go through LobbyAsyncRequests. This serves to ensure the connection to the Lobby service is functional.
/// </summary>
public class LobbyRoundtripTests
{

private Dictionary<string, PlayerDataObject> m_mockUserData; // This is handled in the LobbyAsyncRequest calls normally, but we need to supply this for the direct Lobby API calls.
m_mockUserData = new Dictionary<string, PlayerDataObject>();
m_mockUserData.Add("DisplayName", new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, "TestUser123"));
}
[UnityTearDown]

Response<Lobby> createResponse = null;
timeout = 5;
string lobbyName = "TestLobby-JustATest-123";
LobbyAPIInterface.CreateLobbyAsync(m_auth.GetContent("id"), lobbyName, 100, false, (r) => { createResponse = r; });
LobbyAPIInterface.CreateLobbyAsync(m_auth.GetContent("id"), lobbyName, 100, false, m_mockUserData, (r) => { createResponse = r; });
while (createResponse == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);
timeout -= 0.25f;

Assert.Fail("Did not sign in.");
bool? didComplete = null;
LobbyAPIInterface.CreateLobbyAsync("ThisStringIsInvalidHere", "lobby name", 123, false, (r) => { didComplete = (r == null); });
LobbyAPIInterface.CreateLobbyAsync("ThisStringIsInvalidHere", "lobby name", 123, false, m_mockUserData, (r) => { didComplete = (r == null); });
float timeout = 5;
while (didComplete == null && timeout > 0)
{ yield return new WaitForSeconds(0.25f);

12
Assets/Scripts/UI/InLobbyUserUI.cs


switch (status)
{
case UserStatus.Lobby:
return "<color=#56B4E9>Lobby.</color>"; // Light Blue
return "<color=#56B4E9>In Lobby</color>"; // Light Blue
return "<color=#009E73>Ready!</color>"; // Light Mint
return "<color=#009E73>Ready</color>"; // Light Mint
return "<color=#F0E442>Connecting.</color>"; // Bright Yellow
case UserStatus.Connected:
return "<color=#005500>Connected.</color>"; //Orange
return "<color=#F0E442>Connecting</color>"; // Bright Yellow
case UserStatus.InGame:
return "<color=#005500>In Game</color>"; //Orange
return "<color=#56B4E9>In Lobby.</color>";
return "";
}
}
}
正在加载...
取消
保存