浏览代码

Stopping POint

/main/staging/2021_Upgrade/Async_Refactor
当前提交
7e47c4ab
共有 35 个文件被更改,包括 948 次插入1053 次删除
  1. 80
      Assets/Prefabs/GameManager.prefab
  2. 70
      Assets/Prefabs/UI/GameCanvas.prefab
  3. 56
      Assets/Prefabs/UI/LobbyCanvas.prefab
  4. 2
      Assets/Prefabs/UI/LobbyUserList.prefab
  5. 34
      Assets/Prefabs/UI/RelayCodeCanvas.prefab
  6. 84
      Assets/Prefabs/UI/UserInteractionPanel.prefab
  7. 93
      Assets/Scenes/mainScene.unity
  8. 49
      Assets/Scripts/GameLobby/Game/Countdown.cs
  9. 115
      Assets/Scripts/GameLobby/Game/GameManager.cs
  10. 293
      Assets/Scripts/GameLobby/Game/LocalLobby.cs
  11. 152
      Assets/Scripts/GameLobby/Game/LocalPlayer.cs
  12. 5
      Assets/Scripts/GameLobby/Infrastructure/Actionvalue.cs
  13. 112
      Assets/Scripts/GameLobby/Lobby/LobbyConverters.cs
  14. 8
      Assets/Scripts/GameLobby/Lobby/LobbyManager.cs
  15. 132
      Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs
  16. 2
      Assets/Scripts/GameLobby/NGO/InGameRunner.cs
  17. 66
      Assets/Scripts/GameLobby/NGO/SetupInGame.cs
  18. 18
      Assets/Scripts/GameLobby/Relay/RelayUtpClient.cs
  19. 69
      Assets/Scripts/GameLobby/Relay/RelayUtpHost.cs
  20. 2
      Assets/Scripts/GameLobby/Relay/RelayUtpSetup.cs
  21. 267
      Assets/Scripts/GameLobby/Tests/PlayMode/LobbyRoundtripTests.cs
  22. 11
      Assets/Scripts/GameLobby/UI/CountdownUI.cs
  23. 12
      Assets/Scripts/GameLobby/UI/CreateMenuUI.cs
  24. 31
      Assets/Scripts/GameLobby/UI/InLobbyUserList.cs
  25. 56
      Assets/Scripts/GameLobby/UI/InLobbyUserUI.cs
  26. 2
      Assets/Scripts/GameLobby/UI/JoinMenuUI.cs
  27. 16
      Assets/Scripts/GameLobby/UI/RecolorForLobbyType.cs
  28. 14
      Assets/Scripts/GameLobby/UI/RelayAddressUI.cs
  29. 8
      Assets/Scripts/GameLobby/UI/ShowWhenLobbyStateUI.cs
  30. 15
      Assets/Scripts/GameLobby/UI/UserNameUI.cs
  31. 6
      Assets/Scripts/GameLobby/UI/UserStateVisibilityUI.cs
  32. 25
      Packages/manifest.json
  33. 90
      Packages/packages-lock.json
  34. 4
      ProjectSettings/ProjectVersion.txt
  35. 2
      ProjectSettings/RiderScriptEditorPersistedState.asset

80
Assets/Prefabs/GameManager.prefab


m_Script: {fileID: 11500000, guid: 3f7533ddeca587549a9798a65a8670ba, type: 3}
m_Name:
m_EditorClassIdentifier:
m_NetworkManagerPrefab: {fileID: 5021303663353436182, guid: b963f71e4874d4066bc72b9224e3ffce, type: 3}
m_IngameRunnerPrefab: {fileID: 238192747445020667, guid: 73173c97c128d614aa2a1167a2eaea68, type: 3}
m_disableWhileInGame: []
--- !u!114 &5235782363599194820

m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!1 &6398156408753789057
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7186897169282866166}
- component: {fileID: 472628663792594525}
- component: {fileID: 8721980719610883967}
- component: {fileID: 2066355358714747406}
m_Layer: 0
m_Name: Countdown
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7186897169282866166
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6398156408753789057}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 693.65497, y: 426.1525, z: 1.3733984}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7716713811812636911}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!225 &472628663792594525
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6398156408753789057}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!114 &8721980719610883967
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6398156408753789057}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5b3b588e7ae40ec4ca35fdb9404513ab, type: 3}
m_Name:
m_EditorClassIdentifier:
m_onVisibilityChange:
m_PersistentCalls:
m_Calls: []
m_CountDownText: {fileID: 0}
--- !u!114 &2066355358714747406
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6398156408753789057}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d125c6cac111c6442ac5b07a1f313fa4, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &7716713811812636896
GameObject:
m_ObjectHideFlags: 0

m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 802462301182945455}
- {fileID: 7186897169282866166}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

m_Script: {fileID: 11500000, guid: b4f7225f73bfe6a4d9133ee45ac9cd73, type: 3}
m_Name:
m_EditorClassIdentifier:
m_LocalMenuStateObservers: []
m_LobbyServiceObservers: []
m_setupInGame: {fileID: 6265861362966661484}
m_countdown: {fileID: 2066355358714747406}
m_vivoxUserHandlers: []
--- !u!114 &5193415626965589893
MonoBehaviour:

70
Assets/Prefabs/UI/GameCanvas.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: 3862000843821052302}
m_RootOrder: 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: 3168212744299139599}
m_Father: {fileID: 5323557791684491924}

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: 1166753073431177940}
m_RootOrder: 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: 4033840405646935}
- {fileID: 8211845410637438851}

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: 8651475152227732998}
m_RootOrder: 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: 3933490046232978046}
m_RootOrder: 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: 3513144943005353968}
m_Father: {fileID: 5323557791684491924}

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: 5265762631451841679}
m_RootOrder: 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: 8628454958398271548}
m_Father: {fileID: 2437121395950827588}

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: 6891852016103666983}
m_RootOrder: 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: 8628454959107311804}
- {fileID: 8628454957908509635}

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: 2017918237437651757}
- {fileID: 2761609676931020092}

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: 8578661244454260921}
m_RootOrder: 0

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_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4100836674054655770}
- {fileID: 5323557791684491924}

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: 9059265328890417471}
m_RootOrder: 0

m_RemovedComponents:
- {fileID: 418819164531713121, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_SourcePrefab: {fileID: 100100000, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!224 &3933490046232978046 stripped
--- !u!224 &1166753073431177940 stripped
m_CorrespondingSourceObject: {fileID: 6777302673485755179, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_CorrespondingSourceObject: {fileID: 8694871130870774657, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!224 &8578661244454260921 stripped
--- !u!224 &3862000843821052302 stripped
m_CorrespondingSourceObject: {fileID: 2276224879528722924, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_CorrespondingSourceObject: {fileID: 6702435737022390491, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!224 &6891852016103666983 stripped
--- !u!224 &3933490046232978046 stripped
m_CorrespondingSourceObject: {fileID: 3981048342783665266, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_CorrespondingSourceObject: {fileID: 6777302673485755179, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!224 &9059265328890417471 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 1523264875846598762, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!114 &4174114467305540031 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 5869930269783152874, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!224 &1166753073431177940 stripped
--- !u!224 &6891852016103666983 stripped
m_CorrespondingSourceObject: {fileID: 8694871130870774657, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_CorrespondingSourceObject: {fileID: 3981048342783665266, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!224 &3862000843821052302 stripped
--- !u!224 &8578661244454260921 stripped
m_CorrespondingSourceObject: {fileID: 6702435737022390491, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_CorrespondingSourceObject: {fileID: 2276224879528722924, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!224 &8651475152227732998 stripped
--- !u!224 &8628454958398271548 stripped
m_CorrespondingSourceObject: {fileID: 1192176632984182611, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_CorrespondingSourceObject: {fileID: 2244251207921394025, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!114 &4174114467305540031 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 5869930269783152874, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
--- !u!224 &8651475152227732998 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 1192176632984182611, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!224 &8628454958398271548 stripped
--- !u!224 &9059265328890417471 stripped
m_CorrespondingSourceObject: {fileID: 2244251207921394025, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_CorrespondingSourceObject: {fileID: 1523264875846598762, guid: 247f79ab5aefc6d40bcbdade4d9467b7, type: 3}
m_PrefabInstance: {fileID: 7537689341060714837}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &8628454958156142197

propertyPath: m_Name
value: RenameButtonCanvas
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedComponents:
- {fileID: 3817649292031598177, guid: 0828349f6319d084bbba8edd08991e62, type: 3}
m_SourcePrefab: {fileID: 100100000, guid: 0828349f6319d084bbba8edd08991e62, type: 3}
--- !u!224 &3513144943005353968 stripped
RectTransform:

56
Assets/Prefabs/UI/LobbyCanvas.prefab


m_Component:
- component: {fileID: 466693923092094802}
- component: {fileID: 2177115228008557851}
- component: {fileID: 5843558592166708181}
- component: {fileID: 6860436446719602335}
- component: {fileID: 6749879306276389991}
m_Layer: 5

m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4637522307789944801}
m_CullTransparentMesh: 1
--- !u!114 &5843558592166708181
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4637522307789944801}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
OnObservedUpdated:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 6860436446719602335}
m_TargetAssemblyTypeName: LobbyRooms.UI.ServerNameUI, Assembly-CSharp
m_MethodName: ObservedUpdated
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &6860436446719602335
MonoBehaviour:
m_ObjectHideFlags: 0

m_Component:
- component: {fileID: 1906352097507614706}
- component: {fileID: 8834330287924717091}
- component: {fileID: 7526558453095601547}
- component: {fileID: 279783410280127446}
- component: {fileID: 8630015524497407890}
m_Layer: 5

m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7079534792968695919}
m_CullTransparentMesh: 1
--- !u!114 &7526558453095601547
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7079534792968695919}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
OnObservedUpdated:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 279783410280127446}
m_TargetAssemblyTypeName: LobbyRooms.UI.ServerAddressUI, LobbyRooms
m_MethodName: ObservedUpdated
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &279783410280127446
MonoBehaviour:
m_ObjectHideFlags: 0

2
Assets/Prefabs/UI/LobbyUserList.prefab


m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
m_PresetInfoIsWorld: 1
--- !u!114 &4463750083940306577
MonoBehaviour:
m_ObjectHideFlags: 0

34
Assets/Prefabs/UI/RelayCodeCanvas.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: 5867107070128564602}
m_RootOrder: 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: 4488536198637490663}
- {fileID: 2168532048885830928}

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: 5867107070128564602}
m_Father: {fileID: 4102997489641105917}

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: 4102997489641105917}
m_RootOrder: 0

- component: {fileID: 4102997489641105913}
- component: {fileID: 7676491730539518990}
- component: {fileID: 3340928240658051873}
- component: {fileID: 4523467532116611583}
m_Layer: 5
m_Name: RelayCodeCanvas
m_TagString: Untagged

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: 5389159257502379616}
- {fileID: 6476122475963766563}

m_Calls: []
m_outputText: {fileID: 8798075752901962210}
m_codeType: 1
--- !u!114 &4523467532116611583
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4102997489641105918}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
OnObservedUpdated:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 3340928240658051873}
m_TargetAssemblyTypeName: LobbyRelaySample.UI.DisplayCodeUI, LobbyRelaySample
m_MethodName: ObservedUpdated
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!1 &4142193733183565639
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: 5867107070128564602}
m_RootOrder: 1

84
Assets/Prefabs/UI/UserInteractionPanel.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: 0.999}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2119950809865779987}
m_Father: {fileID: 6664205945102926799}

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: 474140199862162053}
m_RootOrder: 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: 1611213509401803489}
m_RootOrder: 2

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: 8979361099148208042}
- {fileID: 1135759803389522016}

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: 0.999}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1135759803389522016}
m_RootOrder: 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: 8894954517847697079}
- {fileID: 506300669760714432}

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: 6979352419326739443}
- {fileID: 7173726187766461959}

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: 4215597292682722394}
- {fileID: 2758617697121517356}

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: 6541171577144360681}
m_RootOrder: 1

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: 0.999}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4315664788983455717}
m_Father: {fileID: 1919168897190896396}

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: 1309712272618711075}
m_RootOrder: 1

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: 1135759803389522016}
m_RootOrder: 1

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: 1611213509401803489}
m_RootOrder: 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: 5151586559654887469}
m_RootOrder: 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: 6870502395855781705}
- {fileID: 6666831914584290858}

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: 4191270325501815023}
- {fileID: 1770059644204541294}

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: 233260285268708796}
m_Father: {fileID: 5026269005358103012}

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: 2848144350161010536}
- {fileID: 1150714971814560546}

m_Component:
- component: {fileID: 794717087570398863}
- component: {fileID: 2987822160017223264}
- component: {fileID: 26462937623057251}
m_Layer: 5
m_Name: ToggleContainer
m_TagString: Untagged

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: 5272370078138508777}
m_Father: {fileID: 5151586559654887469}

- {fileID: 5621563591146875997}
- {fileID: 4807901211461671397}
- {fileID: 6687484736792569641}
--- !u!114 &26462937623057251
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2945500889587214856}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
OnObservedUpdated:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 2987822160017223264}
m_TargetAssemblyTypeName: LobbyRelaySample.UI.RecolorForLobbyType, LobbyRelaySample
m_MethodName: UpdateLobby
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
--- !u!1 &2988716332856923472
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: 6004833744538984969}
m_RootOrder: 1

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.02, y: 1.02, z: 1.02}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 2848144350161010536}
m_RootOrder: 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: 3197502779878208215}
m_RootOrder: 1

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: 2677764702249492100}
- {fileID: 1568259630799150366}

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: 0.999}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1309712272618711075}
m_RootOrder: 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: 5543134037343114586}
- {fileID: 794717087570398863}

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: 3620679801590011701}
m_RootOrder: 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: 474140199862162053}
m_Father: {fileID: 5026269005358103012}

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: 6075935838108688747}
m_RootOrder: 1

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: 2056817220376623591}
- {fileID: 5151586559654887469}

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: 6004833744538984969}
m_Father: {fileID: 4558362294547660329}

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: 759037105273515699}
- {fileID: 1108938163892239274}

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: 233260285268708796}
m_RootOrder: 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: 5671183532491584052}
m_Father: {fileID: 5026269005358103012}

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: 8540831390494494413}
- {fileID: 2147051606546077830}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.02, y: 1.02, z: 1.02}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3197502779878208215}
m_RootOrder: 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: 2848144350161010536}
m_RootOrder: 1

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: 4080264242426406278}
- {fileID: 6424283572969246263}

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: 1883476059105349212}
- {fileID: 7811003188552691971}

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: 0.999}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6004833744538984969}
m_RootOrder: 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: 1611213509401803489}
m_Father: {fileID: 4558362294547660329}

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: 3210254045315593125}
- {fileID: 3214500912641965185}

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: 5026269005358103012}
m_Father: {fileID: 8905267601204628791}

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: 1568259630799150366}
m_RootOrder: 1

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: 1309712272618711075}
m_Father: {fileID: 4558362294547660329}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.02, y: 1.02, z: 1.02}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4080264242426406278}
m_RootOrder: 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: 6870502395855781705}
m_RootOrder: 1

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: 2677764702249492100}
m_RootOrder: 1

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: 3197502779878208215}
- {fileID: 6159542077236467165}

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: 8905267601204628791}
m_Father: {fileID: 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: 3620679801590011701}
m_Father: {fileID: 5026269005358103012}

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: 0.999}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3404880114691434542}
m_Father: {fileID: 3210254045315593125}

m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.02, y: 1.02, z: 1.02}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6870502395855781705}
m_RootOrder: 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: 0.999}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6205733139118454793}
m_Father: {fileID: 3214500912641965185}

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: 4080264242426406278}
m_RootOrder: 1

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: 552927569557660881}
- {fileID: 7414980691921613268}

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: 5671183532491584052}
m_RootOrder: 0

93
Assets/Scenes/mainScene.unity


m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
m_Name:
m_EditorClassIdentifier:
m_SendPointerHoverToParent: 1
m_HorizontalAxis: Horizontal
m_VerticalAxis: Vertical
m_SubmitButton: Submit

m_Father: {fileID: 0}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &309485569 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 65572184039752926, 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: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &457234252
GameObject:
m_ObjectHideFlags: 0

m_Script: {fileID: 11500000, guid: 78d292f3bd9f1614cb744dcb4fe3ac12, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &2074106027 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 5162490130543683956, 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: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &2126854580 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 6900625576974141932, 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: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &2130620598 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 4144223224519676544, 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: 70dfc2fde0a9ef04eaff29a138f0bf45, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &2637199315837045695 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 4847401366899542036, 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: a03b37d5b8df06948b36dfbc430a1ea5, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &2637199315837045699 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 142122066828140637, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}

objectReference: {fileID: 0}
- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LocalUserObservers.Array.size
value: 7
value: 6
objectReference: {fileID: 0}
- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LocalLobbyObservers.Array.size

- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LobbyDataObservers.Array.data[1]
value:
objectReference: {fileID: 2126854580}
objectReference: {fileID: 0}
objectReference: {fileID: 2130620598}
objectReference: {fileID: 0}
objectReference: {fileID: 2074106027}
objectReference: {fileID: 0}
objectReference: {fileID: 309485569}
objectReference: {fileID: 0}
- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LobbyDataObservers.Array.data[5]
value:

- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LocalUserObservers.Array.data[0]
value:
objectReference: {fileID: 2637199315837045695}
objectReference: {fileID: 1217229506}
objectReference: {fileID: 1217229506}
objectReference: {fileID: 1969944515}
objectReference: {fileID: 1969944515}
objectReference: {fileID: 1583737884}
objectReference: {fileID: 1583737884}
objectReference: {fileID: 1793980663}
objectReference: {fileID: 1793980663}
objectReference: {fileID: 2637199315837045699}
objectReference: {fileID: 2637199315837045699}
objectReference: {fileID: 151543605}
objectReference: {fileID: 151543605}
objectReference: {fileID: 0}
objectReference: {fileID: 2130620598}
objectReference: {fileID: 0}
objectReference: {fileID: 2074106027}
objectReference: {fileID: 0}
objectReference: {fileID: 309485569}
objectReference: {fileID: 0}
objectReference: {fileID: 2126854580}
objectReference: {fileID: 0}
- target: {fileID: 7716713811812636910, guid: f80fc24bab3dcda459a2669321e2e5a4, type: 3}
propertyPath: m_LocalLobbyObservers.Array.data[4]
value:

objectReference: {fileID: 0}
- target: {fileID: 1282422528845055840, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}
propertyPath: m_AnchoredPosition.x
value: -0.000019460917
value: -0.000004887581
value: 0.000024497509
value: -0.0000111372065
objectReference: {fileID: 0}
- target: {fileID: 1386321973193631240, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}
propertyPath: m_AnchorMax.x

objectReference: {fileID: 0}
m_RemovedComponents:
- {fileID: 3143918963127177442, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}
- {fileID: 6900625576974141932, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}
m_SourcePrefab: {fileID: 100100000, guid: 9aae991127b410c45a001ecd7f75311d, type: 3}

49
Assets/Scripts/GameLobby/Game/Countdown.cs


/// since precise timing isn't necessary.
/// </summary>
[RequireComponent(typeof(UI.CountdownUI))]
public class Countdown : MonoBehaviour, IReceiveMessages
public class Countdown : MonoBehaviour
public class Data : Observed<Countdown.Data>
{
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.*/ }
}
CallbackValue<float> TimeLeft = new CallbackValue<float>();
Data m_data = new Data();
private UI.CountdownUI m_ui;
private const int k_countdownTime = 4;

m_ui = GetComponent<UI.CountdownUI>();
m_data.TimeLeft = -1;
Locator.Get.Messenger.Subscribe(this);
m_ui.BeginObserving(m_data);
TimeLeft.onChanged += m_ui.OnTimeChanged;
TimeLeft.Value = -1;
public void OnDisable()
public void StartCountDown()
Locator.Get.Messenger.Unsubscribe(this);
m_ui.EndObserving();
TimeLeft.Value = k_countdownTime;
public void OnReceiveMessage(MessageType type, object msg)
public void CancelCountDown()
if (type == MessageType.StartCountdown)
{
m_data.TimeLeft = k_countdownTime;
}
else if (type == MessageType.CancelCountdown)
{
m_data.TimeLeft = -1;
}
TimeLeft.Value = -1;
if (m_data.TimeLeft < 0)
if (TimeLeft.Value < 0)
m_data.TimeLeft -= Time.deltaTime;
if (m_data.TimeLeft < 0)
Locator.Get.Messenger.OnReceiveMessage(MessageType.CompleteCountdown, null);
TimeLeft.Value -= Time.deltaTime;
if (TimeLeft.Value < 0)
GameManager.Instance.FinishedCountDown();
}
}

115
Assets/Scripts/GameLobby/Game/GameManager.cs


using System.Collections.Generic;
using System.Threading.Tasks;
using LobbyRelaySample.lobby;
using LobbyRelaySample.ngo;
using Unity.Services.Authentication;
using UnityEngine;
#if UNITY_EDITOR

[SerializeField]
List<LobbyUserObserver> m_LocalUserObservers = new List<LobbyUserObserver>();
#endregion
public LocalLobby LocalLobby => m_LocalLobby;

public GameState LocalGameState { get; private set; }
public LobbyManager LobbyManager { get; private set; }
[SerializeField]
SetupInGame m_setupInGame;
[SerializeField]
Countdown m_countdown;
LocalPlayer m_LocalUser;
LocalLobby m_LocalLobby;
LobbySynchronizer m_LobbySynchronizer;

m_lobbyColorFilter = (LobbyColor)color;
}
public async Task CreateLobby(string name, bool isPrivate, int maxPlayers = 4)
{
var lobby = await LobbyManager.CreateLobbyAsync(
name,
maxPlayers,
isPrivate, m_LocalUser);
if (lobby != null)
{
LobbyConverters.RemoteToLocal(lobby, m_LocalLobby);
CreateLobby();
}
else
{
SetGameState(GameState.JoinMenu);
}
}
/// <summary>
/// The Messaging System handles most of the core Lobby Service calls, and catches the callbacks from those calls.
/// These In turn update the observed variables and propagates the events to the game.

public async void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.CreateLobbyRequest)
{
LocalLobby.LobbyData createLobbyData = (LocalLobby.LobbyData)msg;
var lobby = await LobbyManager.CreateLobbyAsync(
createLobbyData.LobbyName,
createLobbyData.MaxPlayerCount,
createLobbyData.Private, m_LocalUser);
if (lobby != null)
{
LobbyConverters.RemoteToLocal(lobby, m_LocalLobby);
CreateLobby();
}
else
{
SetGameState(GameState.JoinMenu);
}
}
else if (type == MessageType.JoinLobbyRequest)
if (type == MessageType.JoinLobbyRequest)
{
LocalLobby lobbyInfo = (LocalLobby)msg;
var lobby = await LobbyManager.JoinLobbyAsync(lobbyInfo.LobbyID.Value, lobbyInfo.LobbyCode.Value,

return;
}
m_LocalUser.DisplayName = (string)msg;
m_LocalUser.DisplayName.Value = (string)msg;
m_LocalUser.Emote = emote;
m_LocalUser.Emote.Value = emote;
m_LocalUser.UserStatus = (UserStatus)msg;
}
else if (type == MessageType.StartCountdown)
{
m_LocalLobby.LobbyState = LobbyState.CountDown;
}
else if (type == MessageType.CancelCountdown)
{
m_LocalLobby.LobbyState = LobbyState.Lobby;
m_LocalUser.UserStatus.Value = (UserStatus)msg;
//Start game for everyone
if (m_RelayClient is RelayUtpHost)
(m_RelayClient as RelayUtpHost).SendInGameState();
}

}
else if (type == MessageType.ConfirmInGameState)
{
m_LocalUser.UserStatus = UserStatus.InGame;
m_LocalLobby.LobbyState = LobbyState.InGame;
}
else if (type == MessageType.ConfirmInGameState) { }
m_LocalLobby.LobbyState = LobbyState.Lobby;
m_LocalLobby.LocalLobbyState.Value = LobbyState.Lobby;
}
public void BeginCountdown()
{
m_LocalLobby.LocalLobbyState.Value = LobbyState.CountDown;
m_countdown.StartCountDown();
}
public void CancelCountDown()
{
m_countdown.CancelCountDown();
m_LocalLobby.LocalLobbyState.Value = LobbyState.Lobby;
}
public void FinishedCountDown()
{
m_LocalUser.UserStatus.Value = UserStatus.InGame;
m_LocalLobby.LocalLobbyState.Value = LobbyState.InGame;
m_setupInGame.StartNetworkedGame(m_LocalLobby, m_LocalUser);
}
#region Setup

