浏览代码

Merge pull request #18 from Unity-Technologies/master

Master
/main
GitHub 3 年前
当前提交
16ed3868
共有 32 个文件被更改,包括 1120 次插入1136 次删除
  1. 988
      Assets/Art/Font/CheckboxFLF SDF.asset
  2. 60
      Assets/Prefabs/UI/GameCanvas.prefab
  3. 13
      Assets/Prefabs/UI/JoinContent.prefab
  4. 15
      Assets/Prefabs/UI/JoinCreateCanvas.prefab
  5. 13
      Assets/Prefabs/UI/PlayerInteractionPanel.prefab
  6. 98
      Assets/Prefabs/UI/RenamePopup.prefab
  7. 27
      Assets/Scenes/mainScene.unity
  8. 102
      Assets/Scripts/Game/GameManager.cs
  9. 32
      Assets/Scripts/Game/LobbyUser.cs
  10. 31
      Assets/Scripts/Game/LocalLobby.cs
  11. 25
      Assets/Scripts/Infrastructure/Messenger.cs
  12. 2
      Assets/Scripts/Infrastructure/Observed.cs
  13. 12
      Assets/Scripts/Lobby/LobbyAsyncRequests.cs
  14. 53
      Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
  15. 4
      Assets/Scripts/Lobby/ToLocalLobby.cs
  16. 29
      Assets/Scripts/Relay/RelayUtpClient.cs
  17. 39
      Assets/Scripts/Relay/RelayUtpHost.cs
  18. 2
      Assets/Scripts/Relay/RelayUtpSetup.cs
  19. 1
      Assets/Scripts/Tests/PlayMode/UtpTests.cs
  20. 10
      Assets/Scripts/UI/CountdownUI.cs
  21. 3
      Assets/Scripts/UI/JoinCreateLobbyUI.cs
  22. 6
      Assets/Scripts/UI/JoinMenuUI.cs
  23. 35
      Assets/Scripts/Vivox/VivoxSetup.cs
  24. 472
      Assets/TextMesh Pro/Resources/Fonts & Materials/Roboto-Bold SDF.asset
  25. 8
      Packages/manifest.json
  26. 21
      Packages/packages-lock.json
  27. 6
      ProjectSettings/ProjectSettings.asset
  28. 4
      ProjectSettings/ProjectVersion.txt
  29. 66
      Assets/Scripts/Game/Countdown.cs
  30. 11
      Assets/Scripts/Game/Countdown.cs.meta
  31. 57
      Assets/Scripts/Relay/RelayPendingApproval.cs
  32. 11
      Assets/Scripts/Relay/RelayPendingApproval.cs.meta

988
Assets/Art/Font/CheckboxFLF SDF.asset
文件差异内容过多而无法显示
查看文件

60
Assets/Prefabs/UI/GameCanvas.prefab


