using UnityEngine; using System.Collections.Generic; using Unheimlich.Core; using Unheimlich.Features.DetectiveBoard.Data; namespace Unheimlich.Features.DetectiveBoard.UI { /// /// Manages the clue hand fan displayed at the bottom of the screen (Screen Space canvas). /// Reuses the PostItNode prefab for each card — ClueHandCard is added at runtime. /// Hovering a card pops it up and straightens it; clicking starts a lerp-depth /// placement animation then hands the clue to DetectiveBoardCanvas. /// public class ClueHandUI : Singleton { [Header("References")] [SerializeField] private GameObject _postItNodePrefab; [SerializeField] private RectTransform _handContainer; [Header("Fan Layout Settings")] [SerializeField] private float _cardSpacingPixels = 80f; [SerializeField] private float _fanArcRadius = 800f; [SerializeField] private float _cardBaseY = -120f; [SerializeField] private float _maxFanHalfAngle = 40f; [Header("Placement Settings")] // [SerializeField] private float _placementLerpSpeed = 5f; private readonly List _handCards = new List(); private bool _isPlacing; protected override void OnAwake() { } protected override void OnEnabled() { // With Scene Reload disabled, stale card references from the previous session // may point to destroyed GameObjects. Clear and rebuild from manager state. if (_handCards.Count > 0) { foreach (ClueHandCard card in _handCards) { if (card != null && card.gameObject != null) Destroy(card.gameObject); } _handCards.Clear(); } if (DetectiveBoardManager.Instance != null) { foreach (var clue in DetectiveBoardManager.Instance.GetUnlockedClues()) { // Skip clues already placed on the board — they must not reappear in the hand if (DetectiveBoardCanvas.Instance != null && DetectiveBoardCanvas.Instance.HasNodeForClue(clue.ClueID)) continue; AddClueCard(clue); } } } private void Start() { if (DetectiveBoardManager.Instance != null) { DetectiveBoardManager.Instance.OnClueUnlocked += OnClueUnlocked; foreach (var clue in DetectiveBoardManager.Instance.GetUnlockedClues()) AddClueCard(clue); } // Start hidden — shown when the board activates SetHandVisible(false); } private void OnDestroy() { if (DetectiveBoardManager.Instance != null) DetectiveBoardManager.Instance.OnClueUnlocked -= OnClueUnlocked; } private void OnClueUnlocked(ClueData clueData) { AddClueCard(clueData); } // ────────────────────────────────────────────────────────────────────── // Card management // ────────────────────────────────────────────────────────────────────── /// /// Instantiates a PostItNode prefab, injects ClueHandCard, and adds it to the fan. /// public void AddClueCard(ClueData clueData) { if (_postItNodePrefab == null || _handContainer == null) { Debug.LogError("[ClueHandUI] Missing _postItNodePrefab or _handContainer reference."); return; } if (_handCards.Exists(c => c.ClueData != null && c.ClueData.ClueID == clueData.ClueID)) return; // Instantiate the PostItNode prefab inside the screen-space hand container GameObject cardObj = Instantiate(_postItNodePrefab, _handContainer); // PostItNode is nested one level inside "Canvas (Environment)" wrapper in the prefab // — grab it directly PostItNode node = cardObj.GetComponentInChildren(true); if (node == null) { Debug.LogError("[ClueHandUI] PostItNode not found on prefab."); Destroy(cardObj); return; } // Create a dummy NodeData for the hand display (position handled by fan layout) PostItNodeData dummyData = new PostItNodeData(clueData.ClueID, Vector2.zero); node.Initialize(clueData, dummyData); // Add ClueHandCard wrapper on the PostItNode GameObject ClueHandCard card = node.gameObject.AddComponent(); card.Initialize(node, this); // Move the PostItNode GameObject up to be a direct child of _handContainer // so its RectTransform is in screen space node.transform.SetParent(_handContainer, false); // Destroy the now-empty prefab root (the "Canvas (Environment)" wrapper) if (cardObj != node.gameObject) Destroy(cardObj); // Normalize scale — PostItNode prefab uses 0.5 scale for world space, reset for screen space node.RectTransform.localScale = Vector3.one; _handCards.Add(card); RebuildFanLayout(); } /// /// Recomputes fan arc positions and rotations for all non-placed cards. /// Uses fixed pixel spacing between cards, capped by a max half-angle. /// private void RebuildFanLayout() { List visible = _handCards.FindAll(c => !c.IsPlaced); int count = visible.Count; if (count == 0) return; // Derive the angle step from pixel spacing on the arc float angleStepDeg = (_cardSpacingPixels / _fanArcRadius) * Mathf.Rad2Deg; // Total spread is (count-1) steps, capped so it never exceeds _maxFanHalfAngle on each side float totalHalfAngle = angleStepDeg * (count - 1) * 0.5f; float clampedHalfAngle = Mathf.Min(totalHalfAngle, _maxFanHalfAngle); for (int i = 0; i < count; i++) { float t = count == 1 ? 0.5f : (float)i / (count - 1); float angle = Mathf.Lerp(-clampedHalfAngle, clampedHalfAngle, t); float angleRad = angle * Mathf.Deg2Rad; float x = _fanArcRadius * Mathf.Sin(angleRad); float y = _cardBaseY - _fanArcRadius * (1f - Mathf.Cos(angleRad)); visible[i].SetFanSlot(new Vector2(x, y), -angle); } } // ────────────────────────────────────────────────────────────────────── // Hover callbacks // ────────────────────────────────────────────────────────────────────── /// Brings the hovered card in front of its siblings. public void OnCardHovered(ClueHandCard card) { card.transform.SetAsLastSibling(); } /// Restores natural sibling order after hover ends. public void OnCardUnhovered(ClueHandCard card) { RebuildSiblingOrder(); } private void RebuildSiblingOrder() { int index = 0; foreach (ClueHandCard c in _handCards) { if (!c.IsPlaced) c.transform.SetSiblingIndex(index++); } } // ────────────────────────────────────────────────────────────────────── // Selection & placement // ────────────────────────────────────────────────────────────────────── /// /// Called when the player clicks a card. Creates the node on the board /// directly under the mouse cursor and immediately starts drag mode. /// public void OnCardSelected(ClueHandCard card) { if (_isPlacing) return; DetectiveBoardCanvas boardCanvas = DetectiveBoardCanvas.Instance; if (boardCanvas == null) { Debug.LogWarning("[ClueHandUI] DetectiveBoardCanvas not found."); return; } DetectiveBoardInteraction interaction = DetectiveBoardInteraction.Instance; if (interaction == null) { Debug.LogWarning("[ClueHandUI] DetectiveBoardInteraction not found."); return; } boardCanvas.AddClueNode(card.ClueData); PostItNode boardNode = boardCanvas.GetNodeForClue(card.ClueData.ClueID); if (boardNode == null) { Debug.LogWarning("[ClueHandUI] Node not found on board after AddClueNode."); return; } // Position node under the mouse on the board canvas plane, then start drag interaction.SpawnNodeUnderMouseAndDrag(boardNode); card.MarkAsPlaced(); RebuildFanLayout(); } // ────────────────────────────────────────────────────────────────────── // Visibility // ────────────────────────────────────────────────────────────────────── /// /// Shows or hides the fan hand. Called by DetectiveBoardController. /// public void SetHandVisible(bool visible) { if (_handContainer != null) _handContainer.gameObject.SetActive(visible); } } }