GitHub
2 年前
当前提交
6a10872c
共有 202 个文件被更改,包括 3757 次插入 和 2699 次删除
-
2.gitignore
-
21Assets/Art/Glyph/Symbols.png.meta
-
9Assets/Art/Icons/ClickParticleMat.mat
-
81Assets/Prefabs/GameManager.prefab
-
199Assets/Prefabs/NGO/InGameLogic.prefab
-
13Assets/Prefabs/Runes/icon_particle.mat
-
31Assets/Prefabs/UI/BackButtonBG.prefab
-
70Assets/Prefabs/UI/GameCanvas.prefab
-
88Assets/Prefabs/UI/JoinContent.prefab
-
89Assets/Prefabs/UI/JoinCreateCanvas.prefab
-
171Assets/Prefabs/UI/LobbyCanvas.prefab
-
34Assets/Prefabs/UI/LobbyCodeCanvas.prefab
-
106Assets/Prefabs/UI/LobbyUserList.prefab
-
41Assets/Prefabs/UI/MainMenuCanvas.prefab
-
34Assets/Prefabs/UI/RelayCodeCanvas.prefab
-
35Assets/Prefabs/UI/SpinnerUI.prefab
-
58Assets/Prefabs/UI/UserCardPanel.prefab
-
371Assets/Prefabs/UI/UserInteractionPanel.prefab
-
49Assets/Prefabs/UI/LobbyEntryUI.prefab
-
21Assets/Renderer/glyphGameURP.asset
-
14Assets/Renderer/glyphGameURP_Renderer.asset
-
423Assets/Scenes/mainScene.unity
-
2Assets/Scripts/GameLobby/Auth/Auth.cs.meta
-
49Assets/Scripts/GameLobby/Game/Countdown.cs
-
586Assets/Scripts/GameLobby/Game/GameManager.cs
-
339Assets/Scripts/GameLobby/Game/LocalLobby.cs
-
24Assets/Scripts/GameLobby/Game/ServerAddress.cs
-
26Assets/Scripts/GameLobby/Infrastructure/LogHandlerSettings.cs
-
2Assets/Scripts/GameLobby/Lobby/AsyncRequestLobby.cs
-
139Assets/Scripts/GameLobby/Lobby/LobbyConverters.cs
-
8Assets/Scripts/GameLobby/LobbyRelaySample.asmdef
-
104Assets/Scripts/GameLobby/NGO/InGameRunner.cs
-
12Assets/Scripts/GameLobby/NGO/IntroOutroRunner.cs
-
14Assets/Scripts/GameLobby/NGO/NetworkedDataStore.cs
-
52Assets/Scripts/GameLobby/NGO/PlayerCursor.cs
-
8Assets/Scripts/GameLobby/NGO/ResultsUserUI.cs
-
10Assets/Scripts/GameLobby/NGO/Scorer.cs
-
135Assets/Scripts/GameLobby/NGO/SequenceSelector.cs
-
171Assets/Scripts/GameLobby/NGO/SetupInGame.cs
-
45Assets/Scripts/GameLobby/NGO/SymbolContainer.cs
-
2Assets/Scripts/GameLobby/NGO/SymbolData.cs
-
4Assets/Scripts/GameLobby/NGO/SymbolKillVolume.cs
-
44Assets/Scripts/GameLobby/NGO/SymbolObject.cs
-
6Assets/Scripts/GameLobby/Relay/AsyncRequestRelay.cs
-
4Assets/Scripts/GameLobby/Relay/RelayAPIInterface.cs
-
12Assets/Scripts/GameLobby/Tests/Editor/MessengerTests.cs
-
217Assets/Scripts/GameLobby/Tests/PlayMode/LobbyRoundtripTests.cs
-
4Assets/Scripts/GameLobby/Tests/PlayMode/Tests.Play.asmdef
-
190Assets/Scripts/GameLobby/Tests/PlayMode/UtpTests.cs
-
7Assets/Scripts/GameLobby/UI/BackButtonUI.cs
-
11Assets/Scripts/GameLobby/UI/CountdownUI.cs
-
11Assets/Scripts/GameLobby/UI/CreateMenuUI.cs
-
20Assets/Scripts/GameLobby/UI/DisplayCodeUI.cs
-
7Assets/Scripts/GameLobby/UI/EmoteButtonUI.cs
-
6Assets/Scripts/GameLobby/UI/EndGameButtonUI.cs
-
22Assets/Scripts/GameLobby/UI/GameStateVisibilityUI.cs
-
99Assets/Scripts/GameLobby/UI/InLobbyUserUI.cs
-
22Assets/Scripts/GameLobby/UI/JoinCreateLobbyUI.cs
-
121Assets/Scripts/GameLobby/UI/JoinMenuUI.cs
-
7Assets/Scripts/GameLobby/UI/LobbyNameUI.cs
-
12Assets/Scripts/GameLobby/UI/LobbyUserVolumeUI.cs
-
2Assets/Scripts/GameLobby/UI/NameChangeUI.cs
-
19Assets/Scripts/GameLobby/UI/RateLimitVisibility.cs
-
10Assets/Scripts/GameLobby/UI/ReadyCheckUI.cs
-
14Assets/Scripts/GameLobby/UI/RelayAddressUI.cs
-
47Assets/Scripts/GameLobby/UI/ShowWhenLobbyStateUI.cs
-
38Assets/Scripts/GameLobby/UI/SpinnerUI.cs
-
6Assets/Scripts/GameLobby/UI/StartLobbyButtonUI.cs
-
12Assets/Scripts/GameLobby/UI/UIPanelBase.cs
-
15Assets/Scripts/GameLobby/UI/UserNameUI.cs
-
46Assets/Scripts/GameLobby/UI/UserStateVisibilityUI.cs
-
71Assets/Scripts/GameLobby/UI/LobbyUserListUI.cs
-
109Assets/Scripts/GameLobby/UI/LobbyEntryUI.cs
-
40Assets/Scripts/GameLobby/Vivox/VivoxSetup.cs
-
2Assets/StreamingAssets.meta
-
26Packages/manifest.json
-
102Packages/packages-lock.json
-
9ProjectSettings/GraphicsSettings.asset
-
20ProjectSettings/PackageManagerSettings.asset
-
15ProjectSettings/ProjectSettings.asset
-
4ProjectSettings/ProjectVersion.txt
-
3ProjectSettings/QualitySettings.asset
-
2ProjectSettings/URPProjectSettings.asset
-
15README.md
-
2Assets/Scripts/GameLobby/Infrastructure/CallbackValue.cs.meta
-
112Assets/Prefabs/NGO/NetworkManager.prefab
-
7Assets/Prefabs/NGO/NetworkManager.prefab.meta
-
117Assets/Scripts/GameLobby/Auth/Auth.cs
-
42Assets/Scripts/GameLobby/Game/LocalLobbyList.cs
-
53Assets/Scripts/GameLobby/Game/LocalPlayer.cs
-
45Assets/Scripts/GameLobby/Infrastructure/CallbackValue.cs
-
611Assets/Scripts/GameLobby/Lobby/LobbyManager.cs
-
179Assets/Scripts/GameLobby/Lobby/LobbySynchronizer.cs
-
27Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs
-
11Assets/Scripts/GameLobby/Tests/PlayMode/AsyncTestHelper.cs.meta
-
71Assets/Scripts/GameLobby/UI/ColorLobbyUI.cs
-
27Assets/UniversalRenderPipelineGlobalSettings.asset
-
8Assets/UniversalRenderPipelineGlobalSettings.asset.meta
-
3Packages/ParrelSync.meta
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Threading.Tasks; |
|||
using LobbyRelaySample; |
|||
using Test.Tools; |
|||
using Unity.Services.Core; |
|||
namespace Test |
|||
{ |
|||
public class UtpTests |
|||
{ |
|||
private class RelayUtpTest : RelayUtpSetupHost |
|||
{ |
|||
public Action<NetworkEndPoint, bool> OnGetEndpoint { private get; set; } |
|||
|
|||
public void JoinRelayPublic() |
|||
{ |
|||
JoinRelay(); |
|||
} |
|||
|
|||
protected override void JoinRelay() |
|||
{ |
|||
RelayAPIInterface.AllocateAsync(1, OnAllocation); |
|||
|
|||
void OnAllocation(Allocation allocation) |
|||
{ |
|||
bool isSecure = false; |
|||
NetworkEndPoint endpoint = GetEndpointForAllocation(allocation.ServerEndpoints, out isSecure); |
|||
OnGetEndpoint?.Invoke(endpoint, isSecure); |
|||
// The allocation will be cleaned up automatically, since we won't be pinging it regularly.
|
|||
} |
|||
} |
|||
} |
|||
|
|||
private LobbyRelaySample.Auth.SubIdentity_Authentication m_auth; |
|||
GameObject m_dummy; |
|||
//Only used when testing DTLS
|
|||
#pragma warning disable CS0414 // This is the "assigned but its value is never used" warning, which will otherwise appear when DTLS is unavailable.
|
|||
private bool m_didSigninComplete = false; |
|||
#pragma warning restore CS0414
|
|||
|
|||
[OneTimeSetUp] |
|||
public void Setup() |
|||
{ |
|||
m_dummy = new GameObject(); |
|||
m_auth = new LobbyRelaySample.Auth.SubIdentity_Authentication(() => { m_didSigninComplete = true; }); |
|||
} |
|||
|
|||
[OneTimeTearDown] |
|||
public void Teardown() |
|||
{ |
|||
m_auth?.Dispose(); |
|||
GameObject.Destroy(m_dummy); |
|||
} |
|||
|
|||
[UnityTest] |
|||
public IEnumerator DTLSCheck() |
|||
{ |
|||
#if ENABLE_MANAGED_UNITYTLS
|
|||
|
|||
if (!m_didSigninComplete) |
|||
yield return new WaitForSeconds(3); |
|||
if (!m_didSigninComplete) |
|||
Assert.Fail("Did not sign in."); |
|||
yield return new WaitForSeconds(1); // To prevent a possible 429 after a previous test.
|
|||
|
|||
RelayUtpTest relaySetup = m_dummy.AddComponent<RelayUtpTest>(); |
|||
relaySetup.OnGetEndpoint = OnGetEndpoint; |
|||
bool? isSecure = null; |
|||
NetworkEndPoint endpoint = default; |
|||
|
|||
relaySetup.JoinRelayPublic(); |
|||
float timeout = 5; |
|||
while (!isSecure.HasValue && timeout > 0) |
|||
{ |
|||
timeout -= 0.25f; |
|||
yield return new WaitForSeconds(0.25f); |
|||
} |
|||
Component.Destroy(relaySetup); |
|||
Assert.IsTrue(timeout > 0, "Timeout check."); |
|||
|
|||
Assert.IsTrue(isSecure, "Should have a secure server endpoint."); |
|||
Assert.IsTrue(endpoint.IsValid, "Endpoint should be valid."); |
|||
|
|||
void OnGetEndpoint(NetworkEndPoint resultEndpoint, bool resultIsSecure) |
|||
{ |
|||
endpoint = resultEndpoint; |
|||
isSecure = resultIsSecure; |
|||
} |
|||
|
|||
#else
|
|||
|
|||
Assert.Ignore("DTLS encryption for Relay is not currently available for this version of Unity."); |
|||
yield break; |
|||
|
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
// namespace Test
|
|||
// {
|
|||
// public class UtpTests
|
|||
// {
|
|||
// class RelayUtpTest : RelayUtpSetupHost
|
|||
// {
|
|||
// public Action<NetworkEndPoint, bool> OnGetEndpoint { private get; set; }
|
|||
//
|
|||
// public void JoinRelayPublic()
|
|||
// {
|
|||
// JoinRelay();
|
|||
// }
|
|||
//
|
|||
// protected override void JoinRelay()
|
|||
// {
|
|||
// RelayAPIInterface.AllocateAsync(1, OnAllocation);
|
|||
//
|
|||
// void OnAllocation(Allocation allocation)
|
|||
// {
|
|||
// bool isSecure = false;
|
|||
// NetworkEndPoint endpoint = GetEndpointForAllocation(allocation.ServerEndpoints, allocation.RelayServer.IpV4, allocation.RelayServer.Port, out isSecure);
|
|||
// OnGetEndpoint?.Invoke(endpoint, isSecure);
|
|||
// // The allocation will be cleaned up automatically, since we won't be pinging it regularly.
|
|||
// }
|
|||
// }
|
|||
// }
|
|||
//
|
|||
// GameObject m_dummy;
|
|||
// //Only used when testing DTLS
|
|||
// #pragma warning disable CS0414 // This is the "assigned but its value is never used" warning, which will otherwise appear when DTLS is unavailable.
|
|||
// private bool m_didSigninComplete = false;
|
|||
// #pragma warning restore CS0414
|
|||
//
|
|||
// [OneTimeSetUp]
|
|||
// public void Setup()
|
|||
// {
|
|||
// m_dummy = new GameObject();
|
|||
// #pragma warning disable 4014
|
|||
// TestAuthSetup();
|
|||
// #pragma warning restore 4014
|
|||
// }
|
|||
//
|
|||
// async Task TestAuthSetup()
|
|||
// {
|
|||
// await Auth.Authenticate("test");
|
|||
// }
|
|||
//
|
|||
// [OneTimeTearDown]
|
|||
// public void Teardown()
|
|||
// {
|
|||
// GameObject.Destroy(m_dummy);
|
|||
// }
|
|||
//
|
|||
// [UnityTest]
|
|||
// public IEnumerator DTLSCheck()
|
|||
// {
|
|||
// #if ENABLE_MANAGED_UNITYTLS
|
|||
//
|
|||
// yield return AsyncTestHelper.Await(async () => await Auth.Authenticating());
|
|||
//
|
|||
//
|
|||
// RelayUtpTest relaySetup = m_dummy.AddComponent<RelayUtpTest>();
|
|||
// relaySetup.OnGetEndpoint = OnGetEndpoint;
|
|||
// bool? isSecure = null;
|
|||
// NetworkEndPoint endpoint = default;
|
|||
//
|
|||
// relaySetup.JoinRelayPublic();
|
|||
// float timeout = 5;
|
|||
// while (!isSecure.HasValue && timeout > 0)
|
|||
// {
|
|||
// timeout -= 0.25f;
|
|||
// yield return new WaitForSeconds(0.25f);
|
|||
// }
|
|||
// Component.Destroy(relaySetup);
|
|||
// Assert.IsTrue(timeout > 0, "Timeout check.");
|
|||
//
|
|||
// Assert.IsTrue(isSecure, "Should have a secure server endpoint.");
|
|||
// Assert.IsTrue(endpoint.IsValid, "Endpoint should be valid.");
|
|||
//
|
|||
// void OnGetEndpoint(NetworkEndPoint resultEndpoint, bool resultIsSecure)
|
|||
// {
|
|||
// endpoint = resultEndpoint;
|
|||
// isSecure = resultIsSecure;
|
|||
// }
|
|||
//
|
|||
// #else
|
|||
//
|
|||
// Assert.Ignore("DTLS encryption for Relay is not currently available for this version of Unity.");
|
|||
// yield break;
|
|||
//
|
|||
// #endif
|
|||
// }
|
|||
// }
|
|||
// }
|
|
|||
using UnityEngine; |
|||
|
|||
public class EndGameButtonUI : MonoBehaviour |
|||
public class EndGameButtonUI : UIPanelBase |
|||
Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame, null); |
|||
Manager.EndGame(); |
|||
} |
|||
} |
|||
} |
|
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample.UI |
|||
{ |
|||
using System; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample.UI |
|||
{ |
|||
/// </summary>
|
|||
public class ShowWhenLobbyStateUI : ObserverPanel<LocalLobby> |
|||
{ |
|||
[SerializeField] |
|||
private LobbyState m_ShowThisWhen; |
|||
|
|||
public override void ObservedUpdated(LocalLobby observed) |
|||
{ |
|||
if (m_ShowThisWhen.HasFlag(observed.State)) |
|||
Show(); |
|||
else |
|||
Hide(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// </summary>
|
|||
public class ShowWhenLobbyStateUI : UIPanelBase |
|||
{ |
|||
[SerializeField] |
|||
LobbyState m_ShowThisWhen; |
|||
|
|||
public void LobbyChanged(LobbyState lobbyState) |
|||
{ |
|||
if (m_ShowThisWhen.HasFlag(lobbyState)) |
|||
Show(); |
|||
else |
|||
Hide(); |
|||
} |
|||
|
|||
public override void Start() |
|||
{ |
|||
base.Start(); |
|||
Manager.LocalLobby.LocalLobbyState.onChanged += LobbyChanged; |
|||
} |
|||
|
|||
} |
|||
} |
|
|||
using TMPro; |
|||
using UnityEngine; |
|||
using UnityEngine.Events; |
|||
|
|||
namespace LobbyRelaySample.UI |
|||
{ |
|||
/// <summary>
|
|||
/// Controls an entry in the join menu's list of lobbies, acting as a clickable button as well as displaying info about the lobby.
|
|||
/// </summary>
|
|||
[RequireComponent(typeof(LocalLobbyObserver))] |
|||
public class LobbyButtonUI : MonoBehaviour |
|||
{ |
|||
[SerializeField] |
|||
TMP_Text lobbyNameText; |
|||
[SerializeField] |
|||
TMP_Text lobbyCountText; |
|||
|
|||
/// <summary>
|
|||
/// Subscribed to on instantiation to pass our lobby data back
|
|||
/// </summary>
|
|||
public UnityEvent<LocalLobby> onLobbyPressed; |
|||
LocalLobbyObserver m_DataObserver; |
|||
|
|||
void Awake() |
|||
{ |
|||
m_DataObserver = GetComponent<LocalLobbyObserver>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// UI CallBack
|
|||
/// </summary>
|
|||
public void OnLobbyClicked() |
|||
{ |
|||
onLobbyPressed?.Invoke(m_DataObserver.observed); |
|||
} |
|||
|
|||
public void UpdateLobby(LocalLobby lobby) |
|||
{ |
|||
m_DataObserver.observed.CopyObserved(lobby); |
|||
} |
|||
|
|||
public void OnLobbyUpdated(LocalLobby data) |
|||
{ |
|||
lobbyNameText.SetText(data.LobbyName); |
|||
lobbyCountText.SetText($"{data.PlayerCount}/{data.MaxPlayerCount}"); |
|||
} |
|||
} |
|||
} |
|||
using System; |
|||
using TMPro; |
|||
using UnityEngine; |
|||
using UnityEngine.Events; |
|||
|
|||
namespace LobbyRelaySample.UI |
|||
{ |
|||
/// <summary>
|
|||
/// Controls an entry in the join menu's list of lobbies, acting as a clickable button as well as displaying info about the lobby.
|
|||
/// </summary>
|
|||
public class LobbyEntryUI : MonoBehaviour |
|||
{ |
|||
[SerializeField] |
|||
ColorLobbyUI m_ColorLobbyUI; |
|||
[SerializeField] |
|||
TMP_Text lobbyNameText; |
|||
[SerializeField] |
|||
TMP_Text lobbyCountText; |
|||
|
|||
/// <summary>
|
|||
/// Subscribed to on instantiation to pass our lobby data back
|
|||
/// </summary>
|
|||
public UnityEvent<LocalLobby> onLobbyPressed; |
|||
LocalLobby m_Lobby; |
|||
|
|||
/// <summary>
|
|||
/// UI CallBack
|
|||
/// </summary>
|
|||
public void OnLobbyClicked() |
|||
{ |
|||
onLobbyPressed.Invoke(m_Lobby); |
|||
} |
|||
|
|||
public void SetLobby(LocalLobby lobby) |
|||
{ |
|||
m_Lobby = lobby; |
|||
SetLobbyname(m_Lobby.LobbyName.Value); |
|||
SetLobbyCount(m_Lobby.PlayerCount); |
|||
m_ColorLobbyUI.SetLobby(lobby); |
|||
m_Lobby.LobbyName.onChanged += SetLobbyname; |
|||
m_Lobby.onUserJoined += (_) => |
|||
{ |
|||
SetLobbyCount(m_Lobby.PlayerCount); |
|||
}; |
|||
m_Lobby.onUserLeft += (_) => |
|||
{ |
|||
SetLobbyCount(m_Lobby.PlayerCount); |
|||
}; |
|||
} |
|||
|
|||
void SetLobbyname(string lobbyName) |
|||
{ |
|||
lobbyNameText.SetText(m_Lobby.LobbyName.Value); |
|||
} |
|||
|
|||
void SetLobbyCount(int count) |
|||
{ |
|||
lobbyCountText.SetText($"{count}/{m_Lobby.MaxPlayerCount.Value}"); |
|||
} |
|||
} |
|||
} |
|
|||
m_EditorVersion: 2020.3.31f1 |
|||
m_EditorVersionWithRevision: 2020.3.31f1 (6b54b7616050) |
|||
m_EditorVersion: 2021.2.19f1 |
|||
m_EditorVersionWithRevision: 2021.2.19f1 (602ecdbb2fb0) |
|
|||
%YAML 1.1 |
|||
%TAG !u! tag:unity3d.com,2011: |
|||
--- !u!1 &5481270937743757918 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 5665493986528408842} |
|||
- component: {fileID: 5021303663353436182} |
|||
- component: {fileID: 1765689956051871792} |
|||
m_Layer: 0 |
|||
m_Name: NetworkManager |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!4 &5665493986528408842 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5481270937743757918} |
|||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} |
|||
m_LocalPosition: {x: 0, y: 52.01943, z: -6} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_ConstrainProportionsScale: 0 |
|||
m_Children: [] |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
--- !u!114 &5021303663353436182 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5481270937743757918} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
RunInBackground: 0 |
|||
LogLevel: 1 |
|||
NetworkConfig: |
|||
ProtocolVersion: 0 |
|||
NetworkTransport: {fileID: 1765689956051871792} |
|||
PlayerPrefab: {fileID: 0} |
|||
NetworkPrefabs: |
|||
- Override: 0 |
|||
Prefab: {fileID: 3227847727972158006, guid: 905594b4ee5bb864a84af916cc445d1b, type: 3} |
|||
SourcePrefabToOverride: {fileID: 0} |
|||
SourceHashToOverride: 0 |
|||
OverridingTargetPrefab: {fileID: 0} |
|||
- Override: 0 |
|||
Prefab: {fileID: 5240148789413552765, guid: f42ed38d10b57ec48870f76a7a63389e, type: 3} |
|||
SourcePrefabToOverride: {fileID: 0} |
|||
SourceHashToOverride: 0 |
|||
OverridingTargetPrefab: {fileID: 0} |
|||
- Override: 0 |
|||
Prefab: {fileID: 8828823320646980938, guid: e371ca3112f9e244ab574b472387b64b, type: 3} |
|||
SourcePrefabToOverride: {fileID: 0} |
|||
SourceHashToOverride: 0 |
|||
OverridingTargetPrefab: {fileID: 0} |
|||
TickRate: 30 |
|||
ClientConnectionBufferTimeout: 10 |
|||
ConnectionApproval: 0 |
|||
ConnectionData: |
|||
EnableTimeResync: 0 |
|||
TimeResyncInterval: 30 |
|||
EnsureNetworkVariableLengthSafety: 0 |
|||
EnableSceneManagement: 1 |
|||
ForceSamePrefabs: 1 |
|||
RecycleNetworkIds: 1 |
|||
NetworkIdRecycleDelay: 120 |
|||
RpcHashSize: 0 |
|||
LoadSceneTimeOut: 120 |
|||
SpawnTimeout: 1 |
|||
EnableNetworkLogs: 1 |
|||
--- !u!114 &1765689956051871792 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5481270937743757918} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_ProtocolType: 1 |
|||
m_MaxPacketQueueSize: 512 |
|||
m_MaxPayloadSize: 6144 |
|||
m_MaxSendQueueSize: 98304 |
|||
m_HeartbeatTimeoutMS: 500 |
|||
m_ConnectTimeoutMS: 1000 |
|||
m_MaxConnectAttempts: 60 |
|||
m_DisconnectTimeoutMS: 30000 |
|||
ConnectionData: |
|||
Address: 127.0.0.1 |
|||
Port: 7777 |
|||
ServerListenAddress: |
|||
DebugSimulator: |
|||
PacketDelayMS: 0 |
|||
PacketJitterMS: 0 |
|||
PacketDropRate: 0 |
|
|||
fileFormatVersion: 2 |
|||
guid: b963f71e4874d4066bc72b9224e3ffce |
|||
PrefabImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Threading.Tasks; |
|||
using Unity.Services.Authentication; |
|||
using Unity.Services.Core; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
public enum AuthState |
|||
{ |
|||
Initialized, |
|||
Authenticating, |
|||
Authenticated, |
|||
Error, |
|||
TimedOut |
|||
} |
|||
|
|||
public static class Auth |
|||
{ |
|||
public static AuthState AuthenticationState { get; private set; } = AuthState.Initialized; |
|||
|
|||
public static async Task<AuthState> Authenticate(string profile,int tries = 5) |
|||
{ |
|||
//If we are already authenticated, just return Auth
|
|||
if (AuthenticationState == AuthState.Authenticated) |
|||
{ |
|||
return AuthenticationState; |
|||
} |
|||
|
|||
if (AuthenticationState == AuthState.Authenticating) |
|||
{ |
|||
Debug.LogWarning("Cant Authenticate if we are authenticating or authenticated"); |
|||
await Authenticating(); |
|||
return AuthenticationState; |
|||
} |
|||
|
|||
var profileOptions = new InitializationOptions(); |
|||
profileOptions.SetProfile(profile); |
|||
await UnityServices.InitializeAsync(profileOptions); |
|||
await SignInAnonymouslyAsync(tries); |
|||
|
|||
return AuthenticationState; |
|||
} |
|||
|
|||
//Awaitable task that will pass the clientID once authentication is done.
|
|||
public static string ID() |
|||
{ |
|||
return AuthenticationService.Instance.PlayerId; |
|||
} |
|||
|
|||
//Awaitable task that will pass once authentication is done.
|
|||
public static async Task<AuthState> Authenticating() |
|||
{ |
|||
while (AuthenticationState == AuthState.Authenticating || AuthenticationState == AuthState.Initialized) |
|||
{ |
|||
await Task.Delay(200); |
|||
} |
|||
|
|||
return AuthenticationState; |
|||
} |
|||
|
|||
public static bool DoneAuthenticating() |
|||
{ |
|||
return AuthenticationState != AuthState.Authenticating && |
|||
AuthenticationState != AuthState.Initialized; |
|||
} |
|||
|
|||
static async Task SignInAnonymouslyAsync(int maxRetries) |
|||
{ |
|||
AuthenticationState = AuthState.Authenticating; |
|||
var tries = 0; |
|||
while (AuthenticationState == AuthState.Authenticating && tries < maxRetries) |
|||
{ |
|||
try |
|||
{ |
|||
|
|||
//To ensure staging login vs non staging
|
|||
await AuthenticationService.Instance.SignInAnonymouslyAsync(); |
|||
|
|||
if (AuthenticationService.Instance.IsSignedIn && AuthenticationService.Instance.IsAuthorized) |
|||
{ |
|||
AuthenticationState = AuthState.Authenticated; |
|||
break; |
|||
} |
|||
} |
|||
catch (AuthenticationException ex) |
|||
{ |
|||
// Compare error code to AuthenticationErrorCodes
|
|||
// Notify the player with the proper error message
|
|||
Debug.LogError(ex); |
|||
AuthenticationState = AuthState.Error; |
|||
} |
|||
catch (RequestFailedException exception) |
|||
{ |
|||
// Compare error code to CommonErrorCodes
|
|||
// Notify the player with the proper error message
|
|||
Debug.LogError(exception); |
|||
AuthenticationState = AuthState.Error; |
|||
} |
|||
|
|||
tries++; |
|||
await Task.Delay(1000); |
|||
} |
|||
|
|||
if (AuthenticationState != AuthState.Authenticated) |
|||
{ |
|||
Debug.LogWarning($"Player was not signed in successfully after {tries} attempts"); |
|||
AuthenticationState = AuthState.TimedOut; |
|||
} |
|||
} |
|||
|
|||
public static void SignOut() |
|||
{ |
|||
AuthenticationService.Instance.SignOut(false); |
|||
AuthenticationState = AuthState.Initialized; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
/// <summary>
|
|||
/// Used when displaying the lobby list, to indicate when we are awaiting an updated lobby query.
|
|||
/// </summary>
|
|||
public enum LobbyQueryState |
|||
{ |
|||
Empty, |
|||
Fetching, |
|||
Error, |
|||
Fetched |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Holds data related to the Lobby service itself - The latest retrieved lobby list, the state of retrieval.
|
|||
/// </summary>
|
|||
[System.Serializable] |
|||
public class LocalLobbyList |
|||
{ |
|||
public CallbackValue<LobbyQueryState> QueryState = new CallbackValue<LobbyQueryState>(); |
|||
|
|||
public Action<Dictionary<string, LocalLobby>> onLobbyListChange; |
|||
Dictionary<string, LocalLobby> m_currentLobbies = new Dictionary<string, LocalLobby>(); |
|||
|
|||
/// <summary>
|
|||
/// Maps from a lobby's ID to the local representation of it. This allows us to remember which remote lobbies are which LocalLobbies.
|
|||
/// Will only trigger if the dictionary is set wholesale. Changes in the size or contents will not trigger OnChanged.
|
|||
/// </summary>
|
|||
public Dictionary<string, LocalLobby> CurrentLobbies |
|||
{ |
|||
get { return m_currentLobbies; } |
|||
set |
|||
{ |
|||
m_currentLobbies = value; |
|||
onLobbyListChange?.Invoke(m_currentLobbies); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
/// <summary>
|
|||
/// Current state of the user in the lobby.
|
|||
/// This is a Flags enum to allow for the Inspector to select multiples for various UI features.
|
|||
/// </summary>
|
|||
[Flags] |
|||
public enum PlayerStatus |
|||
{ |
|||
None = 0, |
|||
Connecting = 1, // User has joined a lobby but has not yet connected to Relay.
|
|||
Lobby = 2, // User is in a lobby and connected to Relay.
|
|||
Ready = 4, // User has selected the ready button, to ready for the "game" to start.
|
|||
InGame = 8, // User is part of a "game" that has started.
|
|||
Menu = 16 // User is not in a lobby, in one of the main menus.
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Data for a local player instance. This will update data and is observed to know when to push local player changes to the entire lobby.
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public class LocalPlayer |
|||
{ |
|||
public CallbackValue<bool> IsHost = new CallbackValue<bool>(false); |
|||
public CallbackValue<string> DisplayName = new CallbackValue<string>(""); |
|||
public CallbackValue<EmoteType> Emote = new CallbackValue<EmoteType>(EmoteType.None); |
|||
public CallbackValue<PlayerStatus> UserStatus = new CallbackValue<PlayerStatus>((PlayerStatus)0); |
|||
public CallbackValue<string> ID = new CallbackValue<string>(""); |
|||
public CallbackValue<int> Index = new CallbackValue<int>(0); |
|||
|
|||
public DateTime LastUpdated; |
|||
|
|||
public LocalPlayer(string id, int index, bool isHost, string displayName = default, |
|||
EmoteType emote = default, PlayerStatus status = default) |
|||
{ |
|||
ID.Value = id; |
|||
IsHost.Value = isHost; |
|||
Index.Value = index; |
|||
DisplayName.Value = displayName; |
|||
Emote.Value = emote; |
|||
UserStatus.Value = status; |
|||
} |
|||
|
|||
public void ResetState() |
|||
{ |
|||
IsHost.Value = false; |
|||
Emote.Value = EmoteType.None; |
|||
UserStatus.Value = PlayerStatus.Menu; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
public class CallbackValue<T> |
|||
{ |
|||
public Action<T> onChanged; |
|||
|
|||
|
|||
public CallbackValue() |
|||
{ |
|||
|
|||
} |
|||
public CallbackValue(T cachedValue) |
|||
{ |
|||
m_CachedValue = cachedValue; |
|||
} |
|||
|
|||
public T Value |
|||
{ |
|||
get => m_CachedValue; |
|||
set |
|||
{ |
|||
if (m_CachedValue!=null&&m_CachedValue.Equals(value)) |
|||
return; |
|||
m_CachedValue = value; |
|||
onChanged?.Invoke(m_CachedValue); |
|||
} |
|||
} |
|||
|
|||
public void ForceSet(T value) |
|||
{ |
|||
m_CachedValue = value; |
|||
onChanged?.Invoke(m_CachedValue); |
|||
} |
|||
|
|||
public void SetNoCallback(T value) |
|||
{ |
|||
m_CachedValue = value; |
|||
} |
|||
|
|||
T m_CachedValue = default; |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Unity.Services.Authentication; |
|||
using Unity.Services.Lobbies; |
|||
using Unity.Services.Lobbies.Models; |
|||
using UnityEngine; |
|||
|
|||
namespace LobbyRelaySample |
|||
{ |
|||
/// <summary>
|
|||
/// An abstraction layer between the direct calls into the Lobby API and the outcomes you actually want. E.g. you can request to get a readable list of
|
|||
/// current lobbies and not need to make the query call directly.
|
|||
/// </summary>
|
|||
///
|
|||
/// Manages one Lobby at a time, Only entry points to a lobby with ID is via JoinAsync, CreateAsync, and QuickJoinAsync
|
|||
public class LobbyManager : IDisposable |
|||
{ |
|||
const string key_RelayCode = nameof(LocalLobby.RelayCode); |
|||
const string key_LobbyState = nameof(LocalLobby.LocalLobbyState); |
|||
const string key_LobbyColor = nameof(LocalLobby.LocalLobbyColor); |
|||
|
|||
const string key_Displayname = nameof(LocalPlayer.DisplayName); |
|||
const string key_Userstatus = nameof(LocalPlayer.UserStatus); |
|||
const string key_Emote = nameof(LocalPlayer.Emote); |
|||
|
|||
//Once connected to a lobby, cache the local lobby object so we don't query for it for every lobby operation.
|
|||
// (This assumes that the game will be actively in just one lobby at a time, though they could be in more on the service side.)
|
|||
|
|||
public Lobby CurrentLobby => m_CurrentLobby; |
|||
Lobby m_CurrentLobby; |
|||
LobbyEventCallbacks m_LobbyEventCallbacks = new LobbyEventCallbacks(); |
|||
const int |
|||
k_maxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters.
|
|||
|
|||
Task m_HeartBeatTask; |
|||
|
|||
#region Rate Limiting
|
|||
|
|||
public enum RequestType |
|||
{ |
|||
Query = 0, |
|||
Join, |
|||
QuickJoin, |
|||
Host |
|||
} |
|||
|
|||
public bool InLobby() |
|||
{ |
|||
if (m_CurrentLobby == null) |
|||
{ |
|||
Debug.LogWarning("LobbyManager not currently in a lobby. Did you CreateLobbyAsync or JoinLobbyAsync?"); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public ServiceRateLimiter GetRateLimit(RequestType type) |
|||
{ |
|||
if (type == RequestType.Join) |
|||
return m_JoinCooldown; |
|||
else if (type == RequestType.QuickJoin) |
|||
return m_QuickJoinCooldown; |
|||
else if (type == RequestType.Host) |
|||
return m_CreateCooldown; |
|||
return m_QueryCooldown; |
|||
} |
|||
|
|||
// Rate Limits are posted here: https://docs.unity.com/lobby/rate-limits.html
|
|||
|
|||
ServiceRateLimiter m_QueryCooldown = new ServiceRateLimiter(1, 1f); |
|||
ServiceRateLimiter m_CreateCooldown = new ServiceRateLimiter(2, 6f); |
|||
ServiceRateLimiter m_JoinCooldown = new ServiceRateLimiter(2, 6f); |
|||
ServiceRateLimiter m_QuickJoinCooldown = new ServiceRateLimiter(1, 10f); |
|||
ServiceRateLimiter m_GetLobbyCooldown = new ServiceRateLimiter(1, 1f); |
|||
ServiceRateLimiter m_DeleteLobbyCooldown = new ServiceRateLimiter(2, 1f); |
|||
ServiceRateLimiter m_UpdateLobbyCooldown = new ServiceRateLimiter(5, 5f); |
|||
ServiceRateLimiter m_UpdatePlayerCooldown = new ServiceRateLimiter(5, 5f); |
|||
ServiceRateLimiter m_LeaveLobbyOrRemovePlayer = new ServiceRateLimiter(5, 1); |
|||
ServiceRateLimiter m_HeartBeatCooldown = new ServiceRateLimiter(5, 30); |
|||
|
|||
#endregion
|
|||
|
|||
Dictionary<string, PlayerDataObject> CreateInitialPlayerData(LocalPlayer user) |
|||
{ |
|||
Dictionary<string, PlayerDataObject> data = new Dictionary<string, PlayerDataObject>(); |
|||
|
|||
var displayNameObject = |
|||
new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, user.DisplayName.Value); |
|||
data.Add("DisplayName", displayNameObject); |
|||
return data; |
|||
} |
|||
|
|||
public async Task<Lobby> CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, |
|||
LocalPlayer localUser) |
|||
{ |
|||
if (m_CreateCooldown.IsCoolingDown) |
|||
{ |
|||
Debug.LogWarning("Create Lobby hit the rate limit."); |
|||
return null; |
|||
} |
|||
|
|||
await m_CreateCooldown.QueueUntilCooldown(); |
|||
|
|||
try |
|||
{ |
|||
string uasId = AuthenticationService.Instance.PlayerId; |
|||
|
|||
CreateLobbyOptions createOptions = new CreateLobbyOptions |
|||
{ |
|||
IsPrivate = isPrivate, |
|||
Player = new Player(id: uasId, data: CreateInitialPlayerData(localUser)) |
|||
}; |
|||
m_CurrentLobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions); |
|||
StartHeartBeat(); |
|||
|
|||
return m_CurrentLobby; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Debug.LogError($"Lobby Create failed:\n{ex}"); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public async Task<Lobby> JoinLobbyAsync(string lobbyId, string lobbyCode, LocalPlayer localUser) |
|||
{ |
|||
if (m_JoinCooldown.IsCoolingDown || |
|||
(lobbyId == null && lobbyCode == null)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
await m_JoinCooldown.QueueUntilCooldown(); |
|||
|
|||
string uasId = AuthenticationService.Instance.PlayerId; |
|||
var playerData = CreateInitialPlayerData(localUser); |
|||
|
|||
if (!string.IsNullOrEmpty(lobbyId)) |
|||
{ |
|||
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions |
|||
{ Player = new Player(id: uasId, data: playerData) }; |
|||
m_CurrentLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions); |
|||
} |
|||
else |
|||
{ |
|||
JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions |
|||
{ Player = new Player(id: uasId, data: playerData) }; |
|||
m_CurrentLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions); |
|||
} |
|||
|
|||
return m_CurrentLobby; |
|||
} |
|||
|
|||
public async Task<Lobby> QuickJoinLobbyAsync(LocalPlayer localUser, LobbyColor limitToColor = LobbyColor.None) |
|||
{ |
|||
//We dont want to queue a quickjoin
|
|||
if (m_QuickJoinCooldown.IsCoolingDown) |
|||
{ |
|||
UnityEngine.Debug.LogWarning("Quick Join Lobby hit the rate limit."); |
|||
return null; |
|||
} |
|||
|
|||
await m_QuickJoinCooldown.QueueUntilCooldown(); |
|||
var filters = LobbyColorToFilters(limitToColor); |
|||
string uasId = AuthenticationService.Instance.PlayerId; |
|||
|
|||
var joinRequest = new QuickJoinLobbyOptions |
|||
{ |
|||
Filter = filters, |
|||
Player = new Player(id: uasId, data: CreateInitialPlayerData(localUser)) |
|||
}; |
|||
|
|||
return m_CurrentLobby = await LobbyService.Instance.QuickJoinLobbyAsync(joinRequest); |
|||
} |
|||
|
|||
public async Task<QueryResponse> RetrieveLobbyListAsync(LobbyColor limitToColor = LobbyColor.None) |
|||
{ |
|||
var filters = LobbyColorToFilters(limitToColor); |
|||
|
|||
if (m_QueryCooldown.TaskQueued) |
|||
return null; |
|||
await m_QueryCooldown.QueueUntilCooldown(); |
|||
|
|||
QueryLobbiesOptions queryOptions = new QueryLobbiesOptions |
|||
{ |
|||
Count = k_maxLobbiesToShow, |
|||
Filters = filters |
|||
}; |
|||
|
|||
return await LobbyService.Instance.QueryLobbiesAsync(queryOptions); |
|||
} |
|||
|
|||
public async Task BindLocalLobbyToRemote(string lobbyID, LocalLobby localLobby) |
|||
{ |
|||
m_LobbyEventCallbacks.LobbyChanged += async changes => |
|||
{ |
|||
if (changes.LobbyDeleted) |
|||
{ |
|||
await LeaveLobbyAsync(); |
|||
return; |
|||
} |
|||
|
|||
//Lobby Fields
|
|||
if (changes.Name.Changed) |
|||
localLobby.LobbyName.Value = changes.Name.Value; |
|||
if (changes.HostId.Changed) |
|||
localLobby.HostID.Value = changes.HostId.Value; |
|||
if (changes.IsPrivate.Changed) |
|||
localLobby.Private.Value = changes.IsPrivate.Value; |
|||
if (changes.IsLocked.Changed) |
|||
localLobby.Locked.Value = changes.IsLocked.Value; |
|||
if (changes.AvailableSlots.Changed) |
|||
localLobby.AvailableSlots.Value = changes.AvailableSlots.Value; |
|||
if (changes.MaxPlayers.Changed) |
|||
localLobby.MaxPlayerCount.Value = changes.MaxPlayers.Value; |
|||
|
|||
if (changes.LastUpdated.Changed) |
|||
localLobby.LastUpdated.Value = changes.LastUpdated.Value.ToFileTimeUtc(); |
|||
|
|||
//Custom Lobby Fields
|
|||
if (changes.Data.Changed) |
|||
LobbyChanged(); |
|||
|
|||
if (changes.PlayerJoined.Changed) |
|||
PlayersJoined(); |
|||
|
|||
if (changes.PlayerLeft.Changed) |
|||
PlayersLeft(); |
|||
|
|||
if (changes.PlayerData.Changed) |
|||
PlayerDataChanged(); |
|||
|
|||
void LobbyChanged() |
|||
{ |
|||
foreach (var change in changes.Data.Value) |
|||
{ |
|||
var changedValue = change.Value; |
|||
var changedKey = change.Key; |
|||
|
|||
if (changedValue.Removed) |
|||
{ |
|||
RemoveCustomLobbyData(changedKey); |
|||
} |
|||
|
|||
if (changedValue.Changed) |
|||
{ |
|||
ParseCustomLobbyData(changedKey, changedValue.Value); |
|||
} |
|||
} |
|||
|
|||
void ParseCustomLobbyData(string changedKey, DataObject playerDataObject) |
|||
{ |
|||
if (changedKey == key_RelayCode) |
|||
localLobby.RelayCode.Value = playerDataObject.Value; |
|||
|
|||
if (changedKey == key_LobbyState) |
|||
localLobby.LocalLobbyState.Value = (LobbyState)int.Parse(playerDataObject.Value); |
|||
|
|||
if (changedKey == key_LobbyColor) |
|||
localLobby.LocalLobbyColor.Value = (LobbyColor)int.Parse(playerDataObject.Value); |
|||
} |
|||
|
|||
void RemoveCustomLobbyData(string changedKey) |
|||
{ |
|||
if (changedKey == key_RelayCode) |
|||
localLobby.RelayCode.Value = ""; |
|||
} |
|||
} |
|||
|
|||
void PlayersJoined() |
|||
{ |
|||
foreach (var playerChanges in changes.PlayerJoined.Value) |
|||
{ |
|||
Player joinedPlayer = playerChanges.Player; |
|||
|
|||
var id = joinedPlayer.Id; |
|||
var index = playerChanges.PlayerIndex; |
|||
var isHost = localLobby.HostID.Value == id; |
|||
|
|||
var newPlayer = new LocalPlayer(id, index, isHost); |
|||
|
|||
foreach (var dataEntry in joinedPlayer.Data) |
|||
{ |
|||
var dataObject = dataEntry.Value; |
|||
ParseCustomPlayerData(newPlayer, dataEntry.Key, dataObject.Value); |
|||
} |
|||
|
|||
localLobby.AddPlayer(index, newPlayer); |
|||
} |
|||
} |
|||
|
|||
void PlayersLeft() |
|||
{ |
|||
foreach (var leftPlayerIndex in changes.PlayerLeft.Value) |
|||
{ |
|||
localLobby.RemovePlayer(leftPlayerIndex); |
|||
} |
|||
} |
|||
|
|||
void PlayerDataChanged() |
|||
{ |
|||
foreach (var lobbyPlayerChanges in changes.PlayerData.Value) |
|||
{ |
|||
var playerIndex = lobbyPlayerChanges.Key; |
|||
var localPlayer = localLobby.GetLocalPlayer(playerIndex); |
|||
if (localPlayer == null) |
|||
continue; |
|||
var playerChanges = lobbyPlayerChanges.Value; |
|||
if (playerChanges.ConnectionInfoChanged.Changed) |
|||
{ |
|||
var connectionInfo = playerChanges.ConnectionInfoChanged.Value; |
|||
Debug.Log( |
|||
$"ConnectionInfo for player {playerIndex} changed to {connectionInfo}"); |
|||
} |
|||
|
|||
if (playerChanges.LastUpdatedChanged.Changed) { } |
|||
|
|||
//There are changes on the Player
|
|||
if (playerChanges.ChangedData.Changed) |
|||
{ |
|||
foreach (var playerChange in playerChanges.ChangedData.Value) |
|||
{ |
|||
var changedValue = playerChange.Value; |
|||
|
|||
//There are changes on some of the changes in the player list of changes
|
|||
|
|||
if (changedValue.Changed) |
|||
{ |
|||
if (changedValue.Removed) |
|||
{ |
|||
Debug.LogWarning("This Sample does not remove Player Values currently."); |
|||
continue; |
|||
} |
|||
|
|||
var playerDataObject = changedValue.Value; |
|||
ParseCustomPlayerData(localPlayer, playerChange.Key, playerDataObject.Value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
m_LobbyEventCallbacks.LobbyEventConnectionStateChanged += lobbyEventConnectionState => |
|||
{ |
|||
Debug.Log($"Lobby ConnectionState Changed to {lobbyEventConnectionState}"); |
|||
}; |
|||
|
|||
m_LobbyEventCallbacks.KickedFromLobby += () => |
|||
{ |
|||
Debug.Log("Left Lobby"); |
|||
Dispose(); |
|||
}; |
|||
await LobbyService.Instance.SubscribeToLobbyEventsAsync(lobbyID, m_LobbyEventCallbacks); |
|||
} |
|||
|
|||
void ParseCustomPlayerData(LocalPlayer player, string dataKey, string playerDataValue) |
|||
{ |
|||
if (dataKey == key_Emote) |
|||
player.Emote.Value = (EmoteType)int.Parse(playerDataValue); |
|||
else if (dataKey == key_Userstatus) |
|||
player.UserStatus.Value = (PlayerStatus)int.Parse(playerDataValue); |
|||
else if (dataKey == key_Displayname) |
|||
player.DisplayName.Value = playerDataValue; |
|||
} |
|||
|
|||
public async Task<Lobby> GetLobbyAsync(string lobbyId = null) |
|||
{ |
|||
if (!InLobby()) |
|||
return null; |
|||
await m_GetLobbyCooldown.QueueUntilCooldown(); |
|||
lobbyId ??= m_CurrentLobby.Id; |
|||
return m_CurrentLobby = await LobbyService.Instance.GetLobbyAsync(lobbyId); |
|||
} |
|||
|
|||
public async Task LeaveLobbyAsync() |
|||
{ |
|||
await m_LeaveLobbyOrRemovePlayer.QueueUntilCooldown(); |
|||
if (!InLobby()) |
|||
return; |
|||
string playerId = AuthenticationService.Instance.PlayerId; |
|||
|
|||
await LobbyService.Instance.RemovePlayerAsync(m_CurrentLobby.Id, playerId); |
|||
m_CurrentLobby = null; |
|||
} |
|||
|
|||
public async Task UpdatePlayerDataAsync(Dictionary<string, string> data) |
|||
{ |
|||
if (!InLobby()) |
|||
return; |
|||
|
|||
string playerId = AuthenticationService.Instance.PlayerId; |
|||
Dictionary<string, PlayerDataObject> dataCurr = new Dictionary<string, PlayerDataObject>(); |
|||
foreach (var dataNew in data) |
|||
{ |
|||
PlayerDataObject dataObj = new PlayerDataObject(visibility: PlayerDataObject.VisibilityOptions.Member, |
|||
value: dataNew.Value); |
|||
if (dataCurr.ContainsKey(dataNew.Key)) |
|||
dataCurr[dataNew.Key] = dataObj; |
|||
else |
|||
dataCurr.Add(dataNew.Key, dataObj); |
|||
} |
|||
|
|||
if (m_UpdatePlayerCooldown.TaskQueued) |
|||
return; |
|||
await m_UpdatePlayerCooldown.QueueUntilCooldown(); |
|||
|
|||
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions |
|||
{ |
|||
Data = dataCurr, |
|||
AllocationId = null, |
|||
ConnectionInfo = null |
|||
}; |
|||
m_CurrentLobby = await LobbyService.Instance.UpdatePlayerAsync(m_CurrentLobby.Id, playerId, updateOptions); |
|||
} |
|||
|
|||
public async Task UpdatePlayerRelayInfoAsync(string lobbyID, string allocationId, string connectionInfo) |
|||
{ |
|||
if (!InLobby()) |
|||
return; |
|||
|
|||
string playerId = AuthenticationService.Instance.PlayerId; |
|||
|
|||
if (m_UpdatePlayerCooldown.TaskQueued) |
|||
return; |
|||
await m_UpdatePlayerCooldown.QueueUntilCooldown(); |
|||
|
|||
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions |
|||
{ |
|||
Data = new Dictionary<string, PlayerDataObject>(), |
|||
AllocationId = allocationId, |
|||
ConnectionInfo = connectionInfo |
|||
}; |
|||
m_CurrentLobby = await LobbyService.Instance.UpdatePlayerAsync(lobbyID, playerId, updateOptions); |
|||
} |
|||
|
|||
public async Task UpdateLobbyDataAsync(Dictionary<string, string> data) |
|||
{ |
|||
if (!InLobby()) |
|||
return; |
|||
|
|||
Dictionary<string, DataObject> dataCurr = m_CurrentLobby.Data ?? new Dictionary<string, DataObject>(); |
|||
|
|||
var shouldLock = false; |
|||
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 == "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.
|
|||
if (dataCurr.ContainsKey(dataNew.Key)) |
|||
dataCurr[dataNew.Key] = dataObj; |
|||
else |
|||
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" LocalLobbyState
|
|||
if (dataNew.Key == "LocalLobbyState") |
|||
{ |
|||
Enum.TryParse(dataNew.Value, out LobbyState lobbyState); |
|||
shouldLock = lobbyState != LobbyState.Lobby; |
|||
} |
|||
} |
|||
|
|||
//We can still update the latest data to send to the service, but we will not send multiple UpdateLobbySyncCalls
|
|||
if (m_UpdateLobbyCooldown.TaskQueued) |
|||
return; |
|||
await m_UpdateLobbyCooldown.QueueUntilCooldown(); |
|||
|
|||
UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = dataCurr, IsLocked = shouldLock }; |
|||
m_CurrentLobby = await LobbyService.Instance.UpdateLobbyAsync(m_CurrentLobby.Id, updateOptions); |
|||
} |
|||
|
|||
public async Task DeleteLobbyAsync() |
|||
{ |
|||
if (!InLobby()) |
|||
return; |
|||
await m_DeleteLobbyCooldown.QueueUntilCooldown(); |
|||
|
|||
await LobbyService.Instance.DeleteLobbyAsync(m_CurrentLobby.Id); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
m_CurrentLobby = null; |
|||
m_LobbyEventCallbacks = new LobbyEventCallbacks(); |
|||
} |
|||
|
|||
#region HeartBeat
|
|||
|
|||
List<QueryFilter> LobbyColorToFilters(LobbyColor limitToColor) |
|||
{ |
|||
List<QueryFilter> filters = new List<QueryFilter>(); |
|||
if (limitToColor == LobbyColor.Orange) |
|||
filters.Add(new QueryFilter(QueryFilter.FieldOptions.N1, ((int)LobbyColor.Orange).ToString(), |
|||
QueryFilter.OpOptions.EQ)); |
|||
else if (limitToColor == LobbyColor.Green) |
|||
filters.Add(new QueryFilter(QueryFilter.FieldOptions.N1, ((int)LobbyColor.Green).ToString(), |
|||
QueryFilter.OpOptions.EQ)); |
|||
else if (limitToColor == LobbyColor.Blue) |
|||
filters.Add(new QueryFilter(QueryFilter.FieldOptions.N1, ((int)LobbyColor.Blue).ToString(), |
|||
QueryFilter.OpOptions.EQ)); |
|||
return filters; |
|||
} |
|||
|
|||
//Since the LobbyManager maintains the "connection" to the lobby, we will continue to heartbeat until host leaves.
|
|||
async Task SendHeartbeatPingAsync() |
|||
{ |
|||
if (!InLobby()) |
|||
return; |
|||
if (m_HeartBeatCooldown.IsCoolingDown) |
|||
return; |
|||
await m_HeartBeatCooldown.QueueUntilCooldown(); |
|||
|
|||
await LobbyService.Instance.SendHeartbeatPingAsync(m_CurrentLobby.Id); |
|||
} |
|||
|
|||
void StartHeartBeat() |
|||
{ |
|||
#pragma warning disable 4014
|
|||
m_HeartBeatTask = HeartBeatLoop(); |
|||
#pragma warning restore 4014
|
|||
} |
|||
|
|||
async Task HeartBeatLoop() |
|||
{ |
|||
while (m_CurrentLobby != null) |
|||
{ |
|||
await SendHeartbeatPingAsync(); |
|||
await Task.Delay(8000); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
|
|||
//Manages the Amount of times you can hit a service call.
|
|||
//Adds a buffer to account for ping times.
|
|||
//Will Queue the latest overflow task for when the cooldown ends.
|
|||
//Created to mimic the way rate limits are implemented Here: https://docs.unity.com/lobby/rate-limits.html
|
|||
public class ServiceRateLimiter |
|||
{ |
|||
public Action<bool> onCooldownChange; |
|||
public readonly int coolDownMS; |
|||
public bool TaskQueued { get; private set; } = false; |
|||
|
|||
readonly int m_ServiceCallTimes; |
|||
bool m_CoolingDown = false; |
|||
int m_TaskCounter; |
|||
|
|||
//(If you're still getting rate limit errors, try increasing the pingBuffer)
|
|||
public ServiceRateLimiter(int callTimes, float coolDown, int pingBuffer = 100) |
|||
{ |
|||
m_ServiceCallTimes = callTimes; |
|||
m_TaskCounter = m_ServiceCallTimes; |
|||
coolDownMS = |
|||
Mathf.CeilToInt(coolDown * 1000) + |
|||
pingBuffer; |
|||
} |
|||
|
|||
public async Task QueueUntilCooldown() |
|||
{ |
|||
if (!m_CoolingDown) |
|||
{ |
|||
#pragma warning disable 4014
|
|||
ParallelCooldownAsync(); |
|||
#pragma warning restore 4014
|
|||
} |
|||
|
|||
m_TaskCounter--; |
|||
|
|||
if (m_TaskCounter > 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!TaskQueued) |
|||
TaskQueued = true; |
|||
else |
|||
return; |
|||
|
|||
while (m_CoolingDown) |
|||
{ |
|||
await Task.Delay(10); |
|||
} |
|||
} |
|||
|
|||
async Task ParallelCooldownAsync() |
|||
{ |
|||
IsCoolingDown = true; |
|||
await Task.Delay(coolDownMS); |
|||
IsCoolingDown = false; |
|||
TaskQueued = false; |
|||
m_TaskCounter = m_ServiceCallTimes; |
|||
} |
|||
|
|||
public bool IsCoolingDown |
|||
{ |
|||
get => m_CoolingDown; |
|||
private set |
|||
{ |
|||
if (m_CoolingDown != value) |
|||
{ |
|||
m_CoolingDown = value; |
|||
onCooldownChange?.Invoke(m_CoolingDown); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using LobbyRelaySample.lobby; |
|||
using Unity.Services.Lobbies.Models; |
|||
using UnityEngine; |
|||
|
|||
// namespace LobbyRelaySample
|
|||
// {
|
|||
// /// <summary>
|
|||
// /// Keep updated on changes to a joined lobby, at a speed compliant with Lobby's rate limiting.
|
|||
// /// </summary>
|
|||
// public class LobbySynchronizer : IDisposable
|
|||
// {
|
|||
// LocalLobby m_LocalLobby;
|
|||
// LocalPlayer m_LocalUser;
|
|||
// LobbyManager m_LobbyManager;
|
|||
// bool m_LocalChanges = false;
|
|||
//
|
|||
// const int k_approvalMaxMS = 10000; // Used for determining if a user should timeout if they are unable to connect.
|
|||
//
|
|||
// int m_lifetime = 0;
|
|||
// const int k_UpdateIntervalMS = 1000;
|
|||
//
|
|||
// public LobbySynchronizer(LobbyManager lobbyManager)
|
|||
// {
|
|||
// m_LobbyManager = lobbyManager;
|
|||
// }
|
|||
//
|
|||
// public void StartSynch(LocalLobby localLobby, LocalPlayer localUser)
|
|||
// {
|
|||
// m_LocalUser = localUser;
|
|||
// m_LocalLobby = localLobby;
|
|||
// m_LocalLobby.LobbyID.onChanged += OnLobbyIdChanged;
|
|||
// m_LocalChanges = true;
|
|||
// #pragma warning disable 4014
|
|||
// UpdateLoopAsync();
|
|||
// #pragma warning restore 4014
|
|||
// m_lifetime = 0;
|
|||
// }
|
|||
//
|
|||
//
|
|||
// public void EndSynch()
|
|||
// {
|
|||
// m_LocalChanges = false;
|
|||
//
|
|||
// if (m_LocalLobby != null)
|
|||
// m_LocalLobby.LobbyID.onChanged -= OnLobbyIdChanged;
|
|||
//
|
|||
// m_LocalLobby = null;
|
|||
// }
|
|||
//
|
|||
// /// <summary>
|
|||
// /// If there have been any data changes since the last update, push them to Lobby. Regardless, pull for the most recent data.
|
|||
// /// (Unless we're already awaiting a query, in which case continue waiting.)
|
|||
// /// </summary>
|
|||
// async Task UpdateLoopAsync()
|
|||
// {
|
|||
// Lobby latestLobby = null;
|
|||
//
|
|||
// while (m_LocalLobby != null)
|
|||
// {
|
|||
// latestLobby = await GetLatestRemoteLobby();
|
|||
//
|
|||
// if (IfRemoteLobbyChanged(latestLobby))
|
|||
// {
|
|||
// //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);
|
|||
// }
|
|||
// Debug.Log(m_LocalLobby.ToString());
|
|||
//
|
|||
// if (!LobbyHasHost())
|
|||
// {
|
|||
// LeaveLobbyBecauseNoHost();
|
|||
// break;
|
|||
// }
|
|||
//
|
|||
// 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();
|
|||
// }
|
|||
//
|
|||
// m_lifetime += k_UpdateIntervalMS;
|
|||
// await Task.Delay(k_UpdateIntervalMS);
|
|||
// }
|
|||
// }
|
|||
//
|
|||
// async Task<Lobby> GetLatestRemoteLobby()
|
|||
// {
|
|||
// Lobby latestLobby = null;
|
|||
// if (m_LocalLobby.IsLobbyChanged())
|
|||
// {
|
|||
// latestLobby = await PushDataToLobby();
|
|||
// }
|
|||
// else
|
|||
// {
|
|||
// latestLobby = await m_LobbyManager.GetLobbyAsync();
|
|||
// }
|
|||
//
|
|||
// return latestLobby;
|
|||
// }
|
|||
//
|
|||
// bool IfRemoteLobbyChanged(Lobby remoteLobby)
|
|||
// {
|
|||
// var remoteLobbyTime = remoteLobby.LastUpdated.ToFileTimeUtc();
|
|||
// var localLobbyTime = m_LocalLobby.LastUpdated.Value;
|
|||
// var isLocalOutOfDate = remoteLobbyTime > localLobbyTime;
|
|||
// return isLocalOutOfDate;
|
|||
// }
|
|||
//
|
|||
// async Task<Lobby> PushDataToLobby()
|
|||
// {
|
|||
// m_LocalChanges = false;
|
|||
//
|
|||
// if (m_LocalUser.IsHost.Value)
|
|||
// await m_LobbyManager.UpdateLobbyDataAsync(
|
|||
// LobbyConverters.LocalToRemoteLobbyData(m_LocalLobby));
|
|||
//
|
|||
// return await m_LobbyManager.UpdatePlayerDataAsync(
|
|||
// LobbyConverters.LocalToRemoteUserData(m_LocalUser));
|
|||
// }
|
|||
//
|
|||
// bool AreAllUsersReady()
|
|||
// {
|
|||
// foreach (var lobbyUser in m_LocalLobby.LocalPlayers.Values)
|
|||
// {
|
|||
// if (lobbyUser.PlayerStatus.Value != PlayerStatus.Ready)
|
|||
// {
|
|||
// return false;
|
|||
// }
|
|||
// }
|
|||
//
|
|||
// return true;
|
|||
// }
|
|||
//
|
|||
// bool LobbyHasHost()
|
|||
// {
|
|||
// if (!m_LocalUser.IsHost.Value)
|
|||
// {
|
|||
// foreach (var lobbyUser in m_LocalLobby.LocalPlayers)
|
|||
// {
|
|||
// if (lobbyUser.Value.IsHost.Value)
|
|||
// return true;
|
|||
// }
|
|||
//
|
|||
// return false;
|
|||
// }
|
|||
//
|
|||
// return true;
|
|||
// }
|
|||
//
|
|||
// void LeaveLobbyBecauseNoHost()
|
|||
// {
|
|||
// LogHandlerSettings.Instance.SpawnErrorPopup(
|
|||
// "Host left the lobby! Disconnecting...");
|
|||
// Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame, null);
|
|||
// GameManager.Instance.UIChangeMenuState(GameState.JoinMenu);
|
|||
// }
|
|||
//
|
|||
// public void OnLobbyIdChanged(string lobbyID)
|
|||
// {
|
|||
// if (string.IsNullOrEmpty(lobbyID)
|
|||
// ) // When the player leaves, their LocalLobby is cleared out.
|
|||
// {
|
|||
// EndSynch();
|
|||
// }
|
|||
// }
|
|||
//
|
|||
// public void Dispose()
|
|||
// {
|
|||
// EndSynch();
|
|||
// }
|
|||
// }
|
|||
// }
|
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Test.Tools |
|||
{ |
|||
public class AsyncTestHelper |
|||
{ |
|||
public static IEnumerator Await(Task task) |
|||
{ |
|||
while (!task.IsCompleted) |
|||
{ |
|||
yield return null; |
|||
} |
|||
|
|||
if (task.IsFaulted) |
|||
{ |
|||
throw task.Exception; |
|||
} |
|||
} |
|||
|
|||
public static IEnumerator Await(Func<Task> taskDelegate) |
|||
{ |
|||
return Await(taskDelegate.Invoke()); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: cfbe0210f43014b7989e0ca76171c90d |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.Serialization; |
|||
using UnityEngine.UI; |
|||
|
|||
namespace LobbyRelaySample.UI |
|||
{ |
|||
/// <summary>
|
|||
/// 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>
|
|||
public class ColorLobbyUI : MonoBehaviour |
|||
{ |
|||
public bool m_UseLocalLobby; |
|||
static readonly Color s_orangeColor = new Color(0.83f, 0.36f, 0); |
|||
static readonly Color s_greenColor = new Color(0, 0.61f, 0.45f); |
|||
static readonly Color s_blueColor = new Color(0.0f, 0.44f, 0.69f); |
|||
static readonly Color[] s_colorsOrdered = new Color[] |
|||
{ new Color(0.9f, 0.9f, 0.9f, 0.7f), s_orangeColor, s_greenColor, s_blueColor }; |
|||
|
|||
[SerializeField] |
|||
Graphic[] m_toRecolor; |
|||
LocalLobby m_lobby; |
|||
|
|||
void Start() |
|||
{ |
|||
if (m_UseLocalLobby) |
|||
SetLobby(GameManager.Instance.LocalLobby); |
|||
} |
|||
|
|||
public void SetLobby(LocalLobby lobby) |
|||
{ |
|||
ChangeColors(lobby.LocalLobbyColor.Value); |
|||
lobby.LocalLobbyColor.onChanged += ChangeColors; |
|||
} |
|||
|
|||
public void ToggleWhite(bool toggle) |
|||
{ |
|||
if (!toggle) |
|||
return; |
|||
GameManager.Instance.SetLocalLobbyColor(0); |
|||
} |
|||
|
|||
public void ToggleOrange(bool toggle) |
|||
{ |
|||
if (!toggle) |
|||
return; |
|||
GameManager.Instance.SetLocalLobbyColor(1); |
|||
} |
|||
|
|||
public void ToggleGreen(bool toggle) |
|||
{ |
|||
if (!toggle) |
|||
return; |
|||
GameManager.Instance.SetLocalLobbyColor(2); |
|||
} |
|||
|
|||
public void ToggleBlue(bool toggle) |
|||
{ |
|||
if (!toggle) |
|||
return; |
|||
GameManager.Instance.SetLocalLobbyColor(3); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
} |
|
|||
%YAML 1.1 |
|||
%TAG !u! tag:unity3d.com,2011: |
|||
--- !u!114 &11400000 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 0} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 2ec995e51a6e251468d2a3fd8a686257, type: 3} |
|||
m_Name: UniversalRenderPipelineGlobalSettings |
|||
m_EditorClassIdentifier: |
|||
k_AssetVersion: 2 |
|||
lightLayerName0: Light Layer default |
|||
lightLayerName1: Light Layer 1 |
|||
lightLayerName2: Light Layer 2 |
|||
lightLayerName3: Light Layer 3 |
|||
lightLayerName4: Light Layer 4 |
|||
lightLayerName5: Light Layer 5 |
|||
lightLayerName6: Light Layer 6 |
|||
lightLayerName7: Light Layer 7 |
|||
m_StripDebugVariants: 1 |
|||
m_StripUnusedPostProcessingVariants: 0 |
|||
m_StripUnusedVariants: 1 |
|||
supportRuntimeDebugDisplay: 0 |
|
|||
fileFormatVersion: 2 |
|||
guid: c96e53b1d712ac24eaccfd7f849c709d |
|||
NativeFormatImporter: |
|||
externalObjects: {} |
|||
mainObjectFileID: 11400000 |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 520a1688a3c84604b0e7836ecc15cee3 |
|||
timeCreated: 1654698051 |
部分文件因为文件数量过多而无法显示
撰写
预览
正在加载...
取消
保存
Reference in new issue