propertyPath: onValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName
value: LobbyRelaySample.GameManager, LobbyRelaySample
objectReference: {fileID: 0}
- target: {fileID: 663819175912117418, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_MinWidth
value: 0
objectReference: {fileID: 0}
- target: {fileID: 663819175912117418, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_MinHeight
value: 0
objectReference: {fileID: 0}
- target: {fileID: 663819175912117418, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_FlexibleWidth
value: 0
objectReference: {fileID: 0}
- target: {fileID: 663819175912117418, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_FlexibleHeight
value: 0
objectReference: {fileID: 0}
- target: {fileID: 663819175912117418, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_LayoutPriority
value: 0
objectReference: {fileID: 0}
- target: {fileID: 663819175912117418, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_PreferredWidth
value: 0
objectReference: {fileID: 0}
- target: {fileID: 663819175912117418, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_PreferredHeight
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1119140321553661053, guid: 404728f5cffe43940b290121bd31f601, type: 3}
propertyPath: m_AnchorMax.y
value: 0

propertyPath: m_Name
value: TongueIcon
objectReference: {fileID: 0}
- target: {fileID: 1070826112545139196, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1097905206279711496, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_AnchorMax.y
value: 0

- target: {fileID: 1729213530568520925, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1733240969009089461, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1812972113098954427, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_AnchorMax.y

propertyPath: m_PreferredHeight
value: 40
objectReference: {fileID: 0}
- target: {fileID: 2905825510828002570, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2989330310034162163, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_fontSize
value: 25

propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4393070972951313089, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4515428844496569119, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4551996994186358382, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_AnchorMax.y
value: 0

- target: {fileID: 4927412524127554603, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_PreferredHeight
value: 40
objectReference: {fileID: 0}
- target: {fileID: 5019529806587931366, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 5263639458761575591, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_AnchorMax.y

propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6455527238533904754, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 6520888008628790540, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0

- target: {fileID: 8077960435604160994, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8160491488537189612, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8314514420049872699, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
propertyPath: m_Color.b

13
Assets/Prefabs/UI/JoinContent.prefab


m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
- m_Target: {fileID: 1462126939442648229}
m_TargetAssemblyTypeName: LobbyRelaySample.UI.JoinMenuUI, LobbyRelaySample
m_MethodName: JoinMenuChangedVisibility
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_JoinCodeField: {fileID: 8659642538454988273}
--- !u!114 &7550446569341709048
MonoBehaviour:
m_ObjectHideFlags: 0

15
Assets/Prefabs/UI/JoinCreateCanvas.prefab


value: 0
objectReference: {fileID: 0}
- target: {fileID: 1462126939442648229, guid: c308ffc2a02e5ab4bbe70a8b2e8108c6, type: 3}
propertyPath: m_JoinCodeField
value:
objectReference: {fileID: 2465479314273514634}
- target: {fileID: 1462126939442648229, guid: c308ffc2a02e5ab4bbe70a8b2e8108c6, type: 3}
propertyPath: m_onVisibilityChange.m_PersistentCalls.m_Calls.Array.data[1].m_Mode
value: 0
objectReference: {fileID: 0}

m_Script: {fileID: 11500000, guid: 3b8c744e110596042b40ee73862efaab, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &2465479314273514634 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 8659642538454988273, guid: c308ffc2a02e5ab4bbe70a8b2e8108c6, type: 3}
m_PrefabInstance: {fileID: 6492536299820417403}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3}
m_Name:
m_EditorClassIdentifier:

13
Assets/Prefabs/UI/PlayerInteractionPanel.prefab


- component: {fileID: 4155147274821193738}
- component: {fileID: 4969504009400302254}
- component: {fileID: 8388542991005336197}
- component: {fileID: 1918870058264573867}
m_Layer: 5
m_Name: CountDownUI
m_TagString: Untagged

m_Interactable: 0
m_BlocksRaycasts: 0
m_IgnoreParentGroups: 0
--- !u!114 &1918870058264573867
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 580481917308754637}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d125c6cac111c6442ac5b07a1f313fa4, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &609596107351326384
GameObject:
m_ObjectHideFlags: 0

98
Assets/Prefabs/UI/RenamePopup.prefab


m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &557953118367534400
RectTransform:
m_ObjectHideFlags: 0

m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Rename
m_text: Close
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 5302535af1044152a457ed104f1f4b91, type: 2}
m_sharedMaterial: {fileID: 2100000, guid: b246c4190f4e46ec9352fe15a7b09ce0, type: 2}

m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinWidth: 400
m_PreferredWidth: 800
m_PreferredWidth: 1200
m_FlexibleWidth: -1
m_FlexibleWidth: 1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &6042065811192405319

m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinWidth: 400
m_PreferredWidth: 800
m_PreferredWidth: 700
m_PreferredHeight: 50
m_FlexibleWidth: -1
m_FlexibleHeight: -1

m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 15
--- !u!1 &8714174394522057228
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3465787506840996812}
- component: {fileID: 8314980018121597183}
- component: {fileID: 7056713423232892002}
m_Layer: 5
m_Name: Panel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3465787506840996812
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8714174394522057228}
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: 4299827590990313863}
m_RootOrder: 2
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: -10, y: -4}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8314980018121597183
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8714174394522057228}
m_CullTransparentMesh: 1
--- !u!114 &7056713423232892002
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8714174394522057228}
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: 0, g: 0, b: 0, a: 0.5019608}
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: 21300000, guid: 4354914e98ed5184596c36700cb95cd6, type: 3}
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 &9073342297542081171
GameObject:
m_ObjectHideFlags: 0

- component: {fileID: 8711958881930819624}
- component: {fileID: 6511168087757074800}
m_Layer: 5
m_Name: RenameButton
m_Name: BackButton
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0

m_Children:
- {fileID: 557953118367534400}
- {fileID: 3886665443646860209}
- {fileID: 3465787506840996812}
m_Father: {fileID: 1113109783147550039}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.9490197, g: 0.6862745, b: 0.5176471, a: 1}
m_Color: {r: 0.9622642, g: 0.27687788, b: 0.6599941, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1

m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 100
m_MinWidth: 50
m_PreferredWidth: 100
m_PreferredWidth: 50
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1

27
Assets/Scenes/mainScene.unity


m_Script: {fileID: 11500000, guid: 78d292f3bd9f1614cb744dcb4fe3ac12, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &1014339014 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 4601477874412550420, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}
m_PrefabInstance: {fileID: 8628454959146822954}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5b3b588e7ae40ec4ca35fdb9404513ab, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &1217229506 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 6326316181187829680, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}

objectReference: {fileID: 0}
- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LocalLobbyObservers.Array.size
value: 8
value: 7
objectReference: {fileID: 0}
- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_vivoxUserHandlers.Array.data[0]

- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LocalLobbyObservers.Array.data[0]
value:
objectReference: {fileID: 1014339014}
objectReference: {fileID: 1412109061}
objectReference: {fileID: 1412109061}
objectReference: {fileID: 297599733}
objectReference: {fileID: 297599733}
objectReference: {fileID: 2130620598}
objectReference: {fileID: 2130620598}
objectReference: {fileID: 2074106027}
objectReference: {fileID: 2074106027}
objectReference: {fileID: 309485569}
objectReference: {fileID: 309485569}
objectReference: {fileID: 2126854580}
objectReference: {fileID: 2126854580}
objectReference: {fileID: 1511612118}
- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LocalLobbyObservers.Array.data[7]
value:

102
Assets/Scripts/Game/GameManager.cs


