浏览代码

Bugs: Adding clarifying comments for Relay + UTP and for some of the design patterns. Adding a rate limit on the lobby host button. Adding a blocker on the spinner so that the lobby list UI can't be interacted with while the spinner is visible (to prevent someone from starting to join a room that's about to be removed from the list).

/main/staging/bugs_various
nathaniel.buck@unity3d.com 3 年前
当前提交
e32921b2
共有 12 个文件被更改,包括 222 次插入63 次删除
  1. 45
      Assets/Prefabs/UI/CreateContent.prefab
  2. 121
      Assets/Prefabs/UI/JoinContent.prefab
  3. 17
      Assets/Prefabs/UI/SpinnerUI.prefab
  4. 1
      Assets/Scripts/Infrastructure/Locator.cs
  5. 3
      Assets/Scripts/Infrastructure/Messenger.cs
  6. 1
      Assets/Scripts/Infrastructure/Observed.cs
  7. 13
      Assets/Scripts/Lobby/LobbyAsyncRequests.cs
  8. 3
      Assets/Scripts/Relay/RelayAPIInterface.cs
  9. 34
      Assets/Scripts/Relay/RelayUtpClient.cs
  10. 4
      Assets/Scripts/Relay/RelayUtpHost.cs
  11. 11
      Assets/Scripts/Relay/RelayUtpSetup.cs
  12. 32
      Assets/Scripts/UI/SpinnerUI.cs

45
Assets/Prefabs/UI/CreateContent.prefab


- component: {fileID: 5377961148241680866}
- component: {fileID: 1079176168591545865}
- component: {fileID: 5149602142760426050}
- component: {fileID: 7339923225503164155}
- component: {fileID: 6735354375166727125}
- component: {fileID: 7555225420414765091}
m_Layer: 5
m_Name: CreateButton
m_TagString: Untagged

m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!225 &7339923225503164155
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8424691209185600525}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!114 &6735354375166727125
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8424691209185600525}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ab355d5994635724dbde297a055fb586, type: 3}
m_Name:
m_EditorClassIdentifier:
m_onVisibilityChange:
m_PersistentCalls:
m_Calls: []
--- !u!114 &7555225420414765091
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8424691209185600525}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 771ddda4ce2ee2a4dad57866ecc1170d, type: 3}
m_Name:
m_EditorClassIdentifier:
m_target: {fileID: 6735354375166727125}
m_alphaWhenHidden: 0.5
m_requestType: 3
--- !u!1 &8891926578961704898
GameObject:
m_ObjectHideFlags: 0

121
Assets/Prefabs/UI/JoinContent.prefab


m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 4325310634866704869}
- {fileID: 7970272424148618348}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

m_Name:
m_EditorClassIdentifier:
m_AllowSwitchOff: 0
--- !u!1 &2398639361971652739
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7970272424148618348}
- component: {fileID: 8120981699771385937}
- component: {fileID: 5477591940033768985}
- component: {fileID: 9093001432108375031}
- component: {fileID: 4619572163995016148}
m_Layer: 5
m_Name: RaycastBlocker
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7970272424148618348
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2398639361971652739}
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_Children: []
m_Father: {fileID: 785260762106121647}
m_RootOrder: 1
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!225 &8120981699771385937
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2398639361971652739}
m_Enabled: 1
m_Alpha: 0
m_Interactable: 0
m_BlocksRaycasts: 0
m_IgnoreParentGroups: 0
--- !u!114 &5477591940033768985
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2398639361971652739}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ab355d5994635724dbde297a055fb586, type: 3}
m_Name:
m_EditorClassIdentifier:
m_onVisibilityChange:
m_PersistentCalls:
m_Calls: []
--- !u!222 &9093001432108375031
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2398639361971652739}
m_CullTransparentMesh: 1
--- !u!114 &4619572163995016148
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2398639361971652739}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &2530186012912267203
GameObject:
m_ObjectHideFlags: 0

propertyPath: m_Color.a
value: 0.6666667
objectReference: {fileID: 0}
- target: {fileID: 6317050540274997421, guid: c9b04951bd45e154b8096955d9bc8a0b, type: 3}
propertyPath: m_raycastBlocker
value:
objectReference: {fileID: 5477591940033768985}
- target: {fileID: 7144088886657378797, guid: c9b04951bd45e154b8096955d9bc8a0b, type: 3}
propertyPath: m_Name
value: Spinner

objectReference: {fileID: 0}
- target: {fileID: 8141644855275361747, guid: c9b04951bd45e154b8096955d9bc8a0b, type: 3}
propertyPath: m_AnchorMax.x
value: 0.5
value: 1
value: 0.5
value: 1
value: 0.5
value: 0
value: 0.5
value: 0
value: 100
value: 0
value: 100
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8141644855275361747, guid: c9b04951bd45e154b8096955d9bc8a0b, type: 3}
propertyPath: m_LocalPosition.x

17
Assets/Prefabs/UI/SpinnerUI.prefab


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

