This commit is contained in:
Matt F 2026-05-10 22:26:55 -07:00
parent f7720a9915
commit 6c37e569ab
18 changed files with 1169 additions and 323 deletions

View file

@ -4,6 +4,7 @@ using Unity.Netcode;
using UnityEngine;
using TD.Core;
using TD.Towers;
using TD.UI.Minimap;
namespace TD.Gameplay
{
@ -45,7 +46,7 @@ namespace TD.Gameplay
/// traversal.</para>
/// </remarks>
[RequireComponent(typeof(NetworkObject))]
public class Builder : NetworkBehaviour
public class Builder : NetworkBehaviour, IMinimapEntity
{
// ----- Static registry --------------------------------------------
@ -188,6 +189,7 @@ namespace TD.Gameplay
public override void OnNetworkSpawn()
{
s_byClientId[OwnerClientId] = this;
MinimapEntityRegistry.Register(this);
if (IsServer)
{
@ -217,6 +219,7 @@ namespace TD.Gameplay
{
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
s_byClientId.Remove(OwnerClientId);
MinimapEntityRegistry.Deregister(this);
// Server-only cleanup: despawn any remaining build-site visuals so they
// don't leak when a player disconnects mid-construction.
@ -233,6 +236,32 @@ namespace TD.Gameplay
// (NetworkList is owned by NGO; no manual Dispose needed in NGO 2.x.)
// ----- IMinimapEntity ---------------------------------------------
//
// Read every minimap refresh tick. Position is read live (NetworkTransform
// interpolation handles remote-client smoothing). Color comes from the same
// OwnerClientId → PlayerSlot stub mapping used by ApplyOwnerColor; both will pick
// up the real mapping when MatchState lands.
Vector3 IMinimapEntity.WorldPosition => transform.position;
Color IMinimapEntity.MinimapColor
{
get
{
byte slotByte = (byte)(OwnerClientId + 1);
PlayerSlot slot = (slotByte >= 1 && slotByte <= 9)
? (PlayerSlot)slotByte : PlayerSlot.None;
return PlayerColors.Get(slot);
}
}
MinimapIconKind IMinimapEntity.IconKind => MinimapIconKind.Builder;
// Diameter of the builder dot in world units. The view enforces a pixel-size floor so
// builders remain visible at full zoom-out regardless of this value.
float IMinimapEntity.MinimapWorldSize => 0.6f;
// ----- Owner color tinting ----------------------------------------
// Lazily allocated; reused across renderers. Construction in a field initializer

View file

@ -3,6 +3,7 @@ using UnityEngine;
using UnityEngine.InputSystem;
using TD.Core;
using TD.Levels;
using TD.UI;
namespace TD.Gameplay
{
@ -218,6 +219,12 @@ namespace TD.Gameplay
var kb = Keyboard.current;
if (mouse == null) return;
// If the cursor is over an interactive HUD element (e.g., the minimap, which has
// its own scroll-wheel zoom), don't also drive the world camera. UI Toolkit's
// WheelEvent.StopPropagation only blocks UI-side bubbling — it has no effect on
// raw Input System polling, so we have to gate here explicitly.
if (HUDController.IsPointerOverInteractiveHud(mouse.position.ReadValue())) return;
// Scroll wheel values are inconsistent across platforms and devices:
// - Windows click-wheel mice: ±120 per click (legacy Win32 convention)
// - Free-spin wheels and trackpads: small fractional values per tick

View file

@ -4,6 +4,7 @@ using Unity.Netcode;
using UnityEngine;
using TD.Core;
using TD.Towers;
using TD.UI.Minimap;
namespace TD.Gameplay
{
@ -42,7 +43,7 @@ namespace TD.Gameplay
/// <c>TowerCombat</c> component added to the same prefab.</para>
/// </remarks>
[RequireComponent(typeof(NetworkObject))]
public class TowerInstance : NetworkBehaviour
public class TowerInstance : NetworkBehaviour, IMinimapEntity
{
// ----- Networked state ------------------------------------------------
@ -167,6 +168,9 @@ namespace TD.Gameplay
// Apply owner color to the mesh renderer.
ApplyOwnerColor();
// Register for minimap rendering.
MinimapEntityRegistry.Register(this);
if (resolvedDefinition != null)
{
Debug.Log($"[TowerInstance] Spawned '{resolvedDefinition.DisplayName}' " +
@ -180,6 +184,33 @@ namespace TD.Gameplay
// Un-stamp the footprint when the tower is destroyed (sold, wave end, etc.)
// so the tiles become walkable and buildable again.
StampFootprint(walkable: true, occupied: false);
MinimapEntityRegistry.Deregister(this);
}
// ----- IMinimapEntity -------------------------------------------------
//
// Towers are static, so WorldPosition is cheap (no movement to track). Color reflects
// the replicated ownerSlot; reads safely on every client because ownerSlot is set in
// OnNetworkSpawn before this entity is added to the registry.
Vector3 IMinimapEntity.WorldPosition => transform.position;
Color IMinimapEntity.MinimapColor => PlayerColors.Get(ownerSlot.Value);
MinimapIconKind IMinimapEntity.IconKind => MinimapIconKind.Tower;
// Tower footprint in world units. Uses the larger axis if the footprint isn't square,
// so an Nx1 tower still occupies its full long-side on the minimap.
// Falls back to one tile if the definition hasn't resolved yet (transient, harmless).
float IMinimapEntity.MinimapWorldSize
{
get
{
if (resolvedDefinition == null) return GridCoordinates.TILE_SIZE;
int extent = Mathf.Max(
resolvedDefinition.FootprintSize.x,
resolvedDefinition.FootprintSize.y);
return extent * GridCoordinates.TILE_SIZE;
}
}
// ----- Private helpers ------------------------------------------------