Adding new Races, Main Menu -> Lobby flow, and what's needed to set up multiplayer testing.
This commit is contained in:
parent
60fa58b07f
commit
fdada6f132
29 changed files with 2581 additions and 176 deletions
152
Assets/_Project/Scripts/Gameplay/RaceRegistry.cs
Normal file
152
Assets/_Project/Scripts/Gameplay/RaceRegistry.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// Assets/_Project/Scripts/Gameplay/RaceRegistry.cs
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TD.Core;
|
||||
|
||||
namespace TD.Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// Persistent (DontDestroyOnLoad) singleton that holds every
|
||||
/// <see cref="RaceDefinition"/> available in the current build and lets
|
||||
/// any code look one up by <see cref="RaceId"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Inspector setup.</b> Place ONE <c>RaceRegistry</c> GameObject in
|
||||
/// the <b>MainMenu scene</b> only. Drag every <c>RaceDefinition</c> asset
|
||||
/// into the <c>Definitions</c> array. The registry marks itself
|
||||
/// <c>DontDestroyOnLoad</c> on Awake, so it survives the scene transitions
|
||||
/// MainMenu → Lobby → Match → back to Lobby and is available to all of
|
||||
/// them through <see cref="Instance"/>.</para>
|
||||
///
|
||||
/// <para><b>Why not also in Lobby/Match scenes?</b> Maintaining the
|
||||
/// <c>Definitions</c> array in multiple places is a designer trap — update
|
||||
/// one, forget the other, runtime mismatch. Single source of truth in
|
||||
/// MainMenu eliminates that class of bug. Duplicate instances in other
|
||||
/// scenes are detected in <see cref="Awake"/> and self-destruct, so an
|
||||
/// accidental copy doesn't break anything but does log a warning.</para>
|
||||
///
|
||||
/// <para><b>Editor-only standalone testing.</b> If you open the Lobby or
|
||||
/// Match scene directly from the editor (without going through MainMenu
|
||||
/// first), no RaceRegistry will exist and race-dependent code falls back
|
||||
/// gracefully (<see cref="Get"/> returns null; UI shows "Coming Soon" or
|
||||
/// the default builder is used). For standalone-scene testing, you can
|
||||
/// temporarily add a registry to whatever scene you're testing — but don't
|
||||
/// commit it as part of normal play flow.</para>
|
||||
///
|
||||
/// <para><b>Slot model.</b> The lobby grid shows 16 slots (one per
|
||||
/// <see cref="RaceId"/> value 1-16) regardless of how many are filled.
|
||||
/// <see cref="Get"/> returns null for unregistered slots, which the UI
|
||||
/// renders as a "Coming Soon" placeholder.</para>
|
||||
///
|
||||
/// <para><b>Plain MonoBehaviour.</b> Not a NetworkBehaviour — the registry
|
||||
/// is identical on every peer (same ScriptableObject assets), so nothing
|
||||
/// to sync. Network state tracks only the chosen <see cref="RaceId"/> on
|
||||
/// <c>PlayerMatchState</c>; the rest is local lookup.</para>
|
||||
/// </remarks>
|
||||
public class RaceRegistry : MonoBehaviour
|
||||
{
|
||||
// ----- Singleton -------------------------------------------------
|
||||
|
||||
public static RaceRegistry Instance { get; private set; }
|
||||
|
||||
// ----- Inspector --------------------------------------------------
|
||||
|
||||
[Tooltip("All RaceDefinition assets available in this build. Drag each asset " +
|
||||
"into the array. Duplicate Ids are rejected with a warning; null entries " +
|
||||
"are skipped.")]
|
||||
[SerializeField] private RaceDefinition[] definitions;
|
||||
|
||||
// ----- Internal lookup -------------------------------------------
|
||||
|
||||
private readonly Dictionary<RaceId, RaceDefinition> byId
|
||||
= new Dictionary<RaceId, RaceDefinition>();
|
||||
|
||||
// ----- Lifecycle --------------------------------------------------
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Persistent-singleton pattern: the FIRST instance to wake up wins
|
||||
// and survives scene loads. Subsequent instances (e.g. a stale
|
||||
// copy left over in the Lobby or Match scene) are self-destroyed,
|
||||
// not just ignored — we want the scene to "self-heal" if someone
|
||||
// accidentally drops a second copy in.
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[RaceRegistry] Persistent instance already exists. " +
|
||||
$"Destroying duplicate in scene '{gameObject.scene.name}'. " +
|
||||
$"Keep RaceRegistry in the MainMenu scene only.");
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
BuildLookup();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
// ----- Public API -------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="RaceDefinition"/> for the given id, or null
|
||||
/// if no asset is registered for that id (e.g. "Coming Soon" slots).
|
||||
/// </summary>
|
||||
public RaceDefinition Get(RaceId id)
|
||||
{
|
||||
byId.TryGetValue(id, out var def);
|
||||
return def;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates the canonical 16 lobby slots (Race1..Race16). For each
|
||||
/// slot returns either the registered <see cref="RaceDefinition"/> or
|
||||
/// null. UI consumers use this to render a stable 16-cell grid where
|
||||
/// unfilled slots show a placeholder.
|
||||
/// </summary>
|
||||
public IEnumerable<(RaceId id, RaceDefinition def)> AllSlots()
|
||||
{
|
||||
for (int i = (int)RaceId.Race1; i <= (int)RaceId.Race16; i++)
|
||||
{
|
||||
var id = (RaceId)i;
|
||||
yield return (id, Get(id));
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Private ----------------------------------------------------
|
||||
|
||||
private void BuildLookup()
|
||||
{
|
||||
byId.Clear();
|
||||
if (definitions == null || definitions.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[RaceRegistry] No RaceDefinition assets assigned. " +
|
||||
"Drag assets into the Definitions array.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var def in definitions)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.Id == RaceId.None)
|
||||
{
|
||||
Debug.LogWarning($"[RaceRegistry] '{def.name}' has Id=None — set it to " +
|
||||
"an unused Race1..Race16 value.");
|
||||
continue;
|
||||
}
|
||||
if (byId.ContainsKey(def.Id))
|
||||
{
|
||||
Debug.LogWarning($"[RaceRegistry] Duplicate Id '{def.Id}' detected on " +
|
||||
$"'{def.name}'. Earlier registration kept; rename one.");
|
||||
continue;
|
||||
}
|
||||
byId[def.Id] = def;
|
||||
}
|
||||
|
||||
Debug.Log($"[RaceRegistry] Registered {byId.Count} race(s).");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue