chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
376
Assets/Scripts/Managers/MinimapExpandController.cs
Normal file
376
Assets/Scripts/Managers/MinimapExpandController.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user