Application.wantsToQuit += OnWantToQuit;
LobbyManager = new LobbyManager();
m_LobbySynchronizer = new LobbySynchronizer(LobbyManager);
InitLocalInstances();
InitializeLocalValues();
InitLocalPlayerId();
StartVivoxLogin();
Locator.Get.Messenger.Subscribe(this);
BeginObservers();

await Auth.Authenticate(serviceProfileName);
}
void InitializeLocalValues()
void InitLocalInstances()
m_LocalLobby = new LocalLobby { LobbyState = LobbyState.Lobby };
m_LocalUser = new LocalPlayer();
m_LocalUser.ID = AuthenticationService.Instance.PlayerId;
m_LocalUser.DisplayName = NameGenerator.GetName(m_LocalUser.ID);
m_LocalLobby.AddPlayer(m_LocalUser); // The local LocalPlayer object will be hooked into UI
m_LocalLobby = new LocalLobby { LocalLobbyState = { Value = LobbyState.Lobby } };
}
// before the LocalLobby is populated during lobby join,
// so the LocalLobby must know about it already when that happens.
void InitLocalPlayerId()
{
var localId = AuthenticationService.Instance.PlayerId;
var randomName = NameGenerator.GetName(localId);
m_LocalUser = new LocalPlayer(localId, false, randomName);
m_LocalLobby.AddPlayer(m_LocalUser); // The local LocalPlayer object will be hooked into UI
}
void BeginObservers()

void CreateLobby()
{
m_LocalUser.IsHost = true;
m_LocalUser.IsHost.Value = true;
JoinLobby();
}

void StartRelayConnection()
{
if (m_LocalUser.IsHost)
if (m_LocalUser.IsHost.Value)
m_RelaySetup = gameObject.AddComponent<RelayUtpSetupHost>();
else
m_RelaySetup = gameObject.AddComponent<RelayUtpSetupClient>();

void ResetLocalLobby()
{
m_LocalLobby.CopyObserved(new LocalLobby.LobbyData(), new Dictionary<string, LocalPlayer>());
m_LocalLobby.ResetLobby();
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.RelayServer = null;

293
Assets/Scripts/GameLobby/Game/LocalLobby.cs


using System;
using System.Collections.Generic;
using System.Text;
using Unity.Services.Lobbies.Models;
using UnityEngine;
using UnityEngine.Serialization;

[System.Serializable]
public class LocalLobby : Observed<LocalLobby>
{
//Should be set to true when pushing lobby to the cloud, and set to false when done pulling.
//This is because we could get more than just our changes when we receive the latest lobby from our calls.
public bool changedByLobbySynch;
public Action<LocalLobby> onLobbyChanged { get; set; }
public bool CanSetChanged = true;
Dictionary<string, LocalPlayer> m_LobbyUsers = new Dictionary<string, LocalPlayer>();
public Dictionary<string, LocalPlayer> LobbyUsers => m_LobbyUsers;
Dictionary<string, LocalPlayer> m_LocalPlayers = new Dictionary<string, LocalPlayer>();
public Dictionary<string, LocalPlayer> LocalPlayers => m_LocalPlayers;
public Lobby CloudLobby => m_CloudLobby;
Lobby m_CloudLobby;
public struct LobbyData
{
public string LobbyID { get; set; }
public string LobbyCode { get; set; }
public string RelayCode { get; set; }
public string RelayNGOCode { get; set; }
public string LobbyName { get; set; }
public bool Private { get; set; }
public bool Locked { get; set; }
public int AvailableSlots { get; set; }
public int MaxPlayerCount { get; set; }
public LobbyState LobbyState { get; set; }
public LobbyColor LobbyColor { get; set; }
public long LastEdit { get; set; }
public LobbyData(LobbyData existing)
{
LobbyID = existing.LobbyID;
LobbyCode = existing.LobbyCode;
RelayCode = existing.RelayCode;
RelayNGOCode = existing.RelayNGOCode;
LobbyName = existing.LobbyName;
Private = existing.Private;
MaxPlayerCount = existing.MaxPlayerCount;
LobbyState = existing.LobbyState;
LobbyColor = existing.LobbyColor;
LastEdit = existing.LastEdit;
AvailableSlots = existing.AvailableSlots;
Locked = existing.Locked;
}
public LobbyData(string lobbyCode)
{
LobbyID = null;
LobbyCode = lobbyCode;
RelayCode = null;
RelayNGOCode = null;
LobbyName = null;
Private = false;
MaxPlayerCount = -1;
LobbyState = LobbyState.Lobby;
LobbyColor = LobbyColor.None;
LastEdit = 0;
AvailableSlots = 4;
Locked = false;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder("Lobby : ");
sb.AppendLine(LobbyName);
sb.Append("ID: ");
sb.AppendLine(LobbyID);
sb.Append("Code: ");
sb.AppendLine(LobbyCode);
sb.Append("Private: ");
sb.AppendLine(Private.ToString());
sb.Append("Locked: ");
sb.AppendLine(Locked.ToString());
sb.Append("Max Players: ");
sb.AppendLine(MaxPlayerCount.ToString());
sb.Append("AvailableSlots: ");
sb.AppendLine(AvailableSlots.ToString());
sb.Append("LobbyState: ");
sb.AppendLine(LobbyState.ToString());
sb.Append("Lobby LobbyState Last Edit: ");
sb.AppendLine(new DateTime(LastEdit).ToString());
sb.Append("LobbyColor: ");
sb.AppendLine(LobbyColor.ToString());
sb.Append("RelayCode: ");
sb.AppendLine(RelayCode);
sb.Append("RelayNGO: ");
sb.AppendLine(RelayNGOCode);
return sb.ToString();
}
}
public LobbyData Data => m_Data;
LobbyData m_Data;
m_CloudLobby = new Lobby();
onChanged += (lobby) => { m_Data.LastEdit = DateTime.Now.ToFileTimeUtc(); };
LastUpdated.Value = DateTime.Now.ToFileTimeUtc();
LobbyID.onChanged = (s) => { SetValueChanged(); };
LobbyCode.onChanged = (s) => { SetValueChanged(); };
RelayCode.onChanged = (s) => { SetValueChanged(); };
RelayNGOCode.onChanged = (s) => { SetValueChanged(); };
RelayServer.onChanged = (s) => { SetValueChanged(); };
LobbyName.onChanged = (s) => { SetValueChanged(); };
LocalLobbyState.onChanged = (s) => { SetValueChanged(); };
Private.onChanged = (s) => { SetValueChanged(); };
AvailableSlots.onChanged = (s) => { SetValueChanged(); };
MaxPlayerCount.onChanged = (s) => { SetValueChanged(); };
LocalLobbyColor.onChanged = (s) => { SetValueChanged(); };
LastUpdated.onChanged = (s) => { SetValueChanged(); };
}
/// <summary>Used only for visual output of the Relay connection info. The obfuscated Relay server IP is obtained during allocation in the RelayUtpSetup.</summary>

public void AddPlayer(LocalPlayer user)
{
if (m_LobbyUsers.ContainsKey(user.ID))
{
Debug.LogError($"Cant add player {user.DisplayName}({user.ID}) to lobby: {LobbyID} twice");
return;
}
public CallbackValue<string> LobbyID = new CallbackValue<string>();
AddUser(user);
onUserListChanged?.Invoke(m_LobbyUsers);
}
public CallbackValue<string> LobbyCode = new CallbackValue<string>();
void AddUser(LocalPlayer user)
{
Debug.Log($"Adding User: {user.DisplayName} - {user.ID}");
m_CloudLobby.Players.Add(new Player());
m_LobbyUsers.Add(user.ID, user);
}
public CallbackValue<string> RelayCode = new CallbackValue<string>();
public void RemovePlayer(LocalPlayer user)
{
DoRemoveUser(user);
onUserListChanged?.Invoke(m_LobbyUsers);
}
public CallbackValue<string> RelayNGOCode = new CallbackValue<string>();
private void DoRemoveUser(LocalPlayer user)
{
if (!m_LobbyUsers.ContainsKey(user.ID))
{
Debug.LogWarning($"Player {user.DisplayName}({user.ID}) does not exist in lobby: {LobbyID}");
return;
}
public CallbackValue<ServerAddress> RelayServer = new CallbackValue<ServerAddress>();
public CallbackValue<string> LobbyName = new CallbackValue<string>();
m_LobbyUsers.Remove(user.ID);
}
public CallbackValue<LobbyState> LocalLobbyState = new CallbackValue<LobbyState>();
public CallbackValue<string> LobbyID = new CallbackValue<string>();
public CallbackValue<bool> Private = new CallbackValue<bool>();
public CallbackValue<string> LobbyCode = new CallbackValue<string>();
public CallbackValue<int> AvailableSlots = new CallbackValue<int>();
public CallbackValue<string> RelayCode = new CallbackValue<string>();
public CallbackValue<int> MaxPlayerCount = new CallbackValue<int>();
public CallbackValue<string> RelayNGOCode = new CallbackValue<string>();
public CallbackValue<LobbyColor> LocalLobbyColor = new CallbackValue<LobbyColor>();
public CallbackValue<ServerAddress> RelayServer = new CallbackValue<ServerAddress>();
public CallbackValue<long> LastUpdated = new CallbackValue<long>();
public CallbackValue<string> LobbyName = new CallbackValue<string>();
public int PlayerCount => m_LocalPlayers.Count;
public LobbyState LobbyState
public void ResetLobby()
get => m_Data.LobbyState;
set
{
m_Data.LobbyState = value;
OnChanged(this);
}
m_LocalPlayers.Clear();
LobbyName.Value = "";
LobbyID.Value = "";
LobbyCode.Value = "";
Private.Value = false;
LocalLobbyColor.Value = LobbyRelaySample.LobbyColor.None;
AvailableSlots.Value = 4;
MaxPlayerCount.Value = 4;
public bool Private
/// <summary>
/// A locking mechanism for registering when something has looked at the Lobby to see if anything has changed
/// </summary>
/// <returns></returns>
public bool IsLobbyChanged()
get => m_Data.Private;
set
{
m_Data.Private = value;
OnChanged(this);
}
bool isChanged = m_ValuesChanged;
m_ValuesChanged = false;
return isChanged;
public int PlayerCount => m_LobbyUsers.Count;
public int MaxPlayerCount
void SetValueChanged()
get => m_Data.MaxPlayerCount;
set
{
m_Data.MaxPlayerCount = value;
OnChanged(this);
}
if(CanSetChanged)
m_ValuesChanged = true;
bool m_ValuesChanged;
public LobbyColor LobbyColor
public void AddPlayer(LocalPlayer user)
get => m_Data.LobbyColor;
set
if (m_LocalPlayers.ContainsKey(user.ID.Value))
if (m_Data.LobbyColor != value)
{
m_Data.LobbyColor = value;
OnChanged(this);
}
Debug.LogError($"Cant add player {user.DisplayName}({user.ID}) to lobby: {LobbyID} twice");
return;
Debug.Log($"Adding User: {user.DisplayName} - {user.ID}");
m_LocalPlayers.Add(user.ID.Value, user);
onUserListChanged?.Invoke(m_LocalPlayers);
public void OnChanged()
public void RemovePlayer(LocalPlayer user)
onLobbyChanged?.Invoke(this);
DoRemoveUser(user);
onUserListChanged?.Invoke(m_LocalPlayers);
public void CopyObserved(LobbyData lobbyData, Dictionary<string, LocalPlayer> lobbyUsers)
void DoRemoveUser(LocalPlayer user)
// 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.
m_Data = lobbyData;
LobbyState = lobbyData.LobbyState;
;
LobbyColor = lobbyData.LobbyColor;
m_Data.RelayNGOCode = lobbyData.RelayNGOCode;
if (lobbyUsers == null)
m_LobbyUsers = new Dictionary<string, LocalPlayer>();
else
if (!m_LocalPlayers.ContainsKey(user.ID.Value))
List<LocalPlayer> toRemove = new List<LocalPlayer>();
foreach (var oldUser in m_LobbyUsers)
{
if (lobbyUsers.ContainsKey(oldUser.Key))
oldUser.Value.CopyObserved(lobbyUsers[oldUser.Key]);
else
toRemove.Add(oldUser.Value);
}
Debug.LogWarning($"Player {user.DisplayName}({user.ID}) does not exist in lobby: {LobbyID}");
return;
}
foreach (var remove in toRemove)
{
DoRemoveUser(remove);
}
m_LocalPlayers.Remove(user.ID.Value);
}
foreach (var currUser in lobbyUsers)
{
if (!m_LobbyUsers.ContainsKey(currUser.Key))
AddUser(currUser.Value);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder("Lobby : ");
sb.AppendLine(LobbyName.Value);
sb.Append("ID: ");
sb.AppendLine(LobbyID.Value);
sb.Append("Code: ");
sb.AppendLine(LobbyCode.Value);
sb.Append("Private: ");
sb.AppendLine(Private.Value.ToString());
sb.Append("AvailableSlots: ");
sb.AppendLine(AvailableSlots.Value.ToString());
sb.Append("Max Players: ");
sb.AppendLine(MaxPlayerCount.Value.ToString());
sb.Append("LocalLobbyState: ");
sb.AppendLine(LocalLobbyState.Value.ToString());
sb.Append("Lobby LocalLobbyState Last Edit: ");
sb.AppendLine(new DateTime(LastUpdated.Value).ToString());
sb.Append("LocalLobbyColor: ");
sb.AppendLine(LocalLobbyColor.Value.ToString());
sb.Append("RelayCode: ");
sb.AppendLine(RelayCode.Value);
sb.Append("RelayNGO: ");
sb.AppendLine(RelayNGOCode.Value);
OnChanged(this);
return sb.ToString();
CopyObserved(oldObserved.Data, oldObserved.m_LobbyUsers);
// CopyObserved(oldObserved.Data, oldObserved.m_LocalPlayers);
}
}
}

152
Assets/Scripts/GameLobby/Game/LocalPlayer.cs


[Serializable]
public class LocalPlayer : Observed<LocalPlayer>
{
public LocalPlayer(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, isApproved);
}
const string key_Displayname = nameof(DisplayName);
const string key_Userstatus = nameof(UserStatus);
const string key_Emote = nameof(Emote);
#region Local UserData
public Player CloudPlayer { get; private set; }
public struct UserData
{
public bool IsHost { get; set; }
public string DisplayName { get; set; }
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,
bool isApproved)
{
IsHost = isHost;
DisplayName = displayName;
ID = id;
Emote = emote;
UserStatus = userStatus;
IsApproved = isApproved;
}
}
UserData m_data;
public CallbackValue<bool> IsHost = new CallbackValue<bool>();
public CallbackValue<string> DisplayName = new CallbackValue<string>();
public CallbackValue<EmoteType> Emote = new CallbackValue<EmoteType>();
public CallbackValue<UserStatus> UserStatus = new CallbackValue<UserStatus>();
public CallbackValue<string> ID = new CallbackValue<string>();
public LocalPlayer()
public LocalPlayer(string id, bool isHost, string displayName,
EmoteType emote = default, UserStatus status = default)
CloudPlayer = new Player();
DeesplayName.onChanged += SynchDisplayName;
IsHost.Value = isHost;
DisplayName.Value = displayName;
Emote.Value = emote;
UserStatus.Value = status;
ID.Value = id;
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
/// <summary>
/// Used for limiting costly OnChanged actions to just the members which actually changed.
/// </summary>
[Flags]
public enum UserMembers
{
IsHost = 1,
DisplayName = 2,
Emote = 4,
ID = 8,
UserStatus = 16,
}
public bool IsHost
{
get { return m_data.IsHost; }
set
{
if (m_data.IsHost == value)
return;
m_data.IsHost = value;
OnChanged(this);
}
}
public CallbackValue<string> DeesplayName = new CallbackValue<string>();
void SynchDisplayName(string name)
{
PlayerDataObject playerDataObject;
if (CloudPlayer.Data.TryGetValue(key_Displayname, out playerDataObject))
{
playerDataObject.Value = name;
}
else
{
CloudPlayer.Data.Add(key_Displayname,
new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, name));
}
}
//TODO Finish this, for now i'm going to the LocalLobby
public string DisplayName
{
get => CloudPlayer.Data[key_Displayname].Value;
set
{
if (m_data.DisplayName == value)
return;
m_data.DisplayName = value;
OnChanged(this);
}
}
public EmoteType Emote
{
get => m_data.Emote;
set
{
if (m_data.Emote == value)
return;
m_data.Emote = value;
OnChanged(this);
}
}
public string ID
{
get => m_data.ID;
set
{
if (m_data.ID == value)
return;
m_data.ID = value;
OnChanged(this);
}
IsHost.Value = false;
Emote.Value = EmoteType.None;
UserStatus.Value = LobbyRelaySample.UserStatus.Menu;
UserStatus m_userStatus = UserStatus.Menu;
public UserStatus UserStatus
{
get => m_userStatus;
set
{
if (m_userStatus == value)
return;
m_userStatus = value;
OnChanged(this);
}
}
m_data = observed.m_data;
;
OnChanged(this);
}
}

5
Assets/Scripts/GameLobby/Infrastructure/Actionvalue.cs


}
}
public void SetNoCallback(T value)
{
m_CachedValue = value;
}
T m_CachedValue = default;
}
}

112
Assets/Scripts/GameLobby/Lobby/LobbyConverters.cs


/// </summary>
public static class LobbyConverters
{
const string key_RelayCode = nameof(LocalLobby.LobbyData.RelayCode);
const string key_RelayNGOCode = nameof(LocalLobby.LobbyData.RelayNGOCode);
const string key_LobbyState = nameof(LocalLobby.LobbyData.LobbyState);
const string key_LobbyColor = nameof(LocalLobby.LobbyData.LobbyColor);
const string key_LastEdit = nameof(LocalLobby.LobbyData.LastEdit);
const string key_RelayCode = nameof(LocalLobby.RelayCode);
const string key_RelayNGOCode = nameof(LocalLobby.RelayNGOCode);
const string key_LobbyState = nameof(LocalLobby.LocalLobbyState);
const string key_LobbyColor = nameof(LocalLobby.LocalLobbyColor);
const string key_LastEdit = nameof(LocalLobby.LastUpdated);
public static Dictionary<string, string> LocalToRemoteData(LocalLobby lobby)
{

data.Add(key_LobbyState, ((int)lobby.LobbyState).ToString()); // Using an int is smaller than using the enum state's name.
data.Add(key_LobbyColor, ((int)lobby.LobbyColor).ToString());
data.Add(key_LastEdit, lobby.Data.LastEdit.ToString());
data.Add(key_LobbyState,
((int)lobby.LocalLobbyState.Value)
.ToString()); // Using an int is smaller than using the enum state's name.
data.Add(key_LobbyColor, ((int)lobby.LocalLobbyColor.Value).ToString());
data.Add(key_LastEdit, lobby.LastUpdated.Value.ToString());
return data;
}

Dictionary<string, string> data = new Dictionary<string, string>();
if (user == null || string.IsNullOrEmpty(user.ID))
if (user == null || string.IsNullOrEmpty(user.ID.Value))
data.Add(key_Displayname, user.DisplayName);
data.Add(key_Userstatus, ((int)user.UserStatus).ToString());
data.Add(key_Emote , ((int)user.Emote).ToString());
data.Add(key_Displayname, user.DisplayName.Value);
data.Add(key_Userstatus,
((int)user.UserStatus.Value)
.ToString()); // Cheaper to send the string int of the enum over the string enum
data.Add(key_Emote, (user.Emote).ToString());
return data;
}

public static void RemoteToLocal(Lobby remoteLobby, LocalLobby localLobby)
public static void RemoteToLocal(Lobby remoteLobby, LocalLobby localLobby, bool allowSetLobbyChanged = true)
localLobby.CanSetChanged = allowSetLobbyChanged;
localLobby.LobbyName.Value = remoteLobby.Name;
localLobby.Private.Value = remoteLobby.IsPrivate;
localLobby.AvailableSlots.Value = remoteLobby.AvailableSlots;
localLobby.MaxPlayerCount.Value = remoteLobby.MaxPlayers;
localLobby.LastUpdated.Value = remoteLobby.LastUpdated.ToFileTimeUtc();
//Custom Data Conversion
localLobby.RelayCode.Value = remoteLobby.Data?.ContainsKey(key_RelayCode) == true
? remoteLobby.Data[key_RelayCode].Value
: localLobby.RelayCode.Value;

LocalLobby.LobbyData lobbyData = new LocalLobby.LobbyData(localLobby.Data)
{
Private = remoteLobby.IsPrivate,
LobbyName = remoteLobby.Name,
MaxPlayerCount = remoteLobby.MaxPlayers,
LastEdit = remoteLobby.LastUpdated.ToFileTimeUtc(),
LobbyState = remoteLobby.Data?.ContainsKey(key_LobbyState) == true ? (LobbyState)int.Parse(remoteLobby.Data[key_LobbyState].Value) : LobbyState.Lobby,
LobbyColor = remoteLobby.Data?.ContainsKey(key_LobbyColor) == true ? (LobbyColor)int.Parse(remoteLobby.Data[key_LobbyColor].Value) : LobbyColor.None,
};
localLobby.LocalLobbyState.Value = remoteLobby.Data?.ContainsKey(key_LobbyState) == true
? (LobbyState)int.Parse(remoteLobby.Data[key_LobbyState].Value)
: LobbyState.Lobby;
localLobby.LocalLobbyColor.Value = remoteLobby.Data?.ContainsKey(key_LobbyColor) == true
? (LobbyColor)int.Parse(remoteLobby.Data[key_LobbyColor].Value)
: LobbyColor.None;
Dictionary<string, LocalPlayer> lobbyUsers = new Dictionary<string, LocalPlayer>();
List<string> remotePlayerIDs = new List<string>();
// (If we haven't seen this player yet, a new local representation of the player will have already been added by the LocalLobby.)
LocalPlayer incomingData = new LocalPlayer
var id = player.Id;
remotePlayerIDs.Add(id);
var isHost = remoteLobby.HostId.Equals(player.Id);
var displayName = player.Data?.ContainsKey(key_Displayname) == true
? player.Data[key_Displayname].Value
: default;
var emote = player.Data?.ContainsKey(key_Emote) == true
? (EmoteType)int.Parse(player.Data[key_Emote].Value)
: default;
var userStatus = player.Data?.ContainsKey(key_Userstatus) == true
? (UserStatus)int.Parse(player.Data[key_Userstatus].Value)
: UserStatus.Connecting;
LocalPlayer localPlayer;
//See if we have the remote player locally already
if (localLobby.LocalPlayers.ContainsKey(player.Id))
IsHost = remoteLobby.HostId.Equals(player.Id),
DisplayName = player.Data?.ContainsKey(key_Displayname) == true ? player.Data[key_Displayname].Value : default,
Emote = player.Data?.ContainsKey(key_Emote) == true ? (EmoteType)int.Parse(player.Data[key_Emote].Value) : default,
UserStatus = player.Data?.ContainsKey(key_Userstatus) == true ? (UserStatus)int.Parse(player.Data[key_Userstatus].Value) : UserStatus.Connecting,
ID = player.Id,
};
lobbyUsers.Add(incomingData.ID, incomingData);
localPlayer = localLobby.LocalPlayers[player.Id];
localPlayer.ID.Value = id;
localPlayer.DisplayName.Value = displayName;
localPlayer.Emote.Value = emote;
localPlayer.UserStatus.Value = userStatus;
}
else
{
localPlayer = new LocalPlayer(id, isHost, displayName, emote, userStatus);
localLobby.AddPlayer(localPlayer);
}
//Push all the data at once so we don't call OnChanged for each variable
localLobby.CopyObserved(lobbyData, lobbyUsers);
var disconnectedUsers = new List<LocalPlayer>();
foreach (var (id, player) in localLobby.LocalPlayers)
{
if (!remotePlayerIDs.Contains(id))
disconnectedUsers.Add(player);
}
foreach (var remove in disconnectedUsers)
{
localLobby.RemovePlayer(remove);
}
localLobby.CanSetChanged = true;
}
/// <summary>

return data;
}
}
}
}

8
Assets/Scripts/GameLobby/Lobby/LobbyManager.cs


{
Dictionary<string, PlayerDataObject> data = new Dictionary<string, PlayerDataObject>();
var displayNameObject = new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, user.DisplayName);
var displayNameObject = new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, user.DisplayName.Value);
data.Add("DisplayName", displayNameObject);
return data;
}