m_onVisibilityChange:
m_PersistentCalls:
m_Calls: []
showing: 0
--- !u!1 &3885698492068079262
GameObject:
m_ObjectHideFlags: 0

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

m_onVisibilityChange:
m_PersistentCalls:
m_Calls: []
showing: 0
--- !u!1 &5113050813143078450
GameObject:
m_ObjectHideFlags: 0

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

m_onVisibilityChange:
m_PersistentCalls:
m_Calls: []
showing: 0
--- !u!1 &7144088886657378797
GameObject:
m_ObjectHideFlags: 0

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

m_onVisibilityChange:
m_PersistentCalls:
m_Calls: []
showing: 0
errorText: {fileID: 7859013680193933450}
spinnerImage: {fileID: 50617661778815274}
noServerText: {fileID: 1576294672583716881}
errorTextVisibility: {fileID: 8041986936720407558}
m_errorText: {fileID: 7859013680193933450}
m_spinnerImage: {fileID: 50617661778815274}
m_noServerText: {fileID: 1576294672583716881}
m_errorTextVisibility: {fileID: 8041986936720407558}
m_raycastBlocker: {fileID: 0}
--- !u!114 &2592976293271768794
MonoBehaviour:
m_ObjectHideFlags: 0

1
Assets/Scripts/Infrastructure/Locator.cs


/// <summary>
/// Anything which provides itself to a Locator can then be globally accessed. This should be a single access point for things that *want* to be singleton (that is,
/// when they want to be available for use by arbitrary, unknown clients) but might not always be available or might need alternate flavors for tests, logging, etc.
/// (See http://gameprogrammingpatterns.com/service-locator.html to learn more.)
/// </summary>
public class Locator : LocatorBase
{

3
Assets/Scripts/Infrastructure/Messenger.cs


CancelCountdown = 10,
ConfirmInGameState = 11,
DisplayErrorPopup = 12,
SetPlayerSound = 13,
SetPlayerSound = 13,
}
/// <summary>

1
Assets/Scripts/Infrastructure/Observed.cs


/// <summary>
/// Something that exposes some data that, when changed, an observer would want to be notified about automatically.
/// Used for UI elements and for keeping our local Lobby state synchronized with the remote Lobby service data.
/// (See http://gameprogrammingpatterns.com/observer.html to learn more.)
///
/// In your Observed child implementations, be sure to call OnChanged when setting the value of any property.
/// </summary>

13
Assets/Scripts/Lobby/LobbyAsyncRequests.cs


#region Lobby API calls are rate limited, and some other operations might want an alert when the rate limits have passed.
// Note that some APIs limit to 1 call per N seconds, while others limit to M calls per N seconds. We'll treat all APIs as though they limited to 1 call per N seconds.
// Also, this is serialized by some MonoBehaviours, so don't reorder the values unless you know what that will affect.
QuickJoin
QuickJoin,
Host
}
public RateLimitCooldown GetRateLimit(RequestType type)

else if (type == RequestType.QuickJoin)
return m_rateLimitQuickJoin;
else if (type == RequestType.Host)
return m_rateLimitHost;
return m_rateLimitQuery;
}

private RateLimitCooldown m_rateLimitHost = new RateLimitCooldown(3f);
// TODO: Shift to using this to do rate limiting for all API calls? E.g. the lobby data pushing is on its own loop.

/// </summary>
public void CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, LobbyUser localUser, Action<Lobby> onSuccess, Action onFailure)
{
if (!m_rateLimitHost.CanCall())
{
onFailure?.Invoke();
return;
}
string uasId = AuthenticationService.Instance.PlayerId;
LobbyAPIInterface.CreateLobbyAsync(uasId, lobbyName, maxPlayers, isPrivate, CreateInitialPlayerData(localUser), OnLobbyCreated);

3
Assets/Scripts/Relay/RelayAPIInterface.cs


{
/// <summary>
/// Wrapper for all the interaction with the Relay API.
/// Relay acts as an intermediary between hosts and clients for privacy. Each player will connect to an obfuscated IP address provided by Relay as though connecting directly to other players.
/// </summary>
public static class RelayAPIInterface
{

/// <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.
/// such that players can retrieve the Relay join code only after connecting to the lobby. (Note that this is not the same as the lobby code.)
/// </summary>
public static void GetJoinCodeAsync(Guid hostAllocationId, Action<string> onComplete)
{

34
Assets/Scripts/Relay/RelayUtpClient.cs


else if (msgType == MsgType.EndInGame)
Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame, null);
ProcessNetworkEventDataAdditional(conn, strm, msgType, id);
ProcessNetworkEventDataAdditional(conn, msgType, id);
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, DataStreamReader strm, MsgType msgType, string id) { }
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id) { }
protected virtual void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)
{
// The host disconnected, and Relay does not support host migration. So, all clients should disconnect.

}
/// <summary>
/// Relay uses raw pointers for efficiency. This converts them to byte arrays, assuming the stream contents are 1 byte for array length followed by contents.
/// UTP uses raw pointers for efficiency (i.e. C-style byte* instead of byte[]).
/// ReadMessageContents converts them back to byte arrays, assuming the stream contains 1 byte for array length followed by contents.
/// Any actual pointer manipulation and so forth happens service-side, so we simply need to convert back to a byte array here.
unsafe private byte[] ReadMessageContents(ref DataStreamReader strm)
unsafe private byte[] ReadMessageContents(ref DataStreamReader strm) // unsafe is required to access the pointer.
{
int length = strm.Length;
byte[] bytes = new byte[length];

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);
List<byte> message = new List<byte>(idBytes.Length + strBytes.Length + 3); // Extra 3 bytes for the msgType plus the ID and message lengths.
if (driver.BeginSend(connection, out var dataStream) == 0)
{
byte[] bytes = message.ToArray();
unsafe
{
fixed (byte* bytesPtr = bytes)
{
dataStream.WriteBytes(bytesPtr, message.Count);
driver.EndSend(dataStream);
}
}
}
SendMessageData(driver, connection, message);
}
/// <summary>