/// </summary>
public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.RenameRequest)
{ m_localUser.DisplayName = (string)msg;
}
else if (type == MessageType.CreateLobbyRequest)
if (type == MessageType.CreateLobbyRequest)
{
var createLobbyData = (LocalLobby)msg;
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, m_localUser, (r) =>

},
m_lobbyColorFilter);
}
else if (type == MessageType.ChangeGameState)
{ SetGameState((GameState)msg);
else if (type == MessageType.QuickJoin)
{
LobbyAsyncRequests.Instance.QuickJoinLobbyAsync(m_localUser, m_lobbyColorFilter, (r) =>
{ lobby.ToLocalLobby.Convert(r, m_localLobby);
OnJoinedLobby();
},
OnFailedJoin);
}
else if (type == MessageType.RenameRequest)
{
string name = (string)msg;
if (string.IsNullOrWhiteSpace(name))
{
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Empty Name not allowed."); // Lobby error type, then HTTP error type.
return;
}
m_localUser.DisplayName = (string)msg;
}
else if (type == MessageType.ClientUserApproved)
{ ConfirmApproval();
}
else if (type == MessageType.UserSetEmote)
{ EmoteType emote = (EmoteType)msg;

{ m_localUser.UserStatus = (UserStatus)msg;
}
else if (type == MessageType.StartCountdown)
{ BeginCountDown();
{ m_localLobby.State = LobbyState.CountDown;
m_localLobby.CountDownTime = 0;
}
else if (type == MessageType.CompleteCountdown)
{ if (m_relayClient is RelayUtpHost)
(m_relayClient as RelayUtpHost).SendInGameState();
}
else if (type == MessageType.ChangeGameState)
{ SetGameState((GameState)msg);
}
else if (type == MessageType.ConfirmInGameState)
{ m_localUser.UserStatus = UserStatus.InGame;

{ m_localLobby.State = LobbyState.Lobby;
m_localLobby.CountDownTime = 0;
else if (type == MessageType.QuickJoin)
{
LobbyAsyncRequests.Instance.QuickJoinLobbyAsync(m_localUser, m_lobbyColorFilter, (r) =>
{ lobby.ToLocalLobby.Convert(r, m_localLobby);
OnJoinedLobby();
},
OnFailedJoin);
}
else if (type == MessageType.SetPlayerSound)
{
var playerSound = (LobbyUserAudio)msg;
}
}
}
private void SetGameState(GameState state)
{

LobbyAsyncRequests.Instance.BeginTracking(m_localLobby.LobbyID);
m_lobbyContentHeartbeat.BeginTracking(m_localLobby, m_localUser);
SetUserLobbyState();
StartRelayConnection();
StartVivoxJoin();
// The host has the opportunity to reject incoming players, but to do so the player needs to connect to Relay without having game logic available.
// In particular, we should prevent players from joining voice chat until they are approved.
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Connecting);
if (m_localUser.IsHost)
{
StartRelayConnection();
StartVivoxJoin();
}
else
{
StartRelayConnection();
}
}
private void OnLeftLobby()

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)

}
m_relayClient = client;
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby);
if (m_localUser.IsHost)
CompleteRelayConnection();
else
Debug.Log("Client is now waiting for approval...");
}
}

doConnection?.Invoke();
}
private void BeginCountDown()
private void ConfirmApproval()
if (m_localLobby.State == LobbyState.CountDown)
return;
m_localLobby.CountDownTime = 4;
m_localLobby.State = LobbyState.CountDown;
StartCoroutine(CountDown());
if (!m_localUser.IsHost && m_localUser.IsApproved)
{
CompleteRelayConnection();
StartVivoxJoin();
}
/// <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()
private void CompleteRelayConnection()
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();
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby);
}
private void SetUserLobbyState()

{
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;
}

32
Assets/Scripts/Game/LobbyUser.cs


[Serializable]
public class LobbyUser : Observed<LobbyUser>
{
public LobbyUser(bool isHost = false, string displayName = null, string id = null, EmoteType emote = EmoteType.None, UserStatus userStatus = UserStatus.Menu)
public LobbyUser(bool isHost = false, string displayName = null, string id = null, EmoteType emote = EmoteType.None, UserStatus userStatus = UserStatus.Menu, bool isApproved = false)
m_data = new UserData(isHost, displayName, id, emote, userStatus);
m_data = new UserData(isHost, displayName, id, emote, userStatus, isApproved);
}
#region Local UserData

public string ID { get; set; }
public EmoteType Emote { get; set; }
public UserStatus UserStatus { get; set; }
public bool IsApproved { get; set; }
public UserData(bool isHost, string displayName, string id, EmoteType emote, UserStatus userStatus)
public UserData(bool isHost, string displayName, string id, EmoteType emote, UserStatus userStatus, bool isApproved)
{
IsHost = isHost;
DisplayName = displayName;

IsApproved = isApproved;
}
}

{
m_data = new UserData(false, m_data.DisplayName, m_data.ID, EmoteType.None, UserStatus.Menu); // ID and DisplayName should persist since this might be the local user.
m_data = new UserData(false, m_data.DisplayName, m_data.ID, EmoteType.None, UserStatus.Menu, false); // ID and DisplayName should persist since this might be the local user.
}
#endregion

DisplayName = 2,
Emote = 4,
ID = 8,
UserStatus = 16
}
UserStatus = 16,
IsApproved = 32
}
private UserMembers m_lastChanged;
public UserMembers LastChanged => m_lastChanged;