foreach (var dataNew in data)
{
// Special case: We want to be able to filter on our color data, so we need to supply an arbitrary index to retrieve later. Uses N# for numerics, instead of S# for strings.
DataObject.IndexOptions index = dataNew.Key == "LobbyColor" ? DataObject.IndexOptions.N1 : 0;
DataObject.IndexOptions index = dataNew.Key == "LocalLobbyColor" ? DataObject.IndexOptions.N1 : 0;
DataObject
dataObj = new DataObject(DataObject.VisibilityOptions.Public, dataNew.Value,
index); // Public so that when we request the list of lobbies, we can get info about them for filtering.

dataCurr.Add(dataNew.Key, dataObj);
//Special Use: Get the state of the Local lobby so we can lock it from appearing in queries if it's not in the "Lobby" LobbyState
if (dataNew.Key == "LobbyState")
//Special Use: Get the state of the Local lobby so we can lock it from appearing in queries if it's not in the "Lobby" LocalLobbyState
if (dataNew.Key == "LocalLobbyState")
{
Enum.TryParse(dataNew.Value, out LobbyState lobbyState);
shouldLock = lobbyState != LobbyState.Lobby;

132
Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs


{
m_LocalUser = localUser;
m_LocalLobby = localLobby;
m_LocalLobby.onChanged += OnLocalLobbyChanged;
m_LocalLobby.LobbyID.onChanged += OnLobbyIdChanged;
m_LocalChanges = true;
Locator.Get.Messenger.Subscribe(this);
#pragma warning disable 4014

Locator.Get.Messenger.Unsubscribe(this);
if (m_LocalLobby != null)
m_LocalLobby.onChanged -= OnLocalLobbyChanged;
m_LocalLobby.LobbyID.onChanged -= OnLobbyIdChanged;
m_LocalLobby = null;
}

// if (type == MessageType.ClientUserSeekingDisapproval)
// {
// bool shouldDisapprove =
// m_LocalLobby.LobbyState !=
// 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.
// m_LocalLobby.LocalLobbyState !=
// LocalLobbyState.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);
// }

while (m_LocalLobby != null)
{
if (m_LocalChanges)
{
latestLobby = await PushDataToLobby();
}
else
{
latestLobby = await m_LobbyManager.GetLobbyAsync();
}
latestLobby = await GetLatestRemoteLobby();
m_LocalLobby.changedByLobbySynch = true;
LobbyConverters.RemoteToLocal(latestLobby, m_LocalLobby);
m_LocalLobby.changedByLobbySynch = false;
//Pulling remote changes, and applying them to the local lobby usually flags it as changed,
//Causing another pull, the RemoteToLocal converter ensures this does not happen by flagging the lobby.
LobbyConverters.RemoteToLocal(latestLobby, m_LocalLobby, false);
}
if (!LobbyHasHost())

}
var areAllusersReady = AreAllUsersReady();
if (areAllusersReady && m_LocalLobby.LocalLobbyState.Value == LobbyState.Lobby)
{
GameManager.Instance.BeginCountdown();
}
else if (!areAllusersReady && m_LocalLobby.LocalLobbyState.Value == LobbyState.CountDown)
{
GameManager.Instance.CancelCountDown();
}
bool IfRemoteLobbyChanged(Lobby remoteLobby)
async Task<Lobby> GetLatestRemoteLobby()
{
Lobby latestLobby = null;
if (m_LocalLobby.IsLobbyChanged())
var remoteLobbyTime = remoteLobby.LastUpdated.ToFileTimeUtc();
var localLobbyTime = m_LocalLobby.Data.LastEdit;
var isLocalOutOfDate = remoteLobbyTime > localLobbyTime;
return isLocalOutOfDate;
latestLobby = await PushDataToLobby();
async Task<Lobby> PushDataToLobby()
else
m_LocalChanges = false;
latestLobby = await m_LobbyManager.GetLobbyAsync();
}
if (m_LocalUser.IsHost)
await m_LobbyManager.UpdateLobbyDataAsync(
LobbyConverters.LocalToRemoteData(m_LocalLobby));
return latestLobby;
}
return await m_LobbyManager.UpdatePlayerDataAsync(
LobbyConverters.LocalToRemoteUserData(m_LocalUser));
}
bool IfRemoteLobbyChanged(Lobby remoteLobby)
{
var remoteLobbyTime = remoteLobby.LastUpdated.ToFileTimeUtc();
var localLobbyTime = m_LocalLobby.LastUpdated.Value;
var isLocalOutOfDate = remoteLobbyTime > localLobbyTime;
return isLocalOutOfDate;
}
bool LobbyHasHost()
async Task<Lobby> PushDataToLobby()
{
m_LocalChanges = false;
if (m_LocalUser.IsHost.Value)
await m_LobbyManager.UpdateLobbyDataAsync(
LobbyConverters.LocalToRemoteData(m_LocalLobby));
return await m_LobbyManager.UpdatePlayerDataAsync(
LobbyConverters.LocalToRemoteUserData(m_LocalUser));
}
bool AreAllUsersReady()
{
foreach (var lobbyUser in m_LocalLobby.LocalPlayers.Values)
if (!m_LocalUser.IsHost)
if (lobbyUser.UserStatus.Value != UserStatus.Ready)
foreach (var lobbyUser in m_LocalLobby.LobbyUsers)
{
if (lobbyUser.Value.IsHost)
return true;
}
return true;
void LeaveLobbyBecauseNoHost()
return true;
}
bool LobbyHasHost()
{
if (!m_LocalUser.IsHost.Value)
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup,
"Host left the lobby! Disconnecting...");
Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame, null);
Locator.Get.Messenger.OnReceiveMessage(MessageType.ChangeMenuState, GameState.JoinMenu);
foreach (var lobbyUser in m_LocalLobby.LocalPlayers)
{
if (lobbyUser.Value.IsHost.Value)
return true;
}
return false;
return true;
void OnLocalLobbyChanged(LocalLobby localLobby)
void LeaveLobbyBecauseNoHost()
{
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup,
"Host left the lobby! Disconnecting...");
Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame, null);
Locator.Get.Messenger.OnReceiveMessage(MessageType.ChangeMenuState, GameState.JoinMenu);
}
public void OnLobbyIdChanged(string lobbyID)
if (string.IsNullOrEmpty(localLobby.LobbyID.Value)
if (string.IsNullOrEmpty(lobbyID)
return;
//Catch for infinite update looping from the synchronizer.
if (localLobby.changedByLobbySynch)
{
return;
}
m_LocalChanges = true;
}
public void Dispose()

2
Assets/Scripts/GameLobby/NGO/InGameRunner.cs


m_expectedPlayerCount = expectedPlayerCount;
m_onGameEnd = onGameEnd;
m_canSpawnInGameObjects = null;
m_localUserData = new PlayerData(localUser.DisplayName, 0);
m_localUserData = new PlayerData(localUser.DisplayName.Value, 0);
Locator.Get.Provide(this); // Simplifies access since some networked objects can't easily communicate locally (e.g. the host might call a ClientRpc without that client knowing where the call originated).
}

66
Assets/Scripts/GameLobby/NGO/SetupInGame.cs


using Unity.Netcode.Transports.UTP;
using Unity.Services.Relay;
using UnityEngine;
using UnityEngine.SocialPlatforms;
/// Once the local player is in a lobby and that lobby has entered the In-Game state, this will load in whatever is necessary to actually run the game part.
/// Once the local localPlayer is in a localLobby and that localLobby has entered the In-Game state, this will load in whatever is necessary to actually run the game part.
[SerializeField] GameObject m_IngameRunnerPrefab = default;
[SerializeField] private GameObject[] m_disableWhileInGame = default;
[SerializeField]
GameObject m_IngameRunnerPrefab = default;
[SerializeField]
private GameObject[] m_disableWhileInGame = default;
private InGameRunner m_inGameRunner;

private LocalLobby m_lobby;
private LocalPlayer m_localUser;
{ Locator.Get.Messenger.Subscribe(this);
{
Locator.Get.Messenger.Subscribe(this);
{ Locator.Get.Messenger.Unsubscribe(this);
{
Locator.Get.Messenger.Unsubscribe(this);
}
private void SetMenuVisibility(bool areVisible)

/// The prefab with the NetworkManager contains all of the assets and logic needed to set up the NGO minigame.
/// The UnityTransport needs to also be set up with a new Allocation from Relay.
/// </summary>
private async Task CreateNetworkManager()
async Task CreateNetworkManager(LocalLobby localLobby, LocalPlayer localPlayer)
m_lobby = localLobby;
m_inGameRunner.Initialize(OnConnectionVerified, m_lobby.PlayerCount, OnGameEnd, m_localUser);
if (m_localUser.IsHost)
m_inGameRunner.Initialize(OnConnectionVerified, m_lobby.PlayerCount, OnGameEnd, localPlayer);
if (localPlayer.IsHost.Value)
{
await SetRelayHostData();
NetworkManager.Singleton.StartHost();

await SetRelayClientData();
NetworkManager.Singleton.StartClient();
}
}
async Task SetRelayHostData()

var allocation = await Relay.Instance.CreateAllocationAsync(m_lobby.MaxPlayerCount);
var allocation = await Relay.Instance.CreateAllocationAsync(m_lobby.MaxPlayerCount.Value);
var joincode = await Relay.Instance.GetJoinCodeAsync(allocation.AllocationId);
m_lobby.RelayNGOCode.Value = joincode;

}
private void OnConnectionVerified()
{ m_hasConnectedViaNGO = true;
{
m_hasConnectedViaNGO = true;
// These are public for use in the Inspector.
public void OnLobbyChange(LocalLobby lobby)
{ m_lobby = lobby; // Most of the time this is redundant, but we need to get multiple members of the lobby to the Relay setup components, so might as well just hold onto the whole thing.
public void StartNetworkedGame(LocalLobby localLobby, LocalPlayer localPlayer)
{
m_doesNeedCleanup = true;
SetMenuVisibility(false);
#pragma warning disable 4014
CreateNetworkManager(localLobby, localPlayer);
#pragma warning restore 4014
public void OnLocalUserChange(LocalPlayer user)
{ m_localUser = user; // Same, regarding redundancy.
}
if (type == MessageType.ConfirmInGameState)
{
m_doesNeedCleanup = true;
SetMenuVisibility(false);
#pragma warning disable 4014
CreateNetworkManager();
#pragma warning restore 4014
}
if (type == MessageType.ConfirmInGameState) { }
// If this player hasn't successfully connected via NGO, forcibly exit the minigame.
// If this localPlayer hasn't successfully connected via NGO, forcibly exit the minigame.
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Failed to join the game.");
OnGameEnd();
}

// Once we're in-game, any state change reflects the player leaving the game, so we should clean up.
// Once we're in-game, any state change reflects the localPlayer leaving the game, so we should clean up.
/// Return to the lobby after the game, whether due to the game ending or due to a failed connection.
/// Return to the localLobby after the game, whether due to the game ending or due to a failed connection.
/// </summary>
private void OnGameEnd()
{

GameObject.Destroy(m_inGameRunner.gameObject); // Since this destroys the NetworkManager, that will kick off cleaning up networked objects.
Destroy(m_inGameRunner
.gameObject); // Since this destroys the NetworkManager, that will kick off cleaning up networked objects.
SetMenuVisibility(true);
m_lobby.RelayNGOCode = null;
m_doesNeedCleanup = false;

}
}

18
Assets/Scripts/GameLobby/Relay/RelayUtpClient.cs


{
int nameLength = msgContents[0];
string name = System.Text.Encoding.UTF8.GetString(msgContents.GetRange(1, nameLength).ToArray());
m_localLobby.LobbyUsers[id].DisplayName = name;
m_localLobby.LocalPlayers[id].DisplayName.Value = name;
m_localLobby.LobbyUsers[id].Emote = emote;
m_localLobby.LocalPlayers[id].Emote.Value = emote;
m_localLobby.LobbyUsers[id].UserStatus = status;
m_localLobby.LocalPlayers[id].UserStatus.Value = status;
}
else if (msgType == MsgType.StartCountdown)
Locator.Get.Messenger.OnReceiveMessage(MessageType.StartCountdown, null);

{
// 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 true;// != m_localUser.ID && (m_localUser.IsApproved && m_localLobby.LobbyUsers.ContainsKey(id) || type == MsgType.PlayerApprovalState);
return true;// != m_localUser.ID && (m_localUser.IsApproved && m_localLobby.LocalPlayers.ContainsKey(id) || type == MsgType.PlayerApprovalState);
}
protected virtual void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id) { }
protected virtual void ProcessDisconnectEvent(NetworkConnection conn, DataStreamReader strm)

/// </summary>
private void SendInitialMessage(NetworkDriver driver, NetworkConnection connection)
{
WriteByte(driver, connection, m_localUser.ID, MsgType.NewPlayer, 0);
WriteByte(driver, connection, m_localUser.ID.Value, MsgType.NewPlayer, 0);
m_hasSentInitialMessage = true;
}
private void OnApproved(NetworkDriver driver, NetworkConnection connection)

protected void ForceFullUserUpdate(NetworkDriver driver, NetworkConnection connection, LocalPlayer user)
{
// Note that it would be better to send a single message with the full state, but for the sake of shorter code we'll leave that out here.
WriteString(driver, connection, user.ID, MsgType.PlayerName, user.DisplayName);
WriteByte(driver, connection, user.ID, MsgType.Emote, (byte)user.Emote);
WriteByte(driver, connection, user.ID, MsgType.ReadyState, (byte)user.UserStatus);
WriteString(driver, connection, user.ID.Value, MsgType.PlayerName, user.DisplayName.Value);
WriteByte(driver, connection, user.ID.Value, MsgType.Emote, (byte)user.Emote.Value);
WriteByte(driver, connection, user.ID.Value, MsgType.ReadyState, (byte)user.UserStatus.Value);
}
/// <summary>

{
foreach (NetworkConnection connection in m_connections)
// If the client calls Disconnect, the host might not become aware right away (depending on when the PubSub messages get pumped), so send a message over UTP instead.
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.PlayerDisconnect, 0);
WriteByte(m_networkDriver, connection, m_localUser.ID.Value, MsgType.PlayerDisconnect, 0);
m_localLobby.RelayServer = null;
}
}

