241 lines
11 KiB
C#
241 lines
11 KiB
C#
using System;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace ShrinkTutorial
|
|
{
|
|
public class ShrinkTutorialDialog : MonoBehaviour
|
|
{
|
|
[Header("Explicit scene bindings")]
|
|
[SerializeField] private RectTransform _rectTransform;
|
|
[SerializeField] private RectTransform _arrowRectTransform;
|
|
[SerializeField] private TextMeshProUGUI _contentText;
|
|
[SerializeField] private GameObject _skipButtonObject;
|
|
private Action _skipAction;
|
|
|
|
/// <summary>
|
|
/// Uses UI objects that already exist in the scene. This path intentionally does not
|
|
/// create children or add listeners at runtime; scene-authored persistent callbacks
|
|
/// remain the source of truth.
|
|
/// </summary>
|
|
public bool BindExisting()
|
|
{
|
|
if (!_rectTransform)
|
|
_rectTransform = GetComponent<RectTransform>();
|
|
|
|
if (_rectTransform && _arrowRectTransform && _contentText)
|
|
return true;
|
|
|
|
Debug.LogError("[ShrinkTutorial] Explicit dialog bindings are incomplete.", this);
|
|
return false;
|
|
}
|
|
|
|
public void Initialize(Transform parent, Action skipAction)
|
|
{
|
|
_skipAction = skipAction;
|
|
_rectTransform = GetComponent<RectTransform>();
|
|
if (!_rectTransform)
|
|
_rectTransform = gameObject.AddComponent<RectTransform>();
|
|
_rectTransform.SetParent(parent, false);
|
|
_rectTransform.sizeDelta = new Vector2(360f, 180f);
|
|
_rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
|
|
_rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
|
|
_rectTransform.pivot = new Vector2(0.5f, 0.5f);
|
|
|
|
var background = gameObject.AddComponent<Image>();
|
|
background.color = new Color(0.08f, 0.1f, 0.14f, 0.96f);
|
|
background.raycastTarget = true;
|
|
|
|
var content = CreateText("Content", "Tutorial", 24, TextAlignmentOptions.TopLeft);
|
|
var contentRect = content.rectTransform;
|
|
contentRect.SetParent(_rectTransform, false);
|
|
contentRect.anchorMin = new Vector2(0f, 0f);
|
|
contentRect.anchorMax = new Vector2(1f, 1f);
|
|
contentRect.offsetMin = new Vector2(24f, 64f);
|
|
contentRect.offsetMax = new Vector2(-24f, -24f);
|
|
_contentText = content;
|
|
|
|
var skipButton = new GameObject("SkipButton", typeof(RectTransform), typeof(Image), typeof(Button));
|
|
var skipRect = skipButton.GetComponent<RectTransform>();
|
|
skipRect.SetParent(_rectTransform, false);
|
|
skipRect.anchorMin = new Vector2(1f, 0f);
|
|
skipRect.anchorMax = new Vector2(1f, 0f);
|
|
skipRect.pivot = new Vector2(1f, 0f);
|
|
skipRect.anchoredPosition = new Vector2(-16f, 16f);
|
|
skipRect.sizeDelta = new Vector2(96f, 36f);
|
|
|
|
var skipImage = skipButton.GetComponent<Image>();
|
|
skipImage.color = new Color(0.22f, 0.34f, 0.5f, 1f);
|
|
|
|
var skipButtonComponent = skipButton.GetComponent<Button>();
|
|
skipButtonComponent.onClick.AddListener(() => _skipAction?.Invoke());
|
|
_skipButtonObject = skipButton;
|
|
|
|
var skipLabel = CreateText("Label", "Skip", 20, TextAlignmentOptions.Center);
|
|
var skipLabelRect = skipLabel.rectTransform;
|
|
skipLabelRect.SetParent(skipRect, false);
|
|
skipLabelRect.anchorMin = Vector2.zero;
|
|
skipLabelRect.anchorMax = Vector2.one;
|
|
skipLabelRect.offsetMin = Vector2.zero;
|
|
skipLabelRect.offsetMax = Vector2.zero;
|
|
|
|
var arrow = new GameObject("Arrow", typeof(RectTransform), typeof(Image));
|
|
_arrowRectTransform = arrow.GetComponent<RectTransform>();
|
|
_arrowRectTransform.SetParent(_rectTransform, false);
|
|
_arrowRectTransform.sizeDelta = new Vector2(20f, 20f);
|
|
_arrowRectTransform.localRotation = Quaternion.Euler(0f, 0f, 45f);
|
|
var arrowImage = arrow.GetComponent<Image>();
|
|
arrowImage.color = background.color;
|
|
arrowImage.raycastTarget = false;
|
|
}
|
|
|
|
public void Show(string text, Rect? targetRect, ShrinkTutorialDialogAnchor anchor, Vector2 offset, bool showArrow)
|
|
{
|
|
if (!_rectTransform)
|
|
return;
|
|
|
|
gameObject.SetActive(true);
|
|
_contentText.text = string.IsNullOrWhiteSpace(text) ? " " : text;
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(_rectTransform);
|
|
|
|
if (!targetRect.HasValue)
|
|
{
|
|
_rectTransform.anchoredPosition = offset;
|
|
_arrowRectTransform.gameObject.SetActive(false);
|
|
return;
|
|
}
|
|
|
|
var canvasRect = _rectTransform.parent as RectTransform;
|
|
var targetCenterScreen = targetRect.Value.center;
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, targetCenterScreen, null, out var targetCenterLocal);
|
|
var localTargetRect = ConvertScreenRectToLocalRect(canvasRect, targetRect.Value);
|
|
|
|
var resolvedAnchor = ResolveAnchor(localTargetRect, anchor, canvasRect.rect);
|
|
var panelSize = _rectTransform.rect.size;
|
|
var margin = 24f;
|
|
var position = targetCenterLocal + offset;
|
|
|
|
switch (resolvedAnchor)
|
|
{
|
|
case ShrinkTutorialDialogAnchor.Top:
|
|
position += new Vector2(0f, localTargetRect.height * 0.5f + panelSize.y * 0.5f + margin);
|
|
break;
|
|
case ShrinkTutorialDialogAnchor.Bottom:
|
|
position += new Vector2(0f, -(localTargetRect.height * 0.5f + panelSize.y * 0.5f + margin));
|
|
break;
|
|
case ShrinkTutorialDialogAnchor.Left:
|
|
position += new Vector2(-(localTargetRect.width * 0.5f + panelSize.x * 0.5f + margin), 0f);
|
|
break;
|
|
case ShrinkTutorialDialogAnchor.Right:
|
|
position += new Vector2(localTargetRect.width * 0.5f + panelSize.x * 0.5f + margin, 0f);
|
|
break;
|
|
}
|
|
|
|
var halfCanvas = canvasRect.rect.size * 0.5f;
|
|
position.x = Mathf.Clamp(position.x, -halfCanvas.x + panelSize.x * 0.5f + 12f, halfCanvas.x - panelSize.x * 0.5f - 12f);
|
|
position.y = Mathf.Clamp(position.y, -halfCanvas.y + panelSize.y * 0.5f + 12f, halfCanvas.y - panelSize.y * 0.5f - 12f);
|
|
_rectTransform.anchoredPosition = position;
|
|
|
|
ConfigureArrow(resolvedAnchor, showArrow);
|
|
}
|
|
|
|
public void Hide()
|
|
{
|
|
if (gameObject.activeSelf)
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
public bool ContainsScreenPoint(Vector2 screenPoint)
|
|
{
|
|
return _rectTransform && gameObject.activeInHierarchy &&
|
|
RectTransformUtility.RectangleContainsScreenPoint(_rectTransform, screenPoint, null);
|
|
}
|
|
|
|
public void SetSkipVisible(bool visible)
|
|
{
|
|
if (_skipButtonObject)
|
|
_skipButtonObject.SetActive(visible);
|
|
}
|
|
|
|
private void ConfigureArrow(ShrinkTutorialDialogAnchor anchor, bool showArrow)
|
|
{
|
|
_arrowRectTransform.gameObject.SetActive(showArrow);
|
|
if (!showArrow)
|
|
return;
|
|
|
|
switch (anchor)
|
|
{
|
|
case ShrinkTutorialDialogAnchor.Top:
|
|
_arrowRectTransform.anchorMin = new Vector2(0.5f, 0f);
|
|
_arrowRectTransform.anchorMax = new Vector2(0.5f, 0f);
|
|
_arrowRectTransform.anchoredPosition = new Vector2(0f, -10f);
|
|
break;
|
|
case ShrinkTutorialDialogAnchor.Bottom:
|
|
_arrowRectTransform.anchorMin = new Vector2(0.5f, 1f);
|
|
_arrowRectTransform.anchorMax = new Vector2(0.5f, 1f);
|
|
_arrowRectTransform.anchoredPosition = new Vector2(0f, 10f);
|
|
break;
|
|
case ShrinkTutorialDialogAnchor.Left:
|
|
_arrowRectTransform.anchorMin = new Vector2(1f, 0.5f);
|
|
_arrowRectTransform.anchorMax = new Vector2(1f, 0.5f);
|
|
_arrowRectTransform.anchoredPosition = new Vector2(10f, 0f);
|
|
break;
|
|
case ShrinkTutorialDialogAnchor.Right:
|
|
_arrowRectTransform.anchorMin = new Vector2(0f, 0.5f);
|
|
_arrowRectTransform.anchorMax = new Vector2(0f, 0.5f);
|
|
_arrowRectTransform.anchoredPosition = new Vector2(-10f, 0f);
|
|
break;
|
|
default:
|
|
_arrowRectTransform.gameObject.SetActive(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static ShrinkTutorialDialogAnchor ResolveAnchor(Rect targetRect, ShrinkTutorialDialogAnchor preferredAnchor, Rect canvasRect)
|
|
{
|
|
if (preferredAnchor != ShrinkTutorialDialogAnchor.Auto)
|
|
return preferredAnchor;
|
|
|
|
var topSpace = canvasRect.yMax - targetRect.yMax;
|
|
var bottomSpace = targetRect.yMin - canvasRect.yMin;
|
|
if (topSpace >= 240f)
|
|
return ShrinkTutorialDialogAnchor.Top;
|
|
if (bottomSpace >= 240f)
|
|
return ShrinkTutorialDialogAnchor.Bottom;
|
|
|
|
var rightSpace = canvasRect.xMax - targetRect.xMax;
|
|
var leftSpace = targetRect.xMin - canvasRect.xMin;
|
|
return rightSpace >= leftSpace
|
|
? ShrinkTutorialDialogAnchor.Right
|
|
: ShrinkTutorialDialogAnchor.Left;
|
|
}
|
|
|
|
private static Rect ConvertScreenRectToLocalRect(RectTransform canvasRect, Rect screenRect)
|
|
{
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect,
|
|
new Vector2(screenRect.xMin, screenRect.yMin), null, out var minLocal);
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect,
|
|
new Vector2(screenRect.xMax, screenRect.yMax), null, out var maxLocal);
|
|
|
|
return Rect.MinMaxRect(
|
|
Mathf.Min(minLocal.x, maxLocal.x),
|
|
Mathf.Min(minLocal.y, maxLocal.y),
|
|
Mathf.Max(minLocal.x, maxLocal.x),
|
|
Mathf.Max(minLocal.y, maxLocal.y));
|
|
}
|
|
|
|
private static TextMeshProUGUI CreateText(string objectName, string text, float fontSize, TextAlignmentOptions alignment)
|
|
{
|
|
var textObject = new GameObject(objectName, typeof(RectTransform), typeof(TextMeshProUGUI));
|
|
var tmp = textObject.GetComponent<TextMeshProUGUI>();
|
|
tmp.text = text;
|
|
tmp.fontSize = fontSize;
|
|
tmp.alignment = alignment;
|
|
tmp.color = Color.white;
|
|
tmp.enableWordWrapping = true;
|
|
return tmp;
|
|
}
|
|
}
|
|
}
|