Major updates to the HUD and selectable objects

This commit is contained in:
Matt F 2026-05-11 23:57:35 -07:00
parent 5bc757b385
commit c100db52e5
23 changed files with 1615 additions and 614 deletions

View file

@ -3,6 +3,7 @@ using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using TD.Core;
using TD.UI;
namespace TD.Gameplay
{
@ -58,10 +59,11 @@ namespace TD.Gameplay
"against this layer to determine the move target.")]
[SerializeField] private LayerMask buildablePlaneLayerMask;
[Tooltip("Physics layer mask for the builder selection trigger collider. The " +
"builder prefab's child selection collider sits on this layer. The mask " +
"must NOT overlap with BuildablePlane or TerrainGeometry; selection is " +
"a separate concern.")]
[Tooltip("Physics layer mask for selection trigger colliders. Builder selection " +
"colliders AND tower selection colliders both sit on this layer. The " +
"raycast walks up the hit hierarchy to find an ISelectable component, so " +
"any selectable kind on this layer Just Works. The mask must NOT overlap " +
"with BuildablePlane or TerrainGeometry; selection is a separate concern.")]
[SerializeField] private LayerMask selectionLayerMask;
[Tooltip("Physics layer mask for build-site visual click targets. The " +
@ -113,12 +115,19 @@ namespace TD.Gameplay
if (mouse == null) return;
bool isPlacing = IsLocalPlayerPlacing();
Vector2 mousePos = mouse.position.ReadValue();
// UI Toolkit dispatches button click events AFTER Update runs, but raw mouse
// input is already true this frame. Without this gate, clicking a HUD button
// also fires HandleLeftClickSelection — the raycast misses the builder collider
// and deselects before the button's action fires. Same risk on right-click.
bool pointerOverHud = HUDController.IsPointerOverInteractiveHud(mousePos);
// Left-click: selection. Suppressed during placement mode (left-click is
// the placement-submit gesture there).
if (!isPlacing && mouse.leftButton.wasPressedThisFrame)
// the placement-submit gesture there) and when the pointer is over HUD.
if (!isPlacing && !pointerOverHud && mouse.leftButton.wasPressedThisFrame)
{
HandleLeftClickSelection(mouse.position.ReadValue());
HandleLeftClickSelection(mousePos);
}
// Escape: clear selection. Allowed during placement mode too — Escape never
@ -129,11 +138,12 @@ namespace TD.Gameplay
}
// Right-click. Suppressed entirely during placement mode (TowerPlacementController
// handles right-click as cancel-placement there).
// handles right-click as cancel-placement there) and when over HUD.
if (isPlacing) return;
if (pointerOverHud) return;
if (!mouse.rightButton.wasPressedThisFrame) return;
HandleRightClick(mouse.position.ReadValue());
HandleRightClick(mousePos);
}
// ----- Selection (left-click) -------------------------------------
@ -147,17 +157,24 @@ namespace TD.Gameplay
if (cam == null) return;
Ray ray = cam.ScreenPointToRay(new Vector3(screenPos.x, screenPos.y, 0f));
// Single raycast against the unified Selection layer. Whatever we hit, walk
// up its hierarchy to find an ISelectable component (Builder, Tower, or
// any future kind). The closest hit wins automatically — no priority logic
// needed because builders and towers don't visually overlap in practice.
if (Physics.Raycast(ray, out RaycastHit hit, raycastMaxDistance, selectionLayerMask))
{
// Walk up the hierarchy to find a Builder component (the selection
// collider may sit on a child of the Builder's root).
var hitBuilder = hit.collider.GetComponentInParent<Builder>();
// Only allow selecting OUR builder. A click on someone else's builder
// collider clears our selection rather than selecting theirs.
if (hitBuilder != null && hitBuilder == builder)
var hitSelectable = hit.collider.GetComponentInParent<ISelectable>();
if (hitSelectable != null)
{
selection.Select(builder);
// Don't let players select someone else's builder. Treat that as
// "clicked empty" so we clear, rather than steal their selection.
if (hitSelectable is Builder b && b != builder)
{
selection.Clear();
return;
}
selection.Select(hitSelectable);
return;
}
}