- Increased SFX pool size from 8 to 16 in AudioManager - Enabled preloadAudioData for multiple SFX files - Removed unused VisualScripting import from Checkpoint script - Reordered using statements in several scripts for consistency - Added CoinCollectible component to Coin prefab with pickup effects - Updated sorting layers and mesh LOD settings in Coin prefabs - Fixed typo in Interfaces.cs: optionaAttachPoint to optionalAttachPoint - Modified AddCoin method to accept count parameter and handle coin overflow - Moved projectile scripts (BasicProjectile, EnemyBasicProjectile, PhysicsProjectile, PickableProjectile) - Cleaned up unused namespaces and imports across various scripts
271 lines
13 KiB
C#
271 lines
13 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.Rendering;
|
||
using UnityEngine.Rendering.Universal;
|
||
|
||
using RR.Game2D.Core.Managers;
|
||
|
||
namespace RR.Game2D.Behaviour.Lighting
|
||
{
|
||
[RequireComponent(typeof(Collider2D))]
|
||
public class LightDimmer : MonoBehaviour
|
||
{
|
||
[Tooltip("Если не указан, будет использован Collider2D на этом же объекте.")]
|
||
[SerializeField] private Collider2D targetCollider;
|
||
|
||
[Tooltip("Тег игрока (используется для поиска Transform игрока)")]
|
||
[SerializeField] private string playerTag;
|
||
|
||
[Tooltip("Массив со всеми источниками света, которые нужно изменять")]
|
||
[SerializeField] private Light2D[] lights;
|
||
|
||
[Tooltip("Использовать прогресс по оси Y вместо оси X")]
|
||
[SerializeField] private bool useYProgress = false;
|
||
|
||
[Header("Настройки света")]
|
||
[SerializeField][Min(0)] private float startIntensity;
|
||
[SerializeField][Min(0)] private float endIntensity;
|
||
[SerializeField][Min(0)] private float smoothing;
|
||
|
||
[Header("Настройки свечения")]
|
||
[SerializeField] private bool applyBloom = true;
|
||
[SerializeField][Min(0)] private float startBloomIntensity = 0f;
|
||
[SerializeField][Min(0)] private float targetBloomIntensity = 1f;
|
||
|
||
private Transform _playerTransform;
|
||
|
||
// Храним все коллайдеры игрока, которые в данный момент пересекают триггер
|
||
private readonly HashSet<Collider2D> _playerColliders = new HashSet<Collider2D>();
|
||
|
||
private float _boundsMin;
|
||
private float _boundsMax;
|
||
private Volume[] _volumes;
|
||
|
||
/// <summary>
|
||
/// Валидация в редакторе — обновляем границы, если указан collider, и подставляем тег игрока по умолчанию.
|
||
/// Вызвается в редакторе при изменении полей в инспекторе.
|
||
/// </summary>
|
||
private void OnValidate()
|
||
{
|
||
if (targetCollider != null)
|
||
{
|
||
UpdateBounds();
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Инициализация при включении объекта: подставляем collider если не указан, находим игрока и кэшируем Volume для каждого света.
|
||
/// Важно: здесь мы не создаём новых Volume, а только берём существующие компоненты в родительских объектах света.
|
||
/// </summary>
|
||
private void OnEnable()
|
||
{
|
||
if (targetCollider == null)
|
||
targetCollider = GetComponent<Collider2D>();
|
||
|
||
if (targetCollider != null)
|
||
{
|
||
UpdateBounds();
|
||
}
|
||
|
||
// Подготавливаем массив volumes той же длины, что и массив lights.
|
||
// Это кэш для ускорения доступа к Bloom в Update, чтобы не делать GetComponent каждый кадр.
|
||
_volumes = new Volume[lights.Length];
|
||
|
||
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
|
||
|
||
// Пытаемся найти игрока на сцене для удобства — это позволит скрипту работать, даже если playerTransform не задан вручную.
|
||
var playerObj = GameObject.FindGameObjectWithTag(playerTag);
|
||
if (playerObj != null)
|
||
_playerTransform = playerObj.transform;
|
||
|
||
// Кэшируем Volume (если есть) для каждого источника света. Volume может находиться на родителе или выше.
|
||
for (int i = 0; i < lights.Length; i++)
|
||
{
|
||
Light2D light2D = lights[i];
|
||
// Возможна ситуация, когда элемент массива пустой (null) — защищаемся от этого.
|
||
if (light2D == null)
|
||
{
|
||
_volumes[i] = null;
|
||
continue;
|
||
}
|
||
|
||
// Ищем ближайший родительский Volume (он может быть на том же GameObject или выше в иерархии).
|
||
Volume volume = light2D.GetComponentInParent<Volume>();
|
||
if (volume != null)
|
||
{
|
||
// Включаем volume, чтобы настроенные эффекты (Bloom и т.п.) применялись во время работы.
|
||
volume.enabled = true;
|
||
}
|
||
|
||
_volumes[i] = volume;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Start — дополнительная страховка: подставляем collider и обновляем границы на старте сцены.
|
||
/// </summary>
|
||
private void Start()
|
||
{
|
||
if (targetCollider == null)
|
||
{
|
||
targetCollider = GetComponent<Collider2D>();
|
||
}
|
||
|
||
UpdateBounds();
|
||
|
||
if (_playerTransform == null) _playerTransform = GameManager.Instance.PlayerObj.transform;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Основной цикл: если игрок находится внутри триггера, вычисляем прогресс по выбранной оси и плавно интерполируем интенсивность света
|
||
/// и, при необходимости, значение bloom в соответствующем Volume.
|
||
/// </summary>
|
||
private void Update()
|
||
{
|
||
// Требуем наличие: playerTransform, массив lights и targetCollider
|
||
if (!_playerTransform || lights == null || lights.Length == 0 || targetCollider == null)
|
||
return;
|
||
// Debug.Log($"{name}: Player Transform Detected");
|
||
|
||
// Проверяем, что игрок действительно внутри триггера, учитываем все коллайдеры (на случай CompositeCollider/мультиколлайдеров)
|
||
bool playerInside = _playerColliders.Count > 0;
|
||
if (!playerInside)
|
||
return;
|
||
|
||
// Debug.Log($"{name}: Player Inside Trigger");
|
||
|
||
Vector3 playerPos = _playerTransform.position;
|
||
|
||
// Дополнительная проверка: AABB bounds коллайдера (быстрая фильтрация), чтобы избежать лишних вычислений
|
||
// if (!targetCollider.bounds.Contains(playerPos))
|
||
// return;
|
||
|
||
// Debug.Log($"{name}: Player Position: {playerPos}");
|
||
|
||
// Выбираем координату для прогресса: X или Y
|
||
float playerCurrent = useYProgress ? playerPos.y : playerPos.x;
|
||
|
||
// Debug.Log($"{name}: Player Current Coordinate: {playerCurrent}");
|
||
|
||
float t = 0f;
|
||
// Если boundsMin==boundsMax — делить нельзя, поэтому проверяем приблизительное равенство
|
||
if (!Mathf.Approximately(_boundsMax, _boundsMin))
|
||
{
|
||
// Нормализуем позицию игрока по отрезку [boundsMin, boundsMax]
|
||
t = Mathf.InverseLerp(_boundsMin, _boundsMax, playerCurrent);
|
||
t = Mathf.Clamp01(t);
|
||
}
|
||
|
||
|
||
|
||
// Интерполируем целевые значения интенсивности и bloom в зависимости от нормализованной позиции t
|
||
float desiredForPosition = Mathf.Lerp(startIntensity, endIntensity, t);
|
||
float desiredForBloom = Mathf.Lerp(startBloomIntensity, targetBloomIntensity, t);
|
||
|
||
// Проходим по всем светам и плавно (или мгновенно, если smoothing<=0) приводим интенсивность к целевой
|
||
for (int i = 0; i < lights.Length; i++)
|
||
{
|
||
var light2D = lights[i];
|
||
|
||
// Безопасность: если света нет, пропускаем
|
||
if (!light2D)
|
||
continue;
|
||
|
||
// Получаем заранее закэшированный Volume (может быть null)
|
||
var vol = _volumes != null && i < _volumes.Length ? _volumes[i] : null;
|
||
|
||
// Пытаемся извлечь компонент Bloom из профиля, если volume и профиль присутствуют
|
||
Bloom bloom = null;
|
||
if (applyBloom && vol != null && vol.profile != null)
|
||
{
|
||
// TryGet возвращает true если компонент есть, и мы получаем ссылку на него
|
||
vol.profile.TryGet(out bloom);
|
||
}
|
||
|
||
if (smoothing <= 0f)
|
||
{
|
||
// Мгновенное изменение — полезно для теста или резких переключателей
|
||
light2D.intensity = desiredForPosition;
|
||
if (applyBloom && bloom != null) bloom.intensity.value = desiredForBloom;
|
||
}
|
||
else
|
||
{
|
||
// Плавное изменение — используем MoveTowards для стабильного изменения без overshoot
|
||
light2D.intensity = Mathf.MoveTowards(light2D.intensity, desiredForPosition, smoothing * Time.deltaTime);
|
||
if (applyBloom && bloom != null)
|
||
bloom.intensity.value = Mathf.MoveTowards(bloom.intensity.value, desiredForBloom, smoothing * Time.deltaTime);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Триггер: запоминаем коллайдер игрока, когда любой из его коллайдеров входит в область.
|
||
/// Используем HashSet, чтобы корректно работать с объектом, у которого несколько Collider2D (включая CompositeCollider2D).
|
||
/// </summary>
|
||
/// <param name="other">Коллайдер, вошедший в триггер</param>
|
||
private void OnTriggerEnter2D(Collider2D other)
|
||
{
|
||
if (!other.CompareTag(playerTag)) return;
|
||
|
||
// Запоминаем коллайдер игрока
|
||
_playerColliders.Add(other);
|
||
|
||
// Сохраняем Transform игрока (на случай, если раньше не найден)
|
||
if (_playerTransform == null)
|
||
_playerTransform = other.transform;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Триггер выхода: убираем соответствующий коллайдер из набора; если набор пуст — игрок полностью вышел.
|
||
/// </summary>
|
||
/// <param name="other">Коллайдер, вышедший из триггера</param>
|
||
private void OnTriggerExit2D(Collider2D other)
|
||
{
|
||
if (!other.CompareTag(playerTag)) return;
|
||
|
||
// Убираем коллайдер из множества; если множество пустое — игрок полностью вышел
|
||
_playerColliders.Remove(other);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обновляет внутренние границы отслеживаемого Collider2D.
|
||
/// При useYProgress==false возвращает min/max по X, иначе по Y.
|
||
/// </summary>
|
||
private void UpdateBounds()
|
||
{
|
||
var b = targetCollider.bounds;
|
||
_boundsMin = b.min.x;
|
||
_boundsMax = b.max.x;
|
||
|
||
if (useYProgress)
|
||
{
|
||
_boundsMin = b.min.y;
|
||
_boundsMax = b.max.y;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Рисует в редакторе границу collider'а и позицию игрока для удобства отладки.
|
||
/// </summary>
|
||
void OnDrawGizmosSelected()
|
||
{
|
||
if (targetCollider == null) return;
|
||
|
||
var b = targetCollider.bounds;
|
||
Gizmos.color = Color.yellow;
|
||
Gizmos.DrawLine(new Vector3(b.min.x, b.min.y, 0f), new Vector3(b.max.x, b.min.y, 0f));
|
||
Gizmos.DrawLine(new Vector3(b.max.x, b.min.y, 0f), new Vector3(b.max.x, b.max.y, 0f));
|
||
Gizmos.DrawLine(new Vector3(b.max.x, b.max.y, 0f), new Vector3(b.min.x, b.max.y, 0f));
|
||
Gizmos.DrawLine(new Vector3(b.min.x, b.max.y, 0f), new Vector3(b.min.x, b.min.y, 0f));
|
||
|
||
if (_playerTransform != null)
|
||
{
|
||
Gizmos.color = Color.cyan;
|
||
Vector3 p = _playerTransform.position;
|
||
Gizmos.DrawSphere(new Vector3(p.x, p.y, 0f), 0.05f);
|
||
}
|
||
}
|
||
}
|
||
} |