using System; using RR.Game2D.Core.Managers; using UnityEngine; namespace RR.Game2D.Behaviour { /// /// Система очков: /// хранит количество собранных монет и убитых врагов, /// считает итоговый счет при завершении игры. /// Формула: Score = E * (P + 45C + 90K + 10K^2) - 0.5T /// [DefaultExecutionOrder(-900)] public class ScoreSystem : MonoBehaviour { public static ScoreSystem Instance { get; private set; } [Header("Коэффициенты исхода")] [SerializeField] private float winMultiplier = 1f; [SerializeField] private float loseMultiplier = 0.6f; public int CollectedCoins { get; private set; } public int TotalCollectedCoins => CollectedCoins; public int KilledEnemies { get; private set; } public int EnvironmentalPoints { get; private set; } public int CurrentScore { get; private set; } public int FinalScore { get; private set; } public bool IsFinalized { get; private set; } public event Action StatsChanged; public event Action EnvironmentalPointsChanged; public event Action CurrentScoreChanged; public event Action ScoreCalculated; private bool _subscribed; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Bootstrap() { if (Instance != null) return; GameObject go = new(nameof(ScoreSystem)); go.AddComponent(); DontDestroyOnLoad(go); } private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); ResetStats(); } private void Start() { TrySubscribe(); } private void OnEnable() { TrySubscribe(); } private void OnDisable() { Unsubscribe(); } public void RegisterCoinCollected(int amount = 1) { if (amount <= 0 || IsFinalized) return; // This is the cumulative coin count for score calculation. // It is intentionally separate from GameManager.Coins, which is used by gameplay/UI and life-award thresholds. CollectedCoins += amount; StatsChanged?.Invoke(CollectedCoins, KilledEnemies); RefreshCurrentScore(); } public void RegisterEnemyKilled(int amount = 1) { if (amount <= 0 || IsFinalized) return; KilledEnemies += amount; StatsChanged?.Invoke(CollectedCoins, KilledEnemies); RefreshCurrentScore(); } public void RegisterEnvironmentalPoints(int amount = 1) { if (amount <= 0 || IsFinalized) return; EnvironmentalPoints += amount; EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints); RefreshCurrentScore(); } public int CalculatePreviewScore(float gameTime, bool isWin) { float e = isWin ? winMultiplier : loseMultiplier; float baseScore = EnvironmentalPoints + 45f * CollectedCoins + 90f * KilledEnemies + 10f * KilledEnemies * KilledEnemies; float rawScore = e * baseScore - 0.5f * gameTime; return Mathf.Max(0, Mathf.RoundToInt(rawScore)); } public int GetDisplayScore(float gameTime, bool isWin = true) { if (IsFinalized) { return FinalScore; } return CalculatePreviewScore(gameTime, isWin); } public void ResetStats() { CollectedCoins = 0; KilledEnemies = 0; EnvironmentalPoints = 0; CurrentScore = 0; FinalScore = 0; IsFinalized = false; StatsChanged?.Invoke(CollectedCoins, KilledEnemies); EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints); CurrentScoreChanged?.Invoke(CurrentScore); } private void TrySubscribe() { if (_subscribed || GameManager.Instance == null) return; GameManager.Instance.GameWonEvent += OnGameWon; GameManager.Instance.GameOverEvent += OnGameOver; _subscribed = true; } private void Unsubscribe() { if (!_subscribed || GameManager.Instance == null) return; GameManager.Instance.GameWonEvent -= OnGameWon; GameManager.Instance.GameOverEvent -= OnGameOver; _subscribed = false; } private void OnGameWon(float gameTime) { Calculate(gameTime, true); } private void OnGameOver(float gameTime) { Calculate(gameTime, false); } private void Calculate(float gameTime, bool isWin) { if (IsFinalized) return; FinalScore = CalculatePreviewScore(gameTime, isWin); CurrentScore = FinalScore; IsFinalized = true; CurrentScoreChanged?.Invoke(CurrentScore); ScoreCalculated?.Invoke(FinalScore, gameTime, isWin); } private void RefreshCurrentScore() { if (IsFinalized) { return; } float gameTime = GameManager.Instance != null ? GameManager.Instance.GetCurrentGameTime() : 0f; CurrentScore = CalculatePreviewScore(gameTime, true); CurrentScoreChanged?.Invoke(CurrentScore); } } }