// Assets/_Project/Scripts/Net/NetworkBootstrap.cs using System; using Unity.Netcode; using Unity.Netcode.Transports.UTP; using UnityEngine; namespace TD.Net { /// /// Static facade for starting a host, joining a host, and disconnecting. /// Wraps NGO's and /// so the rest of the codebase (lobby UI, main menu UI) doesn't talk to /// NGO directly — and so the transport implementation can be swapped /// without touching lobby code. /// /// /// Why a static facade. Today this is a thin wrapper over /// . When Steam integration lands /// (see Project_Roadmap.md §1.7-Future Steam Lobby Migration), this /// class becomes the seam: it grows into an IConnectionProvider /// abstraction with two implementations (DirectIpConnectionProvider /// for LAN / DRM-free distribution, SteamConnectionProvider for /// Steam friend-invite + lobby-browser flows). The lobby UI never changes; /// only this class does. /// /// Default port. NGO's NetworkConfig holds the canonical /// transport settings (port, listen address). /// is just the value we use when the user doesn't override it in the UI. /// /// Scene management. NGO's SceneManager.LoadScene is /// called by the host AFTER succeeds — see /// . The lobby and match scene names are /// canonicalized in . /// public static class NetworkBootstrap { // ----- Configuration --------------------------------------------- /// Default port used by Host / Join when the UI doesn't override. public const ushort DefaultPort = 7777; /// Default listen address for the host. 0.0.0.0 listens on all interfaces. public const string DefaultListenAddress = "0.0.0.0"; /// Default address clients use when no IP is entered (loopback for solo testing). public const string DefaultConnectAddress = "127.0.0.1"; // ----- Public API ------------------------------------------------ /// True if a host or client connection is currently active. public static bool IsConnected => NetworkManager.Singleton != null && (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsClient); /// True if the local peer is the host (server + client in one process). public static bool IsHost => NetworkManager.Singleton != null && NetworkManager.Singleton.IsHost; /// /// Starts a host on :. /// Returns true on success. Logs and returns false if NetworkManager /// is missing or already running. /// public static bool StartHost(ushort port = DefaultPort, string listenAddress = DefaultListenAddress) { var nm = NetworkManager.Singleton; if (nm == null) { Debug.LogError("[NetworkBootstrap] NetworkManager.Singleton is null. " + "Make sure a NetworkManager exists in the active scene."); return false; } if (IsConnected) { Debug.LogWarning("[NetworkBootstrap] StartHost called while already connected. Ignored."); return false; } ConfigureTransport(listenAddress, port); bool ok = nm.StartHost(); if (!ok) Debug.LogError("[NetworkBootstrap] NetworkManager.StartHost() returned false."); return ok; } /// /// Starts a client and connects to :. /// Returns true on success. Logs and returns false if NetworkManager /// is missing or already running. /// public static bool StartClient(string address, ushort port = DefaultPort) { var nm = NetworkManager.Singleton; if (nm == null) { Debug.LogError("[NetworkBootstrap] NetworkManager.Singleton is null. " + "Make sure a NetworkManager exists in the active scene."); return false; } if (IsConnected) { Debug.LogWarning("[NetworkBootstrap] StartClient called while already connected. Ignored."); return false; } if (string.IsNullOrWhiteSpace(address)) address = DefaultConnectAddress; ConfigureTransport(address, port); bool ok = nm.StartClient(); if (!ok) Debug.LogError("[NetworkBootstrap] NetworkManager.StartClient() returned false."); return ok; } /// /// Shuts down the current host or client connection. Safe to call when /// already disconnected. /// public static void Disconnect() { var nm = NetworkManager.Singleton; if (nm == null) return; if (!IsConnected) return; nm.Shutdown(); } /// /// Server-only helper that transitions every peer to /// via NGO's networked scene manager. Logs and no-ops if not the server, /// if NGO scene management is disabled, or if the scene name is invalid. /// public static void LoadSceneAsHost(string sceneName) { var nm = NetworkManager.Singleton; if (nm == null) { Debug.LogError("[NetworkBootstrap] NetworkManager null."); return; } if (!nm.IsServer) { Debug.LogWarning($"[NetworkBootstrap] LoadSceneAsHost('{sceneName}') called on a client. Ignored."); return; } if (nm.SceneManager == null) { Debug.LogError("[NetworkBootstrap] NetworkManager.SceneManager is null — " + "Enable Scene Management on the NetworkManager."); return; } nm.SceneManager.LoadScene(sceneName, UnityEngine.SceneManagement.LoadSceneMode.Single); } // ----- Internals -------------------------------------------------- // Writes connection data into the UnityTransport component attached to // the NetworkManager. Doesn't allocate; reuses the transport instance // that's already in the scene. // // FUTURE STEAM MIGRATION: when SteamConnectionProvider lands, this // method's body is replaced (or split) to configure the appropriate // transport. The public API above stays as-is so call sites in the // lobby UI don't change. private static void ConfigureTransport(string address, ushort port) { var nm = NetworkManager.Singleton; var transport = nm.NetworkConfig?.NetworkTransport as UnityTransport; if (transport == null) { Debug.LogError("[NetworkBootstrap] NetworkManager's transport is not UnityTransport. " + "If you switched transports (e.g. Steam), update NetworkBootstrap " + "to configure the new one."); return; } transport.SetConnectionData(address, port, DefaultListenAddress); } } /// /// Canonical scene names used by scene transitions. /// Centralized so renames or additions touch one place. /// public static class SceneNames { public const string MainMenu = "MainMenu"; public const string Lobby = "Lobby"; /// The gameplay scene (currently "Main" — the original prototype scene). public const string Match = "Main"; } }