// Assets/_Project/Scripts/Gameplay/TowerRegistry.cs using System.Collections.Generic; using UnityEngine; using TD.Towers; namespace TD.Gameplay { /// /// Scene singleton that holds every available in the /// current match and lets any code look one up by asset name. /// /// /// Why this exists. replicates a tower's /// definition by name (a FixedString64Bytes 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. /// /// Registration. Drag every asset into the /// Definitions list on this component in the inspector. Assets can live anywhere /// in the project — no special folder required. /// /// Path E upgrade path. In Path E the registry will filter to only the /// definitions belonging to the active match's RaceDefinition rosters. For now /// all assigned assets are registered. /// /// Plain MonoBehaviour. Not a NetworkBehaviour — the registry is /// identical on every peer (same assets, same names), so there is nothing to sync. /// public class TowerRegistry : MonoBehaviour { // ----- Singleton -------------------------------------------------- /// /// The active TowerRegistry. Null before Awake or after the scene unloads. /// Always null-check before use. /// 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 byName = new Dictionary(); // ----- 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 ------------------------------------------------- /// /// Returns the whose asset name equals /// , or null if no match is found. /// public TowerDefinition Get(string assetName) { byName.TryGetValue(assetName, out var def); return def; } /// /// Returns all registered tower definitions. Enumerates the internal /// dictionary values — do not modify the returned collection. /// public IEnumerable 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)."); } } }