using System.Collections; using UnityEngine; using UnityEngine.UI; namespace RR.Common.UI { /// /// Показывает красную экранную виньетку во время смерти игрока. /// [AddComponentMenu("RR/UI/Death Vignette Controller")] public class DeathVignetteController : MonoBehaviour { private const string RuntimeObjectName = "Death Vignette"; private const int TextureSize = 256; private static DeathVignetteController _instance; [Header("Визуал")] [SerializeField] private Color vignetteColor = new(0.85f, 0.02f, 0.02f, 1f); [SerializeField, Range(0f, 1f)] private float maxAlpha = 0.75f; [SerializeField, Range(0.01f, 1f)] private float innerRadius = 0.3f; [SerializeField, Range(0.01f, 1f)] private float outerRadius = 0.85f; [Header("Анимация")] [SerializeField, Min(0.01f)] private float fadeInDuration = 0.18f; [SerializeField, Min(0.01f)] private float fadeOutDuration = 0.45f; [SerializeField] private bool useUnscaledTime = true; [Header("Ссылки")] [SerializeField] private Image vignetteImage; private Coroutine _fadeCoroutine; private Canvas _canvas; private Texture2D _texture; public static DeathVignetteController Instance { get { if (_instance) return _instance; return CreateRuntimeInstance(); } } private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); return; } _instance = this; DontDestroyOnLoad(gameObject); EnsureView(); SetAlpha(0f); } private void OnDestroy() { if (_instance == this) { _instance = null; } } public void Show() { EnsureView(); FadeTo(maxAlpha, fadeInDuration); } public void Hide() { EnsureView(); FadeTo(0f, fadeOutDuration); } private static DeathVignetteController CreateRuntimeInstance() { GameObject root = new(RuntimeObjectName); return root.AddComponent(); } private void EnsureView() { if (vignetteImage) { return; } _canvas = GetComponent(); if (!_canvas) { _canvas = gameObject.AddComponent(); _canvas.renderMode = RenderMode.ScreenSpaceOverlay; _canvas.sortingOrder = short.MaxValue; } CanvasScaler scaler = GetComponent(); if (!scaler) { scaler = gameObject.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920f, 1080f); scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight; scaler.matchWidthOrHeight = 0.5f; } if (!GetComponent()) { gameObject.AddComponent(); } GameObject imageObject = new("Red Vignette"); imageObject.transform.SetParent(transform, false); vignetteImage = imageObject.AddComponent(); vignetteImage.raycastTarget = false; vignetteImage.sprite = Sprite.Create( CreateVignetteTexture(), new Rect(0f, 0f, TextureSize, TextureSize), new Vector2(0.5f, 0.5f), 100f); vignetteImage.type = Image.Type.Simple; RectTransform rect = vignetteImage.rectTransform; rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } private Texture2D CreateVignetteTexture() { if (_texture) { Destroy(_texture); } _texture = new Texture2D(TextureSize, TextureSize, TextureFormat.RGBA32, false) { name = "Runtime Death Vignette", wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Bilinear }; Vector2 center = new(0.5f, 0.5f); for (int y = 0; y < TextureSize; y++) { for (int x = 0; x < TextureSize; x++) { Vector2 uv = new Vector2(x / (TextureSize - 1f), y / (TextureSize - 1f)); float distance = Vector2.Distance(uv, center); float alpha = Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(innerRadius, outerRadius, distance)); Color pixel = new(vignetteColor.r, vignetteColor.g, vignetteColor.b, alpha); _texture.SetPixel(x, y, pixel); } } _texture.Apply(); return _texture; } private void FadeTo(float targetAlpha, float duration) { if (_fadeCoroutine != null) { StopCoroutine(_fadeCoroutine); } _fadeCoroutine = StartCoroutine(FadeCoroutine(targetAlpha, duration)); } private IEnumerator FadeCoroutine(float targetAlpha, float duration) { float startAlpha = vignetteImage ? vignetteImage.color.a : 0f; float elapsed = 0f; while (elapsed < duration) { elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime; float t = Mathf.Clamp01(elapsed / duration); t = t * t * (3f - 2f * t); SetAlpha(Mathf.Lerp(startAlpha, targetAlpha, t)); yield return null; } SetAlpha(targetAlpha); _fadeCoroutine = null; } private void SetAlpha(float alpha) { if (!vignetteImage) return; Color color = vignetteColor; color.a = alpha; vignetteImage.color = color; vignetteImage.enabled = alpha > 0.001f; } } }