Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- доработан flow призовой витрины и связанного UI: добавлены `PrizeStandButtonView` и `RulesPrizeListPage`, обновлены `PrizeStandController`, `PrizeStandPrize`, `PrizeStandModal`, `PrizeStandSettingsModal`, `LevelPrizeSettingsModal`, `QRCodeSettingsModal`, `BalanceDisplay`, `ButtonTextFocusColor` и связанные prefab'ы, чтобы улучшить настройку, навигацию, отображение кнопок и prize flow - расширена система настроек и сохранения значений: `OptionPicker` получил отдельный формат `ValueSelector`, обновлены `SettingsDatabase`, `SettingEntryDrawer`, `SettingUIItem`, `SettingsUIGenerator`, `ValueSelectorRow` и `MainSettingsDatabase.asset`, а сохранение option picker-значений, включая `gameplay.gameDifficulty`, исправлено и синхронизировано с остальными настройками - добавлены универсальные спавнеры сущностей: реализованы `EntitySpawner` и `EntitySpawnerManager` с поддержкой весов префабов, точек спавна, заполнения по проценту, ограничений на количество и автоспавна при инициализации - доработана логика игрового спавна и сложности: процент инициализационного спавна может зависеть от настройки `gameplay.gameDifficulty` с градациями 25/50/75/100, в одном цикле массового спавна повторно не используется та же точка, а gizmo спавнера теперь показывает только сами точки без линий - упрощена работа в редакторе Unity и добавлены editor-инструменты: в `GameManager` лицензия пропускается при запуске в `UNITY_EDITOR`, а `HierarchySelectionCounter`, `HierarchySelectionCountOverlay` и `HierarchyBulkRenamerWindow` добавляют подсчет выделения, realtime overlay в `SceneView` и массовое переименование объектов по правилам нумерации из Unity Project Settings - синхронизированы сцены, ресурсы и UI-ассеты под новый flow: обновлены `MainMenu`, `Level UI`, уровни `1-1` и `1-2`, строки настроек, кнопки, TMP/font assets, изображения UI, `prizes.json` и recovery-данные после правок интерфейса и логики - очищены и переразложены часть внешних и служебных ресурсов: удалён пакет `Lava_Flowing_Shader`, переименованы/заменены отдельные UI-prefab'ы и добавлены сопутствующие проектные артефакты, которые сейчас используются обновлённой конфигурацией проекта
213 lines
7.0 KiB
C#
213 lines
7.0 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;
|
|
|
|
[Header("Environmental Collectibles")]
|
|
[SerializeField] private int maxSharedEnvironmentalCollectibles = 50;
|
|
|
|
public int CollectedCoins { get; private set; }
|
|
public int TotalCollectedCoins => CollectedCoins;
|
|
public int KilledEnemies { get; private set; }
|
|
public int EnvironmentalPoints { get; private set; }
|
|
public int SharedEnvironmentalCollectiblesCollected { get; private set; }
|
|
public int CurrentScore { get; private set; }
|
|
public int FinalScore { get; private set; }
|
|
public bool IsFinalized { get; private set; }
|
|
public bool HasReachedSharedEnvironmentalCollectibleLimit =>
|
|
maxSharedEnvironmentalCollectibles > 0 && SharedEnvironmentalCollectiblesCollected >= maxSharedEnvironmentalCollectibles;
|
|
|
|
public event Action<int, int> StatsChanged;
|
|
public event Action<int> EnvironmentalPointsChanged;
|
|
public event Action<int, int> SharedEnvironmentalCollectiblesChanged;
|
|
public event Action<int> CurrentScoreChanged;
|
|
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 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 void RegisterSharedEnvironmentalCollectibleCollected(int points = 1)
|
|
{
|
|
if (IsFinalized || HasReachedSharedEnvironmentalCollectibleLimit) return;
|
|
|
|
SharedEnvironmentalCollectiblesCollected++;
|
|
SharedEnvironmentalCollectiblesChanged?.Invoke(SharedEnvironmentalCollectiblesCollected, maxSharedEnvironmentalCollectibles);
|
|
|
|
if (points > 0)
|
|
{
|
|
EnvironmentalPoints += points;
|
|
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;
|
|
SharedEnvironmentalCollectiblesCollected = 0;
|
|
CurrentScore = 0;
|
|
FinalScore = 0;
|
|
IsFinalized = false;
|
|
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
|
|
EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints);
|
|
SharedEnvironmentalCollectiblesChanged?.Invoke(SharedEnvironmentalCollectiblesCollected, maxSharedEnvironmentalCollectibles);
|
|
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);
|
|
}
|
|
}
|
|
}
|