using RR.Common.Prizes; using RR.Common.Utility; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace RR.Common.UI.FX { /// /// Добавляет QR-коду цветную подсветку, соответствующую рангу приза. /// [DisallowMultipleComponent] [AddComponentMenu("RR/UI/FX/QR Code Tier Highlight")] public sealed class QRCodeTierHighlight : MonoBehaviour { [SerializeField] private QRTier tier; [SerializeField] private Color fallbackColor = new(0.15f, 0.55f, 1f, 1f); [SerializeField, Range(0f, 1f)] private float glowAlpha = 0.55f; [SerializeField, Min(0f)] private float outlineDistance = 4f; [SerializeField, Range(4, 16)] private int outlineSamples = 8; [SerializeField, Range(1, 4)] private int blurLayers = 2; [SerializeField, Min(0f)] private float blurSpread = 2f; [SerializeField, Min(0f)] private float pulseScale = 0.035f; [SerializeField, Min(0.01f)] private float pulseDuration = 1.35f; [SerializeField] private bool pulse = true; private Image _targetImage; private RectTransform _highlightRect; private GameObject _highlightObject; private readonly List _highlightLayers = new(); public static QRCodeTierHighlight Apply(GameObject qrObject, QRTier qrTier) { if (qrObject == null) { return null; } QRCodeTierHighlight highlight = qrObject.GetComponent(); if (highlight == null) { highlight = qrObject.AddComponent(); } highlight.SetTier(qrTier); return highlight; } private void Awake() { ResolveReferences(); EnsureView(); ApplyColor(); } private void OnEnable() { ResolveReferences(); EnsureView(); ApplyColor(); SyncHighlightRect(1f); } private void Update() { if (!pulse || _highlightRect == null) { return; } float wave = (Mathf.Sin(Time.unscaledTime / pulseDuration * Mathf.PI * 2f) + 1f) * 0.5f; float scale = 1f + wave * pulseScale; SyncHighlightRect(scale); } public void SetTier(QRTier qrTier) { tier = qrTier; EnsureView(); ApplyColor(); } private void EnsureView() { if (_highlightLayers.Count > 0) { return; } Transform highlightParent = transform.parent != null ? transform.parent : transform; _highlightObject = new GameObject($"{name} QR Tier Highlight", typeof(RectTransform), typeof(LayoutElement)); _highlightObject.transform.SetParent(highlightParent, false); _highlightObject.GetComponent().ignoreLayout = true; _highlightRect = _highlightObject.GetComponent(); _highlightObject.transform.SetSiblingIndex(transform.GetSiblingIndex()); SyncHighlightRect(1f); RebuildHighlightLayers(); } private void OnDestroy() { if (_highlightObject != null) { Destroy(_highlightObject); } } private void ResolveReferences() { if (_targetImage == null) { _targetImage = GetComponent(); } } private void RebuildHighlightLayers() { _highlightLayers.Clear(); int samples = Mathf.Max(4, outlineSamples); int layerIndex = 0; for (int blur = 0; blur < Mathf.Max(1, blurLayers); blur++) { float distance = outlineDistance + blur * blurSpread; for (int sample = 0; sample < samples; sample++) { GameObject layerObject = new($"QR Tier Outline {layerIndex + 1}", typeof(RectTransform), typeof(Image)); layerObject.transform.SetParent(_highlightRect, false); layerObject.transform.SetSiblingIndex(layerIndex); float angle = sample / (float)samples * Mathf.PI * 2f; Vector2 offset = new(Mathf.Cos(angle) * distance, Mathf.Sin(angle) * distance); RectTransform layerRect = layerObject.GetComponent(); layerRect.anchorMin = Vector2.zero; layerRect.anchorMax = Vector2.one; layerRect.offsetMin = offset; layerRect.offsetMax = offset; Image layerImage = layerObject.GetComponent(); ApplyTargetSprite(layerImage); layerImage.raycastTarget = false; _highlightLayers.Add(layerImage); layerIndex++; } } } private void ApplyTargetSprite(Image image) { if (image == null || _targetImage == null) { return; } image.sprite = _targetImage.sprite; image.type = _targetImage.type; image.preserveAspect = _targetImage.preserveAspect; image.fillCenter = true; image.fillMethod = _targetImage.fillMethod; image.fillOrigin = _targetImage.fillOrigin; image.fillAmount = _targetImage.fillAmount; image.fillClockwise = _targetImage.fillClockwise; image.pixelsPerUnitMultiplier = _targetImage.pixelsPerUnitMultiplier; } private void ApplyColor() { if (_highlightLayers.Count == 0) { return; } for (int i = 0; i < _highlightLayers.Count; i++) { Image layer = _highlightLayers[i]; if (layer == null) { continue; } int samples = Mathf.Max(4, outlineSamples); int blurIndex = i / samples; float layerT = Mathf.Max(1, blurLayers) <= 1 ? 0f : blurIndex / (float)(Mathf.Max(1, blurLayers) - 1); Color color = ResolveTierColor(tier); color.a = glowAlpha * Mathf.Lerp(0.36f, 0.12f, layerT); layer.color = color; } } private Color ResolveTierColor(QRTier qrTier) { if (qrTier == QRTier.None) { return fallbackColor; } Vector3 rgb = QRCodeInfo.GetTierDarkColorVector(qrTier); return new Color(rgb.x, rgb.y, rgb.z, 1f); } private void SyncHighlightRect(float scale) { if (_highlightRect == null) { return; } RectTransform targetRect = transform as RectTransform; if (targetRect == null) { return; } _highlightRect.anchorMin = targetRect.anchorMin; _highlightRect.anchorMax = targetRect.anchorMax; _highlightRect.pivot = targetRect.pivot; _highlightRect.anchoredPosition = targetRect.anchoredPosition; _highlightRect.sizeDelta = targetRect.sizeDelta; _highlightRect.localRotation = targetRect.localRotation; _highlightRect.localScale = targetRect.localScale * scale; if (_highlightRect.parent == targetRect.parent) { _highlightRect.SetSiblingIndex(Mathf.Max(0, targetRect.GetSiblingIndex() - 1)); } } } }