69
Assets/Scripts/GameLobby/Relay/RelayUtpHost.cs


/// <summary>
/// In addition to maintaining a heartbeat with the Relay server to keep it from timing out, the host player must pass network events
/// from clients to all other clients, since they don't connect to each other.
/// If you are using the Unity Networking Package, you can use their Relay instead of building your own packets.
/// If you are using the Unity Networking Package, you can use their Relay instead of building your own packets.
public override void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections, LocalPlayer localUser, LocalLobby localLobby)
public override void Initialize(NetworkDriver networkDriver, List<NetworkConnection> connections,
LocalPlayer localUser, LocalLobby localLobby)
m_hasSentInitialMessage = true; // The host will be alone in the lobby at first, so they need not send any messages right away.
m_hasSentInitialMessage =
true; // The host will be alone in the lobby at first, so they need not send any messages right away.
protected override void Uninitialize()
{
base.Uninitialize();

protected override void OnUpdate()
{
if (!m_IsRelayConnected) // If Relay was disconnected somehow, stop taking actions that will keep the allocation alive.
if (!m_IsRelayConnected
) // If Relay was disconnected somehow, stop taking actions that will keep the allocation alive.
return;
base.OnUpdate();
UpdateConnections();

void NewConnectionApprovalResult(NetworkConnection conn, Approval result)
{
WriteByte(m_networkDriver, conn, m_localUser.ID, MsgType.PlayerApprovalState, (byte)result);
WriteByte(m_networkDriver, conn, m_localUser.ID.Value, MsgType.PlayerApprovalState, (byte)result);
foreach (var user in m_localLobby.LobbyUsers)
foreach (var user in m_localLobby.LocalPlayers)
ForceFullUserUpdate(m_networkDriver, conn, user.Value);
m_connections.Add(conn);
}

protected override bool CanProcessDataEventFor(NetworkConnection conn, MsgType type, string id)
{
// 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);
return id != m_localUser.ID.Value &&
(m_localLobby.LocalPlayers.ContainsKey(id) && m_connections.Contains(conn) ||
type == MsgType.NewPlayer);
}
protected override void ProcessNetworkEventDataAdditional(NetworkConnection conn, MsgType msgType, string id)

{
string name = m_localLobby.LobbyUsers[id].DisplayName;
string name = m_localLobby.LocalPlayers[id].DisplayName.Value;
foreach (NetworkConnection otherConn in m_connections)
{
if (otherConn == conn)

}
else if (msgType == MsgType.Emote || msgType == MsgType.ReadyState)
{
byte value = msgType == MsgType.Emote ? (byte)m_localLobby.LobbyUsers[id].Emote : (byte)m_localLobby.LobbyUsers[id].UserStatus;
byte value = msgType == MsgType.Emote
? (byte)m_localLobby.LocalPlayers[id].Emote.Value
: (byte)m_localLobby.LocalPlayers[id].UserStatus.Value;
foreach (NetworkConnection otherConn in m_connections)
{
if (otherConn == 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.
else if (msgType == MsgType.PlayerDisconnect
) // Clients message the host when they intend to disconnect, or else the host ends up keeping the connection open.
#pragma warning disable 4014
/*var queryCooldownMilliseconds = LobbyManager.Instance.GetRateLimit(LobbyManager.RequestType.Query)

{
if (type == MessageType.LobbyUserStatus)
CheckIfAllUsersReady();
else if (type == MessageType.EndGame) // This assumes that only the host will have the End Game button available; otherwise, clients need to be able to send this message, too.
else if (type == MessageType.EndGame
) // This assumes that only the host will have the End Game button available; otherwise, clients need to be able to send this message, too.
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.EndInGame, 0);
WriteByte(m_networkDriver, connection, m_localUser.ID.Value, MsgType.EndInGame, 0);
//TODO Move this to Host Check. Host pushes Start Signal to all users?
foreach (var user in m_localLobby.LobbyUsers)
foreach (var user in m_localLobby.LocalPlayers)
if (user.Value.UserStatus != UserStatus.Ready)
{ haveAllReadied = false;
if (user.Value.UserStatus.Value != UserStatus.Ready)
{
haveAllReadied = false;
if (haveAllReadied && m_localLobby.LobbyState == LobbyState.Lobby) // Need to notify both this client and all others that all players have readied.
if (haveAllReadied && m_localLobby.LocalLobbyState.Value == LobbyState.Lobby
) // Need to notify both this client and all others that all players have readied.
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.StartCountdown, 0);
WriteByte(m_networkDriver, connection, m_localUser.ID.Value, MsgType.StartCountdown, 0);
else if (!haveAllReadied && m_localLobby.LobbyState == LobbyState.CountDown) // Someone cancelled during the countdown, so abort the countdown.
else if (!haveAllReadied && m_localLobby.LocalLobbyState.Value == LobbyState.CountDown
) // Someone cancelled during the countdown, so abort the countdown.
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.CancelCountdown, 0);
WriteByte(m_networkDriver, connection, m_localUser.ID.Value, MsgType.CancelCountdown, 0);
}
}

{
Locator.Get.Messenger.OnReceiveMessage(MessageType.ConfirmInGameState, null);
foreach (NetworkConnection connection in m_connections)
WriteByte(m_networkDriver, connection, m_localUser.ID, MsgType.ConfirmInGame, 0);
WriteByte(m_networkDriver, connection, m_localUser.ID.Value, MsgType.ConfirmInGame, 0);
}
/// <summary>

if (!m_connections[c].IsCreated)
m_connections.RemoveAt(c);
}
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.
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.
// 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.
}
}

foreach (NetworkConnection connection in m_connections)
connection.Disconnect(m_networkDriver); // Note that Lobby won't receive the disconnect immediately, so its auto-disconnect takes 30-40s, if needed.
connection.Disconnect(
m_networkDriver); // Note that Lobby won't receive the disconnect immediately, so its auto-disconnect takes 30-40s, if needed.
}
}

2
Assets/Scripts/GameLobby/Relay/RelayUtpSetup.cs


protected override void JoinRelay()
{
RelayAPIInterface.AllocateAsync(m_localLobby.MaxPlayerCount, OnAllocation);
RelayAPIInterface.AllocateAsync(m_localLobby.MaxPlayerCount.Value, OnAllocation);
}
private void OnAllocation(Allocation allocation)

267
Assets/Scripts/GameLobby/Tests/PlayMode/LobbyRoundtripTests.cs


namespace Test
{
/// <summary>
/// Accesses the Authentication and Lobby services in order to ensure lobbies can be created and deleted.
/// LobbyAsyncRequests wraps the Lobby API, so go through that in practice. This simply ensures the connection to the Lobby service is functional.
///
/// If the tests pass, you can assume you are connecting to the Lobby service itself properly.
/// </summary>
public class LobbyRoundtripTests
{
string playerID;
/// <summary>
/// Accesses the Authentication and Lobby services in order to ensure lobbies can be created and deleted.
/// LobbyAsyncRequests wraps the Lobby API, so go through that in practice. This simply ensures the connection to the Lobby service is functional.
///
/// If the tests pass, you can assume you are connecting to the Lobby service itself properly.
/// </summary>
public class LobbyRoundtripTests
{
string playerID;
Dictionary<string, PlayerDataObject>
m_mockUserData; // This is handled in the LobbyAsyncRequest calls normally, but we need to supply this for the direct Lobby API calls.
Dictionary<string, PlayerDataObject>
m_mockUserData; // This is handled in the LobbyAsyncRequest calls normally, but we need to supply this for the direct Lobby API calls.
LocalPlayer m_LocalUser;
LobbyManager m_LobbyManager;
LocalPlayer m_LocalUser;
LobbyManager m_LobbyManager;
[OneTimeSetUp]
public void Setup()
{
m_mockUserData = new Dictionary<string, PlayerDataObject>();
m_mockUserData.Add("DisplayName",
new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, "TestUser123"));
[OneTimeSetUp]
public void Setup()
{
m_mockUserData = new Dictionary<string, PlayerDataObject>();
m_mockUserData.Add("DisplayName",
new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, "TestUser123"));
m_LocalUser = new LocalPlayer(true);
m_LobbyManager = new LobbyManager();
TestAuthSetup();
TestAuthSetup();
}
m_LocalUser = new LocalPlayer(Auth.ID(), false, "TESTPLAYER");
m_LobbyManager = new LobbyManager();
}
async Task TestAuthSetup()
{
await Auth.Authenticate("test");
}
async Task TestAuthSetup()
{
await Auth.Authenticate("test");
}
[UnityTearDown]
public IEnumerator PerTestTeardown()
{
if (m_LobbyManager.CurrentLobby != null)
{
yield return AsyncTestHelper.Await(async () => await m_LobbyManager.LeaveLobbyAsync());
}
}
[UnityTearDown]
public IEnumerator PerTestTeardown()
{
if (m_LobbyManager.CurrentLobby != null)
{
yield return AsyncTestHelper.Await(async () => await m_LobbyManager.LeaveLobbyAsync());
}
}
/// <summary>
/// Make sure the entire roundtrip for Lobby works: Once signed in, create a lobby, query to make sure it exists, then delete it.
/// </summary>
[UnityTest]
public IEnumerator DoRoundtrip()
{
#region Setup
/// <summary>
/// Make sure the entire roundtrip for Lobby works: Once signed in, create a lobby, query to make sure it exists, then delete it.
/// </summary>
[UnityTest]
public IEnumerator DoRoundtrip()
{
#region Setup
yield return AsyncTestHelper.Await(async () => await Auth.Authenticating());
yield return AsyncTestHelper.Await(async () => await Auth.Authenticating());
// Since we're signed in through the same pathway as the actual game, the list of lobbies will include any that have been made in the game itself, so we should account for those.
// If you want to get around this, consider having a secondary project using the same assets with its own credentials.
yield return
new WaitForSeconds(
1); // To prevent a possible 429 with the upcoming Query request, in case a previous test had one; Query requests can only occur at a rate of 1 per second.
QueryResponse queryResponse = null;
Debug.Log("Getting Lobby List 1");
// Since we're signed in through the same pathway as the actual game, the list of lobbies will include any that have been made in the game itself, so we should account for those.
// If you want to get around this, consider having a secondary project using the same assets with its own credentials.
yield return
new WaitForSeconds(
1); // To prevent a possible 429 with the upcoming Query request, in case a previous test had one; Query requests can only occur at a rate of 1 per second.
QueryResponse queryResponse = null;
Debug.Log("Getting Lobby List 1");
yield return AsyncTestHelper.Await(
async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
yield return AsyncTestHelper.Await(
async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#0)");
int numLobbiesIni = queryResponse.Results?.Count ?? 0;
#endregion
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#0)");
int numLobbiesIni = queryResponse.Results?.Count ?? 0;
// Create a test lobby.
Lobby createLobby = null;
string lobbyName = "TestLobby-JustATest-123";
#endregion
yield return AsyncTestHelper.Await(async () =>
createLobby = await m_LobbyManager.CreateLobbyAsync(
lobbyName,
100,
false,
m_LocalUser));
// Create a test lobby.
Lobby createLobby = null;
string lobbyName = "TestLobby-JustATest-123";
Assert.IsNotNull(createLobby, "CreateLobbyAsync should return a non-null result.");
Assert.AreEqual(lobbyName, createLobby.Name, "Created lobby should match the provided name.");
var createLobbyId = createLobby.Id;
yield return AsyncTestHelper.Await(async () =>
createLobby = await m_LobbyManager.CreateLobbyAsync(
lobbyName,
100,
false,
m_LocalUser));
// Query for the test lobby via QueryAllLobbies.
Debug.Log("Getting Lobby List 2");
Assert.IsNotNull(createLobby, "CreateLobbyAsync should return a non-null result.");
Assert.AreEqual(lobbyName, createLobby.Name, "Created lobby should match the provided name.");
var createLobbyId = createLobby.Id;
// Query for the test lobby via QueryAllLobbies.
Debug.Log("Getting Lobby List 2");
yield return AsyncTestHelper.Await(
async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
yield return AsyncTestHelper.Await(
async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#1)");
Assert.AreEqual(1 + numLobbiesIni, queryResponse.Results.Count,
"Queried lobbies list should contain the test lobby.");
Assert.IsTrue(queryResponse.Results.Where(r => r.Name == lobbyName).Count() == 1,
"Checking queried lobby for name.");
Assert.IsTrue(queryResponse.Results.Where(r => r.Id == createLobbyId).Count() == 1,
"Checking queried lobby for ID.");
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#1)");
Assert.AreEqual(1 + numLobbiesIni, queryResponse.Results.Count,
"Queried lobbies list should contain the test lobby.");
Assert.IsTrue(queryResponse.Results.Where(r => r.Name == lobbyName).Count() == 1,
"Checking queried lobby for name.");
Assert.IsTrue(queryResponse.Results.Where(r => r.Id == createLobbyId).Count() == 1,
"Checking queried lobby for ID.");
Debug.Log("Getting current Lobby");
Lobby currentLobby = m_LobbyManager.CurrentLobby;
Assert.IsNotNull(currentLobby, "GetLobbyAsync should return a non-null result.");
Assert.AreEqual(lobbyName, currentLobby.Name, "Checking the lobby we got for name.");
Assert.AreEqual(createLobbyId, currentLobby.Id, "Checking the lobby we got for ID.");
Debug.Log("Getting current Lobby");
Debug.Log("Deleting current Lobby");
Lobby currentLobby = m_LobbyManager.CurrentLobby;
Assert.IsNotNull(currentLobby, "GetLobbyAsync should return a non-null result.");
Assert.AreEqual(lobbyName, currentLobby.Name, "Checking the lobby we got for name.");
Assert.AreEqual(createLobbyId, currentLobby.Id, "Checking the lobby we got for ID.");
// Delete the test lobby.
yield return AsyncTestHelper.Await(async () => await m_LobbyManager.LeaveLobbyAsync());
Debug.Log("Deleting current Lobby");
// Delete the test lobby.
yield return AsyncTestHelper.Await(async () => await m_LobbyManager.LeaveLobbyAsync());
createLobbyId = null;
Debug.Log("Getting Lobby List 3");
yield return AsyncTestHelper.Await(
async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
createLobbyId = null;
Debug.Log("Getting Lobby List 3");
yield return AsyncTestHelper.Await(
async () => queryResponse = await m_LobbyManager.RetrieveLobbyListAsync());
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#2)");
Assert.AreEqual(numLobbiesIni, queryResponse.Results.Count, "Queried lobbies list should be empty.");
}
/// <summary>
/// If the Lobby create call fails, we return null
/// </summary>
[UnityTest]
public IEnumerator CreateFailsWithNull()
{
yield return AsyncTestHelper.Await(async () => await Auth.Authenticating());
Assert.IsNotNull(queryResponse, "QueryAllLobbiesAsync should return a non-null result. (#2)");
Assert.AreEqual(numLobbiesIni, queryResponse.Results.Count, "Queried lobbies list should be empty.");
LogAssert.ignoreFailingMessages = true; // Multiple errors will appears for the exception.
Lobby createLobby = null;
yield return AsyncTestHelper.Await(async () =>
createLobby = await m_LobbyManager.CreateLobbyAsync(
"lobby name",
123,
false,
m_LocalUser));
}
LogAssert.ignoreFailingMessages = false;
Assert.Null(createLobby, "The returned object will be null, so expect to need to handle it.");
yield return
new WaitForSeconds(
3); //Since CreateLobby cannot be queued, we need to give this a buffer before moving on to other tests.
}
/// <summary>
/// If the Lobby create call fails, we return null
/// </summary>
[UnityTest]
public IEnumerator CreateFailsWithNull()
{
yield return AsyncTestHelper.Await(async () => await Auth.Authenticating());
LogAssert.ignoreFailingMessages = true; // Multiple errors will appears for the exception.
Lobby createLobby = null;
yield return AsyncTestHelper.Await(async () =>
createLobby = await m_LobbyManager.CreateLobbyAsync(
"lobby name",
123,
false,
m_LocalUser));
LogAssert.ignoreFailingMessages = false;
Assert.Null(createLobby, "The returned object will be null, so expect to need to handle it.");
yield return new WaitForSeconds(3); //Since CreateLobby cannot be queued, we need to give this a buffer before moving on to other tests.
}
[UnityTest]
public IEnumerator CooldownTest()
{
var rateLimiter = new RateLimiter(3);
Stopwatch timer = new Stopwatch();
timer.Start();
[UnityTest]
public IEnumerator CooldownTest()
{
var rateLimiter = new RateLimiter(3);
Stopwatch timer = new Stopwatch();
timer.Start();
//pass Through the first request, which triggers the cooldown.
yield return AsyncTestHelper.Await(async () => await rateLimiter.WaitUntilCooldown());
//Should wait for one second total
yield return AsyncTestHelper.Await(async () => await rateLimiter.WaitUntilCooldown());
timer.Stop();
var elapsedMS = timer.ElapsedMilliseconds;
Debug.Log($"Cooldown took {elapsedMS}/{rateLimiter.coolDownMS} milliseconds.");
var difference = Mathf.Abs(elapsedMS - rateLimiter.coolDownMS);
Assert.IsTrue(difference<50&&difference>=0);
//pass Through the first request, which triggers the cooldown.
yield return AsyncTestHelper.Await(async () => await rateLimiter.WaitUntilCooldown());
}
}
}
//Should wait for one second total
yield return AsyncTestHelper.Await(async () => await rateLimiter.WaitUntilCooldown());
timer.Stop();
var elapsedMS = timer.ElapsedMilliseconds;
Debug.Log($"Cooldown took {elapsedMS}/{rateLimiter.coolDownMS} milliseconds.");
var difference = Mathf.Abs(elapsedMS - rateLimiter.coolDownMS);
Assert.IsTrue(difference < 50 && difference >= 0);
}
}
}

11
Assets/Scripts/GameLobby/UI/CountdownUI.cs


{
/// <summary>
/// After all players ready up for the game, this will show the countdown that occurs.
/// This countdown is purely visual, to give clients a moment if they need to un-ready before entering the game;
/// This countdown is purely visual, to give clients a moment if they need to un-ready before entering the game;
public class CountdownUI : ObserverBehaviour<Countdown.Data>
public class CountdownUI : UIPanelBase
protected override void UpdateObserver(Countdown.Data data)
public void OnTimeChanged(float time)
base.UpdateObserver(data);
if (observed.TimeLeft <= 0)
if (time <= 0)
m_CountDownText.SetText($"Starting in: {observed.TimeLeft:0}"); // Note that the ":0" formatting rounds, not truncates.
m_CountDownText.SetText($"Starting in: {time:0}"); // Note that the ":0" formatting rounds, not truncates.
}
}
}

12
Assets/Scripts/GameLobby/UI/CreateMenuUI.cs


public class CreateMenuUI : UIPanelBase
{
public JoinCreateLobbyUI m_JoinCreateLobbyUI;
private LocalLobby.LobbyData m_ServerRequestData = new LocalLobby.LobbyData { LobbyName = "New Lobby", MaxPlayerCount = 4 };
string m_ServerName;
bool m_IsServerPrivate;
public override void Start()
{

public void SetServerName(string serverName)
{
m_ServerRequestData.LobbyName = serverName;
m_ServerName = serverName;
m_ServerRequestData.Private = priv;
m_IsServerPrivate = priv;
Locator.Get.Messenger.OnReceiveMessage(MessageType.CreateLobbyRequest, m_ServerRequestData);
//Disabled as it's a one-off butto call
#pragma warning disable 4014
GameManager.Instance.CreateLobby(m_ServerName, m_IsServerPrivate);
#pragma warning restore 4014
}
}
}

31
Assets/Scripts/GameLobby/UI/InLobbyUserList.cs


namespace LobbyRelaySample.UI
{
/// <summary>
/// Contains the InLobbyUserUI instances while showing the UI for a lobby.
/// </summary>
[RequireComponent(typeof(LocalLobbyObserver))]
public class InLobbyUserList : ObserverPanel<LocalLobby>
public class InLobbyUserList : UIPanelBase
List<string> m_CurrentUsers = new List<string>(); // Just for keeping track more easily of which users are already displayed.
List<string>
m_CurrentUsers =
new List<string>(); // Just for keeping track more easily of which users are already displayed.
public override void Start()
{
base.Start();
GameManager.Instance.LocalLobby.onUserListChanged += OnUsersChanged;
}
/// <summary>
/// When the observed data updates, we need to detect changes to the list of players.
/// </summary>
public override void ObservedUpdated(LocalLobby observed)
void OnUsersChanged(Dictionary<string, LocalPlayer> newUserDict)
for (int id = m_CurrentUsers.Count - 1; id >= 0; id--) // We might remove users if they aren't in the new data, so iterate backwards.
for (int id = m_CurrentUsers.Count - 1;
id >= 0;
id--) // We might remove users if they aren't in the new data, so iterate backwards.
if (!observed.LobbyUsers.ContainsKey(userId))
if (!newUserDict.ContainsKey(userId))
{
foreach (var ui in m_UserUIObjects)
{

}
}
foreach (var lobbyUserKvp in observed.LobbyUsers) // If there are new players, we need to hook them into the UI.
foreach (var lobbyUserKvp in newUserDict) // If there are new players, we need to hook them into the UI.
{
if (m_CurrentUsers.Contains(lobbyUserKvp.Key))
continue;

m_CurrentUsers.Remove(userID);
}
}
}
}

56
Assets/Scripts/GameLobby/UI/InLobbyUserUI.cs


/// <summary>
/// When inside a lobby, this will show information about a player, whether local or remote.
/// </summary>
[RequireComponent(typeof(LobbyUserObserver))]
public class InLobbyUserUI : ObserverPanel<LocalPlayer>
public class InLobbyUserUI : UIPanelBase
{
[SerializeField]
TMP_Text m_DisplayNameText;

vivox.VivoxUserHandler m_vivoxUserHandler;
public bool IsAssigned => UserId != null;
LobbyUserObserver m_observer;
LocalPlayer m_localPlayer;
if (m_observer == null)
m_observer = GetComponent<LobbyUserObserver>();
m_observer.BeginObserving(myLocalPlayer);
UserId = myLocalPlayer.ID;
SubscribeToPlayerUpdates();
UserId = myLocalPlayer.ID.Value;
public void SubscribeToPlayerUpdates()
{
m_localPlayer.DisplayName.onChanged += SetDisplayName;
m_localPlayer.UserStatus.onChanged += SetUserStatus;
m_localPlayer.Emote.onChanged += SetEmote;
m_localPlayer.IsHost.onChanged += SetIsHost;
}
public void UnsubscribeToPlayerUpdates()
{
m_localPlayer.DisplayName.onChanged -= SetDisplayName;
m_localPlayer.UserStatus.onChanged -= SetUserStatus;
m_localPlayer.Emote.onChanged -= SetEmote;
m_localPlayer.IsHost.onChanged -= SetIsHost;
}
m_observer.EndObserving();
UnsubscribeToPlayerUpdates();
m_localPlayer = null;
public override void ObservedUpdated(LocalPlayer observed)
void SetDisplayName(string displayName)
m_DisplayNameText.SetText(observed.DisplayName);
m_StatusText.SetText(SetStatusFancy(observed.UserStatus));
m_EmoteImage.sprite = EmoteIcon(observed.Emote);
m_HostIcon.enabled = observed.IsHost;
m_DisplayNameText.SetText(displayName);
}
void SetUserStatus(UserStatus statusText)
{
m_StatusText.SetText(SetStatusFancy(statusText));
}
void SetEmote(EmoteType emote)
{
m_EmoteImage.sprite = EmoteIcon(emote);
}
void SetIsHost(bool isHost)
{
m_HostIcon.enabled = isHost;
}
/// <summary>

}
}
}
}
}

2
Assets/Scripts/GameLobby/UI/JoinMenuUI.cs


bool CanDisplay(LocalLobby lobby)
{
return lobby.Data.LobbyState == LobbyState.Lobby && !lobby.Private;
return lobby.LocalLobbyState.Value == LobbyState.Lobby && !lobby.Private.Value;
}
/// <summary>

16
Assets/Scripts/GameLobby/UI/RecolorForLobbyType.cs


/// We want to illustrate filtering the lobby list by some arbitrary variable. This will allow the lobby host to choose a color for the lobby, and will display a lobby's current color.
/// (Note that this isn't sent over Relay to other clients for realtime updates.)
/// </summary>
[RequireComponent(typeof(LocalLobbyObserver))]
public class RecolorForLobbyType : MonoBehaviour
{
private static readonly Color s_orangeColor = new Color(0.8352942f, 0.3686275f, 0);

private Graphic[] m_toRecolor;
private LocalLobby m_lobby;
public void UpdateLobby(LocalLobby lobby)
public void Start()
m_lobby = lobby;
Color color = s_colorsOrdered[(int)lobby.LobbyColor];
m_lobby = GameManager.Instance.LocalLobby;
m_lobby.LocalLobbyColor.onChanged += ChangeColors;
}
public void ChangeColors(LobbyColor lobbyColor)
{
Color color = s_colorsOrdered[(int)lobbyColor];
foreach (Graphic graphic in m_toRecolor)
graphic.color = new Color(color.r, color.g, color.b, graphic.color.a);
}

/// Triggers the ChangeColors method above
m_lobby.LobbyColor = (LobbyColor)color;
m_lobby.LocalLobbyColor.Value = (LobbyColor)color;
}
}
}

14
Assets/Scripts/GameLobby/UI/RelayAddressUI.cs


/// <summary>
/// Displays the IP when connected to Relay.
/// </summary>
public class RelayAddressUI : ObserverPanel<LocalLobby>
public class RelayAddressUI : UIPanelBase
public override void ObservedUpdated(LocalLobby observed)
public override void Start()
m_IPAddressText.SetText(observed.RelayServer?.ToString());
base.Start();
GameManager.Instance.LocalLobby.RelayServer.onChanged += GotRelayAddress;
}
void GotRelayAddress(ServerAddress address)
{
m_IPAddressText.SetText(address.ToString());
}
}

8
Assets/Scripts/GameLobby/UI/ShowWhenLobbyStateUI.cs


[SerializeField]
LobbyState m_ShowThisWhen;
public void LobbyChanged(LocalLobby lobby)
public void LobbyChanged(LobbyState lobbyState)
if (m_ShowThisWhen.HasFlag(lobby.LobbyState))
if (m_ShowThisWhen.HasFlag(lobbyState))
Show();
else
Hide();

{
base.Start();
Manager.LocalLobby.onLobbyChanged += LobbyChanged;
Manager.LocalLobby.LocalLobbyState.onChanged += LobbyChanged;
}
public void OnDestroy()

Manager.LocalLobby.onLobbyChanged -= LobbyChanged;
Manager.LocalLobby.LocalLobbyState.onChanged -= LobbyChanged;
}
}
}

15
Assets/Scripts/GameLobby/UI/UserNameUI.cs


using LobbyRelaySample.ngo;
using TMPro;
using UnityEngine;

/// Displays the player's name.
/// </summary>
public class UserNameUI : ObserverPanel<LocalPlayer>
public class UserNameUI : UIPanelBase
public override void ObservedUpdated(LocalPlayer observed)
public override void Start()
{
base.Start();
GameManager.Instance.LocalUser.DisplayName.onChanged += SetText;
}
void SetText(string text)
m_TextField.SetText(observed.DisplayName);
m_TextField.SetText(text);
}
}

6
Assets/Scripts/GameLobby/UI/UserStateVisibilityUI.cs


public override void ObservedUpdated(LocalPlayer observed)
{
var hasStatusFlags = ShowThisWhen.HasFlag(observed.UserStatus);
var hasStatusFlags = ShowThisWhen.HasFlag(observed.UserStatus.Value);
if (Permissions.HasFlag(UserPermission.Host) && observed.IsHost)
if (Permissions.HasFlag(UserPermission.Host) && observed.IsHost.Value)
else if (Permissions.HasFlag(UserPermission.Client) && !observed.IsHost)
else if (Permissions.HasFlag(UserPermission.Client) && !observed.IsHost.Value)
{
hasPermissions = true;
}

25
Packages/manifest.json


{
"dependencies": {
"com.unity.2d.sprite": "1.0.0",
"com.unity.collab-proxy": "1.15.15",
"com.unity.collections": "1.0.0-pre.6",
"com.unity.ide.rider": "3.0.13",
"com.unity.ide.visualstudio": "2.0.14",
"com.unity.collab-proxy": "1.17.2",
"com.unity.collections": "1.2.4",
"com.unity.ide.rider": "3.0.15",
"com.unity.ide.visualstudio": "2.0.16",
"com.unity.netcode.gameobjects": "1.0.0-pre.9",
"com.unity.nuget.newtonsoft-json": "2.0.0",
"com.unity.render-pipelines.universal": "12.1.6",
"com.unity.services.authentication": "2.0.0",
"com.unity.services.core": "1.2.0",
"com.unity.services.lobby": "1.0.1",
"com.unity.services.relay": "1.0.1-pre.3",
"com.unity.netcode.gameobjects": "1.0.2",
"com.unity.nuget.newtonsoft-json": "3.0.2",
"com.unity.render-pipelines.universal": "12.1.7",
"com.unity.services.authentication": "2.2.0",
"com.unity.services.core": "1.4.2",
"com.unity.services.lobby": "1.0.3",
"com.unity.services.relay": "1.0.4",
"com.unity.services.wire": "1.0.0",
"com.unity.transport": "1.0.0-pre.9",
"com.unity.transport": "1.1.0",
"com.unity.ugui": "1.0.0",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",

90
Packages/packages-lock.json


"dependencies": {}
},
"com.unity.burst": {
"version": "1.6.5",
"version": "1.7.3",
"depth": 1,
"source": "registry",
"dependencies": {

},
"com.unity.collab-proxy": {
"version": "1.15.15",
"version": "1.17.2",
"depth": 0,
"source": "registry",
"dependencies": {

},
"com.unity.collections": {
"version": "1.2.3",
"depth": 2,
"version": "1.2.4",
"depth": 0,
"com.unity.burst": "1.6.4",
"com.unity.burst": "1.6.6",
"com.unity.test-framework": "1.1.31"
},
"url": "https://packages.unity.com"

"url": "https://packages.unity.com"
},
"com.unity.ide.rider": {
"version": "3.0.13",
"version": "3.0.15",
"depth": 0,
"source": "registry",
"dependencies": {

},
"com.unity.ide.visualstudio": {
"version": "2.0.14",
"version": "2.0.16",
"depth": 0,
"source": "registry",
"dependencies": {

"url": "https://packages.unity.com"
},
"com.unity.mathematics": {
"version": "1.2.5",
"version": "1.2.6",
"depth": 1,
"source": "registry",
"dependencies": {},

"version": "1.0.0-pre.9",
"version": "1.0.2",
"com.unity.transport": "1.0.0"
"com.unity.transport": "1.2.0"
},
"url": "https://packages.unity.com"
},

},
"com.unity.nuget.newtonsoft-json": {
"version": "3.0.2",
"depth": 1,
"depth": 0,
"version": "12.1.6",
"version": "12.1.7",
"depth": 1,
"source": "builtin",
"dependencies": {

}
},
"com.unity.render-pipelines.universal": {
"version": "12.1.6",
"version": "12.1.7",
"com.unity.burst": "1.5.0",
"com.unity.render-pipelines.core": "12.1.6",
"com.unity.shadergraph": "12.1.6"
"com.unity.burst": "1.7.3",
"com.unity.render-pipelines.core": "12.1.7",
"com.unity.shadergraph": "12.1.7"
}
},
"com.unity.searcher": {

"url": "https://packages.unity.com"
},
"com.unity.services.authentication": {
"version": "2.0.0",
"version": "2.2.0",
"com.unity.services.core": "1.3.1",
"com.unity.modules.unitywebrequest": "1.0.0"
"com.unity.services.core": "1.4.3",
"com.unity.modules.unitywebrequest": "1.0.0",
"com.unity.ugui": "1.0.0"
"version": "1.4.0",
"version": "1.4.3",
"depth": 1,
"source": "registry",
"dependencies": {

"url": "https://packages.unity.com"
},
"com.unity.services.lobby": {
"version": "1.0.1",
"version": "1.0.3",
"depth": 0,
"source": "registry",
"dependencies": {

},
"url": "https://packages.unity.com"
},
"com.unity.services.qos": {
"version": "1.0.2",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.services.core": "1.4.0",
"com.unity.modules.unitywebrequest": "1.0.0",
"com.unity.nuget.newtonsoft-json": "3.0.2",
"com.unity.services.authentication": "2.0.0",
"com.unity.collections": "1.2.4"
},
"url": "https://packages.unity.com"
},
"version": "1.0.1-pre.3",
"version": "1.0.4",
"com.unity.services.core": "1.1.0-pre.10",
"com.unity.services.core": "1.4.0",
"com.unity.services.authentication": "2.0.0",
"com.unity.services.qos": "1.0.2",
"com.unity.nuget.newtonsoft-json": "2.0.0",
"com.unity.services.authentication": "1.0.0-pre.6",
"com.unity.transport": "1.0.0-pre.6"
"com.unity.nuget.newtonsoft-json": "3.0.2",
"com.unity.transport": "1.1.0"
},
"url": "https://packages.unity.com"
},

},
"url": "https://packages.unity.com"
},
"com.unity.services.wire": {
"version": "1.0.0",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.services.core": "1.2.0",
"com.unity.nuget.newtonsoft-json": "3.0.1",
"com.unity.services.authentication": "1.0.0-pre.37"
},
"url": "https://packages.unity.com"
},
"com.unity.settings-manager": {
"version": "2.0.0",
"depth": 1,

},
"com.unity.shadergraph": {
"version": "12.1.6",
"version": "12.1.7",
"com.unity.render-pipelines.core": "12.1.6",
"com.unity.render-pipelines.core": "12.1.7",
"com.unity.searcher": "4.9.1"
}
},

"url": "https://packages.unity.com"
},
"com.unity.transport": {
"version": "1.0.0",
"version": "1.2.0",
"com.unity.collections": "1.2.3",
"com.unity.burst": "1.6.4",
"com.unity.mathematics": "1.2.5"
"com.unity.collections": "1.2.4",
"com.unity.burst": "1.6.6",
"com.unity.mathematics": "1.2.6"
},
"url": "https://packages.unity.com"
},

4
ProjectSettings/ProjectVersion.txt


m_EditorVersion: 2021.2.19f1
m_EditorVersionWithRevision: 2021.2.19f1 (602ecdbb2fb0)
m_EditorVersion: 2021.3.9f1
m_EditorVersionWithRevision: 2021.3.9f1 (ad3870b89536)

2
ProjectSettings/RiderScriptEditorPersistedState.asset


m_Script: {fileID: 0}
m_Name:
m_EditorClassIdentifier: Unity.Rider.Editor:Packages.Rider.Editor:RiderScriptEditorPersistedState
lastWriteTicks: -8585462928069479823
lastWriteTicks: -8585383256260152102
正在加载...
取消
保存