Refactor: Remove TowerRegistry, replicate towers by TypeID
Removing redundant workflow and making sure there's just once source of truth for the list of available towers.
This commit is contained in:
parent
63b8195985
commit
a87b221d87
5 changed files with 33 additions and 159 deletions
|
|
@ -68,8 +68,8 @@ namespace TD.Gameplay
|
|||
"the prefab based on the player's RaceSelection.")]
|
||||
public GameObject BuilderPrefab;
|
||||
|
||||
[Tooltip("STUB (Phase 1.8): tower roster available to this race. TowerRegistry will " +
|
||||
"filter to this list when the active player belongs to this race.")]
|
||||
[Tooltip("STUB: tower roster available to this race/builder. The draft pool or " +
|
||||
"starting deck will eventually filter to this list for this race.")]
|
||||
public TowerDefinition[] Towers;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// Assets/_Project/Scripts/Gameplay/TowerInstance.cs
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using TD.Core;
|
||||
|
|
@ -32,11 +31,11 @@ namespace TD.Gameplay
|
|||
/// <see cref="SetOccupied"/> are idempotent writes, so double-stamping is safe.</para>
|
||||
///
|
||||
/// <para><b>Definition reference replication.</b> TowerDefinition assets live in the
|
||||
/// project on all clients. We replicate the asset by name via a
|
||||
/// <see cref="NetworkVariable{T}"/> holding a <c>FixedString64Bytes</c>, then look
|
||||
/// up the asset locally. This avoids serializing the full ScriptableObject over the
|
||||
/// network. The lookup uses a <see cref="TowerRegistry"/> singleton that must be
|
||||
/// present in the scene. (Temporary: will be driven by RaceDefinition in Path E.)</para>
|
||||
/// project on all clients. We replicate the tower's <c>TowerTypeId</c> (its index in
|
||||
/// <see cref="TowerPlacementManager"/>'s catalog) via a <see cref="NetworkVariable{T}"/>,
|
||||
/// then resolve the full asset locally with <see cref="TowerPlacementManager.GetDefinition"/>.
|
||||
/// This avoids serializing the ScriptableObject over the network and reuses the same
|
||||
/// identifier placement and the deck already use — a single source of truth.</para>
|
||||
///
|
||||
/// <para><b>Combat.</b> No combat logic here yet. Combat fields live stubbed on
|
||||
/// <see cref="TowerDefinition"/>; they will be consumed by a future
|
||||
|
|
@ -58,12 +57,13 @@ namespace TD.Gameplay
|
|||
|
||||
// ----- Networked state ------------------------------------------------
|
||||
|
||||
// The name of the TowerDefinition asset for this tower. Replicated so all
|
||||
// clients can look up the full definition locally without sending the whole
|
||||
// ScriptableObject over the wire.
|
||||
private readonly NetworkVariable<FixedString64Bytes> definitionName =
|
||||
new NetworkVariable<FixedString64Bytes>(
|
||||
default,
|
||||
// The TowerTypeId (index into TowerPlacementManager's catalog) for this tower.
|
||||
// Replicated so every client resolves the full definition locally via the catalog —
|
||||
// the same identifier placement and the deck already use. 0 = unset/invalid (the
|
||||
// reserved catalog index). Replaces the old replicate-by-name + TowerRegistry path.
|
||||
private readonly NetworkVariable<int> definitionTypeId =
|
||||
new NetworkVariable<int>(
|
||||
0,
|
||||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server);
|
||||
|
||||
|
|
@ -95,8 +95,8 @@ namespace TD.Gameplay
|
|||
|
||||
// ----- Local resolved state -------------------------------------------
|
||||
|
||||
// Resolved on every client in OnNetworkSpawn from definitionName.
|
||||
// Null if the lookup fails (missing TowerRegistry or unknown name).
|
||||
// Resolved on every client in OnNetworkSpawn from definitionTypeId via the catalog.
|
||||
// Null if the lookup fails (TypeId not in the catalog on this peer).
|
||||
private TowerDefinition resolvedDefinition;
|
||||
|
||||
// ----- Pre-spawn initialization data ----------------------------------
|
||||
|
|
@ -192,7 +192,7 @@ namespace TD.Gameplay
|
|||
hasPendingInit = true;
|
||||
|
||||
// Cache the resolved definition on the server immediately — clients
|
||||
// will resolve via the registry lookup once definitionName arrives.
|
||||
// resolve from the replicated TowerTypeId via the catalog once it arrives.
|
||||
resolvedDefinition = def;
|
||||
}
|
||||
|
||||
|
|
@ -206,7 +206,17 @@ namespace TD.Gameplay
|
|||
// every client sees correct values on its very first OnNetworkSpawn.
|
||||
if (IsServer && hasPendingInit)
|
||||
{
|
||||
definitionName.Value = new FixedString64Bytes(pendingDefinition.name);
|
||||
// Resolve the catalog index for this definition and replicate that.
|
||||
int typeId = 0;
|
||||
var pm = TowerPlacementManager.Instance;
|
||||
if (pm == null || !pm.TryGetTypeId(pendingDefinition, out typeId))
|
||||
{
|
||||
Debug.LogError($"[TowerInstance] Could not resolve a TowerTypeId for " +
|
||||
$"'{pendingDefinition?.name}'. Ensure it is in the " +
|
||||
$"TowerPlacementManager catalog (towerDefinitions).");
|
||||
}
|
||||
|
||||
definitionTypeId.Value = typeId;
|
||||
anchorTile.Value = pendingAnchor;
|
||||
ownerSlot.Value = pendingOwner;
|
||||
|
||||
|
|
@ -331,27 +341,13 @@ namespace TD.Gameplay
|
|||
// Already resolved (server path via InitializeServer).
|
||||
if (resolvedDefinition != null) return;
|
||||
|
||||
string defName = definitionName.Value.ToString();
|
||||
if (string.IsNullOrEmpty(defName))
|
||||
{
|
||||
Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
|
||||
$"definitionName is empty. Cannot resolve TowerDefinition.");
|
||||
return;
|
||||
}
|
||||
|
||||
var registry = TowerRegistry.Instance;
|
||||
if (registry == null)
|
||||
{
|
||||
Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
|
||||
$"No TowerRegistry found in scene. Cannot resolve '{defName}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
resolvedDefinition = registry.Get(defName);
|
||||
int typeId = definitionTypeId.Value;
|
||||
resolvedDefinition = TowerPlacementManager.GetDefinition(typeId);
|
||||
if (resolvedDefinition == null)
|
||||
{
|
||||
Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
|
||||
$"TowerRegistry does not contain a definition named '{defName}'.");
|
||||
$"no TowerDefinition for TypeId {typeId}. Is the " +
|
||||
$"TowerPlacementManager catalog populated on this peer?");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,120 +0,0 @@
|
|||
// 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).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a9dc0fbbe4422bc479ab8db7658c082b
|
||||
|
|
@ -90,7 +90,7 @@ Unity **6.4 (6000.4.4f1)**, URP, IL2CPP, .NET Standard 2.1, Linear color space,
|
|||
- **Visuals are placeholder** throughout (primitive/cone towers, sourced enemy models). See roadmap art TODOs.
|
||||
- **Enemies lack idle animation support** — needs adding across all enemy prefabs.
|
||||
- **Tower footprint visuals don't fill their 2×2 space** — the cone/mesh sits centered and reads smaller than the tile area it occupies; should visually reflect the full footprint.
|
||||
- **Two redundant tower lists:** `TowerPlacementManager.towerDefinitions[]` (by-index catalog) and `TowerRegistry.definitions[]` (by-name, for instance resolution). Candidate for consolidation.
|
||||
- **Tower catalog is now a single source of truth:** `TowerPlacementManager.towerDefinitions[]` (by-index, = `TowerTypeId`). `TowerInstance` replicates that index and resolves via `TowerPlacementManager.GetDefinition`. *(The old by-name `TowerRegistry` was removed 2026-06-24 — add new towers only to `towerDefinitions` + `DraftPool`.)*
|
||||
- **Catalog index 0 is a reserved sentinel** (valid `TowerTypeId`s start at 1) — easy to forget when wiring the catalog in the inspector.
|
||||
- **Catalog-index identifiers aren't session-stable** — blocks cross-match persistence until a stable ID is added.
|
||||
- **Paint system frozen**; **Race vs Builder** naming unresolved; the gold **"Buy Roll available any time"** rule is provisional and may change.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue