184 lines
9.0 KiB
C#
184 lines
9.0 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("Optional: component implementing IHandbookGameplay. Empty uses the prototype backend.")]
|
|
[SerializeField] private MonoBehaviour gameplayBackend;
|
|
[Tooltip("Wartezeit nach erfolgreichem Platzieren, bevor das Buch hochfährt.")]
|
|
[SerializeField, Min(0)] private float successfulPlacementDelay = 0.5f;
|
|
[Header("Tab movement (Build, Statistics, Upgrades, Index)")]
|
|
[Tooltip("Distance left per closed tab. -1 automatically uses its width. Rest position is the authored RectTransform position.")]
|
|
[SerializeField] private float[] closedTabRevealDistances = { -1f, -1f, -1f, -1f };
|
|
[Tooltip("Distance up per open tab. -1 automatically uses its height.")]
|
|
[SerializeField] private float[] openTabRevealDistances = { -1f, -1f, -1f, -1f };
|
|
[Tooltip("Overlap retained when an automatically sized tab is fully revealed.")]
|
|
[SerializeField, Min(0)] private float closedTabOverlap = 10f;
|
|
[SerializeField, Min(0)] private float openTabOverlap = 25f;
|
|
[SerializeField] private float animationSpeed = 14f;
|
|
|
|
private readonly string[] titles = { "Bauen", "Statistik", "Upgrades", "Index" };
|
|
private Vector2[] closedPositions, openPositions;
|
|
private TMP_Text[] closedLabels, openLabels;
|
|
[SerializeField] 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;
|
|
bookButton.onClick.AddListener(() => Open(0));
|
|
closeButton.onClick.AddListener(Close);
|
|
AddHover(bookButton.gameObject, () => bookHovered = true, () => bookHovered = false);
|
|
buildMenu = pages[0].GetComponent<BuildMenuController>();
|
|
buildMenu.Initialize(towerCatalog);
|
|
placement = gameObject.GetComponent<TestPlacementController>();
|
|
placement.Initialize(this, buildMenu, (RectTransform)openBook.transform, testTowerPrefab, gameplayBackend);
|
|
placement.SuccessfulPlacementDelay = successfulPlacementDelay;
|
|
buildMenu.Placement = placement;
|
|
indexMenu = pages[3].GetComponent<HandbookIndexController>();
|
|
indexMenu.Initialize(towerCatalog);
|
|
var upgrades = pages[2].GetComponent<HandbookUpgradesController>();
|
|
upgrades.Initialize(placement);
|
|
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;
|
|
positions[i] = ((RectTransform)tabs[i].transform).anchoredPosition;
|
|
labels[i] = tabs[i].GetComponentInChildren<TMP_Text>(true);
|
|
if (labels[i]) labels[i].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 && index == 3 && indexMenu) indexMenu.ResetForEntry();
|
|
}
|
|
|
|
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.
|
|
bool opened = tabs == openTabs;
|
|
var distances = opened ? openTabRevealDistances : closedTabRevealDistances;
|
|
float configured = distances != null && i < distances.Length ? distances[i] : -1f;
|
|
float automatic = opened
|
|
? Mathf.Max(0f, -positions[i].y - openTabOverlap)
|
|
: Mathf.Max(0f, positions[i].x - closedTabOverlap);
|
|
float distance = configured >= 0f ? configured : automatic;
|
|
var target = positions[i] + (show ? (opened ? Vector2.up : Vector2.left) * distance : Vector2.zero);
|
|
rect.anchoredPosition = Vector2.Lerp(rect.anchoredPosition, target, blend);
|
|
if (labels[i]) labels[i].gameObject.SetActive(show);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|