Comitting lobby code without testing.

This commit is contained in:
Matt F 2026-05-15 14:30:15 -07:00
parent 66f84652dc
commit 60fa58b07f
14 changed files with 1207 additions and 37 deletions

View file

@ -0,0 +1,190 @@
// Assets/_Project/Scripts/Net/NetworkBootstrap.cs
using System;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
namespace TD.Net
{
/// <summary>
/// Static facade for starting a host, joining a host, and disconnecting.
/// Wraps NGO's <see cref="NetworkManager"/> and <see cref="UnityTransport"/>
/// 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.
/// </summary>
/// <remarks>
/// <para><b>Why a static facade.</b> Today this is a thin wrapper over
/// <see cref="UnityTransport"/>. When Steam integration lands
/// (see <c>Project_Roadmap.md</c> §1.7-Future Steam Lobby Migration), this
/// class becomes the seam: it grows into an <c>IConnectionProvider</c>
/// abstraction with two implementations (<c>DirectIpConnectionProvider</c>
/// for LAN / DRM-free distribution, <c>SteamConnectionProvider</c> for
/// Steam friend-invite + lobby-browser flows). The lobby UI never changes;
/// only this class does.</para>
///
/// <para><b>Default port.</b> NGO's NetworkConfig holds the canonical
/// transport settings (port, listen address). <see cref="DefaultPort"/>
/// is just the value we use when the user doesn't override it in the UI.</para>
///
/// <para><b>Scene management.</b> NGO's <c>SceneManager.LoadScene</c> is
/// called by the host AFTER <see cref="StartHost"/> succeeds — see
/// <see cref="LoadSceneAsHost"/>. The lobby and match scene names are
/// canonicalized in <see cref="SceneNames"/>.</para>
/// </remarks>
public static class NetworkBootstrap
{
// ----- Configuration ---------------------------------------------
/// <summary>Default port used by Host / Join when the UI doesn't override.</summary>
public const ushort DefaultPort = 7777;
/// <summary>Default listen address for the host. 0.0.0.0 listens on all interfaces.</summary>
public const string DefaultListenAddress = "0.0.0.0";
/// <summary>Default address clients use when no IP is entered (loopback for solo testing).</summary>
public const string DefaultConnectAddress = "127.0.0.1";
// ----- Public API ------------------------------------------------
/// <summary>True if a host or client connection is currently active.</summary>
public static bool IsConnected =>
NetworkManager.Singleton != null
&& (NetworkManager.Singleton.IsHost
|| NetworkManager.Singleton.IsServer
|| NetworkManager.Singleton.IsClient);
/// <summary>True if the local peer is the host (server + client in one process).</summary>
public static bool IsHost =>
NetworkManager.Singleton != null && NetworkManager.Singleton.IsHost;
/// <summary>
/// Starts a host on <paramref name="listenAddress"/>:<paramref name="port"/>.
/// Returns true on success. Logs and returns false if NetworkManager
/// is missing or already running.
/// </summary>
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;
}
/// <summary>
/// Starts a client and connects to <paramref name="address"/>:<paramref name="port"/>.
/// Returns true on success. Logs and returns false if NetworkManager
/// is missing or already running.
/// </summary>
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;
}
/// <summary>
/// Shuts down the current host or client connection. Safe to call when
/// already disconnected.
/// </summary>
public static void Disconnect()
{
var nm = NetworkManager.Singleton;
if (nm == null) return;
if (!IsConnected) return;
nm.Shutdown();
}
/// <summary>
/// Server-only helper that transitions every peer to <paramref name="sceneName"/>
/// 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.
/// </summary>
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);
}
}
/// <summary>
/// Canonical scene names used by scene transitions.
/// Centralized so renames or additions touch one place.
/// </summary>
public static class SceneNames
{
public const string MainMenu = "MainMenu";
public const string Lobby = "Lobby";
/// <summary>The gameplay scene (currently "Main" — the original prototype scene).</summary>
public const string Match = "Main";
}
}