UnityTowerDefense/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs

120 lines
4.6 KiB
C#

// Assets/_Project/Scripts/Gameplay/TowerRegistry.cs
using System.Collections.Generic;
using UnityEngine;
using TD.Towers;
namespace TD.Gameplay
{
/// <summary>
/// Scene singleton that holds every <see cref="TowerDefinition"/> available in the
/// current match and lets any code look one up by asset name.
/// </summary>
/// <remarks>
/// <para><b>Why this exists.</b> <see cref="TowerInstance"/> replicates a tower's
/// definition by name (a <c>FixedString64Bytes</c> over the network), then resolves
/// the full ScriptableObject locally on every client. TowerRegistry is the lookup
/// table that makes that resolution possible without hard-coding asset paths.</para>
///
/// <para><b>Registration.</b> Drag every <see cref="TowerDefinition"/> asset into the
/// <c>Definitions</c> list on this component in the inspector. Assets can live anywhere
/// in the project — no special folder required.</para>
///
/// <para><b>Path E upgrade path.</b> In Path E the registry will filter to only the
/// definitions belonging to the active match's <c>RaceDefinition</c> rosters. For now
/// all assigned assets are registered.</para>
///
/// <para><b>Plain MonoBehaviour.</b> Not a NetworkBehaviour — the registry is
/// identical on every peer (same assets, same names), so there is nothing to sync.</para>
/// </remarks>
public class TowerRegistry : MonoBehaviour
{
// ----- Singleton --------------------------------------------------
/// <summary>
/// The active TowerRegistry. Null before Awake or after the scene unloads.
/// Always null-check before use.
/// </summary>
public static TowerRegistry Instance { get; private set; }
// ----- Inspector --------------------------------------------------
[Tooltip("All TowerDefinition assets available in this match. " +
"Drag assets here from Assets/_Project/Data/TowerDefinitions/ " +
"(or wherever they live). Asset name is used as the registry key.")]
[SerializeField] private TowerDefinition[] definitions;
// ----- Internal lookup table --------------------------------------
// Keyed by TowerDefinition.name (the asset name, not DisplayName).
private readonly Dictionary<string, TowerDefinition> byName
= new Dictionary<string, TowerDefinition>();
// ----- Lifecycle --------------------------------------------------
private void Awake()
{
if (Instance != null && Instance != this)
{
Debug.LogError("[TowerRegistry] Multiple instances detected. " +
"Only one TowerRegistry should exist per scene.");
return;
}
Instance = this;
BuildLookupTable();
}
private void OnDestroy()
{
if (Instance == this) Instance = null;
}
// ----- Public API -------------------------------------------------
/// <summary>
/// Returns the <see cref="TowerDefinition"/> whose asset name equals
/// <paramref name="assetName"/>, or null if no match is found.
/// </summary>
public TowerDefinition Get(string assetName)
{
byName.TryGetValue(assetName, out var def);
return def;
}
/// <summary>
/// Returns all registered tower definitions. Enumerates the internal
/// dictionary values — do not modify the returned collection.
/// </summary>
public IEnumerable<TowerDefinition> All => byName.Values;
// ----- Private ----------------------------------------------------
private void BuildLookupTable()
{
byName.Clear();
if (definitions == null || definitions.Length == 0)
{
Debug.LogWarning("[TowerRegistry] No TowerDefinition assets assigned. " +
"Drag assets into the Definitions list on the TowerRegistry component.");
return;
}
foreach (var def in definitions)
{
if (def == null) continue;
if (byName.ContainsKey(def.name))
{
Debug.LogWarning($"[TowerRegistry] Duplicate asset name '{def.name}'. " +
$"Only the first entry will be used. Rename one of the assets.");
continue;
}
byName[def.name] = def;
}
Debug.Log($"[TowerRegistry] Registered {byName.Count} tower definition(s).");
}
}
}