m_data.IsHost = value;
m_lastChanged = UserMembers.IsHost;
OnChanged(this);
if (value)
IsApproved = true;
}
}
}

m_userStatus = value;
m_lastChanged = UserMembers.UserStatus;
OnChanged(this);
}
}
}
public bool IsApproved // Clients joining the lobby should be approved by the host before they can interact.
{
get => m_data.IsApproved;
set
{
if (!m_data.IsApproved && value) // Don't be un-approved except by a call to ResetState.
{
m_data.IsApproved = value;
m_lastChanged = UserMembers.IsApproved;
OnChanged(this);
Locator.Get.Messenger.OnReceiveMessage(MessageType.ClientUserApproved, null);
}
}
}

31
Assets/Scripts/Game/LocalLobby.cs


public int MaxPlayerCount { get; set; }
public LobbyState State { get; set; }
public LobbyColor Color { get; set; }
public long State_LastEdit { get; set; }
public long Color_LastEdit { get; set; }
public LobbyData(LobbyData existing)
{

MaxPlayerCount = existing.MaxPlayerCount;
State = existing.State;
Color = existing.Color;
State_LastEdit = existing.State_LastEdit;
Color_LastEdit = existing.Color_LastEdit;
}
public LobbyData(string lobbyCode)

MaxPlayerCount = -1;
State = LobbyState.Lobby;
Color = LobbyColor.None;
State_LastEdit = 0;
Color_LastEdit = 0;
}
}

get { return new LobbyData(m_data); }
}
float m_CountDownTime;
public float CountDownTime
{
get { return m_CountDownTime; }
set
{
m_CountDownTime = value;
OnChanged(this);
}
}
ServerAddress m_relayServer;

set
{
m_data.State = value;
m_data.State_LastEdit = DateTime.Now.Ticks;
OnChanged(this);
}
}

{
if (m_data.Color != value)
{ m_data.Color = value;
m_data.Color_LastEdit = DateTime.Now.Ticks;
OnChanged(this);
}
}

{
// It's possible for the host to edit the lobby in between the time they last pushed lobby data and the time their pull for new lobby data completes.
// If that happens, the edit will be lost, so instead we maintain the time of last edit to detect that case.
var pendingState = data.State;
var pendingColor = data.Color;
if (m_data.State_LastEdit > data.State_LastEdit)
pendingState = m_data.State;
if (m_data.Color_LastEdit > data.Color_LastEdit)
pendingColor = m_data.Color;
m_data.State = pendingState;
m_data.Color = pendingColor;
if (currUsers == null)
m_LobbyUsers = new Dictionary<string, LobbyUser>();
else

25
Assets/Scripts/Infrastructure/Messenger.cs


JoinLobbyRequest = 2,
CreateLobbyRequest = 3,
QueryLobbies = 4,
ChangeGameState = 5,
LobbyUserStatus = 6,
UserSetEmote = 7,
EndGame = 8,
StartCountdown = 9,
CancelCountdown = 10,
ConfirmInGameState = 11,
DisplayErrorPopup = 12,
SetPlayerSound = 13,
QuickJoin = 14
QuickJoin = 5,
ChangeGameState = 100,
ConfirmInGameState = 101,
LobbyUserStatus = 102,
UserSetEmote = 103,
ClientUserApproved = 104,
ClientUserSeekingDisapproval = 105,
EndGame = 106,
StartCountdown = 200,
CancelCountdown = 201,
CompleteCountdown = 202,
DisplayErrorPopup = 300,
}
/// <summary>

2
Assets/Scripts/Infrastructure/Observed.cs


onChanged?.Invoke(observed);
}
protected void OnDestroy(T observed)
protected void OnDestroyed(T observed)
{
onDestroyed?.Invoke(observed);
}

12
Assets/Scripts/Lobby/LobbyAsyncRequests.cs


dataCurr.Add(dataNew.Key, dataObj);
}
LobbyAPIInterface.UpdatePlayerAsync(m_lastKnownLobby.Id, playerId, dataCurr, (r) => { onComplete?.Invoke(); }, null, null);
LobbyAPIInterface.UpdatePlayerAsync(m_lastKnownLobby.Id, playerId, dataCurr, (result) => {
if (result != null)
m_lastKnownLobby = result; // Store the most up-to-date lobby now since we have it, instead of waiting for the next heartbeat.
onComplete?.Invoke();
}, null, null);
}
/// <summary>

dataCurr.Add(dataNew.Key, dataObj);
}
LobbyAPIInterface.UpdateLobbyAsync(lobby.Id, dataCurr, (r) => { onComplete?.Invoke(); });
LobbyAPIInterface.UpdateLobbyAsync(lobby.Id, dataCurr, (result) => {
if (result != null)
m_lastKnownLobby = result;
onComplete?.Invoke();
});
}
/// <summary>

53
Assets/Scripts/Lobby/LobbyContentHeartbeat.cs


using System.Collections.Generic;
using System;
using System.Collections.Generic;
using LobbyRemote = Unity.Services.Lobbies.Models.Lobby;
namespace LobbyRelaySample

/// </summary>
public class LobbyContentHeartbeat
public class LobbyContentHeartbeat : IReceiveMessages
private bool m_isAwaitingQuery = false;
private int m_awaitingQueryCount = 0;
private const float k_approvalMaxTime = 10; // Used for determining if a user should timeout if they are unable to connect.
private float m_lifetime = 0;
Locator.Get.Messenger.Subscribe(this);
m_lifetime = 0;
}
public void EndTracking()

Locator.Get.Messenger.Unsubscribe(this);
if (m_localLobby != null)
m_localLobby.onChanged -= OnLocalLobbyChanged;
m_localLobby = null;

m_shouldPushData = true;
}
public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.ClientUserSeekingDisapproval)
{
bool shouldDisapprove = m_localLobby.State != LobbyState.Lobby; // By not refreshing, it's possible to have a lobby in the lobby list UI after its countdown starts and then try joining.
if (shouldDisapprove)
(msg as Action<relay.Approval>)?.Invoke(relay.Approval.GameAlreadyStarted);
}
}
/// <summary>
/// If there have been any data changes since the last update, push them to Lobby. Regardless, pull for the most recent data.
/// (Unless we're already awaiting a query, in which case continue waiting.)

if (m_isAwaitingQuery || m_localLobby == null)
m_lifetime += dt;
if (m_awaitingQueryCount > 0 || m_localLobby == null)
m_isAwaitingQuery = true; // Note that because we make async calls, if one of them fails and doesn't call our callback, this will never be reset to false.
if (!m_localUser.IsApproved && m_lifetime > k_approvalMaxTime)
{
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Connection attempt timed out!");
Locator.Get.Messenger.OnReceiveMessage(MessageType.ChangeGameState, GameState.JoinMenu);
}
if (m_shouldPushData)
PushDataToLobby();
else

void PushDataToLobby()
{
if (m_localUser == null)
{
m_isAwaitingQuery = false;
return; // Don't revert m_shouldPushData yet, so that we can retry.
}
{
m_awaitingQueryCount++;
else
DoPlayerDataPush();
}
m_awaitingQueryCount++;
DoPlayerDataPush();
LobbyAsyncRequests.Instance.UpdateLobbyDataAsync(RetrieveLobbyData(m_localLobby), () => { DoPlayerDataPush(); });
LobbyAsyncRequests.Instance.UpdateLobbyDataAsync(RetrieveLobbyData(m_localLobby), () => { if (--m_awaitingQueryCount <= 0) OnRetrieve(); });
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(RetrieveUserData(m_localUser), () => { m_isAwaitingQuery = false; });
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(RetrieveUserData(m_localUser), () => { if (--m_awaitingQueryCount <= 0) OnRetrieve(); });
m_isAwaitingQuery = false;
LobbyRemote lobbyRemote = LobbyAsyncRequests.Instance.CurrentLobby;
if (lobbyRemote == null) return;
bool prevShouldPush = m_shouldPushData;

data.Add("RelayCode", lobby.RelayCode);
data.Add("State", ((int)lobby.State).ToString()); // Using an int is smaller than using the enum state's name.
data.Add("Color", ((int)lobby.Color).ToString());
data.Add("State_LastEdit", lobby.Data.State_LastEdit.ToString());
data.Add("Color_LastEdit", lobby.Data.Color_LastEdit.ToString());
return data;
}

4
Assets/Scripts/Lobby/ToLocalLobby.cs


MaxPlayerCount = lobby.MaxPlayers,
RelayCode = lobby.Data?.ContainsKey("RelayCode") == true ? lobby.Data["RelayCode"].Value : null, // By providing RelayCode through the lobby data with Member visibility, we ensure a client is connected to the lobby before they could attempt a relay connection, preventing timing issues between them.
State = lobby.Data?.ContainsKey("State") == true ? (LobbyState) int.Parse(lobby.Data["State"].Value) : LobbyState.Lobby,
Color = lobby.Data?.ContainsKey("Color") == true ? (LobbyColor) int.Parse(lobby.Data["Color"].Value) : LobbyColor.None
Color = lobby.Data?.ContainsKey("Color") == true ? (LobbyColor) int.Parse(lobby.Data["Color"].Value) : LobbyColor.None,
State_LastEdit = lobby.Data?.ContainsKey("State_LastEdit") == true ? long.Parse(lobby.Data["State_LastEdit"].Value) : 0,
Color_LastEdit = lobby.Data?.ContainsKey("Color_LastEdit") == true ? long.Parse(lobby.Data["Color_LastEdit"].Value) : 0
};
Dictionary<string, LobbyUser> lobbyUsers = new Dictionary<string, LobbyUser>();

29
Assets/Scripts/Relay/RelayUtpClient.cs


using System.Collections.Generic;
using Unity.Networking.Transport;
using UnityEngine;
using MsgType = LobbyRelaySample.relay.RelayUtpSetup.MsgType;
public enum Approval { OK = 0, GameAlreadyStarted }
/// <summary>
/// This observes the local player and updates remote players over Relay when there are local changes, demonstrating basic data transfer over the Unity Transport (UTP).
/// Created after the connection to Relay has been confirmed.

