Files
decay-grid/Assets/Scripts/Infrastructure/Unity/GameBootstrap.cs

365 lines
12 KiB
C#

using System.Collections;
using System.Collections.Generic;
using Core.Domain;
using Core.Ports;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Infrastructure.Unity
{
public class GameBootstrap : MonoBehaviour
{
[Header("Infrastructure")]
[SerializeField] private LevelGenerator levelGenerator;
[SerializeField] private OrbViewAdapter orbPrefab;
[SerializeField] private PlayerController playerPrefab;
[SerializeField] private DeathPlaneAdapter deathPlanePrefab;
[SerializeField] private SoundManager soundManager;
[SerializeField] private RumbleManager rumbleManager;
[SerializeField] private BeatPulseController beatPulseController;
[SerializeField] private CameraController cameraController;
[SerializeField] private NpcController npcPrefab;
[SerializeField] private HunterNpcController hunterNpcPrefab;
[SerializeField] private FloorVisibilityManager floorVisibilityManager;
[Header("Ui")]
[SerializeField] private TMP_Text scoreText;
[SerializeField] private TMP_Text highScoreText;
[SerializeField] private GameObject gameOverUi;
[SerializeField] private GameObject startScreenUi;
[Header("Settings")]
[SerializeField] private float restartTime = 3f;
[Header("Power Ups")]
[SerializeField] private PowerUpViewAdapter powerUpPrefab;
private readonly List<Tile> _allTiles = new();
private readonly Dictionary<string, TileViewAdapter> _tileViews = new();
private GameSession _gameSession;
private IPersistenceService _persistenceService;
private InputSystem_Actions _actions;
private PlayerController _playerInstance;
private GameObject _currentOrbInstance;
private bool _isGameRunning;
private int _currentPlayerFloorIndex;
private int _currentDisplayedScore;
private void OnEnable()
{
_actions = new InputSystem_Actions();
_actions.Player.Enable();
}
private void OnDisable()
{
_actions.Player.Disable();
beatPulseController.OnMeasure.RemoveListener(OnBeatMeasure);
}
private void Start()
{
_persistenceService = new PlayerPrefsPersistenceAdapter();
_gameSession = new GameSession(_allTiles, _persistenceService);
// Set Theme based on High Score
ThemeManager.CurrentTheme = ThemeManager.GetTheme(_gameSession.HighScore);
var floorsCount = levelGenerator ? levelGenerator.FloorsCount : 1;
if (levelGenerator)
{
StartCoroutine(levelGenerator.GenerateAsync(soundManager, _allTiles, _tileViews, cameraController,
rumbleManager,
() =>
{
if (!floorVisibilityManager)
{
floorVisibilityManager = gameObject.AddComponent<FloorVisibilityManager>();
}
floorVisibilityManager.Initialize(_gameSession, _allTiles, _tileViews, floorsCount);
SpawnDeathPlane();
SpawnPlayer();
if (gameOverUi) gameOverUi.SetActive(false);
if (startScreenUi) startScreenUi.SetActive(true); // Show start screen NOW
WireEvents();
UpdateScoreUi(_gameSession.Score);
}));
}
if (gameOverUi) gameOverUi.SetActive(false);
if (startScreenUi) startScreenUi.SetActive(true);
}
private void Update()
{
if (!_isGameRunning)
{
if (_actions.Player.StartGame.triggered)
{
StartGameSequence();
}
return;
}
if (_playerInstance)
{
var playerY = _playerInstance.transform.position.y;
// Calculate current floor index based on Y height (inverse logic from Generator)
// Note: Generator uses negative offsets: 0, -15, -30.
// So Floor 0 is at Y=0. Floor 1 is at Y=-15.
var heightDist = levelGenerator.FloorHeightDistance;
var maxFloors = levelGenerator.FloorsCount;
var rawFloor = Mathf.RoundToInt(-playerY / heightDist);
_currentPlayerFloorIndex = Mathf.Clamp(rawFloor, 0, maxFloors - 1);
_gameSession.SetPlayerFloor(_currentPlayerFloorIndex);
floorVisibilityManager.UpdateFloorVisibility(_currentPlayerFloorIndex);
}
var dt = Time.deltaTime;
if (_isGameRunning) _gameSession.Tick(dt);
var dilation = _gameSession.TimeDilation;
// Hard Mode: Decay faster as score increases
var decayMultiplier = 1.0f + (_gameSession.Score / 500f);
for (var i = _allTiles.Count - 1; i >= 0; i--)
{
_allTiles[i].Tick(dt * dilation * decayMultiplier);
}
}
private void SpawnVisualOrb(string tileId)
{
if (_currentOrbInstance) Destroy(_currentOrbInstance);
if (!_tileViews.TryGetValue(tileId, out var tileView)) return;
if (!tileView) return;
var spawnPos = tileView.transform.position + Vector3.up;
var orb = Instantiate(orbPrefab, spawnPos, Quaternion.identity);
orb.OnCollected += () => _gameSession.OrbCollected();
_currentOrbInstance = orb.gameObject;
}
private void HandleOrbReset()
{
if (_currentOrbInstance)
{
Destroy(_currentOrbInstance);
}
}
private void UpdateScoreUi(int newScore)
{
if (!scoreText) return;
LeanTween.cancel(scoreText.gameObject);
scoreText.rectTransform.localScale = Vector3.one;
LeanTween.scale(scoreText.rectTransform, Vector3.one * 1.5f, 0.5f)
.setEasePunch();
LeanTween.value(scoreText.gameObject, (float val) =>
{
var currentVal = Mathf.RoundToInt(val);
var combo = _gameSession?.ComboMultiplier ?? 1;
var comboText = combo > 1 ? $" <color=yellow>x{combo}</color>" : "";
scoreText.text = $"{currentVal}{comboText}";
}, _currentDisplayedScore, newScore, 0.5f)
.setEaseOutExpo();
_currentDisplayedScore = newScore;
if (highScoreText && _gameSession != null)
highScoreText.text = $"BEST: {_gameSession.HighScore}";
}
private void SpawnPlayer()
{
var spawnPos = new Vector3(0f, 5f, 0f);
_playerInstance = Instantiate(playerPrefab, spawnPos, Quaternion.identity);
_playerInstance.enabled = false;
_playerInstance.Rigidbody.isKinematic = true;
if (cameraController)
{
cameraController.SetTarget(_playerInstance.transform);
}
}
private void SpawnDeathPlane()
{
if (!levelGenerator) return;
var lowestY = -(levelGenerator.FloorsCount * levelGenerator.FloorHeightDistance) - 5f;
var pos = new Vector3(levelGenerator.GridSizeX / 2f, lowestY, levelGenerator.GridSizeY / 2f);
var plane = Instantiate(deathPlanePrefab, pos, Quaternion.identity);
plane.transform.localScale = new Vector3(levelGenerator.GridSizeX * 2f, 1f, levelGenerator.GridSizeY * 2f);
plane.OnPlayerFell += () => _gameSession.EndGame();
}
private void HandleGameOver()
{
_isGameRunning = false;
if (beatPulseController) beatPulseController.StopTracking();
if (gameOverUi) gameOverUi.SetActive(true);
StartCoroutine(RestartRoutine());
}
private IEnumerator RestartRoutine()
{
yield return new WaitForSeconds(restartTime);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
private void WireEvents()
{
_gameSession.OnScoreChanged += UpdateScoreUi;
_gameSession.OnOrbSpawned += SpawnVisualOrb;
_gameSession.OnOrbReset += HandleOrbReset;
_gameSession.OnGameOver += HandleGameOver;
_gameSession.OnSpawnNpc += SpawnNpc;
_gameSession.OnSpawnPowerUp += SpawnPowerUp;
if (beatPulseController)
{
beatPulseController.OnMeasure.AddListener(OnBeatMeasure);
}
if (!soundManager) return;
_gameSession.OnScoreChanged += _ => soundManager.PlayScore();
_gameSession.OnGameOver += () =>
{
soundManager.StopMusic();
soundManager.PlayGameOver();
};
}
private void SpawnNpc()
{
var validTiles = _allTiles.FindAll(t => t.Floor == 0 && t.CurrentState == TileState.Stable);
if (validTiles.Count == 0)
{
validTiles = _allTiles.FindAll(t => t.CurrentState == TileState.Stable);
}
if (validTiles.Count == 0) return;
var randomTile = validTiles[Random.Range(0, validTiles.Count)];
if (!_tileViews.TryGetValue(randomTile.Id, out var tileView)) return;
if (!tileView) return;
var spawnPos = tileView.transform.position + Vector3.up * 5f;
if (_playerInstance && hunterNpcPrefab && Random.value < 0.3f)
{
var hunter = Instantiate(hunterNpcPrefab, spawnPos, Quaternion.identity);
hunter.Initialize(_playerInstance, () => _gameSession.EndGame(), () => _gameSession.TimeDilation);
}
else if (npcPrefab)
{
var npc = Instantiate(npcPrefab, spawnPos, Quaternion.identity);
npc.Initialize(() => _gameSession.TimeDilation, () => _gameSession.EndGame());
}
soundManager.PlayNpcSpawn();
}
private void StartGameSequence()
{
_isGameRunning = true;
_currentDisplayedScore = 0;
if (startScreenUi) startScreenUi.SetActive(false);
if (soundManager)
{
soundManager.PlayGameStart();
soundManager.PlayMusic();
if (beatPulseController) beatPulseController.BeginTracking();
}
if (_playerInstance)
{
_playerInstance.enabled = true;
_playerInstance.Rigidbody.isKinematic = false;
}
_gameSession.StartGame();
}
private void SpawnPowerUp(PowerUpType type, string tileId)
{
if (!_tileViews.TryGetValue(tileId, out var tileView)) return;
if (!tileView) return;
var spawnPos = tileView.transform.position + Vector3.up * 0.5f;
var instance = Instantiate(powerUpPrefab, spawnPos, Quaternion.identity);
instance.Configure(type);
instance.OnCollected += (t) =>
{
cameraController?.Shake(0.2f, 0.15f);
rumbleManager?.PulseMedium();
if (t == PowerUpType.TimeSlow)
{
_gameSession.ActivateTimeSlow(10f);
}
};
}
private void OnBeatMeasure()
{
if (_allTiles.Count == 0) return;
var pulseCount = 25;
for (var i = 0; i < pulseCount; i++)
{
var randIndex = Random.Range(0, _allTiles.Count);
var tile = _allTiles[randIndex];
if (tile.Floor < _currentPlayerFloorIndex) continue;
if (tile.Floor > _currentPlayerFloorIndex + 1) continue;
if (tile.CurrentState != TileState.Stable) continue;
if (_tileViews.TryGetValue(tile.Id, out var tileView))
{
tileView.PulseEmission(Random.Range(1.2f, 2.0f));
}
}
}
}
}