377 lines
14 KiB
C#
377 lines
14 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.EventSystems;
|
|
using System.Collections;
|
|
|
|
/// <summary>
|
|
/// MinimapExpandController — Tap the minimap to expand it to a full-screen map view.
|
|
/// Tap again or press the close button to return to mini view.
|
|
/// Attach to the minimap RawImage UI element.
|
|
///
|
|
/// In expanded mode:
|
|
/// - Camera zooms out to show entire play area
|
|
/// - Map stops rotating (north-up for readability)
|
|
/// - Semi-transparent overlay dims the game behind
|
|
/// - Close button appears in the corner
|
|
/// </summary>
|
|
[RequireComponent(typeof(RawImage))]
|
|
public class MinimapExpandController : MonoBehaviour, IPointerClickHandler
|
|
{
|
|
[Header("Expand Settings")]
|
|
[SerializeField] private float expandedZoom = 200f; // Camera ortho size when expanded
|
|
[SerializeField] private float expandDuration = 0.25f; // Animation duration
|
|
[SerializeField] private float overlayAlpha = 0.6f; // Background dim overlay
|
|
|
|
[Header("Expanded Size (screen fraction)")]
|
|
[SerializeField] private float expandedScreenFraction = 0.85f; // 85% of screen
|
|
|
|
[Header("Close Button")]
|
|
[SerializeField] private Sprite closeButtonSprite; // Optional: custom close icon
|
|
[SerializeField] private float closeButtonSize = 50f;
|
|
|
|
// ─── Runtime ─────────────────────────────────────────────
|
|
private RectTransform _rectTransform;
|
|
private MinimapManager _minimapManager;
|
|
private Camera _minimapCam;
|
|
|
|
// Saved mini-view state
|
|
private Vector2 _miniAnchorMin;
|
|
private Vector2 _miniAnchorMax;
|
|
private Vector2 _miniOffsetMin;
|
|
private Vector2 _miniOffsetMax;
|
|
private Vector2 _miniPivot;
|
|
private Vector2 _miniSizeDelta;
|
|
private Vector3 _miniAnchoredPos;
|
|
private float _miniZoom;
|
|
private bool _miniRotateWithPlayer;
|
|
private Vector3 _miniCameraOffset;
|
|
|
|
// Expanded state
|
|
private bool _isExpanded = false;
|
|
private bool _isAnimating = false;
|
|
private GameObject _overlay;
|
|
private GameObject _closeButton;
|
|
private Canvas _parentCanvas;
|
|
|
|
// ─── Lifecycle ───────────────────────────────────────────
|
|
|
|
private void Start()
|
|
{
|
|
_rectTransform = GetComponent<RectTransform>();
|
|
_parentCanvas = GetComponentInParent<Canvas>();
|
|
|
|
// Find MinimapManager
|
|
_minimapManager = FindObjectOfType<MinimapManager>();
|
|
if (_minimapManager != null)
|
|
_minimapCam = _minimapManager.GetComponent<Camera>();
|
|
|
|
// Save initial mini-view layout
|
|
SaveMiniState();
|
|
|
|
// Create overlay (hidden by default)
|
|
CreateOverlay();
|
|
CreateCloseButton();
|
|
}
|
|
|
|
// ─── Click Handler ───────────────────────────────────────
|
|
|
|
public void OnPointerClick(PointerEventData eventData)
|
|
{
|
|
if (_isAnimating) return;
|
|
|
|
if (_isExpanded)
|
|
Collapse();
|
|
else
|
|
Expand();
|
|
}
|
|
|
|
// ─── Expand / Collapse ───────────────────────────────────
|
|
|
|
public void Expand()
|
|
{
|
|
if (_isExpanded || _isAnimating) return;
|
|
_isExpanded = true;
|
|
|
|
SaveMiniState();
|
|
|
|
// Show overlay
|
|
if (_overlay != null) _overlay.SetActive(true);
|
|
if (_closeButton != null) _closeButton.SetActive(true);
|
|
|
|
// Bring minimap on top
|
|
_rectTransform.SetAsLastSibling();
|
|
if (_closeButton != null) _closeButton.transform.SetAsLastSibling();
|
|
|
|
// Switch camera to full-map mode
|
|
if (_minimapManager != null)
|
|
{
|
|
_minimapManager.rotateWithPlayer = false;
|
|
_minimapManager.SetZoom(expandedZoom);
|
|
}
|
|
|
|
// Animate the RectTransform to fill screen
|
|
StartCoroutine(AnimateExpand());
|
|
}
|
|
|
|
public void Collapse()
|
|
{
|
|
if (!_isExpanded || _isAnimating) return;
|
|
_isExpanded = false;
|
|
|
|
// Hide overlay
|
|
if (_overlay != null) _overlay.SetActive(false);
|
|
if (_closeButton != null) _closeButton.SetActive(false);
|
|
|
|
// Restore camera
|
|
if (_minimapManager != null)
|
|
{
|
|
_minimapManager.rotateWithPlayer = _miniRotateWithPlayer;
|
|
_minimapManager.SetZoom(_miniZoom);
|
|
}
|
|
|
|
// Animate back to mini
|
|
StartCoroutine(AnimateCollapse());
|
|
}
|
|
|
|
// ─── State Save/Restore ──────────────────────────────────
|
|
|
|
private void SaveMiniState()
|
|
{
|
|
_miniAnchorMin = _rectTransform.anchorMin;
|
|
_miniAnchorMax = _rectTransform.anchorMax;
|
|
_miniOffsetMin = _rectTransform.offsetMin;
|
|
_miniOffsetMax = _rectTransform.offsetMax;
|
|
_miniPivot = _rectTransform.pivot;
|
|
_miniSizeDelta = _rectTransform.sizeDelta;
|
|
_miniAnchoredPos = _rectTransform.anchoredPosition;
|
|
|
|
if (_minimapManager != null)
|
|
{
|
|
_miniRotateWithPlayer = _minimapManager.rotateWithPlayer;
|
|
_miniCameraOffset = _minimapManager.offset;
|
|
}
|
|
|
|
if (_minimapCam != null)
|
|
_miniZoom = _minimapCam.orthographicSize;
|
|
}
|
|
|
|
private void RestoreMiniState()
|
|
{
|
|
_rectTransform.anchorMin = _miniAnchorMin;
|
|
_rectTransform.anchorMax = _miniAnchorMax;
|
|
_rectTransform.offsetMin = _miniOffsetMin;
|
|
_rectTransform.offsetMax = _miniOffsetMax;
|
|
_rectTransform.pivot = _miniPivot;
|
|
_rectTransform.sizeDelta = _miniSizeDelta;
|
|
_rectTransform.anchoredPosition = _miniAnchoredPos;
|
|
}
|
|
|
|
// ─── Animations ──────────────────────────────────────────
|
|
|
|
private IEnumerator AnimateExpand()
|
|
{
|
|
_isAnimating = true;
|
|
|
|
// Capture start state
|
|
Vector2 startAnchorMin = _rectTransform.anchorMin;
|
|
Vector2 startAnchorMax = _rectTransform.anchorMax;
|
|
Vector2 startOffsetMin = _rectTransform.offsetMin;
|
|
Vector2 startOffsetMax = _rectTransform.offsetMax;
|
|
Vector2 startPivot = _rectTransform.pivot;
|
|
|
|
// Target: centered, filling expandedScreenFraction of screen
|
|
float margin = (1f - expandedScreenFraction) / 2f;
|
|
Vector2 targetAnchorMin = new Vector2(margin, margin);
|
|
Vector2 targetAnchorMax = new Vector2(1f - margin, 1f - margin);
|
|
Vector2 targetOffsetMin = Vector2.zero;
|
|
Vector2 targetOffsetMax = Vector2.zero;
|
|
Vector2 targetPivot = new Vector2(0.5f, 0.5f);
|
|
|
|
float elapsed = 0f;
|
|
while (elapsed < expandDuration)
|
|
{
|
|
elapsed += Time.unscaledDeltaTime;
|
|
float t = Mathf.SmoothStep(0f, 1f, elapsed / expandDuration);
|
|
|
|
_rectTransform.anchorMin = Vector2.Lerp(startAnchorMin, targetAnchorMin, t);
|
|
_rectTransform.anchorMax = Vector2.Lerp(startAnchorMax, targetAnchorMax, t);
|
|
_rectTransform.offsetMin = Vector2.Lerp(startOffsetMin, targetOffsetMin, t);
|
|
_rectTransform.offsetMax = Vector2.Lerp(startOffsetMax, targetOffsetMax, t);
|
|
_rectTransform.pivot = Vector2.Lerp(startPivot, targetPivot, t);
|
|
|
|
yield return null;
|
|
}
|
|
|
|
// Ensure final values are exact
|
|
_rectTransform.anchorMin = targetAnchorMin;
|
|
_rectTransform.anchorMax = targetAnchorMax;
|
|
_rectTransform.offsetMin = targetOffsetMin;
|
|
_rectTransform.offsetMax = targetOffsetMax;
|
|
_rectTransform.pivot = targetPivot;
|
|
|
|
_isAnimating = false;
|
|
}
|
|
|
|
private IEnumerator AnimateCollapse()
|
|
{
|
|
_isAnimating = true;
|
|
|
|
// Capture current (expanded) state
|
|
Vector2 startAnchorMin = _rectTransform.anchorMin;
|
|
Vector2 startAnchorMax = _rectTransform.anchorMax;
|
|
Vector2 startOffsetMin = _rectTransform.offsetMin;
|
|
Vector2 startOffsetMax = _rectTransform.offsetMax;
|
|
Vector2 startPivot = _rectTransform.pivot;
|
|
|
|
float elapsed = 0f;
|
|
while (elapsed < expandDuration)
|
|
{
|
|
elapsed += Time.unscaledDeltaTime;
|
|
float t = Mathf.SmoothStep(0f, 1f, elapsed / expandDuration);
|
|
|
|
_rectTransform.anchorMin = Vector2.Lerp(startAnchorMin, _miniAnchorMin, t);
|
|
_rectTransform.anchorMax = Vector2.Lerp(startAnchorMax, _miniAnchorMax, t);
|
|
_rectTransform.offsetMin = Vector2.Lerp(startOffsetMin, _miniOffsetMin, t);
|
|
_rectTransform.offsetMax = Vector2.Lerp(startOffsetMax, _miniOffsetMax, t);
|
|
_rectTransform.pivot = Vector2.Lerp(startPivot, _miniPivot, t);
|
|
|
|
yield return null;
|
|
}
|
|
|
|
// Ensure exact mini state restored
|
|
RestoreMiniState();
|
|
|
|
_isAnimating = false;
|
|
}
|
|
|
|
// ─── UI Creation ─────────────────────────────────────────
|
|
|
|
private void CreateOverlay()
|
|
{
|
|
if (_parentCanvas == null) return;
|
|
|
|
_overlay = new GameObject("MinimapExpandOverlay");
|
|
_overlay.transform.SetParent(_parentCanvas.transform, false);
|
|
|
|
RectTransform overlayRect = _overlay.AddComponent<RectTransform>();
|
|
overlayRect.anchorMin = Vector2.zero;
|
|
overlayRect.anchorMax = Vector2.one;
|
|
overlayRect.offsetMin = Vector2.zero;
|
|
overlayRect.offsetMax = Vector2.zero;
|
|
|
|
Image overlayImage = _overlay.AddComponent<Image>();
|
|
overlayImage.color = new Color(0f, 0f, 0f, overlayAlpha);
|
|
overlayImage.raycastTarget = true;
|
|
|
|
// Clicking the overlay also closes the expanded map
|
|
Button overlayButton = _overlay.AddComponent<Button>();
|
|
overlayButton.transition = Selectable.Transition.None;
|
|
overlayButton.onClick.AddListener(Collapse);
|
|
|
|
_overlay.SetActive(false);
|
|
}
|
|
|
|
private void CreateCloseButton()
|
|
{
|
|
if (_parentCanvas == null) return;
|
|
|
|
_closeButton = new GameObject("MinimapCloseButton");
|
|
_closeButton.transform.SetParent(_parentCanvas.transform, false);
|
|
|
|
RectTransform btnRect = _closeButton.AddComponent<RectTransform>();
|
|
btnRect.anchorMin = new Vector2(1f, 1f);
|
|
btnRect.anchorMax = new Vector2(1f, 1f);
|
|
btnRect.pivot = new Vector2(1f, 1f);
|
|
btnRect.sizeDelta = new Vector2(closeButtonSize, closeButtonSize);
|
|
btnRect.anchoredPosition = new Vector2(-20f, -20f);
|
|
|
|
Image btnImage = _closeButton.AddComponent<Image>();
|
|
|
|
if (closeButtonSprite != null)
|
|
{
|
|
btnImage.sprite = closeButtonSprite;
|
|
}
|
|
else
|
|
{
|
|
// Create a simple "X" close icon procedurally
|
|
btnImage.color = new Color(1f, 1f, 1f, 0.9f);
|
|
btnImage.sprite = CreateCloseIcon();
|
|
}
|
|
|
|
Button btn = _closeButton.AddComponent<Button>();
|
|
btn.targetGraphic = btnImage;
|
|
btn.onClick.AddListener(Collapse);
|
|
|
|
// Add "X" text as fallback
|
|
GameObject textObj = new GameObject("CloseText");
|
|
textObj.transform.SetParent(_closeButton.transform, false);
|
|
|
|
RectTransform textRect = textObj.AddComponent<RectTransform>();
|
|
textRect.anchorMin = Vector2.zero;
|
|
textRect.anchorMax = Vector2.one;
|
|
textRect.offsetMin = Vector2.zero;
|
|
textRect.offsetMax = Vector2.zero;
|
|
|
|
Text closeText = textObj.AddComponent<Text>();
|
|
closeText.text = "✕";
|
|
closeText.fontSize = (int)(closeButtonSize * 0.6f);
|
|
closeText.alignment = TextAnchor.MiddleCenter;
|
|
closeText.color = Color.white;
|
|
closeText.font = Font.CreateDynamicFontFromOSFont("Arial", 24);
|
|
|
|
_closeButton.SetActive(false);
|
|
}
|
|
|
|
private Sprite CreateCloseIcon()
|
|
{
|
|
int size = 64;
|
|
Texture2D tex = new Texture2D(size, size);
|
|
tex.filterMode = FilterMode.Bilinear;
|
|
Color[] colors = new Color[size * size];
|
|
|
|
// Semi-transparent dark circle
|
|
Vector2 center = new Vector2(size / 2f, size / 2f);
|
|
float radius = size / 2f;
|
|
|
|
for (int y = 0; y < size; y++)
|
|
{
|
|
for (int x = 0; x < size; x++)
|
|
{
|
|
float dist = Vector2.Distance(new Vector2(x, y), center);
|
|
if (dist <= radius)
|
|
{
|
|
float edgeFade = Mathf.Clamp01((radius - dist) * 2f / radius);
|
|
colors[y * size + x] = new Color(0.2f, 0.2f, 0.2f, 0.8f * edgeFade);
|
|
}
|
|
else
|
|
{
|
|
colors[y * size + x] = Color.clear;
|
|
}
|
|
}
|
|
}
|
|
|
|
tex.SetPixels(colors);
|
|
tex.Apply();
|
|
|
|
return Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f));
|
|
}
|
|
|
|
// ─── Public API ──────────────────────────────────────────
|
|
|
|
/// <summary>Is the minimap currently expanded?</summary>
|
|
public bool IsExpanded => _isExpanded;
|
|
|
|
/// <summary>Toggle between expanded and mini views.</summary>
|
|
public void Toggle()
|
|
{
|
|
if (_isExpanded) Collapse();
|
|
else Expand();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_overlay != null) Destroy(_overlay);
|
|
if (_closeButton != null) Destroy(_closeButton);
|
|
}
|
|
}
|