using FlappyBird.Core; using FlappyBird.Ground; using FlappyBird.Pipes; using System; using System.Collections.Generic; using UnityEngine; using Zenject; namespace FlappyBird.Environment { public class GroundSpawner : IInitializable, ITickable, IDisposable { private readonly IObjectPoolManager _objectPoolManager; private readonly IGameManager _gameManager; private readonly PipeSettings _pipeSettings; private readonly GroundSettings _groundSettings; private readonly GameObject _groundPrefab; private readonly List _activeGrounds = new List(); private bool _isRunning; [Inject] public GroundSpawner( IObjectPoolManager objectPoolManager, IGameManager gameManager, PipeSettings pipeSettings, GroundSettings groundSettings, [Inject(Id = "GroundPrefab")] GameObject groundPrefab) { _objectPoolManager = objectPoolManager; _gameManager = gameManager; _pipeSettings = pipeSettings; _groundSettings = groundSettings; _groundPrefab = groundPrefab; } /// /// Initializes the ground system by registering the object pool and /// spawning initial segments. They will remain stationary until the game starts. /// public void Initialize() { _objectPoolManager.RegisterPool(_groundPrefab, _groundSettings.initialPoolSize); _gameManager.OnGameStateChanged += HandleGameStateChanged; // Pre-spawn segments so the floor is visible in the Menu state float currentX = _groundSettings.spawnX; for (int i = 0; i < Mathf.Max(1, _groundSettings.initialPoolSize); i++) { SpawnGround(currentX); currentX += _groundSettings.groundWidth; } // Sets initial movement state based on current GameManager state (Menu = stopped) ApplyState(_gameManager.CurrentState); } public void Dispose() { _gameManager.OnGameStateChanged -= HandleGameStateChanged; } public void Tick() { if (!_isRunning) { return; } MoveActiveGrounds(); } private void HandleGameStateChanged(GameState state) { ApplyState(state); } private void ApplyState(GameState state) { // Ground only moves during active gameplay _isRunning = state == GameState.Playing; } private void MoveActiveGrounds() { float step = _pipeSettings.moveSpeed * Time.deltaTime; float rightmostX = GetRightmostGroundX(); for (int i = _activeGrounds.Count - 1; i >= 0; i--) { GroundView groundView = _activeGrounds[i]; groundView.MoveLeft(step); if (groundView.XPosition > _groundSettings.despawnX) { continue; } _activeGrounds.RemoveAt(i); _objectPoolManager.Return(groundView); // Re-spawn at the end of the chain SpawnGround(rightmostX + _groundSettings.groundWidth - step); rightmostX += _groundSettings.groundWidth; } } private float GetRightmostGroundX() { float maxX = float.MinValue; foreach (var ground in _activeGrounds) { if (ground.XPosition > maxX) maxX = ground.XPosition; } return maxX == float.MinValue ? _groundSettings.spawnX : maxX; } private void SpawnGround(float xPosition) { GroundView groundView = _objectPoolManager.Get(); groundView.SetPosition(new Vector3(xPosition, _groundSettings.yPosition, 0f)); _activeGrounds.Add(groundView); } } }