Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Интегрирована система кастомных визуальных эффектов (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 для предотвращения рассинхронизации буфера обмена платы.
204 lines
6.4 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|