From e8e6c710df1759a1da7135c5f0f072dd94dd5e3a Mon Sep 17 00:00:00 2001 From: Pasha Date: Tue, 19 May 2026 19:14:03 +0100 Subject: [PATCH] Started adding multiplayer --- Assets/DefaultNetworkPrefabs.asset | 7 +- Assets/Prefabs.meta | 8 + Assets/Prefabs/Client.prefab | 59 +++++++ Assets/Prefabs/Client.prefab.meta | 7 + Assets/Prefabs/Transports.meta | 8 + Assets/Prefabs/Transports/Localhost.prefab | 109 +++++++++++++ .../Prefabs/Transports/Localhost.prefab.meta | 7 + Assets/Scenes/Bootstrap.unity | 97 +++++++++++- Assets/Scripts/Bootstrap/BootstrapLoader.cs | 11 -- Assets/Scripts/{Bootstrap.meta => Core.meta} | 0 Assets/Scripts/Core/BootstrapLoader.cs | 22 +++ .../BootstrapLoader.cs.meta | 0 Assets/Scripts/Core/Network.meta | 8 + Assets/Scripts/Core/Network/Authenticator.cs | 26 ++++ .../Core/Network/Authenticator.cs.meta | 3 + .../Scripts/Core/Network/ConnectionManager.cs | 46 ++++++ .../Core/Network/ConnectionManager.cs.meta | 3 + Assets/Scripts/Core/Network/Network.cs | 146 ++++++++++++++++++ Assets/Scripts/Core/Network/Network.cs.meta | 3 + Assets/Scripts/Core/Network/Transports.meta | 3 + .../Network/Transports/LocalhostTransport.cs | 23 +++ .../Transports/LocalhostTransport.cs.meta | 3 + .../Network/Transports/UnityRelayTransport.cs | 61 ++++++++ .../Transports/UnityRelayTransport.cs.meta | 3 + Assets/Scripts/Extensions.meta | 3 + .../Extensions/MonoBehaviourExtensions.cs | 15 ++ .../MonoBehaviourExtensions.cs.meta | 3 + Assets/Scripts/Extensions/TaskExtensions.cs | 26 ++++ .../Scripts/Extensions/TaskExtensions.cs.meta | 3 + .../Extensions/UnityTransportExtensions.cs | 33 ++++ .../UnityTransportExtensions.cs.meta | 3 + Assets/Settings/PlayMode.meta | 8 + Packages/manifest.json | 4 + Packages/packages-lock.json | 127 ++++++++++++++- .../CustomColorSettings.asset | 17 ++ ProjectSettings/VirtualProjectsConfig.json | 4 + 36 files changed, 892 insertions(+), 17 deletions(-) create mode 100644 Assets/Prefabs.meta create mode 100644 Assets/Prefabs/Client.prefab create mode 100644 Assets/Prefabs/Client.prefab.meta create mode 100644 Assets/Prefabs/Transports.meta create mode 100644 Assets/Prefabs/Transports/Localhost.prefab create mode 100644 Assets/Prefabs/Transports/Localhost.prefab.meta delete mode 100644 Assets/Scripts/Bootstrap/BootstrapLoader.cs rename Assets/Scripts/{Bootstrap.meta => Core.meta} (100%) create mode 100644 Assets/Scripts/Core/BootstrapLoader.cs rename Assets/Scripts/{Bootstrap => Core}/BootstrapLoader.cs.meta (100%) create mode 100644 Assets/Scripts/Core/Network.meta create mode 100644 Assets/Scripts/Core/Network/Authenticator.cs create mode 100644 Assets/Scripts/Core/Network/Authenticator.cs.meta create mode 100644 Assets/Scripts/Core/Network/ConnectionManager.cs create mode 100644 Assets/Scripts/Core/Network/ConnectionManager.cs.meta create mode 100644 Assets/Scripts/Core/Network/Network.cs create mode 100644 Assets/Scripts/Core/Network/Network.cs.meta create mode 100644 Assets/Scripts/Core/Network/Transports.meta create mode 100644 Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs create mode 100644 Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs.meta create mode 100644 Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs create mode 100644 Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs.meta create mode 100644 Assets/Scripts/Extensions.meta create mode 100644 Assets/Scripts/Extensions/MonoBehaviourExtensions.cs create mode 100644 Assets/Scripts/Extensions/MonoBehaviourExtensions.cs.meta create mode 100644 Assets/Scripts/Extensions/TaskExtensions.cs create mode 100644 Assets/Scripts/Extensions/TaskExtensions.cs.meta create mode 100644 Assets/Scripts/Extensions/UnityTransportExtensions.cs create mode 100644 Assets/Scripts/Extensions/UnityTransportExtensions.cs.meta create mode 100644 Assets/Settings/PlayMode.meta create mode 100644 ProjectSettings/Packages/com.unity.multiplayer.tools/CustomColorSettings.asset create mode 100644 ProjectSettings/VirtualProjectsConfig.json diff --git a/Assets/DefaultNetworkPrefabs.asset b/Assets/DefaultNetworkPrefabs.asset index 219664e..600a6a5 100644 --- a/Assets/DefaultNetworkPrefabs.asset +++ b/Assets/DefaultNetworkPrefabs.asset @@ -13,4 +13,9 @@ MonoBehaviour: m_Name: DefaultNetworkPrefabs m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkPrefabsList IsDefault: 1 - List: [] + List: + - Override: 0 + Prefab: {fileID: 401049662134316001, guid: 90d7552110f715f4d8c56436b0621b94, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} diff --git a/Assets/Prefabs.meta b/Assets/Prefabs.meta new file mode 100644 index 0000000..2b93c7e --- /dev/null +++ b/Assets/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b2e7bcdd029021044809f3cc700927cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Client.prefab b/Assets/Prefabs/Client.prefab new file mode 100644 index 0000000..dae89b8 --- /dev/null +++ b/Assets/Prefabs/Client.prefab @@ -0,0 +1,59 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &401049662134316001 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5018670842438043798} + - component: {fileID: 4338096495283207142} + m_Layer: 0 + m_Name: Client + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5018670842438043798 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 401049662134316001} + serializedVersion: 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: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4338096495283207142 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 401049662134316001} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject + GlobalObjectIdHash: 0 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 diff --git a/Assets/Prefabs/Client.prefab.meta b/Assets/Prefabs/Client.prefab.meta new file mode 100644 index 0000000..e5468e6 --- /dev/null +++ b/Assets/Prefabs/Client.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 90d7552110f715f4d8c56436b0621b94 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Transports.meta b/Assets/Prefabs/Transports.meta new file mode 100644 index 0000000..098eac3 --- /dev/null +++ b/Assets/Prefabs/Transports.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1c52608074998de408879fc908416d38 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Transports/Localhost.prefab b/Assets/Prefabs/Transports/Localhost.prefab new file mode 100644 index 0000000..337baf7 --- /dev/null +++ b/Assets/Prefabs/Transports/Localhost.prefab @@ -0,0 +1,109 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6715254673078816961 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 177879224478324551} + - component: {fileID: 4030877536017024502} + - component: {fileID: 798290496798916117} + m_Layer: 0 + m_Name: Localhost + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &177879224478324551 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6715254673078816961} + serializedVersion: 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: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4030877536017024502 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6715254673078816961} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkManager + NetworkManagerExpanded: 0 + NetworkConfig: + ProtocolVersion: 0 + NetworkTransport: {fileID: 798290496798916117} + PlayerPrefab: {fileID: 0} + Prefabs: + NetworkPrefabsLists: + - {fileID: 11400000, guid: 7b4919f232aabc04b9891b736d8945bb, type: 2} + 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: 10 + EnableNetworkLogs: 1 + NetworkTopology: 0 + UseCMBService: 0 + AutoSpawnPlayerPrefabClientSide: 1 + NetworkMessageMetrics: 1 + NetworkProfilingMetrics: 1 + OldPrefabList: [] + RunInBackground: 1 + LogLevel: 1 +--- !u!114 &798290496798916117 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6715254673078816961} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Transports.UTP.UnityTransport + m_ProtocolType: 0 + m_UseWebSockets: 0 + m_UseEncryption: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 6144 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + WebSocketPath: / + ServerListenAddress: 127.0.0.1 + ClientBindPort: 0 + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 diff --git a/Assets/Prefabs/Transports/Localhost.prefab.meta b/Assets/Prefabs/Transports/Localhost.prefab.meta new file mode 100644 index 0000000..72d0edd --- /dev/null +++ b/Assets/Prefabs/Transports/Localhost.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 054af26d74325fb4e9aaec18704fb248 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Bootstrap.unity b/Assets/Scenes/Bootstrap.unity index b44c970..6cea0ab 100644 --- a/Assets/Scenes/Bootstrap.unity +++ b/Assets/Scenes/Bootstrap.unity @@ -119,7 +119,102 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &380762915 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 380762917} + - component: {fileID: 380762916} + m_Layer: 0 + m_Name: ConnectionManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &380762916 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 380762915} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ed9f8d66fd664fd68f1901f0120a0daf, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PashaBibko.PenguinChase.Core.Network.ConnectionManager + PrefabForEachClient: {fileID: 401049662134316001, guid: 90d7552110f715f4d8c56436b0621b94, type: 3} +--- !u!4 &380762917 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 380762915} + serializedVersion: 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: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1684958312 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1684958313} + - component: {fileID: 1684958314} + m_Layer: 0 + m_Name: Network + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1684958313 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1684958312} + serializedVersion: 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: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1684958314 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1684958312} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f490d6c8b73d447fbe47c95efede54d0, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PashaBibko.PenguinChase.Core.Network.Network + LocalHostTransport: {fileID: 6715254673078816961, guid: 054af26d74325fb4e9aaec18704fb248, type: 3} + UnityRelayTransport: {fileID: 0} + InternalCurrentTransport: 2 + GameJoinCode: --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 - m_Roots: [] + m_Roots: + - {fileID: 1684958313} + - {fileID: 380762917} diff --git a/Assets/Scripts/Bootstrap/BootstrapLoader.cs b/Assets/Scripts/Bootstrap/BootstrapLoader.cs deleted file mode 100644 index bffb5c5..0000000 --- a/Assets/Scripts/Bootstrap/BootstrapLoader.cs +++ /dev/null @@ -1,11 +0,0 @@ -using UnityEngine.SceneManagement; -using UnityEngine; - -namespace PashaBibko.PenguinChase.Bootstrap -{ - public static class BootstrapLoader - { - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] - private static void LoadBoostrapScene() => SceneManager.LoadScene("Bootstrap", LoadSceneMode.Additive); - } -} diff --git a/Assets/Scripts/Bootstrap.meta b/Assets/Scripts/Core.meta similarity index 100% rename from Assets/Scripts/Bootstrap.meta rename to Assets/Scripts/Core.meta diff --git a/Assets/Scripts/Core/BootstrapLoader.cs b/Assets/Scripts/Core/BootstrapLoader.cs new file mode 100644 index 0000000..d6214e8 --- /dev/null +++ b/Assets/Scripts/Core/BootstrapLoader.cs @@ -0,0 +1,22 @@ +using UnityEngine.SceneManagement; +using UnityEngine; + +namespace PashaBibko.PenguinChase.Core +{ + public static class BootstrapLoader + { + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void LoadBoostrapScene() + { +#if UNITY_EDITOR + // Stops loading in on the bootstrap scene + if (SceneManager.GetActiveScene().name == "Bootstrap") + { + SceneManager.LoadScene("MainMenu", LoadSceneMode.Single); + } +#endif // UNITY_EDITOR + + SceneManager.LoadScene("Bootstrap", LoadSceneMode.Additive); + } + } +} diff --git a/Assets/Scripts/Bootstrap/BootstrapLoader.cs.meta b/Assets/Scripts/Core/BootstrapLoader.cs.meta similarity index 100% rename from Assets/Scripts/Bootstrap/BootstrapLoader.cs.meta rename to Assets/Scripts/Core/BootstrapLoader.cs.meta diff --git a/Assets/Scripts/Core/Network.meta b/Assets/Scripts/Core/Network.meta new file mode 100644 index 0000000..a49adce --- /dev/null +++ b/Assets/Scripts/Core/Network.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 903cd34a044dfb14999cc382d7fb01bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Core/Network/Authenticator.cs b/Assets/Scripts/Core/Network/Authenticator.cs new file mode 100644 index 0000000..d3c8a6e --- /dev/null +++ b/Assets/Scripts/Core/Network/Authenticator.cs @@ -0,0 +1,26 @@ +using Unity.Services.Authentication; +using Unity.Services.Core; +using System.Collections; + +namespace PashaBibko.PenguinChase.Core.Network +{ + public static class Authenticator + { + public static bool IsAuthenticated { get; private set; } + + public static IEnumerator Authenticate() + { + if (IsAuthenticated) + { + // User is already authenticated + yield break; + } + + // TODO: Sign in via current platform + + yield return UnityServices.InitializeAsync(); + yield return AuthenticationService.Instance.SignInAnonymouslyAsync(); + IsAuthenticated = true; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Core/Network/Authenticator.cs.meta b/Assets/Scripts/Core/Network/Authenticator.cs.meta new file mode 100644 index 0000000..c1f7ce9 --- /dev/null +++ b/Assets/Scripts/Core/Network/Authenticator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8ed9598a0f71474ea90df6a62ba55723 +timeCreated: 1779203851 \ No newline at end of file diff --git a/Assets/Scripts/Core/Network/ConnectionManager.cs b/Assets/Scripts/Core/Network/ConnectionManager.cs new file mode 100644 index 0000000..026aa97 --- /dev/null +++ b/Assets/Scripts/Core/Network/ConnectionManager.cs @@ -0,0 +1,46 @@ +using PashaBibko.PenguinChase.Extensions; +using Unity.Netcode; +using UnityEngine; + +namespace PashaBibko.PenguinChase.Core.Network +{ + public class ConnectionManager : MonoBehaviour + { + private static ConnectionManager sInstance; + + [SerializeField] private GameObject PrefabForEachClient; + public static GameObject ClientPrefab => sInstance?.PrefabForEachClient; + + private void Start() + { + // Stops overlapping instances + if (sInstance is not null) + { + Debug.LogError($"Multiple of [{nameof(ConnectionManager)}] cannot exist."); + Destroy(gameObject); + return; + } + + sInstance = this; + } + + public static void CreateNetworkConnectionManager() + { + NetworkManager.Singleton.OnClientConnectedCallback += OnClientJoin; + OnClientJoin(0); // Has to be manually called for local client + } + + public static void DestroyNetworkConnectionManager() + { + NetworkManager.Singleton.OnClientConnectedCallback -= OnClientJoin; + sInstance.DestroyAllChildren(); + } + + private static void OnClientJoin(ulong id) + { + GameObject client = Instantiate(ClientPrefab); + NetworkObject networkObject = client.GetComponent(); + networkObject.SpawnAsPlayerObject(id); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Core/Network/ConnectionManager.cs.meta b/Assets/Scripts/Core/Network/ConnectionManager.cs.meta new file mode 100644 index 0000000..bed8663 --- /dev/null +++ b/Assets/Scripts/Core/Network/ConnectionManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ed9f8d66fd664fd68f1901f0120a0daf +timeCreated: 1779208828 \ No newline at end of file diff --git a/Assets/Scripts/Core/Network/Network.cs b/Assets/Scripts/Core/Network/Network.cs new file mode 100644 index 0000000..e92fe40 --- /dev/null +++ b/Assets/Scripts/Core/Network/Network.cs @@ -0,0 +1,146 @@ +using PashaBibko.Pacore.Attributes; +using Unity.Netcode.Transports.UTP; +using System.Collections; +using Unity.Netcode; +using UnityEngine; +using System; +using JetBrains.Annotations; +using UnityEngine.SceneManagement; + +namespace PashaBibko.PenguinChase.Core.Network +{ + public enum TransportType + { + UnityRelay, + Localhost, + + None + } + + public interface INetworkTransport + { + public IEnumerator Join(string code); + public IEnumerator Host(); + } + + public class Network : MonoBehaviour + { + private static Network sInstance; + + [Header("Transports")] [SerializeField] + private GameObject LocalHostTransport; + + [SerializeField] private GameObject UnityRelayTransport; + + [Header("View only")] [SerializeField, InspectorReadOnly] + private TransportType InternalCurrentTransport = TransportType.None; + + public static TransportType CurrentTransport + { + get => sInstance?.InternalCurrentTransport ?? TransportType.None; + set => sInstance?.StartCoroutine(sInstance.ChangeTransport(value)); + } + +#if UNITY_EDITOR + [Header("Editor Only")] + [SerializeField] private string GameJoinCode; +#endif // UNITY_EDITOR + + private static INetworkTransport sConnectionManager; + private GameObject mCurrentChildTransport; + + public static UnityTransport CurrentTransportComponent + { + get + { + UnityTransport component = null; + sInstance?.mCurrentChildTransport?.TryGetComponent(out component); + return component; + } + } + + private void Start() + { + // Stops overlapping instances + if (sInstance is not null) + { + Debug.LogError($"Multiple of [{nameof(Network)}] cannot exist."); + Destroy(gameObject); + return; + } + + sInstance = this; + } + + [UsedImplicitly, InspectorCallable("Choose Local Host Transport")] + private void ChooseLocalHostTransport() => CurrentTransport = TransportType.Localhost; + + [UsedImplicitly, InspectorCallable("Host")] + public void Host() + { + if (sConnectionManager is null) + { + throw new InvalidOperationException("No connection manager has been set."); + } + + StartCoroutine(sConnectionManager.Host()); + } + + [UsedImplicitly, InspectorCallable("Join")] + public void Join() + { + if (sConnectionManager is null) + { + throw new InvalidOperationException("No connection manager has been set."); + } + + StartCoroutine(sConnectionManager.Join(GameJoinCode)); + } + + private IEnumerator ChangeTransport(TransportType transport) + { + // Stops network from being restarted when unneeded + if (transport == InternalCurrentTransport) + { + yield break; + } + + InternalCurrentTransport = transport; + + // Shutdown existing network manager (if there is one) to stop multiple at once + if (NetworkManager.Singleton is not null) + { + NetworkManager.Singleton.Shutdown(); + while (NetworkManager.Singleton.ShutdownInProgress) + { + yield return null; // Waits for next frame + } + + Destroy(mCurrentChildTransport); + } + + // Loads the new transport controller and lets it setup + switch (InternalCurrentTransport) + { + case TransportType.UnityRelay: + mCurrentChildTransport = Instantiate(UnityRelayTransport); + sConnectionManager = new UnityRelayTransport(); + break; + + case TransportType.Localhost: + mCurrentChildTransport = Instantiate(LocalHostTransport); + sConnectionManager = new LocalhostTransport(); + break; + + case TransportType.None: + // Nothing needs to be done here as there is no network controller + mCurrentChildTransport = null; + yield break; + + default: + throw new ArgumentOutOfRangeException(); + } + DontDestroyOnLoad(mCurrentChildTransport); + } + } +} diff --git a/Assets/Scripts/Core/Network/Network.cs.meta b/Assets/Scripts/Core/Network/Network.cs.meta new file mode 100644 index 0000000..c3b550d --- /dev/null +++ b/Assets/Scripts/Core/Network/Network.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f490d6c8b73d447fbe47c95efede54d0 +timeCreated: 1779196408 \ No newline at end of file diff --git a/Assets/Scripts/Core/Network/Transports.meta b/Assets/Scripts/Core/Network/Transports.meta new file mode 100644 index 0000000..32984c1 --- /dev/null +++ b/Assets/Scripts/Core/Network/Transports.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d478eb44fa9f45258a97c4a57e2e97e9 +timeCreated: 1779205011 \ No newline at end of file diff --git a/Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs b/Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs new file mode 100644 index 0000000..dc0e714 --- /dev/null +++ b/Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs @@ -0,0 +1,23 @@ +using System.Collections; +using Unity.Netcode; + +namespace PashaBibko.PenguinChase.Core.Network +{ + public class LocalhostTransport : INetworkTransport + { + // TODO: Allow connection to different devices on local network + public IEnumerator Join(string _) + { + NetworkManager.Singleton.StartClient(); + yield break; + } + + public IEnumerator Host() + { + NetworkManager.Singleton.StartHost(); + ConnectionManager.CreateNetworkConnectionManager(); + + yield break; + } + } +} diff --git a/Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs.meta b/Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs.meta new file mode 100644 index 0000000..af164cd --- /dev/null +++ b/Assets/Scripts/Core/Network/Transports/LocalhostTransport.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 54594dc099644958a5f437811cc47a5e +timeCreated: 1779205047 \ No newline at end of file diff --git a/Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs b/Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs new file mode 100644 index 0000000..17e3ab4 --- /dev/null +++ b/Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs @@ -0,0 +1,61 @@ +using PashaBibko.PenguinChase.Extensions; +using Unity.Services.Relay.Models; +using Unity.Services.Relay; +using System.Collections; +using Unity.Netcode; +using UnityEngine; + +namespace PashaBibko.PenguinChase.Core.Network +{ + public class UnityRelayTransport : INetworkTransport + { + private const uint MAX_CONNECTIONS = 7; + + public IEnumerator Join(string code) + { + yield return Authenticator.Authenticate(); + JoinAllocation allocation; + { + Result result = new(); + yield return RelayService.Instance + .JoinAllocationAsync(code) + .Await(result); + + allocation = result.Value; + } + + Network.CurrentTransportComponent.SetRelayServerData(allocation); + NetworkManager.Singleton.StartClient(); + } + + public IEnumerator Host() + { + yield return Authenticator.Authenticate(); + Allocation allocation; + { + Result result = new(); + yield return RelayService.Instance + .CreateAllocationAsync(7) + .Await(result); + + allocation = result.Value; + } + + string joinCode = null; + { + Result result = new(); + yield return RelayService.Instance + .GetJoinCodeAsync(allocation.AllocationId) + .Await(result); + + joinCode = result.Value; + } + + Network.CurrentTransportComponent.SetHostRelayData(allocation); + NetworkManager.Singleton.StartHost(); + + ConnectionManager.CreateNetworkConnectionManager(); + Debug.Log($"Started server with code: [{joinCode}]"); + } + } +} diff --git a/Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs.meta b/Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs.meta new file mode 100644 index 0000000..111847e --- /dev/null +++ b/Assets/Scripts/Core/Network/Transports/UnityRelayTransport.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 873c14eb9ab94868b0572ae86759b2a7 +timeCreated: 1779205549 \ No newline at end of file diff --git a/Assets/Scripts/Extensions.meta b/Assets/Scripts/Extensions.meta new file mode 100644 index 0000000..c3ae692 --- /dev/null +++ b/Assets/Scripts/Extensions.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f0270fb2159f4bbfbad1b8d165ef6454 +timeCreated: 1779206080 \ No newline at end of file diff --git a/Assets/Scripts/Extensions/MonoBehaviourExtensions.cs b/Assets/Scripts/Extensions/MonoBehaviourExtensions.cs new file mode 100644 index 0000000..55e122e --- /dev/null +++ b/Assets/Scripts/Extensions/MonoBehaviourExtensions.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +namespace PashaBibko.PenguinChase.Extensions +{ + public static class MonoBehaviourExtensions + { + public static void DestroyAllChildren(this MonoBehaviour go) + { + foreach (Transform child in go.transform) + { + Object.Destroy(child.gameObject); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Extensions/MonoBehaviourExtensions.cs.meta b/Assets/Scripts/Extensions/MonoBehaviourExtensions.cs.meta new file mode 100644 index 0000000..21dd5c4 --- /dev/null +++ b/Assets/Scripts/Extensions/MonoBehaviourExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4b9109919a0b4b969424d31ac875d6bf +timeCreated: 1779211792 \ No newline at end of file diff --git a/Assets/Scripts/Extensions/TaskExtensions.cs b/Assets/Scripts/Extensions/TaskExtensions.cs new file mode 100644 index 0000000..f24399a --- /dev/null +++ b/Assets/Scripts/Extensions/TaskExtensions.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using System.Collections; + +namespace PashaBibko.PenguinChase.Extensions +{ + public sealed class Result + { + public T Value { get; set; } + } + + public static class TaskExtensions + { + public static IEnumerator Await(this Task task, Result result) + where T : class + { + // Waits until the task is completed + while (!task.IsCompleted) + { + yield return null; + } + + // Has to return the value like this because of the wonders of C# + result.Value = task.Result; + } + } +} diff --git a/Assets/Scripts/Extensions/TaskExtensions.cs.meta b/Assets/Scripts/Extensions/TaskExtensions.cs.meta new file mode 100644 index 0000000..4b8c3dc --- /dev/null +++ b/Assets/Scripts/Extensions/TaskExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cf8f135562fd4650924729e686a5a815 +timeCreated: 1779206087 \ No newline at end of file diff --git a/Assets/Scripts/Extensions/UnityTransportExtensions.cs b/Assets/Scripts/Extensions/UnityTransportExtensions.cs new file mode 100644 index 0000000..fde9030 --- /dev/null +++ b/Assets/Scripts/Extensions/UnityTransportExtensions.cs @@ -0,0 +1,33 @@ +using Unity.Netcode.Transports.UTP; +using Unity.Services.Relay.Models; + +namespace PashaBibko.PenguinChase.Extensions +{ + public static class UnityTransportExtensions + { + public static void SetHostRelayData(this UnityTransport transport, Allocation allocation) + { + transport.SetHostRelayData + ( + allocation.RelayServer.IpV4, + (ushort)allocation.RelayServer.Port, + allocation.AllocationIdBytes, + allocation.Key, + allocation.ConnectionData + ); + } + + public static void SetRelayServerData(this UnityTransport transport, JoinAllocation allocation) + { + transport.SetRelayServerData + ( + allocation.RelayServer.IpV4, + (ushort)allocation.RelayServer.Port, + allocation.AllocationIdBytes, + allocation.Key, + allocation.ConnectionData, + allocation.HostConnectionData + ); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Extensions/UnityTransportExtensions.cs.meta b/Assets/Scripts/Extensions/UnityTransportExtensions.cs.meta new file mode 100644 index 0000000..f75fb90 --- /dev/null +++ b/Assets/Scripts/Extensions/UnityTransportExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c69f8839a8834857b6dc5df1627045df +timeCreated: 1779207616 \ No newline at end of file diff --git a/Assets/Settings/PlayMode.meta b/Assets/Settings/PlayMode.meta new file mode 100644 index 0000000..2f5369e --- /dev/null +++ b/Assets/Settings/PlayMode.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5deb209154707d4469ce564404b7cb3e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json index 2fb2cb8..1b365da 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -6,8 +6,12 @@ "com.unity.ide.visualstudio": "2.0.26", "com.unity.inputsystem": "1.19.0", "com.unity.multiplayer.center": "1.0.1", + "com.unity.multiplayer.playmode": "2.0.2", + "com.unity.multiplayer.tools": "2.2.8", "com.unity.netcode.gameobjects": "2.11.2", "com.unity.render-pipelines.universal": "17.3.0", + "com.unity.services.authentication": "3.6.1", + "com.unity.services.multiplayer": "2.2.2", "com.unity.test-framework": "1.6.0", "com.unity.timeline": "1.8.11", "com.unity.ugui": "2.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 3c5a47b..fb87bbd 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -11,7 +11,7 @@ }, "com.unity.burst": { "version": "1.8.28", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": { "com.unity.mathematics": "1.2.1", @@ -28,7 +28,7 @@ }, "com.unity.collections": { "version": "2.6.5", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": { "com.unity.burst": "1.8.27", @@ -74,7 +74,7 @@ }, "com.unity.mathematics": { "version": "1.3.3", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -87,6 +87,30 @@ "com.unity.modules.uielements": "1.0.0" } }, + "com.unity.multiplayer.playmode": { + "version": "2.0.2", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.nuget.newtonsoft-json": "2.0.2" + }, + "url": "https://packages.unity.com" + }, + "com.unity.multiplayer.tools": { + "version": "2.2.8", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.burst": "1.8.18", + "com.unity.collections": "2.5.1", + "com.unity.mathematics": "1.3.2", + "com.unity.profiling.core": "1.0.2", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.modules.uielements": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.2.1" + }, + "url": "https://packages.unity.com" + }, "com.unity.netcode.gameobjects": { "version": "2.11.2", "depth": 0, @@ -104,6 +128,20 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.nuget.newtonsoft-json": { + "version": "3.2.2", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.profiling.core": { + "version": "1.0.3", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.render-pipelines.core": { "version": "17.3.0", "depth": 1, @@ -143,6 +181,87 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.services.authentication": { + "version": "3.6.1", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.services.core": "1.15.1", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.modules.unitywebrequest": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.core": { + "version": "1.16.0", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.modules.androidjni": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.modules.unitywebrequest": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.deployment": { + "version": "1.7.1", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.services.core": "1.15.1", + "com.unity.services.deployment.api": "1.1.2" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.deployment.api": { + "version": "1.1.3", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.services.multiplayer": { + "version": "2.2.2", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.transport": "2.6.0", + "com.unity.collections": "2.2.1", + "com.unity.services.qos": "1.4.1", + "com.unity.services.core": "1.16.0", + "com.unity.services.wire": "1.4.3", + "com.unity.services.deployment": "1.7.1", + "com.unity.nuget.newtonsoft-json": "3.2.2", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.services.authentication": "3.6.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.qos": { + "version": "1.4.1", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.collections": "1.2.4", + "com.unity.services.core": "1.12.5", + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.services.authentication": "3.5.2" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.wire": { + "version": "1.4.3", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.services.core": "1.12.5", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.services.authentication": "2.7.4" + }, + "url": "https://packages.unity.com" + }, "com.unity.shadergraph": { "version": "17.3.0", "depth": 1, @@ -164,7 +283,7 @@ }, "com.unity.test-framework.performance": { "version": "3.2.0", - "depth": 3, + "depth": 2, "source": "registry", "dependencies": { "com.unity.test-framework": "1.1.33", diff --git a/ProjectSettings/Packages/com.unity.multiplayer.tools/CustomColorSettings.asset b/ProjectSettings/Packages/com.unity.multiplayer.tools/CustomColorSettings.asset new file mode 100644 index 0000000..ee86020 --- /dev/null +++ b/ProjectSettings/Packages/com.unity.multiplayer.tools/CustomColorSettings.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 53 + 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: 6762c37d3236e2a4382cd018dcdf221e, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Multiplayer.Tools.Common::Unity.Multiplayer.Tools.Common.Visualization.CustomColorSettings + colors: + m_Keys: + m_Values: [] diff --git a/ProjectSettings/VirtualProjectsConfig.json b/ProjectSettings/VirtualProjectsConfig.json new file mode 100644 index 0000000..931cd64 --- /dev/null +++ b/ProjectSettings/VirtualProjectsConfig.json @@ -0,0 +1,4 @@ +{ + "PlayerTags": [], + "version": "6000.3.12f1" +} \ No newline at end of file