nathaniel.buck@unity3d.com
3 年前
当前提交
363b2f50
共有 90 个文件被更改,包括 3859 次插入 和 1335 次删除
-
47Assets/Prefabs/UI/GameCanvas.prefab
-
883Assets/Prefabs/UI/JoinContent.prefab
-
43Assets/Prefabs/UI/JoinCreateCanvas.prefab
-
32Assets/Prefabs/UI/LobbyButtonUI.prefab
-
79Assets/Prefabs/UI/LobbyGameCanvas.prefab
-
868Assets/Prefabs/UI/PlayerInteractionPanel.prefab
-
7Assets/Prefabs/UI/RenamePopup.prefab
-
249Assets/Scenes/mainScene.unity
-
262Assets/Scripts/Entities/GameStateManager.cs
-
119Assets/Scripts/Entities/LobbyUser.cs
-
125Assets/Scripts/Entities/LocalLobby.cs
-
8Assets/Scripts/Infrastructure/Messenger.cs
-
6Assets/Scripts/Infrastructure/ObserverBehaviour.cs
-
4Assets/Scripts/Infrastructure/UpdateSlow.cs
-
23Assets/Scripts/Lobby/LobbyAPIInterface.cs
-
54Assets/Scripts/Lobby/LobbyAsyncRequests.cs
-
53Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
-
1Assets/Scripts/Lobby/LobbyListHeartbeat.cs
-
67Assets/Scripts/Lobby/ToLocalLobby.cs
-
5Assets/Scripts/LobbyRelaySample.asmdef
-
20Assets/Scripts/Relay/RelayInterface.cs
-
233Assets/Scripts/Tests/PlayMode/LobbyReadyCheckTests.cs
-
16Assets/Scripts/Tests/PlayMode/LobbyRoundtripTests.cs
-
6Assets/Scripts/Tests/PlayMode/UpdateSlowTests.cs
-
12Assets/Scripts/UI/CountdownUI.cs
-
4Assets/Scripts/UI/EmoteButtonUI.cs
-
2Assets/Scripts/UI/EndGameButtonUI.cs
-
14Assets/Scripts/UI/InLobbyUserUI.cs
-
4Assets/Scripts/UI/JoinMenuUI.cs
-
2Assets/Scripts/UI/ReadyCheckUI.cs
-
15Assets/Scripts/UI/UIPanelBase.cs
-
4Packages/com.unity.services.lobby/CHANGELOG.md
-
43Packages/com.unity.services.lobby/Runtime/Apis/LobbyApi.cs
-
111Packages/com.unity.services.lobby/Runtime/Apis/LobbyApiRequests.cs
-
4Packages/com.unity.services.lobby/Runtime/Http/BaseApiClient.cs
-
14Packages/com.unity.services.lobby/Runtime/Http/DeserializationException.cs
-
54Packages/com.unity.services.lobby/Runtime/Http/HttpClient.cs
-
13Packages/com.unity.services.lobby/Runtime/Http/HttpException.cs
-
5Packages/com.unity.services.lobby/Runtime/Http/IHttpClient.cs
-
4Packages/com.unity.services.lobby/Runtime/Http/JsonHelpers.cs
-
58Packages/com.unity.services.lobby/Runtime/Http/ResponseHandler.cs
-
5Packages/com.unity.services.lobby/Runtime/LobbyServiceProvider.cs
-
13Packages/com.unity.services.lobby/Runtime/Models/CreateRequest.cs
-
29Packages/com.unity.services.lobby/Runtime/Models/DataObject.cs
-
13Packages/com.unity.services.lobby/Runtime/Models/Detail.cs
-
17Packages/com.unity.services.lobby/Runtime/Models/ErrorStatus.cs
-
10Packages/com.unity.services.lobby/Runtime/Models/JoinByCodeRequest.cs
-
20Packages/com.unity.services.lobby/Runtime/Models/Lobby.cs
-
11Packages/com.unity.services.lobby/Runtime/Models/Player.cs
-
24Packages/com.unity.services.lobby/Runtime/Models/PlayerDataObject.cs
-
13Packages/com.unity.services.lobby/Runtime/Models/PlayerUpdateRequest.cs
-
8Packages/com.unity.services.lobby/Runtime/Models/QueryFilter.cs
-
8Packages/com.unity.services.lobby/Runtime/Models/QueryOrder.cs
-
11Packages/com.unity.services.lobby/Runtime/Models/QueryRequest.cs
-
13Packages/com.unity.services.lobby/Runtime/Models/QueryResponse.cs
-
10Packages/com.unity.services.lobby/Runtime/Models/QuickJoinRequest.cs
-
15Packages/com.unity.services.lobby/Runtime/Models/UpdateRequest.cs
-
4Packages/com.unity.services.lobby/Runtime/Scheduler/ThreadHelper.cs
-
44Packages/com.unity.services.lobby/package.json
-
9Packages/manifest.json
-
90Packages/packages-lock.json
-
4ProjectSettings/ProjectVersion.txt
-
2Packages/com.unity.services.lobby/Runtime/Http/DeserializationSettings.cs.meta
-
17Assets/Scripts/Entities/EmoteType.cs
-
11Assets/Scripts/Entities/EmoteType.cs.meta
-
229Assets/Scripts/Relay/RelayUtpClient.cs
-
11Assets/Scripts/Relay/RelayUtpClient.cs.meta
-
146Assets/Scripts/Relay/RelayUtpHost.cs
-
11Assets/Scripts/Relay/RelayUtpHost.cs.meta
-
207Assets/Scripts/Relay/RelayUtpSetup.cs
-
11Assets/Scripts/Relay/RelayUtpSetup.cs.meta
-
39Assets/Scripts/UI/RecolorForLobbyType.cs
-
11Assets/Scripts/UI/RecolorForLobbyType.cs.meta
-
9Packages/com.unity.services.lobby/CONTRIBUTING.md
-
7Packages/com.unity.services.lobby/CONTRIBUTING.md.meta
-
15Packages/com.unity.services.lobby/Runtime/Http/DeserializationSettings.cs
-
53Packages/com.unity.services.lobby/Runtime/Http/JsonObject.cs
-
11Packages/com.unity.services.lobby/Runtime/Http/JsonObject.cs.meta
-
28Packages/com.unity.services.lobby/Runtime/Http/JsonObjectConverter.cs
-
11Packages/com.unity.services.lobby/Runtime/Http/JsonObjectConverter.cs.meta
-
34Packages/com.unity.services.lobby/Runtime/Http/ResponseDeserializationException.cs
-
11Packages/com.unity.services.lobby/Runtime/Http/ResponseDeserializationException.cs.meta
-
16ProjectSettings/BurstAotSettings_StandaloneWindows.json
-
6ProjectSettings/CommonBurstAotSettings.json
-
273Assets/Prefabs/UI/CountDownUI.prefab
-
7Assets/Prefabs/UI/CountDownUI.prefab.meta
-
11Assets/Scripts/Lobby/ReadyCheck.cs.meta
-
63Assets/Scripts/Lobby/ReadyCheck.cs
-
0/Packages/com.unity.services.lobby/Runtime/Http/DeserializationSettings.cs.meta
883
Assets/Prefabs/UI/JoinContent.prefab
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
868
Assets/Prefabs/UI/PlayerInteractionPanel.prefab
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
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.
|
|||
// }
|
|||
//}
|
|
|||
{ |
|||
"name": "com.unity.services.lobby", |
|||
"displayName": "Unity Lobby", |
|||
"version": "0.2.0-preview", |
|||
"unity": "2020.3", |
|||
"description": "Client API SDK Package for communicating with the Unity Lobby service", |
|||
"dependencies": { |
|||
"com.unity.services.core": "1.1.0-pre.2", |
|||
"com.unity.modules.unitywebrequest": "1.0.0", |
|||
"com.unity.modules.unitywebrequestassetbundle": "1.0.0", |
|||
"com.unity.modules.unitywebrequestaudio": "1.0.0", |
|||
"com.unity.modules.unitywebrequesttexture": "1.0.0", |
|||
"com.unity.modules.unitywebrequestwww": "1.0.0", |
|||
"com.unity.nuget.newtonsoft-json": "2.0.0" |
|||
}, |
|||
"relatedPackages": { |
|||
"com.unity.services.lobby.tests": "0.2.0-preview" |
|||
}, |
|||
"upmCi": { |
|||
"footprint": "aa7999c19cdbcfe8d97e68dde6d16c73ef3411c1" |
|||
}, |
|||
"repository": { |
|||
"url": "https://github.cds.internal.unity3d.com/unity/com.unity.services.rooms.git", |
|||
"type": "git", |
|||
"revision": "902923a105886dde15661bfeb44db49ee0fdbaff" |
|||
}, |
|||
"samples": [ |
|||
{ |
|||
"displayName": "Hello World Sample", |
|||
"description": "This sample walks through using most of the Lobby APIs in a simple, linear way.", |
|||
"path": "Samples~/HelloWorld" |
|||
"name": "com.unity.services.lobby", |
|||
"displayName":"Unity Lobby", |
|||
"version": "0.2.1-preview", |
|||
"unity": "2020.3", |
|||
"description": "Client API SDK Package for communicating with the Unity Lobby service", |
|||
"dependencies": { |
|||
"com.unity.services.core": "1.1.0-pre.2", |
|||
"com.unity.modules.unitywebrequest": "1.0.0", |
|||
"com.unity.modules.unitywebrequestassetbundle": "1.0.0", |
|||
"com.unity.modules.unitywebrequestaudio": "1.0.0", |
|||
"com.unity.modules.unitywebrequesttexture": "1.0.0", |
|||
"com.unity.modules.unitywebrequestwww": "1.0.0", |
|||
"com.unity.nuget.newtonsoft-json": "2.0.0" |
|||
] |
|||
} |
|
|||
m_EditorVersion: 2021.2.0b1 |
|||
m_EditorVersionWithRevision: 2021.2.0b1 (b0978dae4864) |
|||
m_EditorVersion: 2020.3.14f1 |
|||
m_EditorVersionWithRevision: 2020.3.14f1 (d0d1bb862f9d) |
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
public enum EmoteType { None = 0, Smile, Frown, Shock, Laugh } |
|||
|
|||
public static class EmoteTypeExtensions |
|||
{ |
|||
public static string GetString(this EmoteType emote) |
|||
{ |
|||
return |
|||
emote == EmoteType.Smile ? ":D" : |
|||
emote == EmoteType.Frown ? ":(" : |
|||
emote == EmoteType.Shock ? ":O" : |
|||
emote == EmoteType.Laugh ? "XD" : |
|||
""; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7aacd044390df3c4ca5cd037fec35483 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections.Generic; |
|||
using Unity.Networking.Transport; |
|||
using UnityEngine; |
|||
using MsgType = LobbyRelaySample.Relay.RelayUtpSetup.MsgType; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
/// <summary>
|
|||
/// This will handle observing the local player and updating remote players over Relay when there are local changes.
|
|||
/// Created after the connection to Relay has been confirmed.
|
|||
/// </summary>
|
|||
public class RelayUtpClient : MonoBehaviour // This is a MonoBehaviour merely to have access to Update.
|
|||
{ |
|||
protected LobbyUser 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_hasSentInitialMessage = false; |
|||
|
|||
public virtual void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LobbyUser localUser, LocalLobby localLobby) |
|||
{ |
|||
m_localUser = localUser; |
|||
m_localLobby = localLobby; |
|||
m_localUser.onChanged += OnLocalChange; |
|||
m_networkDriver = networkDriver; |
|||
m_connections = connections; |
|||
Locator.Get.UpdateSlow.Subscribe(UpdateSlow); |
|||
} |
|||
protected virtual void Uninitialize() |
|||
{ |
|||
m_localUser.onChanged -= OnLocalChange; |
|||
Leave(); |
|||
Locator.Get.UpdateSlow.Unsubscribe(UpdateSlow); |
|||
} |
|||
public void OnDestroy() |
|||
{ |
|||
Uninitialize(); |
|||
} |
|||
|
|||
private void OnLocalChange(LobbyUser localUser) |
|||
{ |
|||
if (m_connections.Count == 0) // This could be the case for the host alone in the lobby.
|
|||
return; |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
foreach (NetworkConnection conn in m_connections) |
|||
DoUserUpdate(m_networkDriver, conn, m_localUser); |
|||
} |
|||
|
|||
public void Update() |
|||
{ |
|||
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.
|
|||
ReceiveNetworkEvents(m_networkDriver, m_connections); |
|||
if (!m_hasSentInitialMessage) |
|||
SendInitialMessage(m_networkDriver, m_connections[0]); |
|||
} |
|||
|
|||
private void ReceiveNetworkEvents(NetworkDriver driver, List<NetworkConnection> connections) |
|||
{ |
|||
DataStreamReader strm; |
|||
NetworkEvent.Type cmd; |
|||
foreach (NetworkConnection connection in connections) |
|||
{ |
|||
while ((cmd = connection.PopEvent(driver, out strm)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
ProcessNetworkEvent(connection, strm, cmd); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ProcessNetworkEvent(NetworkConnection conn, DataStreamReader strm, NetworkEvent.Type cmd) |
|||
{ |
|||
if (cmd == NetworkEvent.Type.Data) |
|||
{ |
|||
MsgType msgType = (MsgType)strm.ReadByte(); |
|||
string id = ReadLengthAndString(ref strm); |
|||
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) |
|||
{ |
|||
string name = ReadLengthAndString(ref strm); |
|||
m_localLobby.LobbyUsers[id].DisplayName = name; |
|||
} |
|||
else if (msgType == MsgType.Emote) |
|||
{ |
|||
EmoteType emote = (EmoteType)strm.ReadByte(); |
|||
m_localLobby.LobbyUsers[id].Emote = emote; |
|||
} |
|||
else if (msgType == MsgType.ReadyState) |
|||
{ |
|||
UserStatus status = (UserStatus)strm.ReadByte(); |
|||
m_localLobby.LobbyUsers[id].UserStatus = status; |
|||
} |
|||
else if (msgType == MsgType.StartCountdown) |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.StartCountdown, null); |
|||
else if (msgType == MsgType.CancelCountdown) |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.CancelCountdown, null); |
|||
else if (msgType == MsgType.ConfirmInGame) |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.ConfirmInGameState, null); |
|||
else if (msgType == MsgType.EndInGame) |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame, null); |
|||
|
|||
ProcessNetworkEventDataAdditional(conn, strm, msgType, id); |
|||
} |
|||
else if (cmd == NetworkEvent.Type.Disconnect) |
|||
ProcessDisconnectEvent(conn, strm); |
|||
} |
|||
|
|||
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, DataStreamReader strm, 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.
|
|||
Debug.LogError("Host disconnected! Leaving the lobby."); |
|||
Leave(); |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.ChangeGameState, GameState.JoinMenu); |
|||
} |
|||
|
|||
unsafe private string ReadLengthAndString(ref DataStreamReader strm) |
|||
{ |
|||
byte length = strm.ReadByte(); |
|||
byte[] bytes = new byte[length]; |
|||
fixed (byte* ptr = bytes) |
|||
{ |
|||
strm.ReadBytes(ptr, length); |
|||
} |
|||
return System.Text.Encoding.UTF8.GetString(bytes); |
|||
} |
|||
|
|||
private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection) |
|||
{ |
|||
ForceFullUserUpdate(driver, connection, m_localUser); // Assuming this is only created after the Relay connection is successful.
|
|||
m_hasSentInitialMessage = true; |
|||
} |
|||
|
|||
private void DoUserUpdate(NetworkDriver driver, NetworkConnection connection, LobbyUser user) |
|||
{ |
|||
// Only update with actual changes. (If multiple change at once, we send messages for each separately, but that shouldn't happen often.)
|
|||
if (0 < (user.LastChanged & LobbyUser.UserMembers.DisplayName)) |
|||
WriteString(driver, connection, user.ID, MsgType.PlayerName, user.DisplayName); |
|||
if (0 < (user.LastChanged & LobbyUser.UserMembers.Emote)) |
|||
WriteByte(driver, connection, user.ID, MsgType.Emote, (byte)user.Emote); |
|||
if (0 < (user.LastChanged & LobbyUser.UserMembers.UserStatus)) |
|||
WriteByte(driver, connection, user.ID, MsgType.ReadyState, (byte)user.UserStatus); |
|||
} |
|||
protected void ForceFullUserUpdate(NetworkDriver driver, NetworkConnection connection, LobbyUser user) |
|||
{ |
|||
// TODO: Write full state in one message?
|
|||
WriteString(driver, connection, user.ID, MsgType.PlayerName, user.DisplayName); |
|||
WriteByte(driver, connection, user.ID, MsgType.Emote, (byte)user.Emote); |
|||
WriteByte(driver, connection, user.ID, MsgType.ReadyState, (byte)user.UserStatus); |
|||
} |
|||
|
|||
/// <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); |
|||
message.Add((byte)msgType); |
|||
message.Add((byte)idBytes.Length); |
|||
message.AddRange(idBytes); |
|||
message.Add((byte)strBytes.Length); |
|||
message.AddRange(strBytes); |
|||
|
|||
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
|
|||
{ |
|||
byte[] bytes = message.ToArray(); |
|||
unsafe |
|||
{ |
|||
fixed (byte* bytesPtr = bytes) |
|||
{ |
|||
dataStream.WriteBytes(bytesPtr, message.Count); |
|||
driver.EndSend(dataStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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); |
|||
message.Add((byte)msgType); |
|||
message.Add((byte)idBytes.Length); |
|||
message.AddRange(idBytes); |
|||
message.Add(value); |
|||
|
|||
if (driver.BeginSend(connection, out var dataStream) == 0) // Oh, should check this first?
|
|||
{ |
|||
byte[] bytes = message.ToArray(); |
|||
unsafe |
|||
{ |
|||
fixed (byte* bytesPtr = bytes) |
|||
{ |
|||
dataStream.WriteBytes(bytesPtr, message.Count); |
|||
driver.EndSend(dataStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Leave() |
|||
{ |
|||
foreach (NetworkConnection connection in m_connections) |
|||
connection.Disconnect(m_networkDriver); |
|||
m_localLobby.RelayServer = null; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 57add7ba7b318f04a8781c247344cab8 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections.Generic; |
|||
using Unity.Networking.Transport; |
|||
using MsgType = LobbyRelaySample.Relay.RelayUtpSetup.MsgType; |
|||
|
|||
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, IReceiveMessages |
|||
{ |
|||
public override void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LobbyUser localUser, LocalLobby localLobby) |
|||
{ |
|||
base.Initialize(networkDriver, connections, localUser, localLobby); |
|||
m_hasSentInitialMessage = true; // The host will be alone in the lobby at first, so they need not send any messages right away.
|
|||
Locator.Get.Messenger.Subscribe(this); |
|||
} |
|||
protected override void Uninitialize() |
|||
{ |
|||
base.Uninitialize(); |
|||
Locator.Get.Messenger.Unsubscribe(this); |
|||
} |
|||
|
|||
protected override void OnUpdate() |
|||
{ |
|||
base.OnUpdate(); |
|||
DoHeartbeat(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// When a new client connects, they need to be given all up-to-date info.
|
|||
/// </summary>
|
|||
private void OnNewConnection(NetworkConnection conn) |
|||
{ |
|||
// When a new client connects, they need to be updated with the current state of everyone else.
|
|||
// (We can't exclude this client from the events we send to it, since we don't have its ID in strm, but it will ignore messages about itself on arrival.)
|
|||
foreach (var user in m_localLobby.LobbyUsers) |
|||
ForceFullUserUpdate(m_networkDriver, conn, user.Value); |
|||
} |
|||
|
|||
protected override void ProcessNetworkEventDataAdditional(NetworkConnection conn, DataStreamReader strm, MsgType msgType, string id) |
|||
{ |
|||
// Note that the strm contents might have already been consumed, depending on the msgType.
|
|||
|
|||
// Forward messages from clients to other clients.
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
// If a client has changed state, check if this changes whether all players have readied.
|
|||
if (msgType == MsgType.ReadyState) |
|||
CheckIfAllUsersReady(); |
|||
} |
|||
|
|||
protected override void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm) |
|||
{ |
|||
// TODO: If a client disconnects, see if remaining players are all already ready.
|
|||
} |
|||
|
|||
public void OnReceiveMessage(MessageType type, object msg) |
|||
{ |
|||
if (type == MessageType.LobbyUserStatus) |
|||
CheckIfAllUsersReady(); |
|||
else if (type == MessageType.EndGame) // This assumes that only the host will have the End Game button available; otherwise, clients need to be able to send this message, too.
|
|||
{ |
|||
foreach (NetworkConnection connection in m_connections) |
|||
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.EndInGame, 0); |
|||
} |
|||
} |
|||
|
|||
private void CheckIfAllUsersReady() |
|||
{ |
|||
bool haveAllReadied = true; |
|||
foreach (var user in m_localLobby.LobbyUsers) |
|||
{ |
|||
if (user.Value.UserStatus != UserStatus.Ready) |
|||
{ haveAllReadied = false; |
|||
break; |
|||
} |
|||
} |
|||
if (haveAllReadied && m_localLobby.State == LobbyState.Lobby) // Need to notify both this client and all others that all players have readied.
|
|||
{ |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.StartCountdown, null); |
|||
foreach (NetworkConnection connection in m_connections) |
|||
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.StartCountdown, 0); |
|||
} |
|||
else if (!haveAllReadied && m_localLobby.State == LobbyState.CountDown) // Someone cancelled during the countdown, so abort the countdown.
|
|||
{ |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.CancelCountdown, null); |
|||
foreach (NetworkConnection connection in m_connections) |
|||
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.CancelCountdown, 0); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// In an actual game, after the countdown, there would be some step here where the host and all clients sync up on game state, load assets, etc.
|
|||
/// Here, we will instead just signal an "in game" state that can be ended by the host.
|
|||
/// </summary>
|
|||
public void SendInGameState() |
|||
{ |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.ConfirmInGameState, null); |
|||
foreach (NetworkConnection connection in m_connections) |
|||
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.ConfirmInGame, 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clean out destroyed connections, and accept all new ones.
|
|||
/// </summary>
|
|||
private void DoHeartbeat() |
|||
{ |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
|
|||
for (int c = m_connections.Count - 1; c >= 0; c--) |
|||
{ |
|||
if (!m_connections[c].IsCreated) |
|||
m_connections.RemoveAt(c); |
|||
} |
|||
while (true) |
|||
{ |
|||
var conn = m_networkDriver.Accept(); |
|||
if (!conn.IsCreated) // "Nothing more to accept" is signalled by returning an invalid connection from Accept.
|
|||
break; |
|||
m_connections.Add(conn); |
|||
OnNewConnection(conn); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 567f8fffb5a28a446b1e98cbd2510b0f |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Unity.Jobs; |
|||
using Unity.Networking.Transport; |
|||
using Unity.Networking.Transport.Relay; |
|||
using Unity.Services.Relay.Models; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
/// <summary>
|
|||
/// Responsible for setting up a connection with Relay using UTP, for the lobby host.
|
|||
/// 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 JobHandle m_currentUpdateHandle; |
|||
protected LocalLobby m_localLobby; |
|||
protected LobbyUser m_localUser; |
|||
protected Action<bool, RelayUtpClient> m_onJoinComplete; |
|||
|
|||
public enum MsgType { NewPlayer = 0, Ping = 1, ReadyState = 2, PlayerName = 3, Emote = 4, StartCountdown = 5, CancelCountdown = 6, ConfirmInGame = 7, EndInGame = 8 } |
|||
|
|||
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser, Action<bool, RelayUtpClient> onJoinComplete) |
|||
{ |
|||
m_localLobby = localLobby; |
|||
m_localUser = localUser; |
|||
m_onJoinComplete = onJoinComplete; |
|||
JoinRelay(); |
|||
} |
|||
protected abstract void JoinRelay(); |
|||
|
|||
protected void BindToAllocation(string ip, int port, byte[] allocationIdBytes, byte[] connectionDataBytes, byte[] hostConnectionDataBytes, byte[] hmacKeyBytes, int connectionCapacity) |
|||
{ |
|||
NetworkEndPoint serverEndpoint = NetworkEndPoint.Parse(ip, (ushort)port); |
|||
RelayAllocationId allocationId = ConvertAllocationIdBytes(allocationIdBytes); |
|||
RelayConnectionData connectionData = ConvertConnectionDataBytes(connectionDataBytes); |
|||
RelayConnectionData hostConnectionData = ConvertConnectionDataBytes(hostConnectionDataBytes); |
|||
RelayHMACKey key = ConvertHMACKeyBytes(hmacKeyBytes); |
|||
|
|||
m_endpointForServer = serverEndpoint; |
|||
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key); |
|||
relayServerData.ComputeNewNonce(); |
|||
var relayNetworkParameter = new RelayNetworkParameter { ServerData = relayServerData }; |
|||
|
|||
m_networkDriver = NetworkDriver.Create(new INetworkParameter[] { relayNetworkParameter }); |
|||
m_connections = new List<NetworkConnection>(connectionCapacity); |
|||
|
|||
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0) |
|||
Debug.LogError("Failed to bind to Relay allocation."); |
|||
else |
|||
StartCoroutine(WaitForBindComplete()); // TODO: This is the only reason for being a MonoBehaviour?
|
|||
} |
|||
|
|||
private IEnumerator WaitForBindComplete() |
|||
{ |
|||
while (!m_networkDriver.Bound) |
|||
{ |
|||
m_networkDriver.ScheduleUpdate().Complete(); |
|||
yield return null; // TODO: Does this not proceed until a client connects as well?
|
|||
} |
|||
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.
|
|||
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 RelayHMACKey ConvertHMACKeyBytes(byte[] hmac) |
|||
{ |
|||
fixed (byte* ptr = hmac) |
|||
{ |
|||
return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length); |
|||
} |
|||
} |
|||
#endregion
|
|||
} |
|||
|
|||
public class RelayUtpSetupHost : RelayUtpSetup |
|||
{ |
|||
[Flags] |
|||
private enum JoinState { None = 0, Bound = 1, Joined = 2 } |
|||
private JoinState m_joinState = JoinState.None; |
|||
|
|||
protected override void JoinRelay() |
|||
{ |
|||
RelayInterface.AllocateAsync(m_localLobby.MaxPlayerCount, OnAllocation); |
|||
} |
|||
|
|||
private void OnAllocation(Allocation allocation) |
|||
{ |
|||
RelayInterface.GetJoinCodeAsync(allocation.AllocationId, OnRelayCode); |
|||
BindToAllocation(allocation.RelayServer.IpV4, allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.ConnectionData, allocation.Key, 16); |
|||
} |
|||
|
|||
private void OnRelayCode(string relayCode) |
|||
{ |
|||
m_localLobby.RelayCode = relayCode; |
|||
RelayInterface.JoinAsync(m_localLobby.RelayCode, OnJoin); |
|||
} |
|||
|
|||
private void OnJoin(JoinAllocation joinAllocation) |
|||
{ |
|||
m_localLobby.RelayServer = new ServerAddress(joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port); |
|||
m_joinState |= JoinState.Joined; |
|||
CheckForComplete(); |
|||
} |
|||
|
|||
protected override void OnBindingComplete() |
|||
{ |
|||
if (m_networkDriver.Listen() != 0) |
|||
{ |
|||
Debug.LogError("Server failed to listen"); |
|||
m_onJoinComplete(false, null); |
|||
} |
|||
else |
|||
{ |
|||
Debug.LogWarning("Server is now listening!"); |
|||
m_joinState |= JoinState.Bound; |
|||
CheckForComplete(); |
|||
} |
|||
} |
|||
|
|||
private void CheckForComplete() |
|||
{ |
|||
if (m_joinState == (JoinState.Joined | JoinState.Bound)) |
|||
{ |
|||
m_isRelayConnected = true; |
|||
RelayUtpHost host = gameObject.AddComponent<RelayUtpHost>(); |
|||
host.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby); |
|||
m_onJoinComplete(true, host); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class RelayUtpSetupClient : RelayUtpSetup |
|||
{ |
|||
protected override void JoinRelay() |
|||
{ |
|||
m_localLobby.onChanged += OnLobbyChange; |
|||
} |
|||
|
|||
private void OnLobbyChange(LocalLobby lobby) |
|||
{ |
|||
if (m_localLobby.RelayCode != null) |
|||
{ |
|||
RelayInterface.JoinAsync(m_localLobby.RelayCode, OnJoin); |
|||
m_localLobby.onChanged -= OnLobbyChange; |
|||
} |
|||
} |
|||
|
|||
private void OnJoin(JoinAllocation allocation) |
|||
{ |
|||
if (allocation == null) |
|||
return; |
|||
BindToAllocation(allocation.RelayServer.IpV4, allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.HostConnectionData, allocation.Key, 1); |
|||
m_localLobby.RelayServer = new ServerAddress(allocation.RelayServer.IpV4, allocation.RelayServer.Port); |
|||
} |
|||
|
|||
protected override void OnBindingComplete() |
|||
{ |
|||
StartCoroutine(ConnectToServer()); |
|||
} |
|||
|
|||
private IEnumerator 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(); |
|||
yield return null; |
|||
} |
|||
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected) |
|||
{ |
|||
Debug.LogError("Client failed to connect to server"); |
|||
m_onJoinComplete(false, null); |
|||
} |
|||
else |
|||
{ |
|||
RelayUtpClient watcher = gameObject.AddComponent<RelayUtpClient>(); |
|||
watcher.Initialize(m_networkDriver, m_connections, m_localUser, m_localLobby); |
|||
m_onJoinComplete(true, watcher); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 5857e5666b1ecf844b8280729adb6e6e |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.UI; |
|||
|
|||
namespace LobbyRelaySample.UI |
|||
{ |
|||
/// <summary>
|
|||
/// We want to illustrate filtering the lobby list by some arbitrary variable. This will allow the lobby host to choose a color for the lobby, and will display a lobby's current color.
|
|||
/// (Note that this isn't sent over Relay to other clients for realtime updates.)
|
|||
/// </summary>
|
|||
[RequireComponent(typeof(LocalLobbyObserver))] |
|||
public class RecolorForLobbyType : MonoBehaviour |
|||
{ |
|||
private static readonly Color s_orangeColor = new Color(0.75f, 0.5f, 0.1f); |
|||
private static readonly Color s_greenColor = new Color(0.5f, 1, 0.7f); |
|||
private static readonly Color s_blueColor = new Color(0.75f, 0.7f, 1); |
|||
private static readonly Color[] s_colorsOrdered = new Color[] { Color.white, s_orangeColor, s_greenColor, s_blueColor }; |
|||
|
|||
[SerializeField] |
|||
private Graphic[] m_toRecolor; |
|||
private LocalLobby m_lobby; |
|||
|
|||
public void UpdateLobby(LocalLobby lobby) |
|||
{ |
|||
m_lobby = lobby; |
|||
Color color = s_colorsOrdered[(int)lobby.Color]; |
|||
foreach (Graphic graphic in m_toRecolor) |
|||
graphic.color = new Color(color.r, color.g, color.b, graphic.color.a); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called in-editor by toggles to set the color of the lobby.
|
|||
/// </summary>
|
|||
public void ChangeColor(int color) |
|||
{ |
|||
if (m_lobby != null) |
|||
m_lobby.Color = (LobbyColor)color; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 4079cd003fcd20c40a3bac78acf44b55 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
# Contributing |
|||
|
|||
## If you are interested in contributing, here are some ground rules: |
|||
* ... Define guidelines & rules for what contributors need to know to successfully make Pull requests against your repo ... |
|||
|
|||
## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) |
|||
By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions. |
|||
|
|||
## Once you have a change ready following these ground rules. Simply make a pull request |
|
|||
fileFormatVersion: 2 |
|||
guid: d997183ab5f3e4f1aabd60a9e9d99360 |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace Unity.Services.Lobbies.Http |
|||
{ |
|||
public enum MissingMemberHandling |
|||
{ |
|||
Error, |
|||
Ignore |
|||
} |
|||
public class DeserializationSettings |
|||
{ |
|||
public MissingMemberHandling MissingMemberHandling = MissingMemberHandling.Error; |
|||
} |
|||
|
|||
} |
|
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Converters; |
|||
using Newtonsoft.Json.Utilities; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.Services.Lobbies.Http |
|||
{ |
|||
public class JsonObject |
|||
{ |
|||
internal JsonObject(object obj) |
|||
{ |
|||
this.obj = obj; |
|||
} |
|||
|
|||
internal object obj; |
|||
|
|||
public string GetAsString() |
|||
{ |
|||
try |
|||
{ |
|||
return JsonConvert.SerializeObject(obj); |
|||
} |
|||
catch (System.Exception e) |
|||
{ |
|||
throw new System.Exception("Failed to convert JsonObject to string."); |
|||
} |
|||
} |
|||
|
|||
public T GetAs<T>(DeserializationSettings deserializationSettings = null) |
|||
{ |
|||
// Check if derializationSettings is null so we can use the default value.
|
|||
deserializationSettings = deserializationSettings ?? new DeserializationSettings(); |
|||
JsonSerializerSettings jsonSettings = new JsonSerializerSettings |
|||
{ |
|||
MissingMemberHandling = deserializationSettings.MissingMemberHandling == MissingMemberHandling.Error |
|||
? Newtonsoft.Json.MissingMemberHandling.Error |
|||
: Newtonsoft.Json.MissingMemberHandling.Ignore |
|||
}; |
|||
try |
|||
{ |
|||
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj), jsonSettings); |
|||
} |
|||
catch (Newtonsoft.Json.JsonSerializationException e) |
|||
{ |
|||
throw new DeserializationException(e.Message); |
|||
} |
|||
catch (System.Exception e) |
|||
{ |
|||
throw new DeserializationException("Unable to deserialize object."); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: b3487b36881411343af7b4568aaeaefa |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Converters; |
|||
using Newtonsoft.Json.Linq; |
|||
using Newtonsoft.Json.Utilities; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.Services.Lobbies.Http |
|||
{ |
|||
class JsonObjectConverter : JsonConverter |
|||
{ |
|||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) |
|||
{ |
|||
JsonObject jobj = (JsonObject) value; |
|||
JToken t = JToken.FromObject(jobj.obj); |
|||
t.WriteTo(writer); |
|||
} |
|||
|
|||
public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer) |
|||
{ |
|||
throw new System.NotImplementedException(); |
|||
} |
|||
|
|||
public override bool CanConvert(System.Type objectType) |
|||
{ |
|||
throw new System.NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 8a8551fcf9af54b448b0fc168977d45b |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace Unity.Services.Lobbies.Http |
|||
{ |
|||
[Serializable] |
|||
public class ResponseDeserializationException : Exception |
|||
{ |
|||
public HttpClientResponse response; |
|||
|
|||
public ResponseDeserializationException() : base() |
|||
{ |
|||
} |
|||
|
|||
public ResponseDeserializationException(string message) : base(message) |
|||
{ |
|||
} |
|||
|
|||
ResponseDeserializationException(string message, Exception inner) : base(message, inner) |
|||
{ |
|||
} |
|||
|
|||
public ResponseDeserializationException(HttpClientResponse httpClientResponse) : base( |
|||
"Unable to Deserialize Http Client Response") |
|||
{ |
|||
response = httpClientResponse; |
|||
} |
|||
|
|||
public ResponseDeserializationException(HttpClientResponse httpClientResponse, string message) : base( |
|||
message) |
|||
{ |
|||
response = httpClientResponse; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 180cd9f4e83e82043ab3a392fb6556a3 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"MonoBehaviour": { |
|||
"Version": 3, |
|||
"EnableBurstCompilation": true, |
|||
"EnableOptimisations": true, |
|||
"EnableSafetyChecks": false, |
|||
"EnableDebugInAllBuilds": false, |
|||
"UsePlatformSDKLinker": false, |
|||
"CpuMinTargetX32": 0, |
|||
"CpuMaxTargetX32": 0, |
|||
"CpuMinTargetX64": 0, |
|||
"CpuMaxTargetX64": 0, |
|||
"CpuTargetsX32": 6, |
|||
"CpuTargetsX64": 72 |
|||
} |
|||
} |
|
|||
{ |
|||
"MonoBehaviour": { |
|||
"Version": 3, |
|||
"DisabledWarnings": "" |
|||
} |
|||
} |
|
|||
%YAML 1.1 |
|||
%TAG !u! tag:unity3d.com,2011: |
|||
--- !u!1 &5694388898566309151 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 2473738857440996537} |
|||
- component: {fileID: 9125082141416656856} |
|||
- component: {fileID: 288221731430065532} |
|||
- component: {fileID: 1604604243888226905} |
|||
- component: {fileID: 1102405501498744344} |
|||
- component: {fileID: 3702758058023689559} |
|||
m_Layer: 5 |
|||
m_Name: CountDownUI |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!224 &2473738857440996537 |
|||
RectTransform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5694388898566309151} |
|||
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: 2508362878409625140} |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
m_AnchorMin: {x: 0, y: 0} |
|||
m_AnchorMax: {x: 1, y: 1} |
|||
m_AnchoredPosition: {x: 0, y: -77.436} |
|||
m_SizeDelta: {x: -50, y: -404.7342} |
|||
m_Pivot: {x: 0.5, y: 0.5} |
|||
--- !u!222 &9125082141416656856 |
|||
CanvasRenderer: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5694388898566309151} |
|||
m_CullTransparentMesh: 1 |
|||
--- !u!114 &288221731430065532 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5694388898566309151} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 5b3b588e7ae40ec4ca35fdb9404513ab, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_onVisibilityChange: |
|||
m_PersistentCalls: |
|||
m_Calls: [] |
|||
showing: 0 |
|||
m_CountDownText: {fileID: 6871324529924300226} |
|||
--- !u!114 &1604604243888226905 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5694388898566309151} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: f38cf340acfcd4c64a6968b7386ad570, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_onVisibilityChange: |
|||
m_PersistentCalls: |
|||
m_Calls: [] |
|||
showing: 0 |
|||
m_ShowThisWhen: 2 |
|||
--- !u!114 &1102405501498744344 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5694388898566309151} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
OnObservedUpdated: |
|||
m_PersistentCalls: |
|||
m_Calls: |
|||
- m_Target: {fileID: 288221731430065532} |
|||
m_TargetAssemblyTypeName: CountdownUI, LobbyRooms |
|||
m_MethodName: ObservedUpdated |
|||
m_Mode: 0 |
|||
m_Arguments: |
|||
m_ObjectArgument: {fileID: 0} |
|||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine |
|||
m_IntArgument: 0 |
|||
m_FloatArgument: 0 |
|||
m_StringArgument: |
|||
m_BoolArgument: 0 |
|||
m_CallState: 2 |
|||
- m_Target: {fileID: 1604604243888226905} |
|||
m_TargetAssemblyTypeName: LobbyRooms.UI.LobbyStateVisibilityUI, LobbyRooms |
|||
m_MethodName: ObservedUpdated |
|||
m_Mode: 0 |
|||
m_Arguments: |
|||
m_ObjectArgument: {fileID: 0} |
|||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine |
|||
m_IntArgument: 0 |
|||
m_FloatArgument: 0 |
|||
m_StringArgument: |
|||
m_BoolArgument: 0 |
|||
m_CallState: 2 |
|||
observeOnStart: 0 |
|||
--- !u!225 &3702758058023689559 |
|||
CanvasGroup: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5694388898566309151} |
|||
m_Enabled: 1 |
|||
m_Alpha: 1 |
|||
m_Interactable: 1 |
|||
m_BlocksRaycasts: 1 |
|||
m_IgnoreParentGroups: 0 |
|||
--- !u!1 &6120000553027287259 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 2508362878409625140} |
|||
- component: {fileID: 3642688559417843241} |
|||
- component: {fileID: 6871324529924300226} |
|||
m_Layer: 5 |
|||
m_Name: CountdownText |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!224 &2508362878409625140 |
|||
RectTransform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 6120000553027287259} |
|||
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: 2473738857440996537} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
m_AnchorMin: {x: 0, y: 0} |
|||
m_AnchorMax: {x: 1, y: 1} |
|||
m_AnchoredPosition: {x: 0, y: 0} |
|||
m_SizeDelta: {x: 0, y: 0} |
|||
m_Pivot: {x: 0.5, y: 0.5} |
|||
--- !u!222 &3642688559417843241 |
|||
CanvasRenderer: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 6120000553027287259} |
|||
m_CullTransparentMesh: 1 |
|||
--- !u!114 &6871324529924300226 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 6120000553027287259} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_Material: {fileID: 0} |
|||
m_Color: {r: 1, g: 1, b: 1, a: 1} |
|||
m_RaycastTarget: 1 |
|||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} |
|||
m_Maskable: 1 |
|||
m_OnCullStateChanged: |
|||
m_PersistentCalls: |
|||
m_Calls: [] |
|||
m_text: 'Starting in:' |
|||
m_isRightToLeft: 0 |
|||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} |
|||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} |
|||
m_fontSharedMaterials: [] |
|||
m_fontMaterial: {fileID: 0} |
|||
m_fontMaterials: [] |
|||
m_fontColor32: |
|||
serializedVersion: 2 |
|||
rgba: 4294967295 |
|||
m_fontColor: {r: 1, g: 1, b: 1, a: 1} |
|||
m_enableVertexGradient: 0 |
|||
m_colorMode: 3 |
|||
m_fontColorGradient: |
|||
topLeft: {r: 1, g: 1, b: 1, a: 1} |
|||
topRight: {r: 1, g: 1, b: 1, a: 1} |
|||
bottomLeft: {r: 1, g: 1, b: 1, a: 1} |
|||
bottomRight: {r: 1, g: 1, b: 1, a: 1} |
|||
m_fontColorGradientPreset: {fileID: 0} |
|||
m_spriteAsset: {fileID: 0} |
|||
m_tintAllSprites: 0 |
|||
m_StyleSheet: {fileID: 0} |
|||
m_TextStyleHashCode: -1183493901 |
|||
m_overrideHtmlColors: 0 |
|||
m_faceColor: |
|||
serializedVersion: 2 |
|||
rgba: 4294967295 |
|||
m_fontSize: 21 |
|||
m_fontSizeBase: 36 |
|||
m_fontWeight: 400 |
|||
m_enableAutoSizing: 1 |
|||
m_fontSizeMin: 18 |
|||
m_fontSizeMax: 21 |
|||
m_fontStyle: 0 |
|||
m_HorizontalAlignment: 1 |
|||
m_VerticalAlignment: 512 |
|||
m_textAlignment: 65535 |
|||
m_characterSpacing: 0 |
|||
m_wordSpacing: 0 |
|||
m_lineSpacing: 0 |
|||
m_lineSpacingMax: 0 |
|||
m_paragraphSpacing: 0 |
|||
m_charWidthMaxAdj: 0 |
|||
m_enableWordWrapping: 1 |
|||
m_wordWrappingRatios: 0.4 |
|||
m_overflowMode: 0 |
|||
m_linkedTextComponent: {fileID: 0} |
|||
parentLinkedComponent: {fileID: 0} |
|||
m_enableKerning: 1 |
|||
m_enableExtraPadding: 0 |
|||
checkPaddingRequired: 0 |
|||
m_isRichText: 1 |
|||
m_parseCtrlCharacters: 1 |
|||
m_isOrthographic: 1 |
|||
m_isCullingEnabled: 0 |
|||
m_horizontalMapping: 0 |
|||
m_verticalMapping: 0 |
|||
m_uvLineOffset: 0 |
|||
m_geometrySortingOrder: 0 |
|||
m_IsTextObjectScaleStatic: 0 |
|||
m_VertexBufferAutoSizeReduction: 0 |
|||
m_useMaxVisibleDescender: 1 |
|||
m_pageToDisplay: 1 |
|||
m_margin: {x: 5, y: 5, z: 5, w: 5} |
|||
m_isUsingLegacyAnimationComponent: 0 |
|||
m_isVolumetricText: 0 |
|||
m_hasFontAssetChanged: 0 |
|||
m_baseMaterial: {fileID: 0} |
|||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0} |
|
|||
fileFormatVersion: 2 |
|||
guid: f90e4035352c7ce40b68e50109a9bb4f |
|||
PrefabImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: c16ed59d99a3d22468478da00327e666 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
/// <summary>
|
|||
/// On the host, this will watch for all players to ready, and once they have, it will prepare for a synchronized countdown.
|
|||
/// </summary>
|
|||
public class ReadyCheck : IDisposable |
|||
{ |
|||
float m_ReadyTime = 5; |
|||
|
|||
public ReadyCheck(float readyTime = 5) |
|||
{ |
|||
m_ReadyTime = readyTime; |
|||
} |
|||
|
|||
public void BeginCheckingForReady() |
|||
{ |
|||
Locator.Get.UpdateSlow.Subscribe(OnUpdate); |
|||
} |
|||
|
|||
public void EndCheckingForReady() |
|||
{ |
|||
Locator.Get.UpdateSlow.Unsubscribe(OnUpdate); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks the lobby to see if we have all Readied up. If so, send out a message with the target time at which to end a countdown.
|
|||
/// </summary>
|
|||
void OnUpdate(float dt) |
|||
{ |
|||
var lobby = LobbyAsyncRequests.Instance.CurrentLobby; |
|||
if (lobby == null || lobby.Players.Count == 0) |
|||
return; |
|||
|
|||
int readyCount = lobby.Players.Count((p) => |
|||
{ |
|||
if (p.Data?.ContainsKey("UserStatus") != true) // Needs to be "!= true" to handle null properly.
|
|||
return false; |
|||
UserStatus status; |
|||
if (Enum.TryParse(p.Data["UserStatus"].Value, out status)) |
|||
return status == UserStatus.Ready; |
|||
return false; |
|||
}); |
|||
|
|||
if (readyCount == lobby.Players.Count) |
|||
{ |
|||
Dictionary<string, string> data = new Dictionary<string, string>(); |
|||
DateTime targetTime = DateTime.Now.AddSeconds(m_ReadyTime); |
|||
data.Add("AllPlayersReady", targetTime.Ticks.ToString()); |
|||
LobbyAsyncRequests.Instance.UpdateLobbyDataAsync(data, null); |
|||
EndCheckingForReady(); |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
EndCheckingForReady(); |
|||
} |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue