Files
ShrinkTutorial/Runtime/UI/ShrinkTutorialDialog.cs
T
cneicy 1180be88a6
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:43 +08:00

223 lines
9.9 KiB
C#

using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace ShrinkTutorial
{
public class ShrinkTutorialDialog : MonoBehaviour
{
private RectTransform _rectTransform;
private RectTransform _arrowRectTransform;
private TextMeshProUGUI _contentText;
private GameObject _skipButtonObject;
private Action _skipAction;
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;
}
}
}