Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Исправлен баг `GameManager.NewGame()`: в режиме бесплатной игры (`payment.freeGame = true`) больше не выполняется проверка/списание баланса. - Добавлен явный guard для free-play перед `Balance.CanStartGame()` и `Balance.SpendBalance(...)`, чтобы не было нежелательных списаний. - Устранена ошибка закрытия лог-файла `Cannot access a disposed object`: - `FileLogger` переведен на безопасный паттерн `Dispose(bool)` с идемпотентностью; - добавлена защита от повторного закрытия/записи в уже закрытый поток; - финализатор `FileLogger` больше не выполняет managed-cleanup. - Устранен риск повторной очистки в `CompositeLogger`: - добавлен флаг `_disposed`; - `Dispose(bool)` теперь выполняется только один раз; - финализатор вызывает `Dispose(false)` вместо полного `Dispose()`.
679 lines
26 KiB
C#
679 lines
26 KiB
C#
using System;
|
||
using System.IO;
|
||
using RR.Common.Controls;
|
||
using UnityEngine;
|
||
using UnityEngine.Audio;
|
||
using UnityEngine.SceneManagement;
|
||
|
||
using RR.Common.Managers;
|
||
using RR.Common.Settings;
|
||
using RR.Common.Utility;
|
||
using RR.Game2D.Behaviour;
|
||
using RR.Game2D.Entities;
|
||
|
||
namespace RR.Game2D.Core.Managers
|
||
{
|
||
[DefaultExecutionOrder(-1000)]
|
||
public class GameManager : MonoBehaviour
|
||
{
|
||
// Singleton instance
|
||
public static GameManager Instance { get; private set; }
|
||
// Logger
|
||
public static CompositeLogger Logger { get; private set; }
|
||
// Game settings (новая система)
|
||
public SettingsManager Settings { get; private set; }
|
||
// Audio control for game
|
||
public AudioManager Audio {get; private set;}
|
||
// Payment control for game
|
||
public PaymentManager Payment {get; private set;}
|
||
// Balance control for game
|
||
public BalanceManager Balance {get; private set;}
|
||
// Inactivity control for game
|
||
public InactivityManager Inactivity {get; private set;}
|
||
|
||
// Plate control for game
|
||
public ComSystemManager Plate { get; private set; }
|
||
// Light control for game
|
||
public LightController Light { get; private set; }
|
||
|
||
// Globals
|
||
[Header("Globals")]
|
||
[SerializeField] private string globalsPrefabResourcePath = "Prefabs/Global/Globals";
|
||
[SerializeField] private string AudioMixerResourcePath = "Audio/Master";
|
||
|
||
private GameObject globals;
|
||
|
||
[Header("Пауза")]
|
||
public bool IsPaused { get; private set; }
|
||
|
||
[Header("Базовые настройки игры")]
|
||
public int StartingLives { get; private set; } = 3;
|
||
public int MaxLives { get; private set; } = 3;
|
||
public int LivesIncrement { get; private set; } = 0;
|
||
|
||
[Header("Информация для игры")]
|
||
public int World { get; private set; } = 1;
|
||
public int Stage { get; private set; } = 1;
|
||
private int StagesByWorld { get; set; } = 3;
|
||
public int Lives { get; set; } = 3;
|
||
public int Coins { get; private set; } = 0;
|
||
|
||
private float _gameStartTime;
|
||
|
||
[Header("Игрок")]
|
||
[SerializeField] private GameObject playerPrefab;
|
||
[SerializeField] private string playerPrefabResourcePath = "Prefabs/Player_New"; // Путь в Resources для загрузки префаба игрока, если не назначен в инспекторе
|
||
public GameObject PlayerObj { get; private set; }
|
||
private bool _shouldSpawnPlayer;
|
||
|
||
private void OnEnable() => SceneManager.sceneLoaded += OnSceneLoaded;
|
||
private void OnDisable() => SceneManager.sceneLoaded -= OnSceneLoaded;
|
||
|
||
// События менеджера
|
||
public event Action<bool> PauseStateChanged; // Когда меняется состояние паузы
|
||
public event Action<GameObject> PlayerSpawned; // Когда игрос спавнится на сцене
|
||
public event Action<int> CoinsChanged; // Когда меняется кол-во монет
|
||
public event Action<int> MaxLivesChanged; // Когда меняется максимальное кол-во жизней
|
||
public event Action<int> LivesChanged; // Когда меняется кол-во жизней
|
||
public event Action<float> GameOverEvent; // Когда игрок проигрывает
|
||
public event Action<float> GameWonEvent; // Когда игрок выигрывает
|
||
|
||
#region Unity Lifetime
|
||
|
||
// Singleton
|
||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||
private static void Initialize()
|
||
{
|
||
if (Instance != null) return;
|
||
GameObject go = new(nameof(GameManager));
|
||
Instance = go.AddComponent<GameManager>();
|
||
go.AddComponent<AutoCursorHider>();
|
||
DontDestroyOnLoad(go);
|
||
}
|
||
|
||
private void Awake()
|
||
{
|
||
// ---- Singleton ----
|
||
if (Instance != null && Instance != this)
|
||
{
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
|
||
Instance = this;
|
||
DontDestroyOnLoad(gameObject);
|
||
// -------------------
|
||
|
||
// Initialize Logger
|
||
if (Logger == null)
|
||
{
|
||
Logger = new CompositeLogger();
|
||
FileLogger fileLogger = new(Path.Combine(Directory.GetParent(Application.dataPath)?.ToString() ?? Application.dataPath, "game.log"));
|
||
Logger.Initialize(fileLogger);
|
||
}
|
||
|
||
// Initialize Audio control
|
||
AudioMixer audioMixer = Resources.Load<AudioMixer>(AudioMixerResourcePath);
|
||
if (Audio == null)
|
||
{
|
||
Audio = gameObject.AddComponent<AudioManager>();
|
||
Audio.Initialize(audioMixer);
|
||
}
|
||
|
||
// Initialize Global Scripts from prefab
|
||
if (globals == null)
|
||
{
|
||
GameObject prefab = Resources.Load<GameObject>(globalsPrefabResourcePath);
|
||
if (prefab != null)
|
||
{
|
||
Debug.Log($"[GameManager] Loading Globals prefab from {globalsPrefabResourcePath}");
|
||
globals = Instantiate(prefab);
|
||
DontDestroyOnLoad(globals);
|
||
Debug.Log($"[GameManager] Globals instantiated. SettingsManager.Instance = {(SettingsManager.Instance != null ? "exists" : "null")}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"[GameManager] Failed to load Globals prefab from {globalsPrefabResourcePath}");
|
||
}
|
||
}
|
||
|
||
// Initialize Settings Manager (новая система) from Globals
|
||
if (Settings == null) Settings = globals.GetComponent<SettingsManager>();
|
||
if (Settings == null) Settings = SettingsManager.Instance;
|
||
|
||
if (Settings != null)
|
||
{
|
||
Settings.OnSettingsLoaded += OnSettingsChanged;
|
||
Settings.OnSettingsSaved += OnSettingsChanged;
|
||
}
|
||
|
||
// Initialize Payment control
|
||
if (Payment == null) Payment = globals.GetComponent<PaymentManager>();
|
||
|
||
// Initialize Balance control
|
||
if (Balance == null) Balance = globals.GetComponent<BalanceManager>();
|
||
if (Balance == null) Balance = BalanceManager.Instance;
|
||
|
||
// Initialize Inactivity control
|
||
if (Inactivity == null) Inactivity = gameObject.AddComponent<InactivityManager>();
|
||
if (Inactivity == null) Inactivity = InactivityManager.Instance;
|
||
|
||
if (Plate == null) Plate = globals.GetComponent<ComSystemManager>();
|
||
|
||
if (Plate != null) Light = Plate.Light;
|
||
if (Light) Light.SetLight(LightMode.DiagonalWave)
|
||
.Then(LightMode.DiagonalWave2)
|
||
.Then(LightMode.Rainbow)
|
||
.Then(LightMode.FallingRows)
|
||
.Then(LightMode.SeaWave)
|
||
.Loop();
|
||
|
||
Payment.OnCreditAdded.AddListener(OnCreditAdded);
|
||
|
||
_gameStartTime = Time.unscaledTime;
|
||
|
||
MaxLives = StartingLives;
|
||
MaxLivesChanged?.Invoke(MaxLives);
|
||
|
||
if (SceneManager.GetActiveScene().name != "MainMenu") _shouldSpawnPlayer = true;
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (!Light) return;
|
||
Light.StopBlink(false);
|
||
Light.StopSequence();
|
||
|
||
if (Instance == this) Instance = null;
|
||
}
|
||
|
||
private void OnApplicationQuit()
|
||
{
|
||
OnDestroy();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Поиск префаба игрока.
|
||
/// Если не найдет префаб из инспектора, ищет в "Resources"
|
||
/// </summary>
|
||
/// <returns> Префаб игрока </returns>
|
||
private GameObject ResolvePlayerPrefab()
|
||
{
|
||
if (playerPrefab != null) return playerPrefab;
|
||
|
||
GameObject loaded = Resources.Load<GameObject>(playerPrefabResourcePath);
|
||
|
||
if (loaded == null)
|
||
{
|
||
Logger.LogError(
|
||
gameObject.name,
|
||
$"playerPrefab is null. Resources.Load('{playerPrefabResourcePath}') вернул null." +
|
||
$" Ожидается файл: Assets/Resources/{playerPrefabResourcePath}.prefab (точное совпадение регистра)."
|
||
);
|
||
}
|
||
return loaded;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Payment Event Handlers
|
||
|
||
private void OnCreditAdded(int newCount)
|
||
{
|
||
if (SceneManager.GetActiveScene().name == "MainMenu")
|
||
{
|
||
if (newCount > 1)
|
||
{
|
||
MaxLives += LivesIncrement;
|
||
}
|
||
}
|
||
// Debug.Log($"Max Lives: {MaxLives}");
|
||
|
||
MaxLivesChanged?.Invoke(MaxLives);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Settings Event Handlers
|
||
|
||
private void OnSettingsChanged()
|
||
{
|
||
if (Settings == null) return;
|
||
|
||
// Получаем настройки из нового SettingsManager
|
||
StartingLives = Settings.GetInt("gameplay.startLives");
|
||
LivesIncrement = Settings.GetInt("gameplay.additionalLives");
|
||
|
||
int currentCredits = Payment?.CreditsCount ?? 0;
|
||
MaxLives = currentCredits > 1 ? StartingLives + (currentCredits - 1) * LivesIncrement : StartingLives;
|
||
MaxLivesChanged?.Invoke(MaxLives);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Pause
|
||
|
||
/// <summary>
|
||
/// Метод паузы игры.
|
||
/// Ставит на паузу все SFX и саму игру
|
||
/// </summary>
|
||
/// <param name="pause">Ставить ли на паузу</param>
|
||
public void SetPause(bool pause)
|
||
{
|
||
if (IsPaused == pause) return;
|
||
IsPaused = pause;
|
||
|
||
Time.timeScale = pause ? 0f : 1f;
|
||
Audio.SmoothPauseMusic(pause);
|
||
|
||
PauseStateChanged?.Invoke(IsPaused);
|
||
}
|
||
|
||
// Можно заменить на другое поведение
|
||
/// <summary>
|
||
/// Метод переключателя паузы.
|
||
/// </summary>
|
||
public void TogglePause() => SetPause(!IsPaused);
|
||
// --
|
||
|
||
#endregion
|
||
|
||
#region Levels and Scene management
|
||
|
||
/// <summary>
|
||
/// Начало новой игры.
|
||
/// Загружает первый уровень.
|
||
/// Устанавливает жизни на 3, монеты на 0
|
||
/// </summary>
|
||
public void NewGame()
|
||
{
|
||
Debug.Log("[GameManager] NewGame called");
|
||
bool isFreeGame = Settings != null && Settings.GetBool("payment.freeGame");
|
||
|
||
// Проверка баланса перед началом игры
|
||
if (!isFreeGame && Balance != null && !Balance.CanStartGame())
|
||
{
|
||
Logger?.Log(gameObject.name, "Cannot start game - insufficient balance");
|
||
Debug.LogWarning("[GameManager] Cannot start - insufficient balance");
|
||
return;
|
||
}
|
||
|
||
int livesToGive = StartingLives;
|
||
int balanceToSpend = 0;
|
||
|
||
if (!isFreeGame && Balance != null)
|
||
{
|
||
int currentBalance = Balance.CurrentBalance;
|
||
int impulseCost = Settings?.GetInt("payment.paymentImpulseCost") ?? 10;
|
||
|
||
// Рассчитываем количество оплат
|
||
int totalImpulses = currentBalance / impulseCost;
|
||
|
||
// Первая оплата даёт базовые жизни
|
||
// Каждая дополнительная оплата даёт LivesIncrement жизней
|
||
if (totalImpulses > 0)
|
||
{
|
||
int additionalImpulses = totalImpulses - 1;
|
||
int bonusLives = additionalImpulses * LivesIncrement;
|
||
livesToGive = StartingLives + bonusLives;
|
||
|
||
Debug.Log($"[GameManager] Balance: {currentBalance}, Impulses: {totalImpulses}, Base lives: {StartingLives}, Bonus lives: {bonusLives}, Total lives: {livesToGive}");
|
||
}
|
||
|
||
// Списываем ВЕСЬ баланс
|
||
balanceToSpend = currentBalance;
|
||
|
||
if (!Balance.SpendBalance(balanceToSpend, "начало игры (полный баланс)"))
|
||
{
|
||
Logger?.Log(gameObject.name, "Failed to spend balance for game start");
|
||
Debug.LogError("[GameManager] Failed to spend balance!");
|
||
return;
|
||
}
|
||
|
||
Debug.Log($"[GameManager] Spent entire balance: {balanceToSpend}, Lives given: {livesToGive}");
|
||
}
|
||
else if (isFreeGame)
|
||
{
|
||
Debug.Log("[GameManager] Free game mode enabled. Balance spend skipped.");
|
||
}
|
||
|
||
Lives = livesToGive;
|
||
LivesChanged?.Invoke(Lives);
|
||
Coins = 0;
|
||
CoinsChanged?.Invoke(Coins);
|
||
|
||
_gameStartTime = Time.time;
|
||
Logger.Log(gameObject.name,$"Game start time = {_gameStartTime}");
|
||
|
||
// Активируем шапку и эффекты при начале игры
|
||
if (Light)
|
||
{
|
||
Light.StopBlink();
|
||
if (Light.IsSequencePlaying) Light.StopSequence();
|
||
Light.SetLight(LightMode.Rainbow).Loop();
|
||
}
|
||
|
||
// Включаем отслеживание бездействия
|
||
if (Inactivity != null) Inactivity.EnableInactivityCheck();
|
||
|
||
LoadLevel(1, 1);
|
||
}
|
||
|
||
public void ContinueGame()
|
||
{
|
||
// Проверка баланса перед продолжением игры
|
||
if (Balance != null && !Balance.CanContinueGame())
|
||
{
|
||
Logger?.Log(gameObject.name, "Cannot continue game - insufficient balance");
|
||
return;
|
||
}
|
||
|
||
// Списание баланса за продолжение игры
|
||
if (Balance != null && !Balance.SpendForContinue())
|
||
{
|
||
Logger?.Log(gameObject.name, "Failed to spend balance for continue");
|
||
return;
|
||
}
|
||
|
||
Lives = StartingLives + 1;
|
||
LivesChanged?.Invoke(Lives);
|
||
Coins = 0;
|
||
CoinsChanged?.Invoke(Coins);
|
||
|
||
// Сбрасываем таймер бездействия
|
||
if (Inactivity != null) Inactivity.ResetInactivityTimer();
|
||
|
||
RespawnPlayer();
|
||
}
|
||
|
||
// Можно заменить на другое поведение
|
||
/// <summary>
|
||
/// Окончание игры.
|
||
/// Загружает сцену главного меню
|
||
/// </summary>
|
||
private void GameOver()
|
||
{
|
||
float totalTime = Time.unscaledTime - _gameStartTime;
|
||
|
||
// Отключаем отслеживание бездействия
|
||
if (Inactivity != null) Inactivity.DisableInactivityCheck();
|
||
|
||
Light?.SetLight(LightMode.BlueWave, 30);
|
||
|
||
GameOverEvent?.Invoke(totalTime);
|
||
}
|
||
// --
|
||
|
||
private void GameWin()
|
||
{
|
||
float totalTime = Time.unscaledTime - _gameStartTime;
|
||
|
||
// Отключаем отслеживание бездействия
|
||
if (Inactivity != null) Inactivity.DisableInactivityCheck();
|
||
|
||
Light?.SetLight(LightMode.FinalWave, 30);
|
||
|
||
GameWonEvent?.Invoke(totalTime);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Метод переход на следующий уровень.
|
||
/// Может загружать уровни и миры в прямой зависимости от
|
||
/// Количества уровней на мир(StagesByWorld)
|
||
/// </summary>
|
||
public void NextLevel()
|
||
{
|
||
if (Stage >= StagesByWorld)
|
||
{
|
||
World++;
|
||
Stage = 1;
|
||
}
|
||
else Stage++;
|
||
LoadLevel(World, Stage);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Метод сброса уровня.
|
||
/// При сбросе также сбрасывает монеты и отнимает жизни
|
||
/// </summary>
|
||
[Obsolete("Используйте RespawnPlayer() для респавна игрока без сброса уровня и жизней.")]
|
||
public void ResetLevel()
|
||
{
|
||
Lives--;
|
||
LivesChanged?.Invoke(Lives);
|
||
Coins = 0;
|
||
CoinsChanged?.Invoke(Coins);
|
||
|
||
//print($"Lives: {lives}");
|
||
if (Lives > 0) LoadLevel(World, Stage);
|
||
else GameOver();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Загрузка уровня по данным.
|
||
/// Загружает уровень по номерам мира и уровня
|
||
/// </summary>
|
||
/// <param name="world">Номер мира</param>
|
||
/// <param name="stage">Номер уровня</param>
|
||
public void LoadLevel(int world, int stage)
|
||
{
|
||
this.World = world;
|
||
this.Stage = stage;
|
||
|
||
string sceneName = $"{world}-{stage}";
|
||
if (SceneInBuildSettingsExists(sceneName))
|
||
{
|
||
LoadScene(sceneName);
|
||
}
|
||
else
|
||
{
|
||
// Можно заменить на другое поведение
|
||
|
||
GameWin();
|
||
|
||
// --
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Загрузка сцены по названию.
|
||
/// Вызывает одноименный метод в <see cref="SceneManager"/>.
|
||
/// Может загружать сцены с UI и без.
|
||
/// В сценах без UI игрок не спавнится
|
||
/// </summary>
|
||
/// <param name="sceneName">Строковое название сцены</param>
|
||
/// <param name="useUI">Использовать ли UI</param>
|
||
public void LoadScene(string sceneName, bool useUI = true)
|
||
{
|
||
_shouldSpawnPlayer = true;
|
||
|
||
SceneManager.LoadScene(sceneName);
|
||
|
||
if (useUI) SceneManager.LoadScene("Level UI", LoadSceneMode.Additive);
|
||
else
|
||
{
|
||
Light?.SetLight(LightMode.Off);
|
||
Plate.Blink();
|
||
_shouldSpawnPlayer = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Метод проверки сцены на существование в настройках билда
|
||
/// </summary>
|
||
/// <param name="sceneName">Строковое название сцены</param>
|
||
/// <returns></returns>
|
||
private static bool SceneInBuildSettingsExists(string sceneName)
|
||
{
|
||
var count = SceneManager.sceneCountInBuildSettings;
|
||
for (var i = 0; i < count; i++)
|
||
{
|
||
var path = SceneUtility.GetScenePathByBuildIndex(i);
|
||
if (Path.GetFileNameWithoutExtension(path) == sceneName)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Метод обработки события загрузки сцены.
|
||
/// При загрузке сцены проверяет её название на наличие "-" (уровень формата "1-1")
|
||
/// Если находит, спавнит игрока
|
||
/// Также обновляет номера мира и уровня
|
||
/// </summary>
|
||
/// <param name="scene">Сцена</param>
|
||
/// <param name="mode">Режим загрузки сцены</param>
|
||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||
{
|
||
string currentSceneName = SceneManager.GetActiveScene().name;
|
||
|
||
if (!currentSceneName.Contains("-")) return;
|
||
TrySpawnPlayerForActiveScene();
|
||
|
||
if (currentSceneName == $"{World}-{Stage}") return;
|
||
string[] levelInfo = currentSceneName.Split("-");
|
||
World = Convert.ToInt32(levelInfo[0]);
|
||
Stage = Convert.ToInt32(levelInfo[1]);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Player management
|
||
|
||
/// <summary>
|
||
/// Метод для спавна игрока.
|
||
/// Ищет первый спавнпоинт, спавнит игрока на его позиции.
|
||
/// Если на карте есть объект игрока, удаляет его и спавнит свой
|
||
/// </summary>
|
||
private void TrySpawnPlayerForActiveScene()
|
||
{
|
||
if (!_shouldSpawnPlayer) return;
|
||
PlayerSpawn spawn = FindFirstObjectByType<PlayerSpawn>();
|
||
Vector3 pos = spawn ? spawn.transform.position : new Vector3(0, 1.3f, 0);
|
||
Quaternion rot = spawn ? spawn.transform.rotation : Quaternion.identity;
|
||
|
||
PlayerObj = GameObject.FindGameObjectWithTag("Player");
|
||
if (PlayerObj != null) Destroy(PlayerObj);
|
||
|
||
GameObject prefab = ResolvePlayerPrefab();
|
||
if (prefab == null) return;
|
||
|
||
PlayerObj = Instantiate(prefab, pos, rot);
|
||
SceneManager.MoveGameObjectToScene(PlayerObj, SceneManager.GetActiveScene());
|
||
|
||
PlayerSpawned?.Invoke(PlayerObj);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Метод респавна игрока.
|
||
/// Отнимает жизнь, если жизней не осталось — вызывает GameOver()
|
||
/// Спавнит игрока на активном чекпоинте или на дефолтной позиции
|
||
/// </summary>
|
||
public void RespawnPlayer()
|
||
{
|
||
// отнимем жизнь (оставляем текущее поведение); монеты не сбрасываем
|
||
Lives--;
|
||
LivesChanged?.Invoke(Lives);
|
||
|
||
if (Lives <= 0)
|
||
{
|
||
GameOver();
|
||
return;
|
||
}
|
||
|
||
// Определяем дефолтные позицию и направление из PlayerSpawn в сцене
|
||
PlayerSpawn spawn = FindFirstObjectByType<PlayerSpawn>();
|
||
Vector3 defaultPos = spawn ? spawn.transform.position : new Vector3(0, 1.3f, 0);
|
||
Vector2 defaultForward = spawn ? spawn.defaultForward : Vector2.right;
|
||
|
||
// Если есть активный чекпоинт — берём из него; иначе дефолт
|
||
Vector3 respawnPos = defaultPos;
|
||
Vector2 respawnForward = defaultForward;
|
||
if (CheckpointSystem.Instance != null)
|
||
{
|
||
respawnPos = CheckpointSystem.Instance.GetSpawnPosition(defaultPos);
|
||
respawnForward = CheckpointSystem.Instance.GetSpawnForward(defaultForward);
|
||
|
||
if (CheckpointSystem.Instance.Active != null)
|
||
{
|
||
respawnPos = CheckpointSystem.Instance.Active.GetSpawnPosition();
|
||
respawnForward = CheckpointSystem.Instance.Active.GetForward();
|
||
}
|
||
}
|
||
|
||
// Если потеряли ссылку — попробуем найти игрока в сцене
|
||
if (PlayerObj == null)
|
||
PlayerObj = GameObject.FindGameObjectWithTag("Player");
|
||
|
||
if (PlayerObj == null)
|
||
{
|
||
// Создадим нового игрока, если объект был уничтожен
|
||
GameObject prefab = ResolvePlayerPrefab();
|
||
if (prefab == null) return;
|
||
PlayerObj = Instantiate(prefab, respawnPos, Quaternion.identity);
|
||
SceneManager.MoveGameObjectToScene(PlayerObj, SceneManager.GetActiveScene());
|
||
PlayerSpawned?.Invoke(PlayerObj);
|
||
}
|
||
else
|
||
{
|
||
// Остановим движение и телепортируем
|
||
Rigidbody2D rb = PlayerObj.GetComponent<Rigidbody2D>();
|
||
if (rb != null)
|
||
{
|
||
rb.linearVelocity = Vector2.zero;
|
||
rb.angularVelocity = 0f;
|
||
}
|
||
|
||
PlayerObj.transform.position = respawnPos;
|
||
}
|
||
|
||
// Повернём в нужную сторону (по X)
|
||
if (PlayerObj == null) return;
|
||
if (respawnForward.x != 0)
|
||
{
|
||
PlayerObj.transform.localEulerAngles = respawnForward.x >= 0
|
||
? Vector3.zero
|
||
: new Vector3(0, 180f, 0);
|
||
}
|
||
|
||
// Восстановим здоровье
|
||
Health playerHealth = PlayerObj.GetComponent<Health>();
|
||
if (playerHealth != null)
|
||
playerHealth.ResetHealth();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Coins and Lives management
|
||
|
||
/// <summary>
|
||
/// Метод добавления внутриигровой монеты
|
||
/// </summary>
|
||
public void AddCoin(int count = 1)
|
||
{
|
||
Coins += count;
|
||
// Можно заменить на другое поведение
|
||
if (Coins >= 100)
|
||
{
|
||
Coins -= 100;
|
||
AddLife();
|
||
}
|
||
// --
|
||
CoinsChanged?.Invoke(Coins);
|
||
}
|
||
|
||
// Можно заменить на другое поведение
|
||
/// <summary>
|
||
/// Метод для добавления жизни.
|
||
/// </summary>
|
||
private void AddLife()
|
||
{
|
||
Lives++;
|
||
LivesChanged?.Invoke(Lives);
|
||
}
|
||
// --
|
||
|
||
#endregion
|
||
}
|
||
}
|