Files
DeviantMobile-Rohan/Assets/Scripts/Managers/MinimapExpandController.cs

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);
}
}