using System; using RR.Game2D.Behaviour; using RR.Game2D.FX; using UnityEngine; using UnityEngine.VFX; namespace RR.Common.Entities.Props { [RequireComponent(typeof(Collider2D))] public abstract class EnvironmentalCollectibleBase : MonoBehaviour { [SerializeField] private int points = 1; [SerializeField] private string playerTag = "Player"; [Tooltip("Время жизни в секундах. 0 или меньше — бесконечно.")] [SerializeField] private float defaultLifetime; [Header("FX")] [SerializeField] private VisualEffectAsset collectVFX; [SerializeField] private string collectVFXResourcePath = "VFX/MVP_PickupSparkle_VFX"; [SerializeField] private float collectVFXLifetime = 1f; [SerializeField] private MvpVFXSpawnSettings collectVFXSettings = new MvpVFXSpawnSettings { spreadRadiusX = 0.08f, spreadRadiusY = 0.08f }; private bool _collected; private int _defaultPoints; private float _defaultLifetimeBackup; private float _lifetime; private float _age; private Collider2D _trigger; private Vector3 _spawnPosition; public event Action Collected; public int Points => points; public Vector3 SpawnPosition => _spawnPosition; public virtual bool CountsTowardsSharedCollectionLimit => false; protected virtual void Awake() { _defaultPoints = Mathf.Max(0, points); _defaultLifetimeBackup = defaultLifetime; _trigger = GetComponent(); ValidateTrigger(); } protected virtual void Update() { float dt = Time.deltaTime; if (_lifetime > 0f && !_collected) { _age += dt; if (_age >= _lifetime) { Despawn(); return; } } TickBehaviour(dt); } protected virtual void OnValidate() { if (points < 0) points = 0; _defaultPoints = points; _defaultLifetimeBackup = defaultLifetime; ValidateTrigger(); } private void OnTriggerEnter2D(Collider2D other) { // Unity 2D trigger callbacks usually require at least one of the colliding objects to have a Rigidbody2D. if (_collected || other == null) return; if (!other.CompareTag(playerTag)) return; Collect(other.transform); } public void InitializeForSpawn( Vector3 spawnPosition, int? pointsOverride, float? lifetimeOverride = null) { _spawnPosition = spawnPosition; points = pointsOverride.HasValue ? Mathf.Max(0, pointsOverride.Value) : _defaultPoints; _lifetime = lifetimeOverride.HasValue ? lifetimeOverride.Value : _defaultLifetimeBackup; _age = 0f; ResetStateForReuse(); } public void ResetStateForReuse() { _collected = false; _age = 0f; _spawnPosition = transform.position; OnSpawned(); } protected void Collect(Transform collector) { if (_collected) return; _collected = true; MvpVFXUtility.Spawn(collectVFX, collectVFXResourcePath, transform.position, collectVFXLifetime, collectVFXSettings); OnCollected(); if (CountsTowardsSharedCollectionLimit) { ScoreSystem.Instance?.RegisterSharedEnvironmentalCollectibleCollected(points); } else if (points > 0) { ScoreSystem.Instance?.RegisterEnvironmentalPoints(points); } Collected?.Invoke(this); Destroy(gameObject); } /// /// Деспаун по истечении времени жизни — без начисления очков. /// private void Despawn() { if (_collected) return; _collected = true; OnDespawned(); Collected?.Invoke(this); Destroy(gameObject); } protected virtual void OnSpawned() { } protected virtual void OnCollected() { } protected virtual void OnDespawned() { } protected abstract void TickBehaviour(float dt); private void ValidateTrigger() { if (_trigger == null) _trigger = GetComponent(); if (_trigger == null) { Debug.LogError($"[{nameof(EnvironmentalCollectibleBase)}] Missing Collider2D on {name}.", this); return; } if (!_trigger.isTrigger) Debug.LogError($"[{nameof(EnvironmentalCollectibleBase)}] Collider2D on {name} must have isTrigger enabled.", this); } } }