Files
DepthsOfGreed/Assets/Scripts/HandbookUI/HandbookController.cs
T
2026-10-14 01:45:35 +02:00

200 lines
9.4 KiB
C#

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace DepthsOfGreed.HandbookUI
{
public sealed class HandbookController : MonoBehaviour
{
[SerializeField] private GameObject closedBook;
[SerializeField] private GameObject openBook;
[SerializeField] private Button bookButton;
[SerializeField] private Button closeButton;
[SerializeField] private RectTransform closedTabsRoot;
[SerializeField] private RectTransform openTabsRoot;
[SerializeField] private Button[] closedTabs;
[SerializeField] private Button[] openTabs;
[SerializeField] private GameObject[] pages;
[SerializeField] private GameObject testTowerPrefab;
[SerializeField] private HandbookTowerCatalog towerCatalog;
[Tooltip("Wartezeit nach erfolgreichem Platzieren, bevor das Buch hochfährt.")]
[SerializeField, Min(0)] private float successfulPlacementDelay = 0.5f;
[SerializeField] private float hoverDistance = 115f;
[SerializeField] private float animationSpeed = 14f;
private readonly string[] titles = { "Bauen", "Statistik", "Upgrades", "Index" };
private Vector2[] closedPositions, openPositions;
private TMP_Text[] closedLabels, openLabels;
private RectTransform activeLayer;
private int selected, hoveredClosed = -1, hoveredOpen = -1;
private bool isOpen, bookHovered;
private Vector3 bookScale;
private BuildMenuController buildMenu;
private TestPlacementController placement;
private HandbookIndexController indexMenu;
private void Awake()
{
if (!closedBook || !openBook || !bookButton || !closeButton ||
!openTabsRoot || !closedTabsRoot || closedTabs.Length != 4 ||
openTabs.Length != 4 || pages.Length != 4)
{
Debug.LogError("Handbuch: Inspector-Verbindungen fehlen.", this);
enabled = false;
return;
}
closedPositions = PrepareTabs(closedTabs, out closedLabels, false);
openPositions = PrepareTabs(openTabs, out openLabels, true);
bookScale = bookButton.transform.localScale;
var layer = new GameObject("ActiveTabLayer", typeof(RectTransform));
activeLayer = layer.GetComponent<RectTransform>();
activeLayer.SetParent(openTabsRoot.parent, false);
activeLayer.anchorMin = openTabsRoot.anchorMin;
activeLayer.anchorMax = openTabsRoot.anchorMax;
activeLayer.pivot = openTabsRoot.pivot;
activeLayer.offsetMin = openTabsRoot.offsetMin;
activeLayer.offsetMax = openTabsRoot.offsetMax;
var background = openBook.transform.Find("Background");
if (background) activeLayer.SetSiblingIndex(background.GetSiblingIndex() + 1);
bookButton.onClick.AddListener(() => Open(0));
closeButton.onClick.AddListener(Close);
AddHover(bookButton.gameObject, () => bookHovered = true, () => bookHovered = false);
buildMenu = pages[0].AddComponent<BuildMenuController>();
buildMenu.Initialize(towerCatalog);
placement = gameObject.AddComponent<TestPlacementController>();
placement.Initialize(this, buildMenu, (RectTransform)openBook.transform, testTowerPrefab);
placement.SuccessfulPlacementDelay = successfulPlacementDelay;
buildMenu.Placement = placement;
indexMenu = pages[3].AddComponent<HandbookIndexController>();
indexMenu.Initialize(towerCatalog);
Close();
}
private Vector2[] PrepareTabs(Button[] tabs, out TMP_Text[] labels, bool opened)
{
var positions = new Vector2[tabs.Length];
labels = new TMP_Text[tabs.Length];
for (int i = 0; i < tabs.Length; i++)
{
int index = i;
if (!opened)
{
var marker = (RectTransform)tabs[i].transform;
marker.sizeDelta = new Vector2(marker.sizeDelta.x + 25f, marker.sizeDelta.y);
marker.anchoredPosition += Vector2.right * 25f;
}
positions[i] = ((RectTransform)tabs[i].transform).anchoredPosition;
labels[i] = tabs[i].GetComponentInChildren<TMP_Text>(true);
if (labels[i])
{
var text = labels[i];
text.text = titles[i];
text.raycastTarget = false;
text.fontSize = opened ? 20 : 18;
text.enableAutoSizing = true;
text.fontSizeMin = 14;
text.fontSizeMax = opened ? 20 : 18;
text.alignment = TextAlignmentOptions.MidlineLeft;
var rect = text.rectTransform;
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = new Vector2(opened ? 65 : 48, 4);
rect.offsetMax = new Vector2(-6, -4);
text.gameObject.SetActive(false);
}
tabs[i].onClick.AddListener(() => Open(index));
AddHover(tabs[i].gameObject,
() => { if (opened) hoveredOpen = index; else hoveredClosed = index; },
() => { if (opened) { if (hoveredOpen == index) hoveredOpen = -1; }
else if (hoveredClosed == index) hoveredClosed = -1; });
}
return positions;
}
private static void AddHover(GameObject target, UnityEngine.Events.UnityAction enter,
UnityEngine.Events.UnityAction exit)
{
var trigger = target.GetComponent<EventTrigger>();
if (!trigger) trigger = target.AddComponent<EventTrigger>();
var entry = new EventTrigger.Entry { eventID = EventTriggerType.PointerEnter };
entry.callback.AddListener(_ => enter());
trigger.triggers.Add(entry);
var leave = new EventTrigger.Entry { eventID = EventTriggerType.PointerExit };
leave.callback.AddListener(_ => exit());
trigger.triggers.Add(leave);
}
public void Open(int index)
{
if (index < 0 || index >= pages.Length) return;
bool samePage = isOpen && selected == index;
selected = index;
isOpen = true;
hoveredClosed = -1;
bookHovered = false;
closedBook.SetActive(false);
openBook.SetActive(true);
for (int i = 0; i < pages.Length; i++)
{
pages[i].SetActive(i == index);
var rect = (RectTransform)openTabs[i].transform;
rect.SetParent(i == selected ? activeLayer : openTabsRoot, false);
}
if (index == 3 && !samePage && indexMenu) indexMenu.ResetForEntry();
if (samePage)
foreach (var scroll in pages[index].GetComponentsInChildren<ScrollRect>(true))
scroll.verticalNormalizedPosition = 1f;
}
public void Close()
{
if (placement && placement.IsDragging) placement.Cancel();
if (placement) placement.ResetBookPresentation();
if (buildMenu) buildMenu.ResetForClose();
if (indexMenu) indexMenu.ClearSelection();
isOpen = false;
hoveredOpen = hoveredClosed = -1;
bookHovered = false;
for (int i = 0; i < openTabs.Length; i++)
{
((RectTransform)openTabs[i].transform).anchoredPosition = openPositions[i];
if (openLabels[i]) openLabels[i].gameObject.SetActive(false);
}
openBook.SetActive(false);
closedBook.SetActive(true);
}
private void Update()
{
if (isOpen && (!placement || !placement.ConsumedCancelThisFrame) &&
(!placement || !placement.IsDragging) && Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame)
Close();
float blend = 1f - Mathf.Exp(-animationSpeed * Time.unscaledDeltaTime);
if (!isOpen)
{
bookButton.transform.localScale = Vector3.Lerp(bookButton.transform.localScale,
bookScale * (bookHovered ? 1.04f : 1f), blend);
AnimateTabs(closedTabs, closedPositions, closedLabels, hoveredClosed, -1, blend);
}
else AnimateTabs(openTabs, openPositions, openLabels, hoveredOpen, selected, blend);
}
private void AnimateTabs(Button[] tabs, Vector2[] positions, TMP_Text[] labels,
int hovered, int active, float blend)
{
for (int i = 0; i < tabs.Length; i++)
{
bool show = i == hovered || i == active;
var rect = (RectTransform)tabs[i].transform;
// Reveal the full marker while retaining a small overlap with the page.
float distance = Mathf.Clamp(hoverDistance, 0f, Mathf.Max(0f, positions[i].x - 10f));
var target = positions[i] + (show ? Vector2.left * distance : Vector2.zero);
rect.anchoredPosition = Vector2.Lerp(rect.anchoredPosition, target, blend);
if (labels[i]) labels[i].gameObject.SetActive(show);
}
}
}
}