Danro/Assets/Scripts/UI/DeathVignetteController.cs
Danil Iarosh e8000ffdca
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
feat(vfx, ui, com): add custom vfx, polish ui and update backlight protocol
- Интегрирована система кастомных визуальных эффектов (VFX):
  - Добавлена новая утилита MvpVFXUtility и настройки MvpVFXSpawnSettings для спавна эффектов.
  - Реализован пул эффектов VFXPool с поддержкой отслеживания версий спавна, сброса оверрайдов и анимации масштаба по времени.
  - Добавлены эффекты прыжка и приземления в PlayerAnimationEvents.
  - Интегрирован эффект DeathVignetteController для плавного затемнения экрана при смерти игрока.
  - Добавлены частицы stompImpactVFX при уничтожении врагов прыжком сверху (EnemyController).
  - Реализован эффект конфетти finishVFX при успешном завершении уровня (LevelFinish).
  - Интегрированы эффекты искр при сборе монеток, кристаллов и других собираемых предметов (Collectible, EnvironmentalCollectibleBase).
- Улучшен и визуально доработан пользовательский интерфейс (UI):
  - Добавлен скрипт QRCodeTierHighlight для цветовой подсветки QR-кодов в зависимости от тира приза.
  - Реализована пульсация блока приза в баннере победы (WinBannerController).
  - Обновлено обозначение валюты в BalanceDisplay с "₽" на "руб.".
  - Добавлены новые префабы, сцены, ассеты тайлсетов и шейдеров для настройки визуального стиля.
- Обновлён протокол и инициализация подсветки COM-платы:
  - Изменен префикс команд подсветки с 85/86 на 8550, а диапазон эффектов обновлен с 40...52 на 10...22 (LightController, LightMode, LightCycleDurations).
  - Исправлена хардкод-команда сброса подсветки при инициализации в ComDeviceClient с устаревшей 8600 на новую 8550 для предотвращения рассинхронизации буфера обмена платы.
2026-07-07 16:52:05 +03:00

204 lines
6.4 KiB
C#

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
namespace RR.Common.UI
{
/// <summary>
/// Показывает красную экранную виньетку во время смерти игрока.
/// </summary>
[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<DeathVignetteController>();
}
private void EnsureView()
{
if (vignetteImage)
{
return;
}
_canvas = GetComponent<Canvas>();
if (!_canvas)
{
_canvas = gameObject.AddComponent<Canvas>();
_canvas.renderMode = RenderMode.ScreenSpaceOverlay;
_canvas.sortingOrder = short.MaxValue;
}
CanvasScaler scaler = GetComponent<CanvasScaler>();
if (!scaler)
{
scaler = gameObject.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = 0.5f;
}
if (!GetComponent<GraphicRaycaster>())
{
gameObject.AddComponent<GraphicRaycaster>();
}
GameObject imageObject = new("Red Vignette");
imageObject.transform.SetParent(transform, false);
vignetteImage = imageObject.AddComponent<Image>();
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;
}
}
}