{
byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(id);
List<byte> message = new List<byte>(idBytes.Length + 3);
List<byte> message = new List<byte>(idBytes.Length + 3); // Extra 3 bytes for the msgType, ID length, and the byte value.
SendMessageData(driver, connection, message);
}
private void SendMessageData(NetworkDriver driver, NetworkConnection connection, List<byte> message)
{
unsafe
unsafe // Similarly to ReadMessageContents, our data must be converted to a pointer before being sent.
{
fixed (byte* bytesPtr = bytes)
{

4
Assets/Scripts/Relay/RelayUtpHost.cs


ForceFullUserUpdate(m_networkDriver, conn, user.Value);
}
protected override void ProcessNetworkEventDataAdditional(NetworkConnection conn, DataStreamReader strm, MsgType msgType, string id)
protected override void ProcessNetworkEventDataAdditional(NetworkConnection conn, 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)
{

11
Assets/Scripts/Relay/RelayUtpSetup.cs


namespace LobbyRelaySample.relay
{
/// <summary>
/// Responsible for setting up a connection with Relay using UTP, for the lobby host.
/// Responsible for setting up a connection with Relay using Unity Transport (UTP). A Relay Allocation is created by the host, and then all players
/// bind UTP to that Allocation in order to send data to each other.
/// Must be a MonoBehaviour since the binding process doesn't have asynchronous callback options.
/// </summary>
public abstract class RelayUtpSetup : MonoBehaviour

protected abstract void JoinRelay();
/// <summary>
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Shared behavior for binding UTP to the Relay Allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
/// </summary>
protected void BindToAllocation(string ip, int port, byte[] allocationIdBytes, byte[] connectionDataBytes, byte[] hostConnectionDataBytes, byte[] hmacKeyBytes, int connectionCapacity)

{
if (m_networkDriver.Listen() != 0)
{
Debug.LogError("Server failed to listen");
Debug.LogError("RelayUtpSetupHost failed to bind to the Relay Allocation.");
Debug.LogWarning("Server is now listening!");
Debug.Log("Relay host is bound.");
m_joinState |= JoinState.Bound;
CheckForComplete();
}

}
if (m_networkDriver.GetConnectionState(m_connections[0]) != NetworkConnection.State.Connected)
{
Debug.LogError("Client failed to connect to server");
Debug.LogError("RelayUtpSetupClient could not connect to the host.");
m_onJoinComplete(false, null);
}
else if (this != null)

32
Assets/Scripts/UI/SpinnerUI.cs


using System.Text;
using TMPro;
using UnityEngine;
namespace LobbyRelaySample.UI
{

public class SpinnerUI : ObserverPanel<LobbyServiceData>
{
public TMP_Text errorText;
public UIPanelBase spinnerImage;
public UIPanelBase noServerText;
public UIPanelBase errorTextVisibility;
[SerializeField] private TMP_Text m_errorText;
[SerializeField] private UIPanelBase m_spinnerImage;
[SerializeField] private UIPanelBase m_noServerText;
[SerializeField] private UIPanelBase m_errorTextVisibility;
[Tooltip("This prevents selecting a lobby or Joining while the spinner is visible.")]
[SerializeField] private UIPanelBase m_raycastBlocker;
public override void ObservedUpdated(LobbyServiceData observed)
{

spinnerImage.Show();
noServerText.Hide();
errorTextVisibility.Hide();
m_spinnerImage.Show();
m_raycastBlocker.Show();
m_noServerText.Hide();
m_errorTextVisibility.Hide();
spinnerImage.Hide();
errorTextVisibility.Show();
errorText.SetText("Error. See Unity Console log for details.");
m_spinnerImage.Hide();
m_raycastBlocker.Hide();
m_errorTextVisibility.Show();
m_errorText.SetText("Error. See Unity Console log for details.");
noServerText.Show();
m_noServerText.Show();
noServerText.Hide();
m_noServerText.Hide();
spinnerImage.Hide();
m_spinnerImage.Hide();
m_raycastBlocker.Hide();
}
}
}
正在加载...
取消
保存