// 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. /// /// Auto-discovery. On Awake, all assets /// under Resources/TowerDefinitions/ are loaded automatically. No inspector /// drag-and-drop required — add a new asset to that folder and it is registered at /// runtime with no other changes needed. This scales cleanly to 100+ tower types. /// /// 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 assets in the Resources folder 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; } // ----- Constants -------------------------------------------------- /// /// Resources-relative folder path that TowerDefinition assets must live under /// to be auto-discovered. Create this folder if it doesn't exist. /// Full path: Assets/Resources/TowerDefinitions/ /// private const string ResourcesFolder = "TowerDefinitions"; // ----- 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(); // Resources.LoadAll finds every TowerDefinition asset anywhere under // Assets/Resources/TowerDefinitions/ (including sub-folders). // No manual registration needed — drop an asset in the folder and it // is available on the next play session. var loaded = Resources.LoadAll(ResourcesFolder); if (loaded.Length == 0) { Debug.LogWarning($"[TowerRegistry] No TowerDefinition assets found under " + $"Resources/{ResourcesFolder}/. " + $"Create the folder and add TowerDefinition assets to it."); return; } foreach (var def in loaded) { 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] Auto-discovered and registered " + $"{byName.Count} tower definition(s) from Resources/{ResourcesFolder}/."); } } }