浏览代码

Merge pull request #11 from Unity-Technologies/master-staging

Master staging
/main
GitHub 3 年前
当前提交
3740510f
共有 20 个文件被更改,包括 353 次插入259 次删除
  1. 118
      Assets/Prefabs/UI/GameCanvas.prefab
  2. 71
      Assets/Prefabs/UI/PopUpUI.prefab
  3. 79
      Assets/Scenes/mainScene.unity
  4. 20
      Assets/Scripts/Infrastructure/AsyncRequest.cs
  5. 42
      Assets/Scripts/Infrastructure/LogHandler.cs
  6. 32
      Assets/Scripts/Infrastructure/LogHandlerSettings.cs
  7. 1
      Assets/Scripts/Infrastructure/Messenger.cs
  8. 20
      Assets/Scripts/Lobby/LobbyAPIInterface.cs
  9. 59
      Assets/Scripts/Relay/RelayAPIInterface.cs
  10. 4
      Assets/Scripts/Relay/RelayUtpClient.cs
  11. 4
      Assets/Scripts/Relay/RelayUtpSetup.cs
  12. 1
      Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs
  13. 55
      Assets/Scripts/UI/PopUpUI.cs
  14. 2
      Packages/manifest.json
  15. 19
      Packages/packages-lock.json
  16. 31
      Assets/Scripts/Lobby/AsyncRequestLobby.cs
  17. 11
      Assets/Scripts/Lobby/AsyncRequestLobby.cs.meta
  18. 32
      Assets/Scripts/Relay/AsyncRequestRelay.cs
  19. 11
      Assets/Scripts/Relay/AsyncRequestRelay.cs.meta

118
Assets/Prefabs/UI/GameCanvas.prefab


- {fileID: 2637199316546086228}
- {fileID: 5992334104032192704}
- {fileID: 6907879089325579755}
- {fileID: 7914629187891855367}
m_Father: {fileID: 2637199315671523625}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

- target: {fileID: 1547097154637997990, guid: 65b3782fa4600fa4385f4eea3edd9865, type: 3}
propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[1].m_Target
value:
objectReference: {fileID: 4928485776352161055}
objectReference: {fileID: 0}
- target: {fileID: 1547097154637997990, guid: 65b3782fa4600fa4385f4eea3edd9865, type: 3}
propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[1].m_CallState
value: 2

m_CorrespondingSourceObject: {fileID: 2244251207921394025, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_PrefabInstance: {fileID: 4304342123904612290}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &4940523059930614354
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 2637199316291327119}
m_Modifications:
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_RootOrder
value: 3
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_AnchorMax.x
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2974111728825125462, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
propertyPath: m_Name
value: PopUpUI
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
--- !u!224 &7914629187891855367 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 2974111728825125461, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
m_PrefabInstance: {fileID: 4940523059930614354}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &8387645425390731067
PrefabInstance:
m_ObjectHideFlags: 0

objectReference: {fileID: 0}
- target: {fileID: 3638764283281205412, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_Value
value: 0
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3675426980811071808, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_AnchorMax.x

m_CorrespondingSourceObject: {fileID: 5836614391142406767, guid: 404728f5cffe43940b290121bd31f601, type: 3}
m_PrefabInstance: {fileID: 8387645425390731067}
m_PrefabAsset: {fileID: 0}
--- !u!114 &4928485776352161055 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 3459776754637410852, guid: 404728f5cffe43940b290121bd31f601, type: 3}
m_PrefabInstance: {fileID: 8387645425390731067}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 48ec34a3875818e4690f1bf0be69ccd9, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1001 &8690587081894129692
PrefabInstance:
m_ObjectHideFlags: 0

71
Assets/Prefabs/UI/PopUpUI.prefab


- component: {fileID: 2974111728439300793}
- component: {fileID: 2974111728439300794}
- component: {fileID: 2974111728439300795}
- component: {fileID: 3559536739324575631}
m_Layer: 5
m_Name: ErrorExitButton
m_TagString: Untagged

m_Calls:
- m_Target: {fileID: 2974111728825125460}
m_TargetAssemblyTypeName: LobbyRelaySample.PopUpUI, LobbyRelaySample
m_MethodName: Delete
m_MethodName: ClearPopup
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}

m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!225 &3559536739324575631
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2974111728439300773}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!1 &2974111728825125462
GameObject:
m_ObjectHideFlags: 0

- component: {fileID: 2974111728825125461}
- component: {fileID: 2974111728825125417}
- component: {fileID: 2974111728825125418}
- component: {fileID: 2974111728825125419}
- component: {fileID: 1969158100312030839}
- component: {fileID: 682510267401884749}
m_Layer: 5
m_Name: PopUpUI
m_TagString: Untagged

m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.2}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1

m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!225 &2974111728825125419
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2974111728825125462}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!114 &2974111728825125460
MonoBehaviour:
m_ObjectHideFlags: 0

m_Name:
m_EditorClassIdentifier:
m_popupText: {fileID: 1837149117904640813}
--- !u!223 &1969158100312030839
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2974111728825125462}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 2
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 25
m_SortingLayerID: 0
m_SortingOrder: 0
m_TargetDisplay: 0
--- !u!114 &682510267401884749
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2974111728825125462}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 3
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
m_buttonVisibility: {fileID: 3559536739324575631}
--- !u!1 &2974111728944057206
GameObject:
m_ObjectHideFlags: 0

79
Assets/Scenes/mainScene.unity


serializedVersion: 6
m_Component:
- component: {fileID: 1095306259}
- component: {fileID: 1095306258}
- component: {fileID: 1095306257}
- component: {fileID: 1095306256}
- component: {fileID: 1095306255}
m_Layer: 5
m_Name: LogManager

m_Name:
m_EditorClassIdentifier:
m_editorLogVerbosity: 0
m_popUpPrefab: {fileID: 2974111728825125460, guid: 79d6084439b78bb4eaf5232cb953fd87, type: 3}
m_popUp: {fileID: 1228229694}
m_errorReaction:
m_logMessageCallback:
m_PersistentCalls:

m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &1095306256
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1095306254}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &1095306257
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1095306254}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 800, y: 600}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!223 &1095306258
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1095306254}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 25
m_SortingLayerID: 0
m_SortingOrder: 1
m_TargetDisplay: 0
--- !u!224 &1095306259
RectTransform:
m_ObjectHideFlags: 0

m_GameObject: {fileID: 1095306254}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 4

m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a03b37d5b8df06948b36dfbc430a1ea5, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &1228229694 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 7914629187891855366, guid: f1d618bdc6f1813449d428126e640aa5, type: 3}
m_PrefabInstance: {fileID: 2637199315837045693}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5f1bc94a7a7b5249a02001abc3096ef, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &1412109061 stripped

20
Assets/Scripts/Infrastructure/AsyncRequest.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LobbyRelaySample

/// This will also permit parsing incoming exceptions for any service-specific errors that should be displayed to the player.
public static class AsyncRequest
public abstract class AsyncRequest
public static async void DoRequest(Task task, Action onComplete)
public async void DoRequest(Task task, Action onComplete)
{
string currentTrace = System.Environment.StackTrace; // For debugging. If we don't get the calling context here, it's lost once the async operation begins.
try

{ Exception eFull = new Exception($"Call stack before async call:\n{currentTrace}\n", e);
{
ParseServiceException(e);
Exception eFull = new Exception($"Call stack before async call:\n{currentTrace}\n", e); // TODO: Are we still missing Relay exceptions after the update?
throw eFull;
}
finally

public static async void DoRequest<T>(Task<T> task, Action<T> onComplete)
public async void DoRequest<T>(Task<T> task, Action<T> onComplete)
{
T result = default;
string currentTrace = System.Environment.StackTrace;

catch (Exception e)
{ Exception eFull = new Exception($"Call stack before async call:\n{currentTrace}\n", e);
{
ParseServiceException(e);
Exception eFull = new Exception($"Call stack before async call:\n{currentTrace}\n", e);
throw eFull;
}
finally

protected abstract void ParseServiceException(Exception e);
}
}

42
Assets/Scripts/Infrastructure/LogHandler.cs


using System;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace LobbyRelaySample

static LogHandler s_instance;
ILogHandler m_DefaultLogHandler = Debug.unityLogger.logHandler; // Store the default logger that prints to console.
ErrorReaction m_reaction;
public static LogHandler Get()
{

return s_instance;
}
public void SetLogReactions(ErrorReaction reactions)
{
m_reaction = reactions;
}
public void LogFormat(LogType logType, Object context, string format, params object[] args)
{
if (logType == LogType.Exception) // Exceptions are captured by LogException and should always be logged.

public void LogException(Exception exception, Object context)
{
LogReaction(exception);
}
private void LogReaction(Exception exception)
{
m_reaction?.Filter(exception);
}
}
/// <summary>
/// The idea here is to present the most relevant error first.
/// </summary>
[Serializable]
public class ErrorReaction
{
public UnityEvent<string> m_logMessageCallback;
public void Filter(Exception exception)
{
string message = "";
var rawExceptionMessage = "";
// We want to ensure the most relevant error message is on top.
if (exception.InnerException != null)
rawExceptionMessage = exception.InnerException.ToString();
else
rawExceptionMessage = exception.ToString();
var firstLineIndex = rawExceptionMessage.IndexOf("\n");
var firstRelayString = rawExceptionMessage.Substring(0, firstLineIndex);
message = firstRelayString;
if (string.IsNullOrEmpty(message))
return;
m_logMessageCallback?.Invoke(message);
}
}
}

32
Assets/Scripts/Infrastructure/LogHandlerSettings.cs


using System;
using System.Collections.Generic;
using UnityEngine.Serialization;
public class LogHandlerSettings : MonoBehaviour
/// <summary>
/// Acts as a buffer between receiving requests to display error messages to the player and running the pop-up UI to do so.
/// </summary>
public class LogHandlerSettings : MonoBehaviour, IReceiveMessages
{
[SerializeField]
[Tooltip("Only logs of this level or higher will appear in the console.")]

private PopUpUI m_popUpPrefab;
[SerializeField]
private ErrorReaction m_errorReaction;
private PopUpUI m_popUp;
void Awake()
private void Awake()
LogHandler.Get().SetLogReactions(m_errorReaction);
Locator.Get.Messenger.Subscribe(this);
}
private void OnDestroy()
{
Locator.Get.Messenger.Unsubscribe(this);
public void SpawnErrorPopup(string errorMessage)
public void OnReceiveMessage(MessageType type, object msg)
var popupInstance = Instantiate(m_popUpPrefab, transform);
popupInstance.ShowPopup(errorMessage, Color.red);
if (type == MessageType.DisplayErrorPopup && msg != null)
SpawnErrorPopup((string)msg);
}
private void SpawnErrorPopup(string errorMessage)
{
m_popUp.ShowPopup(errorMessage);
}
}
}

1
Assets/Scripts/Infrastructure/Messenger.cs


StartCountdown = 9,
CancelCountdown = 10,
ConfirmInGameState = 11,
DisplayErrorPopup = 12,
}
/// <summary>

20
Assets/Scripts/Lobby/LobbyAPIInterface.cs


Player = new Player(id: requesterUASId, data: localUserData)
};
var task = Lobbies.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions);
AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
public static void JoinLobbyAsync_ByCode(string requesterUASId, string lobbyCode, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete)

AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
public static void JoinLobbyAsync_ById(string requesterUASId, string lobbyId, Dictionary<string, PlayerDataObject> localUserData, Action<Lobby> onComplete)

AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
public static void QueryAllLobbiesAsync(List<QueryFilter> filters, Action<QueryResponse> onComplete)

Filters = filters
};
var task = Lobbies.Instance.QueryLobbiesAsync(queryOptions);
AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
public static void UpdateLobbyAsync(string lobbyId, Dictionary<string, DataObject> data, Action<Lobby> onComplete)

AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
}
public static void UpdatePlayerAsync(string lobbyId, string playerId, Dictionary<string, PlayerDataObject> data, Action<Lobby> onComplete, string allocationId, string connectionInfo)

ConnectionInfo = connectionInfo
};
var task = Lobbies.Instance.UpdatePlayerAsync(lobbyId, playerId, updateOptions);
AsyncRequest.DoRequest(task, onComplete);
AsyncRequestLobby.Instance.DoRequest(task, onComplete);
AsyncRequest.DoRequest(task, null);
AsyncRequestLobby.Instance.DoRequest(task, null);
}
}
}

59
Assets/Scripts/Relay/RelayAPIInterface.cs


using System;
using Unity.Services.Relay;
using RelayService = Unity.Services.Relay.Relay;
namespace LobbyRelaySample.relay
{

/// <summary>
/// A Relay Allocation represents a "server" for a new host.
/// </summary>
public static async void AllocateAsync(int maxConnections, Action<Allocation> onComplete)
public static void AllocateAsync(int maxConnections, Action<Allocation> onComplete)
try
{
Allocation allocation = await Relay.Instance.CreateAllocationAsync(maxConnections);
var task = RelayService.Instance.CreateAllocationAsync(maxConnections);
AsyncRequestRelay.Instance.DoRequest(task, OnResponse);
onComplete.Invoke(allocation);
}
catch (RelayServiceException ex)
void OnResponse(Allocation response)
Debug.LogError($"Relay AllocateAsync returned a relay exception: {ex.Reason} - {ex.Message}");
throw;
}
if (response == null)
Debug.LogError("Relay returned a null Allocation. This might occur if the Relay service has an outage, if your cloud project ID isn't linked, or if your Relay package version is outdated.");
else
onComplete?.Invoke(response);
};
}
/// <summary>

public static async void GetJoinCodeAsync(Guid hostAllocationId, Action<string> onComplete)
public static void GetJoinCodeAsync(Guid hostAllocationId, Action<string> onComplete)
try
var task = RelayService.Instance.GetJoinCodeAsync(hostAllocationId);
AsyncRequestRelay.Instance.DoRequest(task, OnResponse);
void OnResponse(string response)
string joinCode = await Relay.Instance.GetJoinCodeAsync(hostAllocationId);
onComplete.Invoke(joinCode);
if (response == null)
Debug.LogError("Could not retrieve a Relay join code.");
else
onComplete?.Invoke(response);
catch (RelayServiceException ex)
{
Debug.LogError($"Relay GetJoinCodeAsync returned a relay exception: {ex.Reason} - {ex.Message}");
throw;
}
public static async void JoinAsync(string joinCode, Action<JoinAllocation> onComplete)
public static void JoinAsync(string joinCode, Action<JoinAllocation> onComplete)
try
var task = RelayService.Instance.JoinAllocationAsync(joinCode);
AsyncRequestRelay.Instance.DoRequest(task, OnResponse);
void OnResponse(JoinAllocation response)
JoinAllocation joinAllocation = await Relay.Instance.JoinAllocationAsync(joinCode);
onComplete.Invoke(joinAllocation);
}
catch (RelayServiceException ex)
{
Debug.LogError($"Relay JoinCodeAsync returned a relay exception: {ex.Reason} - {ex.Message}");
throw;
}
if (response == null)
Debug.LogError("Could not join async with Relay join code " + joinCode);
else
onComplete?.Invoke(response);
};
}
}
}

4
Assets/Scripts/Relay/RelayUtpClient.cs


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.");
string msg = "Host disconnected! Leaving the lobby.";
Debug.LogError(msg);
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, msg);
Leave();
Locator.Get.Messenger.OnReceiveMessage(MessageType.ChangeGameState, GameState.JoinMenu);
}

4
Assets/Scripts/Relay/RelayUtpSetup.cs


RelayHMACKey key = ConvertHMACKeyBytes(hmacKeyBytes);
m_endpointForServer = serverEndpoint;
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key);
//TODO Implement DTLS
bool isSecure = false;
var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure);
relayServerData.ComputeNewNonce();
var relayNetworkParameter = new RelayNetworkParameter { ServerData = relayServerData };

1
Assets/Scripts/Tests/PlayMode/RelayRoundTripTests.cs


using System.Collections;
using LobbyRelaySample.relay;
using NUnit.Framework;
using Unity.Services.Relay;
using Unity.Services.Relay.Models;
using UnityEngine;
using UnityEngine.TestTools;

55
Assets/Scripts/UI/PopUpUI.cs


using LobbyRelaySample.UI;
using System.Text;
using UnityEngine.Serialization;
using UnityEngine.UI;
/// <summary>
/// Controls a pop-up message that lays over the rest of the UI, with a button to dismiss. Used for displaying player-facing error messages.
/// </summary>
TMP_InputField m_popupText;
private TMP_InputField m_popupText;
[SerializeField]
private CanvasGroup m_buttonVisibility;
private float m_buttonVisibilityTimeout = -1;
private StringBuilder m_currentText = new StringBuilder();
private void Awake()
{
gameObject.SetActive(false);
}
/// <summary>
/// If the pop-up is not currently visible, display it. If it is, append the incoming text to the existing pop-up.
/// </summary>
public void ShowPopup(string newText)
{
if (!gameObject.activeSelf)
{ m_currentText.Clear();
gameObject.SetActive(true);
}
m_currentText.AppendLine(newText);
m_popupText.SetTextWithoutNotify(m_currentText.ToString());
DisableButton();
}
private void DisableButton()
{
m_buttonVisibilityTimeout = 0.5f; // Briefly prevent the popup from being dismissed, to ensure the player doesn't accidentally click past it without seeing it.
m_buttonVisibility.alpha = 0.5f;
m_buttonVisibility.interactable = false;
}
private void ReenableButton()
{
m_buttonVisibility.alpha = 1;
m_buttonVisibility.interactable = true;
}
public void ShowPopup(string newText, Color textColor = default)
private void Update()
m_popupText.SetTextWithoutNotify(newText);
m_popupText.textComponent.color = textColor;
if (m_buttonVisibilityTimeout >= 0 && m_buttonVisibilityTimeout - Time.deltaTime < 0)
ReenableButton();
m_buttonVisibilityTimeout -= Time.deltaTime;
public void Delete()
public void ClearPopup()
Destroy(gameObject);
gameObject.SetActive(false);
}
}
}

2
Packages/manifest.json


"com.unity.test-framework": "1.1.27",
"com.unity.textmeshpro": "3.0.6",
"com.unity.toolchain.win-x86_64-linux-x86_64": "0.1.20-preview",
"com.unity.transport": "1.0.0-pre.1",
"com.unity.transport": "1.0.0-pre.3",
"com.unity.ugui": "1.0.0",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",

19
Packages/packages-lock.json


{
"dependencies": {
"com.unity.burst": {
"version": "1.5.3",
"depth": 2,
"version": "1.5.5",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.mathematics": "1.2.1"

"url": "https://packages.unity.com"
},
"com.unity.collections": {
"version": "1.0.0-pre.3",
"version": "1.0.0-pre.5",
"com.unity.burst": "1.5.3",
"com.unity.burst": "1.5.4",
"com.unity.test-framework": "1.1.22"
},
"url": "https://packages.unity.com"

"url": "https://packages.unity.com"
},
"com.unity.transport": {
"version": "file:com.unity.transport",
"version": "1.0.0-pre.3",
"source": "embedded",
"source": "registry",
"com.unity.collections": "1.0.0-pre.3",
"com.unity.burst": "1.5.1",
"com.unity.collections": "1.0.0-pre.5",
"com.unity.burst": "1.5.5",
}
},
"url": "https://packages.unity.com"
},
"com.unity.ugui": {
"version": "1.0.0",

31
Assets/Scripts/Lobby/AsyncRequestLobby.cs


using System;
using Unity.Services.Lobbies;
namespace LobbyRelaySample.lobby
{
public class AsyncRequestLobby : AsyncRequest
{
private static AsyncRequestLobby s_instance;
public static AsyncRequestLobby Instance
{
get
{ if (s_instance == null)
s_instance = new AsyncRequestLobby();
return s_instance;
}
}
/// <summary>
/// The Lobby service will wrap HTTP errors in LobbyServiceExceptions. We can filter on LobbyServiceException.Reason for custom behavior.
/// </summary>
protected override void ParseServiceException(Exception e)
{
if (!(e is LobbyServiceException))
return;
var lobbyEx = e as LobbyServiceException;
if (lobbyEx.Reason == LobbyExceptionReason.RateLimited) // We have other ways of preventing players from hitting the rate limit, so the developer-facing 429 error is sufficient here.
return;
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, $"Lobby Error: {lobbyEx.Message} ({lobbyEx.InnerException.Message})"); // Lobby error type, then HTTP error type.
}
}
}

11
Assets/Scripts/Lobby/AsyncRequestLobby.cs.meta


fileFormatVersion: 2
guid: 06b074cf2a829884caf9f52a29d33e5b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

32
Assets/Scripts/Relay/AsyncRequestRelay.cs


using System;
using Unity.Services.Relay;
namespace LobbyRelaySample.relay
{
public class AsyncRequestRelay : AsyncRequest
{
private static AsyncRequestRelay s_instance;
public static AsyncRequestRelay Instance
{
get
{ if (s_instance == null)
s_instance = new AsyncRequestRelay();
return s_instance;
}
}
/// <summary>
/// The Relay service will wrap HTTP errors in RelayServiceExceptions. We can filter on RelayServiceException.Reason for custom behavior.
/// </summary>
protected override void ParseServiceException(Exception e)
{
if (!(e is RelayServiceException))
return;
var relayEx = e as RelayServiceException;
if (relayEx.Reason == RelayExceptionReason.Unknown)
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Relay Error: Relay service had an unknown error.");
else
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, $"Relay Error: {relayEx.Message}");
}
}
}

11
Assets/Scripts/Relay/AsyncRequestRelay.cs.meta


fileFormatVersion: 2
guid: 3fb80ef44d4dad740b6e2950f0768043
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存