当前提交
4e514938
共有 33 个文件被更改,包括 615 次插入 和 815 次删除
-
2Assets/Prefabs/GameManager.prefab
-
232Assets/Scenes/mainScene.unity
-
12Assets/Scripts/Auth/Identity.cs
-
6Assets/Scripts/Auth/NameGenerator.cs
-
2Assets/Scripts/Auth/SubIdentity_Authentication.cs
-
13Assets/Scripts/Game/LobbyServiceData.cs
-
9Assets/Scripts/Game/LobbyUser.cs
-
6Assets/Scripts/Game/LocalGameState.cs
-
1Assets/Scripts/Game/LocalLobby.cs
-
5Assets/Scripts/Game/ServerAddress.cs
-
48Assets/Scripts/Infrastructure/Locator.cs
-
6Assets/Scripts/Infrastructure/LogHandler.cs
-
82Assets/Scripts/Infrastructure/Messenger.cs
-
7Assets/Scripts/Infrastructure/Observed.cs
-
3Assets/Scripts/Infrastructure/ObserverBehaviour.cs
-
37Assets/Scripts/Infrastructure/UpdateSlow.cs
-
7Assets/Scripts/Lobby/LobbyAPIInterface.cs
-
14Assets/Scripts/Lobby/LobbyAsyncRequests.cs
-
19Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
-
8Assets/Scripts/Lobby/ToLocalLobby.cs
-
16Assets/Scripts/Relay/RelayUtpClient.cs
-
4Assets/Scripts/Relay/RelayUtpHost.cs
-
17Assets/Scripts/Relay/RelayUtpSetup.cs
-
6Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs
-
6Assets/Scripts/UI/SpinnerUI.cs
-
323Assets/Scripts/Game/GameManager.cs
-
106Assets/Scripts/Relay/RelayAPIInterface.cs
-
328Assets/Scripts/Game/GameStateManager.cs
-
105Assets/Scripts/Relay/RelayInterface.cs
-
0/Assets/Prefabs/GameManager.prefab.meta
-
0/Assets/Prefabs/GameManager.prefab
-
0/Assets/Scripts/Game/GameManager.cs.meta
-
0/Assets/Scripts/Relay/RelayAPIInterface.cs.meta
|
|||
using LobbyRelaySample.Relay; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
/// <summary>
|
|||
/// Sets up and runs the entire sample.
|
|||
/// </summary>
|
|||
public class GameManager : MonoBehaviour, IReceiveMessages |
|||
{ |
|||
[SerializeField] |
|||
[Tooltip("Only logs of this level or higher will appear in the console.")] |
|||
private LogMode m_logMode = LogMode.Critical; |
|||
/// <summary>
|
|||
/// All these should be assigned the observers in the scene at the start.
|
|||
/// </summary>
|
|||
#region UI elements that observe the local state. These are
|
|||
[SerializeField] |
|||
private List<LocalGameStateObserver> m_GameStateObservers = new List<LocalGameStateObserver>(); |
|||
[SerializeField] |
|||
private List<LocalLobbyObserver> m_LocalLobbyObservers = new List<LocalLobbyObserver>(); |
|||
[SerializeField] |
|||
private List<LobbyUserObserver> m_LocalUserObservers = new List<LobbyUserObserver>(); |
|||
[SerializeField] |
|||
private List<LobbyServiceDataObserver> m_LobbyServiceObservers = new List<LobbyServiceDataObserver>(); |
|||
#endregion
|
|||
|
|||
private LocalGameState m_localGameState = new LocalGameState(); |
|||
private LobbyUser m_localUser; |
|||
private LocalLobby m_localLobby; |
|||
private LobbyServiceData m_lobbyServiceData = new LobbyServiceData(); |
|||
private LobbyContentHeartbeat m_lobbyContentHeartbeat = new LobbyContentHeartbeat(); |
|||
private RelayUtpSetup m_relaySetup; |
|||
private RelayUtpClient m_relayClient; |
|||
|
|||
/// <summary>Rather than a setter, this is usable in-editor. It won't accept an enum, however.</summary>
|
|||
public void SetLobbyColorFilter(int color) { m_lobbyColorFilter = (LobbyColor)color; } |
|||
private LobbyColor m_lobbyColorFilter; |
|||
|
|||
#region Setup
|
|||
private void Awake() |
|||
{ |
|||
// Do some arbitrary operations to instantiate singletons.
|
|||
#pragma warning disable IDE0059 // Unnecessary assignment of a value
|
|||
var unused = Locator.Get; |
|||
#pragma warning restore IDE0059
|
|||
|
|||
LogHandler.Get().mode = m_logMode; |
|||
Locator.Get.Provide(new Auth.Identity(OnAuthSignIn)); |
|||
Application.wantsToQuit += OnWantToQuit; |
|||
} |
|||
|
|||
private void Start() |
|||
{ |
|||
m_localLobby = new LocalLobby { State = LobbyState.Lobby }; |
|||
m_localUser = new LobbyUser(); |
|||
m_localUser.DisplayName = "New Player"; |
|||
Locator.Get.Messenger.Subscribe(this); |
|||
BeginObservers(); |
|||
} |
|||
|
|||
private void OnAuthSignIn() |
|||
{ |
|||
Debug.Log("Signed in."); |
|||
m_localUser.ID = Locator.Get.Identity.GetSubIdentity(Auth.IIdentityType.Auth).GetContent("id"); |
|||
m_localUser.DisplayName = NameGenerator.GetName(m_localUser.ID); |
|||
m_localLobby.AddPlayer(m_localUser); // The local LobbyUser object will be hooked into UI before the LocalLobby is populated during lobby join, so the LocalLobby must know about it already when that happens.
|
|||
} |
|||
|
|||
private void BeginObservers() |
|||
{ |
|||
foreach (var gameStateObs in m_GameStateObservers) |
|||
gameStateObs.BeginObserving(m_localGameState); |
|||
foreach (var serviceObs in m_LobbyServiceObservers) |
|||
serviceObs.BeginObserving(m_lobbyServiceData); |
|||
foreach (var lobbyObs in m_LocalLobbyObservers) |
|||
lobbyObs.BeginObserving(m_localLobby); |
|||
foreach (var userObs in m_LocalUserObservers) |
|||
userObs.BeginObserving(m_localUser); |
|||
} |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Primarily used for UI elements to communicate state changes, this will receive messages from arbitrary providers for user interactions.
|
|||
/// </summary>
|
|||
public void OnReceiveMessage(MessageType type, object msg) |
|||
{ |
|||
if (type == MessageType.RenameRequest) |
|||
{ m_localUser.DisplayName = (string)msg; |
|||
} |
|||
else if (type == MessageType.CreateLobbyRequest) |
|||
{ |
|||
var createLobbyData = (LocalLobby)msg; |
|||
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, m_localUser, (r) => |
|||
{ lobby.ToLocalLobby.Convert(r, m_localLobby); |
|||
OnCreatedLobby(); |
|||
}, |
|||
OnFailedJoin); |
|||
} |
|||
else if (type == MessageType.JoinLobbyRequest) |
|||
{ |
|||
LocalLobby.LobbyData lobbyInfo = (LocalLobby.LobbyData)msg; |
|||
LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode, m_localUser, (r) => |
|||
{ lobby.ToLocalLobby.Convert(r, m_localLobby); |
|||
OnJoinedLobby(); |
|||
}, |
|||
OnFailedJoin); |
|||
} |
|||
else if (type == MessageType.QueryLobbies) |
|||
{ |
|||
m_lobbyServiceData.State = LobbyQueryState.Fetching; |
|||
LobbyAsyncRequests.Instance.RetrieveLobbyListAsync( |
|||
qr => { |
|||
if (qr != null) |
|||
OnLobbiesQueried(lobby.ToLocalLobby.Convert(qr)); |
|||
}, |
|||
er => { |
|||
long errorLong = 0; |
|||
if (er != null) |
|||
errorLong = er.Status; |
|||
OnLobbyQueryFailed(errorLong); |
|||
}, |
|||
m_lobbyColorFilter); |
|||
} |
|||
else if (type == MessageType.ChangeGameState) |
|||
{ SetGameState((GameState)msg); |
|||
} |
|||
else if (type == MessageType.UserSetEmote) |
|||
{ EmoteType emote = (EmoteType)msg; |
|||
m_localUser.Emote = emote; |
|||
} |
|||
else if (type == MessageType.LobbyUserStatus) |
|||
{ m_localUser.UserStatus = (UserStatus)msg; |
|||
} |
|||
else if (type == MessageType.StartCountdown) |
|||
{ BeginCountDown(); |
|||
} |
|||
else if (type == MessageType.CancelCountdown) |
|||
{ m_localLobby.State = LobbyState.Lobby; |
|||
m_localLobby.CountDownTime = 0; |
|||
} |
|||
else if (type == MessageType.ConfirmInGameState) |
|||
{ m_localUser.UserStatus = UserStatus.InGame; |
|||
m_localLobby.State = LobbyState.InGame; |
|||
} |
|||
else if (type == MessageType.EndGame) |
|||
{ m_localLobby.State = LobbyState.Lobby; |
|||
m_localLobby.CountDownTime = 0; |
|||
SetUserLobbyState(); |
|||
} |
|||
} |
|||
|
|||
private void SetGameState(GameState state) |
|||
{ |
|||
bool isLeavingLobby = (state == GameState.Menu || state == GameState.JoinMenu) && m_localGameState.State == GameState.Lobby; |
|||
m_localGameState.State = state; |
|||
if (isLeavingLobby) |
|||
OnLeftLobby(); |
|||
} |
|||
|
|||
private void OnLobbiesQueried(IEnumerable<LocalLobby> lobbies) |
|||
{ |
|||
var newLobbyDict = new Dictionary<string, LocalLobby>(); |
|||
foreach (var lobby in lobbies) |
|||
newLobbyDict.Add(lobby.LobbyID, lobby); |
|||
|
|||
m_lobbyServiceData.State = LobbyQueryState.Fetched; |
|||
m_lobbyServiceData.CurrentLobbies = newLobbyDict; |
|||
} |
|||
|
|||
private void OnLobbyQueryFailed(long errorCode) |
|||
{ |
|||
m_lobbyServiceData.lastErrorCode = errorCode; |
|||
m_lobbyServiceData.State = LobbyQueryState.Error; |
|||
} |
|||
|
|||
private void OnCreatedLobby() |
|||
{ |
|||
m_localUser.IsHost = true; |
|||
OnJoinedLobby(); |
|||
} |
|||
|
|||
private void OnJoinedLobby() |
|||
{ |
|||
LobbyAsyncRequests.Instance.BeginTracking(m_localLobby.LobbyID); |
|||
m_lobbyContentHeartbeat.BeginTracking(m_localLobby, m_localUser); |
|||
SetUserLobbyState(); |
|||
StartRelayConnection(); |
|||
} |
|||
|
|||
private void OnLeftLobby() |
|||
{ |
|||
m_localUser.ResetState(); |
|||
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby.LobbyID, ResetLocalLobby); |
|||
m_lobbyContentHeartbeat.EndTracking(); |
|||
LobbyAsyncRequests.Instance.EndTracking(); |
|||
|
|||
if (m_relaySetup != null) |
|||
{ Component.Destroy(m_relaySetup); |
|||
m_relaySetup = null; |
|||
} |
|||
if (m_relayClient != null) |
|||
{ Component.Destroy(m_relayClient); |
|||
m_relayClient = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Back to Join menu if we fail to join for whatever reason.
|
|||
/// </summary>
|
|||
private void OnFailedJoin() |
|||
{ |
|||
SetGameState(GameState.JoinMenu); |
|||
} |
|||
|
|||
private void StartRelayConnection() |
|||
{ |
|||
if (m_localUser.IsHost) |
|||
m_relaySetup = gameObject.AddComponent<RelayUtpSetupHost>(); |
|||
else |
|||
m_relaySetup = gameObject.AddComponent<RelayUtpSetupClient>(); |
|||
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Connecting); |
|||
m_relaySetup.BeginRelayJoin(m_localLobby, m_localUser, OnRelayConnected); |
|||
} |
|||
|
|||
private void OnRelayConnected(bool didSucceed, RelayUtpClient client) |
|||
{ |
|||
Component.Destroy(m_relaySetup); |
|||
m_relaySetup = null; |
|||
|
|||
if (!didSucceed) |
|||
{ |
|||
Debug.LogError("Relay connection failed! Retrying in 5s..."); |
|||
StartCoroutine(RetryRelayConnection()); |
|||
return; |
|||
} |
|||
m_relayClient = client; |
|||
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby); |
|||
} |
|||
|
|||
private IEnumerator RetryRelayConnection() |
|||
{ |
|||
yield return new WaitForSeconds(5); |
|||
StartRelayConnection(); |
|||
} |
|||
|
|||
private void BeginCountDown() |
|||
{ |
|||
if (m_localLobby.State == LobbyState.CountDown) |
|||
return; |
|||
m_localLobby.CountDownTime = 4; |
|||
m_localLobby.State = LobbyState.CountDown; |
|||
StartCoroutine(CountDown()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The CountdownUI will pick up on changes to the lobby's countdown timer. This can be interrupted if the lobby leaves the countdown state (via a CancelCountdown message).
|
|||
/// </summary>
|
|||
private IEnumerator CountDown() |
|||
{ |
|||
while (m_localLobby.CountDownTime > 0) |
|||
{ |
|||
yield return null; |
|||
if (m_localLobby.State != LobbyState.CountDown) |
|||
yield break; |
|||
m_localLobby.CountDownTime -= Time.deltaTime; |
|||
} |
|||
if (m_relayClient is RelayUtpHost) |
|||
(m_relayClient as RelayUtpHost).SendInGameState(); |
|||
} |
|||
|
|||
private void SetUserLobbyState() |
|||
{ |
|||
SetGameState(GameState.Lobby); |
|||
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby); |
|||
} |
|||
|
|||
private void ResetLocalLobby() |
|||
{ |
|||
m_localLobby.CopyObserved(new LocalLobby.LobbyData(), new Dictionary<string, LobbyUser>()); |
|||
m_localLobby.AddPlayer(m_localUser); // As before, the local player will need to be plugged into UI before the lobby join actually happens.
|
|||
m_localLobby.CountDownTime = 0; |
|||
m_localLobby.RelayServer = null; |
|||
} |
|||
|
|||
#region Teardown
|
|||
/// <summary>
|
|||
/// In builds, if we are in a lobby and try to send a Leave request on application quit, it won't go through if we're quitting on the same frame.
|
|||
/// So, we need to delay just briefly to let the request happen (though we don't need to wait for the result).
|
|||
/// </summary>
|
|||
private IEnumerator LeaveBeforeQuit() |
|||
{ |
|||
ForceLeaveAttempt(); |
|||
yield return null; |
|||
Application.Quit(); |
|||
} |
|||
|
|||
private bool OnWantToQuit() |
|||
{ |
|||
bool canQuit = string.IsNullOrEmpty(m_localLobby?.LobbyID); |
|||
StartCoroutine(LeaveBeforeQuit()); |
|||
return canQuit; |
|||
} |
|||
|
|||
private void OnDestroy() |
|||
{ |
|||
ForceLeaveAttempt(); |
|||
} |
|||
|
|||
private void ForceLeaveAttempt() |
|||
{ |
|||
Locator.Get.Messenger.Unsubscribe(this); |
|||
if (!string.IsNullOrEmpty(m_localLobby?.LobbyID)) |
|||
{ |
|||
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby?.LobbyID, null); |
|||
m_localLobby = null; |
|||
} |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Unity.Services.Relay; |
|||
using Unity.Services.Relay.Allocations; |
|||
using Unity.Services.Relay.Models; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
/// <summary>
|
|||
/// Wrapper for all the interaction with the Relay API.
|
|||
/// </summary>
|
|||
public static class RelayAPIInterface |
|||
{ |
|||
/// <summary>
|
|||
/// API calls are asynchronous, but for debugging and other reasons we want to reify them as objects so that they can be monitored.
|
|||
/// </summary>
|
|||
private class InProgressRequest<T> |
|||
{ |
|||
public InProgressRequest(Task<T> task, Action<T> onComplete) |
|||
{ |
|||
DoRequest(task, onComplete); |
|||
} |
|||
|
|||
private async void DoRequest(Task<T> task, Action<T> onComplete) |
|||
{ |
|||
T result = default; |
|||
string currentTrace = System.Environment.StackTrace; // If we don't get the calling context here, it's lost once the async operation begins.
|
|||
try { |
|||
result = await task; |
|||
} catch (Exception e) { |
|||
Exception eFull = new Exception($"Call stack before async call:\n{currentTrace}\n", e); |
|||
throw eFull; |
|||
} finally { |
|||
onComplete?.Invoke(result); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A Relay Allocation represents a "server" for a new host.
|
|||
/// </summary>
|
|||
public static void AllocateAsync(int maxConnections, Action<Allocation> onComplete) |
|||
{ |
|||
CreateAllocationRequest createAllocationRequest = new CreateAllocationRequest(new AllocationRequest(maxConnections)); |
|||
var task = RelayService.AllocationsApiClient.CreateAllocationAsync(createAllocationRequest); |
|||
new InProgressRequest<Response<AllocateResponseBody>>(task, OnResponse); |
|||
|
|||
void OnResponse(Response<AllocateResponseBody> response) |
|||
{ |
|||
if (response == null) |
|||
Debug.LogError("Relay returned a null Allocation. It's possible the Relay service is currently down."); |
|||
else if (response.Status >= 200 && response.Status < 300) |
|||
onComplete?.Invoke(response.Result.Data.Allocation); |
|||
else |
|||
Debug.LogError($"Allocation returned a non Success code: {response.Status}"); |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Only after an Allocation has been completed can a Relay join code be obtained. This code will be stored in the lobby's data as non-public
|
|||
/// such that players can retrieve the Relay join code only after connecting to the lobby.
|
|||
/// </summary>
|
|||
public static void GetJoinCodeAsync(Guid hostAllocationId, Action<string> onComplete) |
|||
{ |
|||
GetJoinCodeAsync(hostAllocationId, a => |
|||
{ |
|||
if (a.Status >= 200 && a.Status < 300) |
|||
onComplete.Invoke(a.Result.Data.JoinCode); |
|||
else |
|||
{ |
|||
Debug.LogError($"Join Code Get returned a non Success code: {a.Status}"); |
|||
} |
|||
}); |
|||
} |
|||
private static void GetJoinCodeAsync(Guid hostAllocationId, Action<Response<JoinCodeResponseBody>> onComplete) |
|||
{ |
|||
CreateJoincodeRequest joinCodeRequest = new CreateJoincodeRequest(new JoinCodeRequest(hostAllocationId)); |
|||
var task = RelayService.AllocationsApiClient.CreateJoincodeAsync(joinCodeRequest); |
|||
new InProgressRequest<Response<JoinCodeResponseBody>>(task, onComplete); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clients call this to retrieve the host's Allocation via a Relay join code.
|
|||
/// </summary>
|
|||
public static void JoinAsync(string joinCode, Action<JoinAllocation> onComplete) |
|||
{ |
|||
JoinAsync(joinCode, a => |
|||
{ |
|||
if (a.Status >= 200 && a.Status < 300) |
|||
onComplete.Invoke(a.Result.Data.Allocation); |
|||
else |
|||
{ |
|||
Debug.LogError($"Join Call returned a non Success code: {a.Status}"); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void JoinAsync(string joinCode, Action<Response<JoinResponseBody>> onComplete) |
|||
{ |
|||
JoinRelayRequest joinRequest = new JoinRelayRequest(new JoinRequest(joinCode)); |
|||
var task = RelayService.AllocationsApiClient.JoinRelayAsync(joinRequest); |
|||
new InProgressRequest<Response<JoinResponseBody>>(task, onComplete); |
|||
} |
|||
} |
|||
} |
|
|||
using LobbyRelaySample.Relay; |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
// TODO: This is pretty bloated. Additionally, it needs a pass for removing redundant calls and organizing things in a more intuitive way and whatnot
|
|||
|
|||
public class GameStateManager : MonoBehaviour, IReceiveMessages |
|||
{ |
|||
[SerializeField] |
|||
LogMode m_logMode = LogMode.Critical; |
|||
/// <summary>
|
|||
/// All these should be assigned the observers in the scene at the start.
|
|||
/// </summary>
|
|||
[SerializeField] |
|||
List<LocalGameStateObserver> m_GameStateObservers = new List<LocalGameStateObserver>(); |
|||
[SerializeField] |
|||
List<LocalLobbyObserver> m_LocalLobbyObservers = new List<LocalLobbyObserver>(); |
|||
[SerializeField] |
|||
List<LobbyUserObserver> m_LocalUserObservers = new List<LobbyUserObserver>(); |
|||
[SerializeField] |
|||
List<LobbyServiceDataObserver> m_LobbyServiceObservers = new List<LobbyServiceDataObserver>(); |
|||
|
|||
private LobbyContentHeartbeat m_lobbyContentHeartbeat = new LobbyContentHeartbeat(); |
|||
|
|||
LobbyUser m_localUser; |
|||
LocalLobby m_localLobby; |
|||
LobbyServiceData m_lobbyServiceData = new LobbyServiceData(); |
|||
LocalGameState m_localGameState = new LocalGameState(); |
|||
|
|||
RelayUtpSetup m_relaySetup; |
|||
RelayUtpClient m_relayClient; |
|||
|
|||
/// <summary>Rather than a setter, this is usable in-editor. It won't accept an enum, however.</summary>
|
|||
public void SetLobbyColorFilter(int color) { m_lobbyColorFilter = (LobbyColor)color; } |
|||
private LobbyColor m_lobbyColorFilter; |
|||
|
|||
public void Awake() |
|||
{ |
|||
LogHandler.Get().mode = m_logMode; |
|||
// Do some arbitrary operations to instantiate singletons.
|
|||
#pragma warning disable IDE0059 // Unnecessary assignment of a value
|
|||
var unused = Locator.Get; |
|||
#pragma warning restore IDE0059 // Unnecessary assignment of a value
|
|||
Locator.Get.Provide(new Auth.Identity(OnAuthSignIn)); |
|||
Application.wantsToQuit += OnWantToQuit; |
|||
} |
|||
|
|||
private void OnAuthSignIn() |
|||
{ |
|||
Debug.Log("Signed in."); |
|||
m_localUser.ID = Locator.Get.Identity.GetSubIdentity(Auth.IIdentityType.Auth).GetContent("id"); |
|||
m_localUser.DisplayName = NameGenerator.GetName(m_localUser.ID); |
|||
m_localLobby.AddPlayer(m_localUser); // The local LobbyUser object will be hooked into UI before the LocalLobby is populated during lobby join, so the LocalLobby must know about it already when that happens.
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Primarily used for UI elements to communicate state changes, this will receive messages from arbitrary providers for user interactions.
|
|||
/// </summary>
|
|||
public void OnReceiveMessage(MessageType type, object msg) |
|||
{ |
|||
if (type == MessageType.RenameRequest) |
|||
{ |
|||
m_localUser.DisplayName = (string)msg; |
|||
} |
|||
else if (type == MessageType.CreateLobbyRequest) |
|||
{ |
|||
var createLobbyData = (LocalLobby)msg; |
|||
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, m_localUser, (r) => |
|||
{ |
|||
lobby.ToLocalLobby.Convert(r, m_localLobby); |
|||
OnCreatedLobby(); |
|||
}, OnFailedJoin); |
|||
} |
|||
else if (type == MessageType.JoinLobbyRequest) |
|||
{ |
|||
LocalLobby.LobbyData lobbyInfo = (LocalLobby.LobbyData)msg; |
|||
LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode, m_localUser, (r) => |
|||
{ |
|||
lobby.ToLocalLobby.Convert(r, m_localLobby); |
|||
OnJoinedLobby(); |
|||
}, OnFailedJoin); |
|||
} |
|||
else if (type == MessageType.QueryLobbies) |
|||
{ |
|||
m_lobbyServiceData.State = LobbyServiceState.Fetching; |
|||
LobbyAsyncRequests.Instance.RetrieveLobbyListAsync( |
|||
qr => |
|||
{ |
|||
if (qr != null) |
|||
OnRefreshed(lobby.ToLocalLobby.Convert(qr)); |
|||
}, er => |
|||
{ |
|||
long errorLong = 0; |
|||
if (er != null) |
|||
errorLong = er.Status; |
|||
OnRefreshFailed(errorLong); |
|||
}, |
|||
m_lobbyColorFilter); |
|||
} |
|||
else if (type == MessageType.ChangeGameState) |
|||
{ |
|||
SetGameState((GameState)msg); |
|||
} |
|||
else if (type == MessageType.UserSetEmote) |
|||
{ |
|||
EmoteType emote = (EmoteType)msg; |
|||
m_localUser.Emote = emote; |
|||
} |
|||
else if (type == MessageType.LobbyUserStatus) |
|||
{ |
|||
m_localUser.UserStatus = (UserStatus)msg; |
|||
} |
|||
else if (type == MessageType.StartCountdown) |
|||
{ |
|||
BeginCountDown(); |
|||
} |
|||
else if (type == MessageType.CancelCountdown) |
|||
{ |
|||
m_localLobby.State = LobbyState.Lobby; |
|||
m_localLobby.CountDownTime = 0; |
|||
} |
|||
else if (type == MessageType.ConfirmInGameState) |
|||
{ |
|||
m_localUser.UserStatus = UserStatus.InGame; |
|||
m_localLobby.State = LobbyState.InGame; |
|||
} |
|||
else if (type == MessageType.EndGame) |
|||
{ |
|||
m_localLobby.State = LobbyState.Lobby; |
|||
m_localLobby.CountDownTime = 0; |
|||
SetUserLobbyState(); |
|||
} |
|||
} |
|||
|
|||
void Start() |
|||
{ |
|||
m_localLobby = new LocalLobby { State = LobbyState.Lobby }; |
|||
m_localUser = new LobbyUser(); |
|||
m_localUser.DisplayName = "New Player"; |
|||
Locator.Get.Messenger.Subscribe(this); |
|||
BeginObservers(); |
|||
} |
|||
|
|||
void BeginObservers() |
|||
{ |
|||
foreach (var gameStateObs in m_GameStateObservers) |
|||
gameStateObs.BeginObserving(m_localGameState); |
|||
foreach (var serviceObs in m_LobbyServiceObservers) |
|||
serviceObs.BeginObserving(m_lobbyServiceData); |
|||
foreach (var lobbyObs in m_LocalLobbyObservers) |
|||
lobbyObs.BeginObserving(m_localLobby); |
|||
foreach (var userObs in m_LocalUserObservers) |
|||
userObs.BeginObserving(m_localUser); |
|||
} |
|||
|
|||
void SetGameState(GameState state) |
|||
{ |
|||
bool isLeavingLobby = (state == GameState.Menu || state == GameState.JoinMenu) && m_localGameState.State == GameState.Lobby; |
|||
m_localGameState.State = state; |
|||
if (isLeavingLobby) |
|||
OnLeftLobby(); |
|||
} |
|||
|
|||
void OnRefreshed(IEnumerable<LocalLobby> lobbies) |
|||
{ |
|||
var newLobbyDict = new Dictionary<string, LocalLobby>(); |
|||
foreach (var lobby in lobbies) |
|||
{ |
|||
newLobbyDict.Add(lobby.LobbyID, lobby); |
|||
} |
|||
|
|||
m_lobbyServiceData.State = LobbyServiceState.Fetched; |
|||
m_lobbyServiceData.CurrentLobbies = newLobbyDict; |
|||
} |
|||
|
|||
void OnRefreshFailed(long errorCode) |
|||
{ |
|||
m_lobbyServiceData.lastErrorCode = errorCode; |
|||
m_lobbyServiceData.State = LobbyServiceState.Error; |
|||
} |
|||
|
|||
void OnCreatedLobby() |
|||
{ |
|||
m_localUser.IsHost = true; |
|||
OnJoinedLobby(); |
|||
} |
|||
|
|||
void OnJoinedLobby() |
|||
{ |
|||
LobbyAsyncRequests.Instance.BeginTracking(m_localLobby.LobbyID); |
|||
m_lobbyContentHeartbeat.BeginTracking(m_localLobby, m_localUser); |
|||
SetUserLobbyState(); |
|||
StartRelayConnection(); |
|||
} |
|||
|
|||
void StartRelayConnection() |
|||
{ |
|||
if (m_localUser.IsHost) |
|||
m_relaySetup = gameObject.AddComponent<RelayUtpSetupHost>(); |
|||
else |
|||
m_relaySetup = gameObject.AddComponent<RelayUtpSetupClient>(); |
|||
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Connecting); |
|||
m_relaySetup.BeginRelayJoin(m_localLobby, m_localUser, OnRelayConnected); |
|||
} |
|||
|
|||
void OnRelayConnected(bool didSucceed, RelayUtpClient client) |
|||
{ |
|||
Component.Destroy(m_relaySetup); |
|||
m_relaySetup = null; |
|||
|
|||
if (!didSucceed) |
|||
{ |
|||
Debug.LogError("Relay connection failed! Retrying in 5s..."); |
|||
StartCoroutine(RetryRelayConnection()); |
|||
return; |
|||
} |
|||
m_relayClient = client; |
|||
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby); |
|||
} |
|||
|
|||
IEnumerator RetryRelayConnection() |
|||
{ |
|||
yield return new WaitForSeconds(5); |
|||
StartRelayConnection(); |
|||
} |
|||
|
|||
void OnLeftLobby() |
|||
{ |
|||
m_localUser.ResetState(); |
|||
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby.LobbyID, ResetLocalLobby); |
|||
m_lobbyContentHeartbeat.EndTracking(); |
|||
LobbyAsyncRequests.Instance.EndTracking(); |
|||
|
|||
if (m_relaySetup != null) |
|||
{ Component.Destroy(m_relaySetup); |
|||
m_relaySetup = null; |
|||
} |
|||
if (m_relayClient != null) |
|||
{ Component.Destroy(m_relayClient); |
|||
m_relayClient = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Back to Join menu if we fail to join for whatever reason.
|
|||
/// </summary>
|
|||
void OnFailedJoin() |
|||
{ |
|||
SetGameState(GameState.JoinMenu); |
|||
} |
|||
|
|||
void BeginCountDown() |
|||
{ |
|||
if (m_localLobby.State == LobbyState.CountDown) |
|||
return; |
|||
m_localLobby.CountDownTime = 4; |
|||
m_localLobby.State = LobbyState.CountDown; |
|||
StartCoroutine(CountDown()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The CountdownUI will pick up on changes to the lobby's countdown timer. This can be interrupted if the lobby leaves the countdown state (via a CancelCountdown message).
|
|||
/// </summary>
|
|||
IEnumerator CountDown() |
|||
{ |
|||
while (m_localLobby.CountDownTime > 0) |
|||
{ |
|||
yield return null; |
|||
if (m_localLobby.State != LobbyState.CountDown) |
|||
yield break; |
|||
m_localLobby.CountDownTime -= Time.deltaTime; |
|||
} |
|||
if (m_relayClient is RelayUtpHost) |
|||
(m_relayClient as RelayUtpHost).SendInGameState(); |
|||
} |
|||
|
|||
void SetUserLobbyState() |
|||
{ |
|||
SetGameState(GameState.Lobby); |
|||
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby); |
|||
} |
|||
|
|||
void ResetLocalLobby() |
|||
{ |
|||
m_localLobby.CopyObserved(new LocalLobby.LobbyData(), new Dictionary<string, LobbyUser>()); |
|||
m_localLobby.AddPlayer(m_localUser); // As before, the local player will need to be plugged into UI before the lobby join actually happens.
|
|||
m_localLobby.CountDownTime = 0; |
|||
m_localLobby.RelayServer = null; |
|||
} |
|||
|
|||
void OnDestroy() |
|||
{ |
|||
ForceLeaveAttempt(); |
|||
} |
|||
|
|||
bool OnWantToQuit() |
|||
{ |
|||
bool canQuit = string.IsNullOrEmpty(m_localLobby?.LobbyID); |
|||
StartCoroutine(LeaveBeforeQuit()); |
|||
return canQuit; |
|||
} |
|||
|
|||
void ForceLeaveAttempt() |
|||
{ |
|||
Locator.Get.Messenger.Unsubscribe(this); |
|||
if (!string.IsNullOrEmpty(m_localLobby?.LobbyID)) |
|||
{ |
|||
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby?.LobbyID, null); |
|||
m_localLobby = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// In builds, if we are in a lobby and try to send a Leave request on application quit, it won't go through if we're quitting on the same frame.
|
|||
/// So, we need to delay just briefly to let the request happen (though we don't need to wait for the result).
|
|||
/// </summary>
|
|||
IEnumerator LeaveBeforeQuit() |
|||
{ |
|||
ForceLeaveAttempt(); |
|||
yield return null; |
|||
Application.Quit(); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Unity.Services.Relay; |
|||
using Unity.Services.Relay.Allocations; |
|||
using Unity.Services.Relay.Models; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample.Relay |
|||
{ |
|||
/// <summary>
|
|||
/// Does all the interaction with relay.
|
|||
/// </summary>
|
|||
public static class RelayInterface |
|||
{ |
|||
private class InProgressRequest<T> |
|||
{ |
|||
public InProgressRequest(Task<T> task, Action<T> onComplete) |
|||
{ |
|||
DoRequest(task, onComplete); |
|||
} |
|||
|
|||
private async void DoRequest(Task<T> task, Action<T> onComplete) |
|||
{ |
|||
T result = default; |
|||
string currentTrace = System.Environment.StackTrace; |
|||
try { |
|||
result = await task; |
|||
} catch (Exception e) { |
|||
Exception eFull = new Exception($"Call stack before async call:\n{currentTrace}\n", e); |
|||
throw eFull; |
|||
} finally { |
|||
onComplete?.Invoke(result); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a Relay Server, and returns the Allocation (Response.Result.Data.Allocation)
|
|||
/// </summary>
|
|||
public static void AllocateAsync(int maxConnections, Action<Allocation> onComplete) |
|||
{ |
|||
CreateAllocationRequest createAllocationRequest = new CreateAllocationRequest(new AllocationRequest(maxConnections)); |
|||
var task = RelayService.AllocationsApiClient.CreateAllocationAsync(createAllocationRequest); |
|||
new InProgressRequest<Response<AllocateResponseBody>>(task, OnResponse); |
|||
|
|||
void OnResponse(Response<AllocateResponseBody> response) |
|||
{ |
|||
if (response == null) |
|||
Debug.LogError("Relay returned a null Allocation. It's possible the Relay service is currently down."); |
|||
else if (response.Status >= 200 && response.Status < 300) |
|||
onComplete?.Invoke(response.Result.Data.Allocation); |
|||
else |
|||
Debug.LogError($"Allocation returned a non Success code: {response.Status}"); |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get a JoinCode( Response.Result.Data.JoinCode) from an Allocated Server
|
|||
/// </summary>
|
|||
public static void GetJoinCodeAsync(Guid hostAllocationId, Action<Response<JoinCodeResponseBody>> onComplete) |
|||
{ |
|||
CreateJoincodeRequest joinCodeRequest = new CreateJoincodeRequest(new JoinCodeRequest(hostAllocationId)); |
|||
var task = RelayService.AllocationsApiClient.CreateJoincodeAsync(joinCodeRequest); |
|||
|
|||
new InProgressRequest<Response<JoinCodeResponseBody>>(task, onComplete); |
|||
} |
|||
|
|||
public static void GetJoinCodeAsync(Guid hostAllocationId, Action<string> onComplete) |
|||
{ |
|||
GetJoinCodeAsync(hostAllocationId, a => |
|||
{ |
|||
if (a.Status >= 200 && a.Status < 300) |
|||
onComplete.Invoke(a.Result.Data.JoinCode); |
|||
else |
|||
{ |
|||
Debug.LogError($"Join Code Get returned a non Success code: {a.Status}"); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieve an Allocation(Response.Result.Data.Allocation) by join code
|
|||
/// </summary>
|
|||
public static void JoinAsync(string joinCode, Action<Response<JoinResponseBody>> onComplete) |
|||
{ |
|||
JoinRelayRequest joinRequest = new JoinRelayRequest(new JoinRequest(joinCode)); |
|||
var task = RelayService.AllocationsApiClient.JoinRelayAsync(joinRequest); |
|||
|
|||
new InProgressRequest<Response<JoinResponseBody>>(task, onComplete); |
|||
} |
|||
|
|||
public static void JoinAsync(string joinCode, Action<JoinAllocation> onComplete) |
|||
{ |
|||
JoinAsync(joinCode, a => |
|||
{ |
|||
if (a.Status >= 200 && a.Status < 300) |
|||
onComplete.Invoke(a.Result.Data.Allocation); |
|||
else |
|||
{ |
|||
Debug.LogError($"Join Call returned a non Success code: {a.Status}"); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue