Untitled

 avatar
unknown
plain_text
a month ago
19 kB
4
Indexable
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using OnboardingSystem.Scripts.Interfaces;
using OnboardingSystem.Scripts.Utilities;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace OnboardingSystem.Scripts
{
    public class OnboardingUI : MonoBehaviour, IOnboardingUI
    {
        [SerializeField] private GameObject panel;
        [SerializeField] private GameObject messagePanel;
        [SerializeField] private TextMeshProUGUI messageLabel;
        [SerializeField] private GameObject highlightPrefab;
        [SerializeField] private RectTransform highlightParent;
        [SerializeField] private RectTransform finger;
        [SerializeField] private Image fingerImage;
        [SerializeField] private Image highlightBackground;
        [SerializeField] private Image messageBackground;
        private Sprite _fingerPressedSprite;
        private Sprite _fingerReleasedSprite;
        private Coroutine _fingerMoveRoutine;
        private Sequence _activeFingerSequence;
        private readonly List<GameObject> activeHighlights = new();

        private void Start()
        {
            ToggleHighlightBackground(false);
            OnboardingEvents.OnShowOnboarding += Show;
            OnboardingEvents.OnHideOnboarding += Hide;
        }
        
        private void OnDisable()
        {
            finger?.DOKill();
            finger?.transform.DOKill();
            messagePanel?.transform.DOKill();
            foreach (var highlight in activeHighlights)
            {
                highlight?.transform.DOKill();
            }
            OnboardingEvents.OnShowOnboarding -= Show;
            OnboardingEvents.OnHideOnboarding -= Hide;
        }

        public void ShowMessage(string message, float textAnimationSpeed, bool canDismiss, float displayTime, Action onDismiss = null, Vector2? anchor = null, Vector2? offset = null, float scaleAnimationDuration = 0.5f, Ease scaleAnimationEase = Ease.OutBack)
        {
            messagePanel.transform.DOKill();
            messageLabel.DOKill();
            RectTransform messageRect = messagePanel.GetComponent<RectTransform>();
            if (anchor.HasValue)
            {
                messageRect.anchorMin = anchor.Value;
                messageRect.anchorMax = anchor.Value;
                messageRect.pivot = anchor.Value;
            }
            if (offset.HasValue) messageRect.anchoredPosition = offset.Value;
            messageLabel.text = "";
            messagePanel.SetActive(true);
            messageLabel.DOText(message, textAnimationSpeed).SetEase(Ease.Linear);
            Tween tween = messagePanel.transform.DOScale(Vector3.one, scaleAnimationDuration).SetEase(scaleAnimationEase);
            if (canDismiss)
            {
                tween.OnComplete(() =>
                {
                    Button btn = messagePanel.GetComponent<Button>();
                    if (btn == null) btn = messagePanel.AddComponent<Button>();
                    btn.onClick.RemoveAllListeners();
                    btn.onClick.AddListener(() => { HideMessage(() => onDismiss?.Invoke()); });
                });
            }
            else if (messagePanel.TryGetComponent(out Button button)) Destroy(button);
            if (displayTime > 0) DOVirtual.DelayedCall(displayTime, () => HideMessage(null));
        }

        public void SetFontSize(bool useConstantFontSize, float constantFontSize, float minFontSize, float maxFontSize)
        {
            if (useConstantFontSize) messageLabel.fontSize = constantFontSize;
            else
            {
                messageLabel.enableAutoSizing = true;
                messageLabel.fontSizeMin = minFontSize;
                messageLabel.fontSizeMax = maxFontSize;
            }
        }

        public void HideMessage(Action onComplete)
        {
            messagePanel.transform.DOKill();
            messageLabel.text = "";
            messagePanel.transform.DOScale(Vector3.zero, 0.3f).SetEase(Ease.InBack).OnComplete(() =>
            {
                messagePanel.SetActive(false);
                onComplete?.Invoke();
            });
        }

        public void Highlight(Vector3 position, Vector2 size, bool useWorldPosition = true, Sprite highlightSprite = null)
        {
            ToggleHighlightBackground(true);
            Camera uiCamera = highlightParent.GetComponentInParent<Canvas>().worldCamera;
            Vector2 screenPos;
            if (useWorldPosition)
            {
                screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, position);
            }
            else
            {
                screenPos = position;
            }
            RectTransformUtility.ScreenPointToLocalPointInRectangle(highlightParent, screenPos, uiCamera, out Vector2 localPos);
            GameObject newHighlight = Instantiate(highlightPrefab, highlightParent.transform);
            if (highlightSprite != null) newHighlight.GetComponent<Image>().sprite = highlightSprite;
            newHighlight.transform.SetAsFirstSibling();
            RectTransform highlightRect = newHighlight.GetComponent<RectTransform>();
            highlightRect.anchoredPosition = localPos;
            highlightRect.sizeDelta = size;
            activeHighlights.Add(newHighlight);
        }

        public void HighlightWithId(int id, Vector2 size, bool useWorldPosition = true, Sprite highlightSprite = null)
        {
            var target = OnboardingUtilities.GetTargetById(id);
            if (target == null) return;
            Vector3 position;
            if (useWorldPosition)
            {
                position = target.transform.position;
            }
            else
            {
                RectTransform rectTransform = target.GetComponent<RectTransform>();
                if (rectTransform != null)
                {
                    position = rectTransform.position;
                }
                else
                {
                    Camera uiCamera = highlightParent.GetComponentInParent<Canvas>().worldCamera;
                    position = RectTransformUtility.WorldToScreenPoint(uiCamera, target.transform.position);
                }
            }
            Highlight(position, size, useWorldPosition, highlightSprite);
        }

        public void DragDropFingerAnimation(Vector3 startWorldPos, Vector3 endWorldPos, Vector2 offset, float duration, Ease ease, LoopType loopType, int loopCount, float startScale, float pressScale, float endScale)
        {
            HideFinger();
            Camera uiCamera = GetComponentInParent<Canvas>().worldCamera;
            Vector2 startScreenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, startWorldPos);
            Vector2 endScreenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, endWorldPos);
            RectTransformUtility.ScreenPointToLocalPointInRectangle(transform as RectTransform, startScreenPos, uiCamera, out Vector2 startLocalPos);
            RectTransformUtility.ScreenPointToLocalPointInRectangle(transform as RectTransform, endScreenPos, uiCamera, out Vector2 endLocalPos);
            finger.gameObject.SetActive(true);
            finger.anchoredPosition = startLocalPos + offset;
            endLocalPos += offset;
            fingerImage.color = new Color(1, 1, 1, 0);
            finger.localScale = Vector3.one * startScale;
            _activeFingerSequence?.Kill();
            _activeFingerSequence = DOTween.Sequence();
            _activeFingerSequence.Append(fingerImage.DOFade(1, 0.3f).SetEase(Ease.OutQuad));
            _activeFingerSequence.AppendCallback(() => { fingerImage.sprite = _fingerPressedSprite; finger.localScale = Vector3.one * pressScale; });
            _activeFingerSequence.Join(finger.DOScale(Vector3.one * pressScale, 0.2f).SetEase(Ease.OutBack));
            _activeFingerSequence.Append(finger.DOAnchorPos(endLocalPos, duration).SetEase(Ease.InOutQuad));
            _activeFingerSequence.AppendCallback(() => { fingerImage.sprite = _fingerReleasedSprite; finger.localScale = Vector3.one * endScale; });
            _activeFingerSequence.Join(finger.DOScale(Vector3.one * endScale, 0.2f).SetEase(Ease.OutBack));
            _activeFingerSequence.Append(fingerImage.DOFade(0, 0.3f).SetEase(Ease.InQuad));
            _activeFingerSequence.Join(finger.DOScale(Vector3.one * startScale, 0.3f));
            if (loopCount > 0) _activeFingerSequence.SetLoops(loopCount, loopType).OnComplete(() => finger.gameObject.SetActive(false));
            else _activeFingerSequence.SetLoops(-1, loopType);
            _activeFingerSequence.OnKill(() =>
            {
                fingerImage.color = Color.white;
                finger.localScale = Vector3.one;
            });
        }

        public void DragDropFingerAnimationWithIds(int startId, int endId, Vector2 offset, float duration, Ease ease, LoopType loopType, int loopCount, float startScale, float pressScale, float endScale)
        {
            if (finger != null)
            {
                finger.DOKill();
                finger.transform.DOKill();
            }
            if (_fingerMoveRoutine != null) StopCoroutine(_fingerMoveRoutine);
            _fingerMoveRoutine = StartCoroutine(MoveFingerRoutine(startId, endId, offset, duration, ease, loopType, loopCount, startScale, pressScale, endScale));
        }

        private IEnumerator MoveFingerRoutine(int startId, int endId, Vector2 offset, float duration, Ease ease, LoopType loopType, int loopCount, float startScale, float pressScale, float endScale)
        {
            Canvas canvas = GetComponentInParent<Canvas>();
            Camera uiCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
            if (finger == null || fingerImage == null) yield break;
            finger.gameObject.SetActive(true);
            fingerImage.color = new Color(1, 1, 1, 0);
            finger.localScale = Vector3.one * startScale;
            int currentLoop = 0;
            while (loopCount <= 0 || currentLoop < loopCount)
            {
                var onboardingObjects = FindObjectsOfType<OnboardingObject>();
                var startObject = Array.Find(onboardingObjects, obj => obj.id == startId);
                var endObject = Array.Find(onboardingObjects, obj => obj.id == endId);
                if (startObject == null || endObject == null)
                {
                    Debug.LogError("Onboarding object with id not found");
                    yield break;
                }
                Vector2 startScreenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, startObject.transform.position);
                Vector2 endScreenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, endObject.transform.position);
                RectTransform rt = transform as RectTransform;
                if (rt == null || !RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, startScreenPos, uiCamera, out Vector2 startLocalPos) ||
                    !RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, endScreenPos, uiCamera, out Vector2 endLocalPos))
                {
                    yield return null;
                    continue;
                }
                finger.DOKill();
                finger.transform.DOKill();
                finger.anchoredPosition = startLocalPos + offset;
                endLocalPos += offset;
                Sequence seq = DOTween.Sequence();
                seq.SetTarget(finger);
                seq.SetEase(ease);
                seq.Append(fingerImage.DOFade(1, 0.3f).SetEase(Ease.OutQuad));
                seq.AppendCallback(() => { fingerImage.sprite = _fingerPressedSprite; finger.localScale = Vector3.one * pressScale; });
                seq.Join(finger.DOScale(Vector3.one * pressScale, 0.2f).SetEase(Ease.OutBack));
                seq.Append(finger.DOAnchorPos(endLocalPos, duration).SetEase(Ease.InOutQuad));
                seq.AppendCallback(() => { fingerImage.sprite = _fingerReleasedSprite; finger.localScale = Vector3.one * endScale; });
                seq.Join(finger.DOScale(Vector3.one * endScale, 0.2f).SetEase(Ease.OutBack));
                seq.Append(fingerImage.DOFade(0, 0.3f).SetEase(Ease.InQuad));
                seq.Join(finger.DOScale(Vector3.one * startScale, 0.3f));
                seq.AppendInterval(0.3f);
                yield return seq.WaitForCompletion();
                currentLoop++;
            }
            finger.gameObject.SetActive(false);
        }

        public void PointFingerAt(Vector3 worldPosition, Vector2 offset, float duration, Ease ease, LoopType loopType, int loopCount, float startScale, float pressScale, float endScale)
        {
            HideFinger();
            Camera uiCamera = GetComponentInParent<Canvas>().worldCamera;
            Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, worldPosition);
            RectTransformUtility.ScreenPointToLocalPointInRectangle(transform as RectTransform, screenPos, uiCamera, out Vector2 localPos);
            finger.gameObject.SetActive(true);
            finger.anchoredPosition = localPos + offset;
            finger.localScale = Vector3.one * startScale;
            _activeFingerSequence?.Kill();
            _activeFingerSequence = DOTween.Sequence();
            _activeFingerSequence.SetEase(ease);
            _activeFingerSequence.AppendCallback(() => { fingerImage.sprite = _fingerPressedSprite; });
            _activeFingerSequence.Append(finger.DOScale(Vector3.one * pressScale, 0.2f).SetEase(Ease.OutBack));
            _activeFingerSequence.AppendInterval(duration);
            _activeFingerSequence.AppendCallback(() => { fingerImage.sprite = _fingerReleasedSprite; });
            _activeFingerSequence.Append(finger.DOScale(Vector3.one * endScale, 0.3f).SetEase(Ease.OutBack));
            _activeFingerSequence.AppendInterval(0.3f);
            if (loopCount > 0) _activeFingerSequence.SetLoops(loopCount, loopType).OnComplete(() => finger.gameObject.SetActive(false));
            else _activeFingerSequence.SetLoops(-1, loopType);
        }

        public void PointFingerAtWithId(int id, Vector2 offset, float duration, Ease ease, LoopType loopType, int loopCount, float startScale, float pressScale, float endScale)
        {
            if (finger != null)
            {
                finger.DOKill();
                finger.transform.DOKill();
            }
            if (_fingerMoveRoutine != null) StopCoroutine(_fingerMoveRoutine);
            _fingerMoveRoutine = StartCoroutine(PointFingerAtWithIdRoutine(id, offset, duration, ease, loopType, loopCount, startScale, pressScale, endScale));
        }

        private IEnumerator PointFingerAtWithIdRoutine(int endId, Vector2 offset, float duration, Ease ease, LoopType loopType, int loopCount, float startScale, float pressScale, float endScale)
        {
            Canvas canvas = GetComponentInParent<Canvas>();
            Camera uiCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
            if (finger == null || fingerImage == null) yield break;
            int currentLoop = 0;
            while (loopCount <= 0 || currentLoop < loopCount)
            {
                var onboardingObjects = FindObjectsOfType<OnboardingObject>();
                var endObject = Array.Find(onboardingObjects, obj => obj.id == endId);
                if (endObject == null)
                {
                    Debug.LogError("Onboarding object with id not found");
                    yield break;
                }
                Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, endObject.transform.position);
                RectTransformUtility.ScreenPointToLocalPointInRectangle(transform as RectTransform, screenPos, uiCamera, out Vector2 targetLocalPos);
                finger.anchoredPosition = targetLocalPos + offset;
                finger.gameObject.SetActive(true);
                finger.localScale = Vector3.one * startScale;
                Sequence seq = DOTween.Sequence();
                seq.SetEase(ease);
                seq.AppendCallback(() => { fingerImage.sprite = _fingerPressedSprite; });
                seq.Append(finger.DOScale(Vector3.one * pressScale, 0.2f).SetEase(Ease.OutBack));
                seq.AppendInterval(duration);
                seq.AppendCallback(() => { fingerImage.sprite = _fingerReleasedSprite; });
                seq.Append(finger.DOScale(Vector3.one * endScale, 0.3f).SetEase(Ease.OutBack));
                seq.AppendInterval(0.3f);
                yield return seq.WaitForCompletion();
                currentLoop++;
            }
            finger.gameObject.SetActive(false);
        }

        public void HideFinger()
        {
            if (_fingerMoveRoutine != null) StopCoroutine(_fingerMoveRoutine);
            _fingerMoveRoutine = null;
            finger.DOKill();
            finger.transform.DOKill();
            if (_activeFingerSequence != null && _activeFingerSequence.IsActive()) _activeFingerSequence.Kill();
            finger.gameObject.SetActive(false);
            finger.anchoredPosition = Vector2.zero;
            fingerImage.color = Color.white;
            finger.localScale = Vector3.one;
        }

        public void ClearHighlights(bool hideBackground)
        {
            foreach (var highlight in activeHighlights) Destroy(highlight);
            activeHighlights.Clear();
            ToggleHighlightBackground(!hideBackground);
        }

        public void Hide() => panel.SetActive(false);
        public void Show() => panel.SetActive(true);

        public void SetFingerSprites(Sprite released, Sprite pressed)
        {
            _fingerReleasedSprite = released;
            _fingerPressedSprite = pressed;
        }

        public void SetMessageBackground(Sprite sprite) => messageBackground.sprite = sprite;
        public void Deactivate()
        {
            Hide();
            HideMessage(null);
            HideFinger();
            ClearHighlights(true);
        }

        private void ToggleHighlightBackground(bool show) => highlightBackground.gameObject.SetActive(show);
    }
}
Editor is loading...
Leave a Comment