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

383 lines
13 KiB
C#

using System.Collections;
using Core.Domain;
using Core.Domain.Status.Effects;
using Core.Ports;
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 GameUiCoordinator uiCoordinator;
[Header("Settings")]
[SerializeField] private float restartTime = 3f;
[Header("Power Ups")]
[SerializeField] private PowerUpViewAdapter powerUpPrefab;
private readonly TileRegistry _tileRegistry = new();
private GameSession _gameSession;
private IPersistenceService _persistenceService;
private InputSystem_Actions _actions;
private PlayerController _playerInstance;
private GameObject _currentOrbInstance;
private bool _isGameRunning;
private int _currentPlayerFloorIndex;
private float _inputBlockTimer;
private bool _isPaused;
private bool _levelGenerated;
private void OnEnable()
{
_actions = new InputSystem_Actions();
_actions.Player.Enable();
}
private void OnDisable()
{
_actions.Player.Disable();
beatPulseController.OnMeasure.RemoveListener(OnBeatMeasure);
}
private void Start()
{
_inputBlockTimer = 0.5f;
_persistenceService = new PlayerPrefsPersistenceAdapter();
_gameSession = new GameSession(_tileRegistry.AllTiles, _persistenceService);
// Set Theme based on High Score
ThemeManager.CurrentTheme = ThemeManager.GetTheme(_gameSession.HighScore);
var floorsCount = levelGenerator?.Definition != null ? levelGenerator.Definition.FloorCount : 1;
if (levelGenerator)
{
StartCoroutine(levelGenerator.GenerateAsync(soundManager, _tileRegistry, cameraController,
rumbleManager,
() =>
{
if (!floorVisibilityManager)
{
floorVisibilityManager = gameObject.AddComponent<FloorVisibilityManager>();
}
floorVisibilityManager.Initialize(_tileRegistry, floorsCount);
SpawnDeathPlane();
SpawnPlayer();
uiCoordinator?.ShowStartScreen();
uiCoordinator?.UpdateHighScore(_gameSession.HighScore);
WireEvents();
_levelGenerated = true;
}));
}
uiCoordinator?.ShowStartScreen();
}
private void Update()
{
if (_inputBlockTimer > 0f)
{
_inputBlockTimer -= Time.deltaTime;
return;
}
if (_actions.Player.Pause.triggered && _isGameRunning)
{
TogglePause();
}
if (_isPaused) return;
if (!_isGameRunning && _levelGenerated)
{
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 def = levelGenerator.Definition;
if (def == null) return;
var heightDist = def.FloorHeightDistance;
var maxFloors = def.FloorCount;
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);
var allTiles = _tileRegistry.AllTiles;
for (var i = allTiles.Count - 1; i >= 0; i--)
{
allTiles[i].Tick(dt * dilation * decayMultiplier);
}
}
private void TogglePause()
{
_isPaused = !_isPaused;
if (_isPaused)
{
Time.timeScale = 0f;
if (soundManager) soundManager.SetPaused(true);
uiCoordinator?.ShowPauseUi();
if (rumbleManager) rumbleManager.SetPaused(true);
}
else
{
Time.timeScale = 1f;
if (soundManager) soundManager.SetPaused(false);
uiCoordinator?.HidePauseUi();
if (rumbleManager) rumbleManager.SetPaused(false);
}
}
private void SpawnVisualOrb(string tileId)
{
if (_currentOrbInstance) Destroy(_currentOrbInstance);
if (!_tileRegistry.TryGetView(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 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 def = levelGenerator.Definition;
var lowestY = -(def.FloorCount * def.FloorHeightDistance) - 5f;
var pos = new Vector3(def.GridSizeX / 2f, lowestY, def.GridSizeY / 2f);
var plane = Instantiate(deathPlanePrefab, pos, Quaternion.identity);
plane.transform.localScale = new Vector3(def.GridSizeX * 200f, 1f, def.GridSizeY * 200f);
plane.OnPlayerFell += () => _gameSession.EndGame();
}
private void HandleGameOver()
{
_isGameRunning = false;
if (beatPulseController) beatPulseController.StopTracking();
StartCoroutine(RestartRoutine());
}
private IEnumerator RestartRoutine()
{
yield return new WaitForSeconds(restartTime);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
private void WireEvents()
{
uiCoordinator?.Subscribe(_gameSession);
_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 = _tileRegistry.FindTiles(t => t.Floor == 0 && t.CurrentState == TileState.Stable);
if (validTiles.Count == 0)
{
validTiles = _tileRegistry.FindTiles(t => t.CurrentState == TileState.Stable);
}
if (validTiles.Count == 0) return;
var randomTile = validTiles[Random.Range(0, validTiles.Count)];
if (!_tileRegistry.TryGetView(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;
uiCoordinator?.HideStartScreen();
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 (!_tileRegistry.TryGetView(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, dur) =>
{
cameraController?.Shake(0.2f, 0.15f);
rumbleManager?.PulseMedium();
switch (t)
{
case PowerUpType.TimeSlow:
_gameSession.ActivateTimeSlow(dur);
break;
case PowerUpType.LightFooted:
_playerInstance?.Status.AddEffect(new LightFootedEffect(dur));
break;
case PowerUpType.SpeedBoost:
_playerInstance?.Status.AddEffect(new SpeedBoostEffect(dur));
break;
default:
Debug.LogWarning($"GameBootstrap: no effect handler for PowerUpType {t}");
break;
}
};
}
private void OnBeatMeasure()
{
var allTiles = _tileRegistry.AllTiles;
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 (_tileRegistry.TryGetView(tile.Id, out var tileView))
{
tileView.PulseEmission(Random.Range(1.2f, 2.0f));
}
}
}
}
}