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 UnityEngine;
using UnityEngine.UIElements;
using TD.Gameplay;
using TD.Towers;
using TD.UI.Minimap;
namespace TD.UI
{
@ -24,9 +25,9 @@ namespace TD.UI
[Tooltip("The TowerPlacementManager NetworkObject in the scene.")]
[SerializeField] private TowerPlacementManager placementManager;
[Header("Minimap")]
[Tooltip("RenderTexture that the minimap camera renders into.")]
[SerializeField] private RenderTexture minimapRenderTexture;
[Tooltip("The local client's CameraController. Used by the minimap for click-to-jump " +
"and drag-to-pan.")]
[SerializeField] private CameraController cameraController;
[Header("Settings")]
[SerializeField] private float rejectionMessageDuration = 2.5f;
@ -48,6 +49,52 @@ namespace TD.UI
private Coroutine rejectionFadeCoroutine;
private bool gridPopulated;
private bool uiInitialized;
private MinimapView minimapView;
private IPanel myPanel; // tracked separately so OnDestroy only clears the static if it still points at us
// ----- Static hit-test probe --------------------------------------
// Set when InitializeUI succeeds; cleared on OnDestroy. Non-UI systems (camera,
// input handlers) can query IsPointerOverInteractiveHud without taking a direct
// reference to HUDController.
private static IPanel s_hudPanel;
/// <summary>
/// True if <paramref name="screenMousePos"/> falls over an interactive (non-ignore)
/// HUD element. Non-UI systems that consume mouse input (camera scroll-zoom, edge-pan)
/// should gate their handling on this so a cursor over the minimap, command grid, or
/// any other interactive HUD region doesn't drive both the HUD and the world at once.
/// </summary>
/// <remarks>
/// Convention: <paramref name="screenMousePos"/> uses Unity Input System screen coords
/// (origin bottom-left, y up). Returns false before the HUD has initialized; safe to
/// call from any system at any time.
/// </remarks>
public static bool IsPointerOverInteractiveHud(Vector2 screenMousePos)
{
if (s_hudPanel == null) return false;
// Coord convention rabbit hole:
// - Screen mouse position: origin bottom-left, y up (Unity Input System).
// - UI Toolkit panel coords: origin top-left, y down.
//
// RuntimePanelUtils.ScreenToPanel converts the SCALE (e.g., reference resolution
// vs. actual resolution) but does NOT flip Y. We flip manually using the visual
// tree's height so the result works regardless of PanelSettings scale mode.
//
// Subtle: visualTree.worldBound height may be 0 for one frame on the very first
// layout pass. The caller (CameraController) checks the result against "is over
// interactive HUD"; a one-frame false positive (camera zooms when it shouldn't)
// is harmless and self-corrects the next frame.
Vector2 scaled = RuntimePanelUtils.ScreenToPanel(s_hudPanel, screenMousePos);
float panelHeight = s_hudPanel.visualTree.worldBound.height;
Vector2 panelPos = new Vector2(scaled.x, panelHeight - scaled.y);
// panel.Pick returns null when the topmost element under the point has
// PickingMode.Ignore (or there's no element there at all). Non-null means an
// interactive HUD element is under the cursor.
return s_hudPanel.Pick(panelPos) != null;
}
// ----- Lifecycle --------------------------------------------------
@ -101,15 +148,24 @@ namespace TD.UI
SetEnabled(root, "upgrade-btn", false);
SetEnabled(root, "sell-btn", false);
// Minimap RenderTexture.
if (minimapRenderTexture != null)
// Minimap. The MinimapView owns the two sub-elements (terrain + entity overlay)
// and drives them; we just hand it the host container and the camera controller.
// Bake is deferred until LevelLoader is ready — view tries each frame in Tick().
var minimapContainer = root.Q<VisualElement>("minimap");
if (minimapContainer != null)
{
var minimap = root.Q<VisualElement>("minimap");
if (minimap != null)
minimap.style.backgroundImage =
Background.FromRenderTexture(minimapRenderTexture);
if (cameraController == null)
Debug.LogWarning("[HUDController] cameraController not assigned. " +
"Click-to-jump and drag-to-pan on the minimap will be disabled.");
minimapView = new MinimapView(minimapContainer, cameraController);
}
// Publish the panel so non-UI systems can query "is pointer over the HUD".
// Stored on `myPanel` too so OnDestroy only clears the static if it still
// points at this instance (defensive against re-creation overlap).
myPanel = root.panel;
s_hudPanel = myPanel;
uiInitialized = true;
}
@ -131,6 +187,17 @@ namespace TD.UI
TryPopulateCommandGrid();
RefreshGoldDisplay();
minimapView?.Tick();
}
private void OnDestroy()
{
minimapView?.Dispose();
minimapView = null;
if (s_hudPanel != null && s_hudPanel == myPanel)
s_hudPanel = null;
myPanel = null;
}
// ----- Gold display -----------------------------------------------