protected bool m_hasSentInitialMessage = false;
private const float k_heartbeatPeriod = 5;
protected enum MsgType { Ping = 0, NewPlayer, PlayerApprovalState, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }
public virtual void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LobbyUser localUser, LocalLobby localLobby)
{

}
string id = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(2, idLength).ToArray());
if (id == m_localUser.ID || !m_localLobby.LobbyUsers.ContainsKey(id)) // We don't need to hold onto messages if the ID is absent; users are initialized before they send events.
if (!CanProcessDataEventFor(conn, msgType, id))
if (msgType == MsgType.PlayerName)
if (msgType == MsgType.PlayerApprovalState)
{
Approval approval = (Approval)msgContents[0];
if (approval == Approval.OK && !m_localUser.IsApproved)
OnApproved(m_networkDriver, conn);
else if (approval == Approval.GameAlreadyStarted)
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Rejected: Game has already started.");
}
else if (msgType == MsgType.PlayerName)
{
int nameLength = msgContents[0];
string name = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(1, nameLength).ToArray());

ProcessDisconnectEvent(conn, strm);
}
protected virtual bool CanProcessDataEventFor(NetworkConnection conn, MsgType type, string id)
{
// Don't react to our own messages. Also, don't need to hold onto messages if the ID is absent; clients should be initialized and in the lobby before they send events.
// (Note that this enforces lobby membership before processing any events besides an approval request, so a client is unable to fully use Relay unless they're in the lobby.)
return id != m_localUser.ID && (m_localUser.IsApproved && m_localLobby.LobbyUsers.ContainsKey(id) || type == MsgType.PlayerApprovalState);
}
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id) { }
protected virtual void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)
{

private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection)
{
WriteByte(driver, connection, m_localUser.ID, MsgType.NewPlayer, 0);
ForceFullUserUpdate(driver, connection, m_localUser); // Assuming this is only created after the Relay connection is successful.
}
private void OnApproved(NetworkDriver driver, NetworkConnection connection)
{
m_localUser.IsApproved = true;
ForceFullUserUpdate(driver, connection, m_localUser);
}
/// <summary>

39
Assets/Scripts/Relay/RelayUtpHost.cs


using System.Collections.Generic;
using Unity.Networking.Transport;
using MsgType = LobbyRelaySample.relay.RelayUtpSetup.MsgType;
namespace LobbyRelaySample.relay
{

}
/// <summary>
/// When a new client connects, they need to be updated with the current state of everyone else.
/// When a new client connects, first determine if they are allowed to do so.
/// If so, they need to be updated with the current state of everyone else.
/// If not, they should be informed and rejected.
private void OnNewConnection(NetworkConnection conn)
private void OnNewConnection(NetworkConnection conn, string id)
{
new RelayPendingApproval(conn, NewConnectionApprovalResult, id);
}
private void NewConnectionApprovalResult(NetworkConnection conn, Approval result)
{
WriteByte(m_networkDriver, conn, m_localUser.ID, MsgType.PlayerApprovalState, (byte)result);
if (result == Approval.OK && conn.IsCreated)
{
foreach (var user in m_localLobby.LobbyUsers)
ForceFullUserUpdate(m_networkDriver, conn, user.Value);
m_connections.Add(conn);
}
else
{
conn.Disconnect(m_networkDriver);
}
}
protected override bool CanProcessDataEventFor(NetworkConnection conn, MsgType type, string id)
foreach (var user in m_localLobby.LobbyUsers) // The host includes itself here since we don't necessarily have an ID available, but it will ignore its own messages on arrival.
ForceFullUserUpdate(m_networkDriver, conn, user.Value);
// Don't send through data from one client to everyone else if they haven't been approved yet. (They should also not be sending data if not approved, so this is a backup.)
return id != m_localUser.ID && (m_localLobby.LobbyUsers.ContainsKey(id) && m_connections.Contains(conn) || type == MsgType.NewPlayer);
}
protected override void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id)

WriteByte(m_networkDriver, otherConn, id, msgType, value);
}
}
else if (msgType == MsgType.NewPlayer) // This ensures clients in builds are sent player state once they establish that they can send (and receive) events.
OnNewConnection(conn);
else if (msgType == MsgType.NewPlayer)
OnNewConnection(conn, id);
else if (msgType == MsgType.PlayerDisconnect) // Clients message the host when they intend to disconnect, or else the host ends up keeping the connection open.
{
conn.Disconnect(m_networkDriver);

var conn = m_networkDriver.Accept(); // Note that since we pumped the event queue earlier in Update, m_networkDriver has been updated already this frame.
if (!conn.IsCreated) // "Nothing more to accept" is signalled by returning an invalid connection from Accept.
break;
m_connections.Add(conn);
OnNewConnection(conn); // This ensures that clients in editors are sent player state once they establish a connection. The timing differs slightly from builds.
// Although the connection is created (i.e. Accepted), we still need to approve it, which will trigger when receiving the NewPlayer message from that client.
}
}

connection.Disconnect(m_networkDriver); // Note that Lobby won't receive the disconnect immediately, so its auto-disconnect takes 30-40s, if needed.
m_connections.Clear();
m_localLobby.RelayServer = null;
}
}

2
Assets/Scripts/Relay/RelayUtpSetup.cs


protected LobbyUser m_localUser;
protected Action<bool, RelayUtpClient> m_onJoinComplete;
public enum MsgType { Ping = 0, NewPlayer, ReadyState, PlayerName, Emote, StartCountdown, CancelCountdown, ConfirmInGame, EndInGame, PlayerDisconnect }
public void BeginRelayJoin(LocalLobby localLobby, LobbyUser localUser, Action<bool, RelayUtpClient> onJoinComplete)
{
m_localLobby = localLobby;

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


}
private LobbyRelaySample.Auth.SubIdentity_Authentication m_auth;
//Only used when testing DTLS
private bool m_didSigninComplete = false;
GameObject m_dummy;

10
Assets/Scripts/UI/CountdownUI.cs


/// This countdown is purely visual, to give clients a moment if they need to un-ready before entering the game;
/// clients will actually wait for a message from the host confirming that they are in the game, instead of assuming the game is ready to go when the countdown ends.
/// </summary>
public class CountdownUI : LocalLobbyObserver
public class CountdownUI : ObserverBehaviour<Countdown.Data>
protected override void UpdateObserver(LocalLobby obs)
protected override void UpdateObserver(Countdown.Data data)
base.UpdateObserver(obs);
if (observed.CountDownTime <= 0)
base.UpdateObserver(data);
if (observed.TimeLeft <= 0)
m_CountDownText.SetText($"Starting in: {observed.CountDownTime:0}");
m_CountDownText.SetText($"Starting in: {observed.TimeLeft:0}"); // Note that the ":0" formatting rounds, not truncates.
}
}
}

3
Assets/Scripts/UI/JoinCreateLobbyUI.cs


Hide();
}
}
}
}

6
Assets/Scripts/UI/JoinMenuUI.cs


[SerializeField]
RectTransform m_LobbyButtonParent;
[SerializeField]
TMP_InputField m_JoinCodeField;
/// <summary>
/// Key: Lobby ID, Value Lobby UI
/// </summary>

public void JoinMenuChangedVisibility(bool show)
{
if (show)
{
m_JoinCodeField.text = "";
}
}
public void OnQuickJoin()

35
Assets/Scripts/Vivox/VivoxSetup.cs


m_channelSession.BeginConnect(true, false, true, token, result =>
{
try
{ m_channelSession.EndConnect(result);
{
// Special case: It's possible for the player to leave the lobby between the time we called BeginConnect and the time we hit this callback.
// If that's the case, we should abort the rest of the connection process.
if (m_channelSession.ChannelState == ConnectionState.Disconnecting || m_channelSession.ChannelState == ConnectionState.Disconnected)
{
UnityEngine.Debug.LogWarning("Vivox channel is already disconnecting. Terminating the channel connect sequence.");
HandleEarlyDisconnect();
return;
}
m_channelSession.EndConnect(result);
onComplete?.Invoke(true);
foreach (VivoxUserHandler userHandler in m_userHandlers)
userHandler.OnChannelJoined(m_channelSession);

onComplete?.Invoke(false);
m_channelSession?.Disconnect();
}
});
}

