Started adding multiplayer

This commit is contained in:
2026-05-19 19:14:03 +01:00
parent f826689bdd
commit e8e6c710df
36 changed files with 892 additions and 17 deletions

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1616211d81f623845a5cd2ffa978c18a

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 903cd34a044dfb14999cc382d7fb01bd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8ed9598a0f71474ea90df6a62ba55723
timeCreated: 1779203851

View File

@@ -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>();
networkObject.SpawnAsPlayerObject(id);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ed9f8d66fd664fd68f1901f0120a0daf
timeCreated: 1779208828

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f490d6c8b73d447fbe47c95efede54d0
timeCreated: 1779196408

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d478eb44fa9f45258a97c4a57e2e97e9
timeCreated: 1779205011

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 54594dc099644958a5f437811cc47a5e
timeCreated: 1779205047

View File

@@ -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<JoinAllocation> 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<Allocation> result = new();
yield return RelayService.Instance
.CreateAllocationAsync(7)
.Await(result);
allocation = result.Value;
}
string joinCode = null;
{
Result<string> 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}]");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 873c14eb9ab94868b0572ae86759b2a7
timeCreated: 1779205549