Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Добавлен `ScoreSystem` — singleton-компонент, отслеживающий монеты, убитых врагов и экологические очки; финальный счёт вычисляется по формуле `Score = E × (P + 45C + 90K + 10K²) − 0.5T` с раздельными множителями для победы и проигрыша. - Добавлен `EnvironmentalCollectibleBase` — абстрактный базовый класс для подбираемых экологических объектов: срабатывает по триггеру, поддерживает пулинг, при подборе регистрирует очки в `ScoreSystem`. - Добавлен `LeafCollectible` — листик с синусоидальным горизонтальным дрейфом, вращением и детекцией земли. - Добавлен `BatCollectible` — летучая мышь с синусоидальным вертикальным движением, ограниченным горизонтальным блужданием и случайными сменами направления; переворачивает спрайт по X при смене направления. - Добавлен `EnvironmentalPool` — простой объектный пул по prefab-ключу с прогревом (`Warmup`) и возвратом объектов. - Добавлен `EnvironmentalCollectibleSpawner` — взвешенный случайный спаунер, поддерживающий фиксированное количество активных объектов; поддерживает лимиты по типу, принудительный выбор одного prefab и область спауна через `BoxCollider2D` или ручной `Bounds`. - В `CoinCollectible.TryPickup` добавлен вызов `ScoreSystem.Instance?.RegisterCoinCollected()`. - В `EnemyController.Die` добавлен guard через флаг `_isDead` для предотвращения повторного срабатывания; добавлен вызов `ScoreSystem.Instance?.RegisterEnemyKilled()`. - В `GameManager` добавлен вызов `ScoreSystem.Instance?.ResetStats()` при старте новой игры и при потере жизни.
152 lines
4.6 KiB
C#
152 lines
4.6 KiB
C#
using System;
|
|
using RR.Game2D.Core.Managers;
|
|
using UnityEngine;
|
|
|
|
namespace RR.Game2D.Behaviour
|
|
{
|
|
/// <summary>
|
|
/// Система очков:
|
|
/// хранит количество собранных монет и убитых врагов,
|
|
/// считает итоговый счет при завершении игры.
|
|
/// Формула: Score = E * (P + 45C + 90K + 10K^2) - 0.5T
|
|
/// </summary>
|
|
[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 FinalScore { get; private set; }
|
|
public bool IsFinalized { get; private set; }
|
|
|
|
public event Action<int, int> StatsChanged;
|
|
public event Action<int> EnvironmentalPointsChanged;
|
|
public event Action<int, float, bool> ScoreCalculated;
|
|
|
|
private bool _subscribed;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
private static void Bootstrap()
|
|
{
|
|
if (Instance != null) return;
|
|
|
|
GameObject go = new(nameof(ScoreSystem));
|
|
go.AddComponent<ScoreSystem>();
|
|
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 can wrap at 100 to award an extra life.
|
|
CollectedCoins += amount;
|
|
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
|
|
}
|
|
|
|
public void RegisterEnemyKilled(int amount = 1)
|
|
{
|
|
if (amount <= 0 || IsFinalized) return;
|
|
KilledEnemies += amount;
|
|
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
|
|
}
|
|
|
|
public void RegisterEnvironmentalPoints(int amount = 1)
|
|
{
|
|
if (amount <= 0 || IsFinalized) return;
|
|
|
|
EnvironmentalPoints += amount;
|
|
EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints);
|
|
}
|
|
|
|
public void ResetStats()
|
|
{
|
|
CollectedCoins = 0;
|
|
KilledEnemies = 0;
|
|
EnvironmentalPoints = 0;
|
|
FinalScore = 0;
|
|
IsFinalized = false;
|
|
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
|
|
EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints);
|
|
}
|
|
|
|
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;
|
|
|
|
float e = isWin ? winMultiplier : loseMultiplier;
|
|
float baseScore = EnvironmentalPoints + 45f * CollectedCoins + 90f * KilledEnemies + 10f * KilledEnemies * KilledEnemies;
|
|
float rawScore = e * baseScore - 0.5f * gameTime;
|
|
|
|
FinalScore = Mathf.Max(0, Mathf.RoundToInt(rawScore));
|
|
IsFinalized = true;
|
|
|
|
ScoreCalculated?.Invoke(FinalScore, gameTime, isWin);
|
|
}
|
|
}
|
|
}
|