{
if (m_channelSession != null)
{
// Special case: The EndConnect call requires a little bit of time before the connection actually completes, but the player might
// disconnect before then. If so, sending the Disconnect now will fail, and the played would stay connected to voice while no longer
// in the lobby. So, wait until the connection is completed before disconnecting in that case.
if (m_channelSession.ChannelState == ConnectionState.Connecting)
{
UnityEngine.Debug.LogWarning("Vivox channel is trying to disconnect while trying to complete its connection. Will wait until connection completes.");
HandleEarlyDisconnect();
return;
}
ChannelId id = m_channelSession.Channel;
m_channelSession?.Disconnect(
(result) => { m_loginSession.DeleteChannelSession(id); m_channelSession = null; });

}
private void HandleEarlyDisconnect()
{
Locator.Get.UpdateSlow.Subscribe(DisconnectOnceConnected, 0.2f);
}
private void DisconnectOnceConnected(float unused)
{
if (m_channelSession?.ChannelState == ConnectionState.Connecting)
return;
Locator.Get.UpdateSlow.Unsubscribe(DisconnectOnceConnected);
LeaveLobbyChannel();
}
/// <summary>

472
Assets/TextMesh Pro/Resources/Fonts & Materials/Roboto-Bold SDF.asset
文件差异内容过多而无法显示
查看文件

8
Packages/manifest.json


{
"dependencies": {
"com.unity.2d.sprite": "1.0.0",
"com.unity.collab-proxy": "1.9.0",
"com.unity.collab-proxy": "1.11.2",
"com.unity.ide.vscode": "1.2.3",
"com.unity.ide.vscode": "1.2.4",
"com.unity.services.lobby": "1.0.0-pre.4",
"com.unity.services.relay": "1.0.1-pre.1",
"com.unity.services.lobby": "1.0.0-pre.6",
"com.unity.services.relay": "1.0.1-pre.2",
"com.unity.services.vivox": "15.1.150002-pre.1",
"com.unity.sysroot.linux-x86_64": "0.1.15-preview",
"com.unity.test-framework": "1.1.29",

21
Packages/packages-lock.json


"url": "https://packages.unity.com"
},
"com.unity.collab-proxy": {
"version": "1.9.0",
"version": "1.11.2",
"depth": 0,
"source": "registry",
"dependencies": {},

"url": "https://packages.unity.com"
},
"com.unity.ide.vscode": {
"version": "1.2.3",
"version": "1.2.4",
"depth": 0,
"source": "registry",
"dependencies": {},

"url": "https://packages.unity.com"
},
"com.unity.services.lobby": {
"version": "1.0.0-pre.4",
"version": "1.0.0-pre.6",
"com.unity.services.core": "1.1.0-pre.8",
"com.unity.services.core": "1.1.0-pre.10",
"com.unity.modules.unitywebrequest": "1.0.0",
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
"com.unity.modules.unitywebrequestaudio": "1.0.0",

"com.unity.services.authentication": "1.0.0-pre.4"
"com.unity.services.authentication": "1.0.0-pre.6"
"version": "1.0.1-pre.1",
"version": "1.0.1-pre.2",
"com.unity.services.core": "1.1.0-pre.8",
"com.unity.services.core": "1.1.0-pre.10",
"com.unity.modules.unitywebrequest": "1.0.0",
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
"com.unity.modules.unitywebrequestaudio": "1.0.0",

"com.unity.services.authentication": "1.0.0-pre.4"
"com.unity.services.authentication": "1.0.0-pre.6",
"com.unity.transport": "1.0.0-pre.6"
},
"url": "https://packages.unity.com"
},

"url": "https://packages.unity.com"
},
"com.unity.transport": {
"version": "1.0.0-pre.5",
"depth": 0,
"version": "1.0.0-pre.6",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.collections": "1.0.0-pre.5",

6
ProjectSettings/ProjectSettings.asset


m_VersionName:
apiCompatibilityLevel: 6
activeInputHandler: 0
cloudProjectId: 0bf0426b-e1fd-4251-82d0-3eea033ef1ad
cloudProjectId:
projectName: com.unity.services.samples.lobby-rooms
organizationId: operate-samples
projectName:
organizationId:
cloudEnabled: 0
legacyClampBlendShapeWeights: 0
virtualTexturingSupportEnabled: 0

4
ProjectSettings/ProjectVersion.txt


m_EditorVersion: 2020.3.15f2
m_EditorVersionWithRevision: 2020.3.15f2 (6cf78cb77498)
m_EditorVersion: 2020.3.20f1
m_EditorVersionWithRevision: 2020.3.20f1 (41c4e627c95f)

66
Assets/Scripts/Game/Countdown.cs


using System;
using UnityEngine;
namespace LobbyRelaySample
{
/// <summary>
/// Runs the countdown to the in-game state. While the start of the countdown is synced via Relay, the countdown itself is handled locally,
/// since precise timing isn't necessary.
/// </summary>
[RequireComponent(typeof(UI.CountdownUI))]
public class Countdown : MonoBehaviour, IReceiveMessages
{
public class Data : Observed<Countdown.Data>
{
private float m_timeLeft;
public float TimeLeft
{
get => m_timeLeft;
set
{ m_timeLeft = value;
OnChanged(this);
}
}
public override void CopyObserved(Data oldObserved) { /*No-op, since this is unnecessary.*/ }
}
private Data m_data = new Data();
private UI.CountdownUI m_ui;
private const int k_countdownTime = 4;
public void OnEnable()
{
if (m_ui == null)
m_ui = GetComponent<UI.CountdownUI>();
m_data.TimeLeft = -1;
Locator.Get.Messenger.Subscribe(this);
m_ui.BeginObserving(m_data);
}
public void OnDisable()
{
Locator.Get.Messenger.Unsubscribe(this);
m_ui.EndObserving();
}
public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.StartCountdown)
{
m_data.TimeLeft = k_countdownTime;
}
else if (type == MessageType.CancelCountdown)
{
m_data.TimeLeft = -1;
}
}
public void Update()
{
if (m_data.TimeLeft < 0)
return;
m_data.TimeLeft -= Time.deltaTime;
if (m_data.TimeLeft < 0)
Locator.Get.Messenger.OnReceiveMessage(MessageType.CompleteCountdown, null);
}
}
}

11
Assets/Scripts/Game/Countdown.cs.meta


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

57
Assets/Scripts/Relay/RelayPendingApproval.cs


using System;
using Unity.Networking.Transport;
namespace LobbyRelaySample.relay
{
/// <summary>
/// The Relay host doesn't need to know what might approve or disapprove of a pending connection, so this will
/// broadcast a message that approval is being sought, and if nothing disapproves, the connection will be permitted.
/// </summary>
public class RelayPendingApproval : IDisposable
{
private NetworkConnection m_pendingConnection;
private bool m_hasDisposed = false;
private const float k_waitTime = 0.1f;
private Action<NetworkConnection, Approval> m_onResult;
public string ID { get; private set; }
public RelayPendingApproval(NetworkConnection conn, Action<NetworkConnection, Approval> onResult, string id)
{
m_pendingConnection = conn;
m_onResult = onResult;
ID = id;
Locator.Get.UpdateSlow.Subscribe(Approve, k_waitTime);
Locator.Get.Messenger.OnReceiveMessage(MessageType.ClientUserSeekingDisapproval, (Action<Approval>)Disapprove);
}
~RelayPendingApproval() { Dispose(); }
private void Approve(float unused)
{
try
{ m_onResult?.Invoke(m_pendingConnection, Approval.OK);
}
finally
{ Dispose();
}
}
public void Disapprove(Approval reason)
{
try
{ m_onResult?.Invoke(m_pendingConnection, reason);
}
finally
{ Dispose();
}
}
public void Dispose()
{
if (!m_hasDisposed)
{
Locator.Get.UpdateSlow.Unsubscribe(Approve);
m_hasDisposed = true;
}
}
}
}

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


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