Initial Commit

This commit is contained in:
2026-03-16 14:38:46 +02:00
commit b8f7327a21
2327 changed files with 253610 additions and 0 deletions
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6c5199b03c629b0438388962d478af20
+127
View File
@@ -0,0 +1,127 @@
using System;
using UnityEngine;
using Zenject;
namespace FlappyBird.Core
{
public class DeathManager : IDeathManager, IInitializable, ITickable, IDisposable
{
private const float ShakeDuration = 0.2f;
private const float ShakeMagnitude = 0.18f;
private readonly IGameManager _gameManager;
private Transform _cameraTransform;
private Vector3 _cameraBaseLocalPosition;
private float _shakeTimeRemaining;
[Inject]
public DeathManager(IGameManager gameManager)
{
_gameManager = gameManager;
}
public bool IsDead { get; private set; }
public event Action OnDied;
public void Initialize()
{
_gameManager.OnGameStateChanged += HandleGameStateChanged;
CacheMainCamera();
IsDead = false;
}
public void Dispose()
{
_gameManager.OnGameStateChanged -= HandleGameStateChanged;
ResetCameraPosition();
}
public void Tick()
{
if (_shakeTimeRemaining <= 0f)
{
return;
}
if (!CacheMainCamera())
{
_shakeTimeRemaining = 0f;
return;
}
_shakeTimeRemaining -= Time.deltaTime;
if (_shakeTimeRemaining <= 0f)
{
ResetCameraPosition();
return;
}
float strength = ShakeMagnitude * (_shakeTimeRemaining / ShakeDuration);
Vector2 offset = UnityEngine.Random.insideUnitCircle * strength;
_cameraTransform.localPosition = _cameraBaseLocalPosition + new Vector3(offset.x, offset.y, 0f);
}
public void Die()
{
if (IsDead || _gameManager.CurrentState != GameState.Playing)
{
return;
}
IsDead = true;
StartCameraShake();
OnDied?.Invoke();
_gameManager.GameOver();
}
private void HandleGameStateChanged(GameState state)
{
if (state == GameState.Menu)
{
IsDead = false;
_shakeTimeRemaining = 0f;
ResetCameraPosition();
}
}
private void StartCameraShake()
{
if (!CacheMainCamera())
{
return;
}
_shakeTimeRemaining = ShakeDuration;
}
private bool CacheMainCamera()
{
if (_cameraTransform != null)
{
return true;
}
Camera mainCamera = Camera.main;
if (mainCamera == null)
{
return false;
}
_cameraTransform = mainCamera.transform;
_cameraBaseLocalPosition = _cameraTransform.localPosition;
return true;
}
private void ResetCameraPosition()
{
if (_cameraTransform == null)
{
return;
}
_cameraTransform.localPosition = _cameraBaseLocalPosition;
}
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3ab5ec331cec5ae40bb491b4a5003c21
+61
View File
@@ -0,0 +1,61 @@
using System;
using UnityEngine;
using Zenject;
namespace FlappyBird.Core
{
public class GameManager : IGameManager, IInitializable
{
public GameState CurrentState { get; private set; } = GameState.Menu;
public event Action<GameState> OnGameStateChanged;
public void Initialize()
{
CurrentState = GameState.Menu;
Debug.Log($"Gameplay state: {CurrentState}");
}
public void StartGame()
{
if (CurrentState != GameState.Menu)
{
return;
}
TransitionTo(GameState.Playing);
}
public void GameOver()
{
if (CurrentState != GameState.Playing)
{
return;
}
TransitionTo(GameState.GameOver);
}
public void ReturnToMenu()
{
if (CurrentState == GameState.Menu)
{
return;
}
TransitionTo(GameState.Menu);
}
private void TransitionTo(GameState state)
{
if (CurrentState == state)
{
return;
}
CurrentState = state;
Debug.Log($"Gameplay state: {CurrentState}");
OnGameStateChanged?.Invoke(CurrentState);
}
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ba4fbdb4d840a6c48b974ed2f0c910b4
+22
View File
@@ -0,0 +1,22 @@
using System;
namespace FlappyBird.Core
{
public class GameStartNotifier : IGameStartNotifier
{
public bool HasStarted { get; private set; }
public event Action OnGameStarted;
public void NotifyGameStarted()
{
if (HasStarted)
{
return;
}
HasStarted = true;
OnGameStarted?.Invoke();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7c03d9fbb1b6930498065f5321a0fa4b
+9
View File
@@ -0,0 +1,9 @@
namespace FlappyBird.Core
{
public enum GameState
{
Menu,
Playing,
GameOver
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2df155af08d5fdb45a3f4bd5124c26d2
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b9db9961106beb4489ae7da8ae0957dc
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace FlappyBird.Core
{
public interface IDeathManager
{
bool IsDead { get; }
event Action OnDied;
void Die();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9db954e729c053b40844b0a6ee9187f2
+13
View File
@@ -0,0 +1,13 @@
using System;
namespace FlappyBird.Core
{
public interface IGameManager
{
GameState CurrentState { get; }
event Action<GameState> OnGameStateChanged;
void StartGame();
void GameOver();
void ReturnToMenu();
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c1468ec5fa7ce8a4d84f1c5835bf8994
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace FlappyBird.Core
{
public interface IGameStartNotifier
{
bool HasStarted { get; }
event Action OnGameStarted;
void NotifyGameStarted();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: afa8469042fc2df429c88b875f9a96fa
+25
View File
@@ -0,0 +1,25 @@
using UnityEngine;
namespace FlappyBird.Core
{
public interface IObjectPoolManager
{
/// <summary>
/// Registers a new prefab type with the pooling system.
/// </summary>
/// <typeparam name="T">The component type that implements IPoolable.</typeparam>
/// <param name="prefab">The GameObject prefab to instantiate.</param>
/// <param name="initialSize">How many instances to pre-warm in the pool.</param>
void RegisterPool<T>(GameObject prefab, int initialSize = 0) where T : Component, IPoolable;
/// <summary>
/// Retrieves an instance from the pool, or creates a new one if the pool is empty.
/// </summary>
T Get<T>() where T : Component, IPoolable;
/// <summary>
/// Returns an instance to the pool and deactivates it.
/// </summary>
void Return<T>(T item) where T : Component, IPoolable;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bbfbc77fd3ee5cb40a099d0108484aaa
@@ -0,0 +1,10 @@
using System;
namespace FlappyBird.Core
{
public interface IPipePassedNotifier
{
event Action OnPipePassed;
void NotifyPipePassed();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8f4d49c65078b2e4e81f1887586c4c77
@@ -0,0 +1,10 @@
namespace FlappyBird.Core
{
public interface IPlayerPrefsStorage
{
bool HasKey(string key);
int GetInt(string key, int defaultValue = 0);
void SetInt(string key, int value);
void Save();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 679bebf4f786fef409d9c40e69034315
+8
View File
@@ -0,0 +1,8 @@
namespace FlappyBird.Core
{
public interface IPoolable
{
void OnSpawned();
void OnDespawned();
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 555e90e79e1b9d244bb25dc6bdf758e4
+14
View File
@@ -0,0 +1,14 @@
using System;
namespace FlappyBird.Core
{
public interface IScoreManager
{
int CurrentScore { get; }
int BestScore { get; }
event Action<int> OnScoreChanged;
event Action<int> OnBestScoreChanged;
void AddScore(int points);
void ResetScore();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3cc284d25b3acb240b6617c2fbe898e2
+135
View File
@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Zenject;
namespace FlappyBird.Core
{
public class ObjectPoolManager : IObjectPoolManager, IInitializable
{
private readonly DiContainer _container;
private readonly Dictionary<Type, Queue<Component>> _pools = new Dictionary<Type, Queue<Component>>();
private readonly Dictionary<Type, GameObject> _prefabs = new Dictionary<Type, GameObject>();
private readonly Dictionary<Type, Transform> _poolRoots = new Dictionary<Type, Transform>();
private Transform _mainPoolRoot;
[Inject]
public ObjectPoolManager(DiContainer container)
{
_container = container;
}
/// <summary>
/// Initializes the main root transform to keep the scene hierarchy clean.
/// </summary>
public void Initialize()
{
_mainPoolRoot = new GameObject("ObjectPools").transform;
UnityEngine.Object.DontDestroyOnLoad(_mainPoolRoot);
}
public void RegisterPool<T>(GameObject prefab, int initialSize = 0) where T : Component, IPoolable
{
Type type = typeof(T);
if (_pools.ContainsKey(type))
{
Debug.LogWarning($"[ObjectPoolManager] Pool for type {type.Name} is already registered.");
return;
}
_prefabs[type] = prefab;
_pools[type] = new Queue<Component>();
// Create a specific parent transform for this type
GameObject rootGo = new GameObject($"{type.Name}_Pool");
// Check if prefab is a UI element (RectTransform)
if (prefab.transform is RectTransform)
{
// UI elements need to stay under a Canvas even when pooled,
// or be moved to a hidden Canvas to stay "initialized"
rootGo.AddComponent<RectTransform>();
}
Transform typeRoot = rootGo.transform;
typeRoot.SetParent(_mainPoolRoot);
_poolRoots[type] = typeRoot;
// Pre-warm the pool
for (int i = 0; i < initialSize; i++)
{
T instance = CreateInstance<T>();
instance.gameObject.SetActive(false);
_pools[type].Enqueue(instance);
}
}
public T Get<T>() where T : Component, IPoolable
{
Type type = typeof(T);
if (!_pools.TryGetValue(type, out Queue<Component> pool))
{
throw new InvalidOperationException($"Pool for type {type.Name} is not registered.");
}
T instance = (pool.Count == 0) ? CreateInstance<T>() : pool.Dequeue() as T;
instance.gameObject.SetActive(true);
instance.OnSpawned();
return instance;
}
/// <summary>
/// Returns an item to its respective pool.
/// Automatically handles UI parenting back to the pool root.
/// </summary>
public void Return<T>(T item) where T : Component, IPoolable
{
if (item == null) return;
Type type = typeof(T);
if (!_pools.TryGetValue(type, out Queue<Component> pool))
{
// Fallback for cases where T might be an interface or base class
type = item.GetType();
if (!_pools.TryGetValue(type, out pool))
{
Debug.LogError($"[ObjectPoolManager] No pool registered for {type.Name}");
return;
}
}
item.OnDespawned();
// Re-parent to the specific pool root to keep Hierarchy clean
item.transform.SetParent(_poolRoots[type], false);
item.gameObject.SetActive(false);
pool.Enqueue(item);
}
private T CreateInstance<T>() where T : Component, IPoolable
{
Type type = typeof(T);
GameObject prefab = _prefabs[type];
Transform root = _poolRoots[type];
// Zenject InstantiatePrefab ensures [Inject] works inside the prefab (needed for ScorePopupView)
GameObject instance = _container.InstantiatePrefab(prefab, root);
T component = instance.GetComponent<T>();
if (component == null)
{
component = instance.AddComponent<T>();
}
return component;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a7a6735ad66b59a43841c280b0f792a2
+14
View File
@@ -0,0 +1,14 @@
using System;
namespace FlappyBird.Core
{
public class PipePassedNotifier : IPipePassedNotifier
{
public event Action OnPipePassed;
public void NotifyPipePassed()
{
OnPipePassed?.Invoke();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e04163983505da74f89e304b1f594f52
+27
View File
@@ -0,0 +1,27 @@
using UnityEngine;
namespace FlappyBird.Core
{
public class PlayerPrefsStorage : IPlayerPrefsStorage
{
public bool HasKey(string key)
{
return PlayerPrefs.HasKey(key);
}
public int GetInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
public void SetInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
}
public void Save()
{
PlayerPrefs.Save();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 66e3cd5bed2132b4a86b8df540a1dbbf
+83
View File
@@ -0,0 +1,83 @@
using System;
using UnityEngine;
using Zenject;
namespace FlappyBird.Core
{
public class ScoreManager : IScoreManager, IInitializable, IDisposable
{
private const string BestScoreKey = "BestScore";
private readonly IGameManager _gameManager;
private readonly IPlayerPrefsStorage _playerPrefsStorage;
[Inject]
public ScoreManager(IGameManager gameManager, IPlayerPrefsStorage playerPrefsStorage)
{
_gameManager = gameManager;
_playerPrefsStorage = playerPrefsStorage;
}
public int CurrentScore { get; private set; }
public int BestScore { get; private set; }
public event Action<int> OnScoreChanged;
public event Action<int> OnBestScoreChanged;
public void Initialize()
{
BestScore = _playerPrefsStorage.GetInt(BestScoreKey, 0);
OnBestScoreChanged?.Invoke(BestScore);
Debug.Log($"High score loaded: {BestScore}");
_gameManager.OnGameStateChanged += HandleGameStateChanged;
ResetScore();
}
public void Dispose()
{
_gameManager.OnGameStateChanged -= HandleGameStateChanged;
}
public void AddScore(int points)
{
if (points <= 0 || _gameManager.CurrentState != GameState.Playing)
{
return;
}
CurrentScore += points;
OnScoreChanged?.Invoke(CurrentScore);
Debug.Log($"Current score: {CurrentScore}");
UpdateBestScore(CurrentScore);
}
public void ResetScore()
{
CurrentScore = 0;
OnScoreChanged?.Invoke(CurrentScore);
Debug.Log($"Current score reset: {CurrentScore}");
}
private void HandleGameStateChanged(GameState state)
{
if (state == GameState.Menu)
{
ResetScore();
}
}
private void UpdateBestScore(int score)
{
if (score <= BestScore)
{
return;
}
BestScore = score;
_playerPrefsStorage.SetInt(BestScoreKey, BestScore);
_playerPrefsStorage.Save();
OnBestScoreChanged?.Invoke(BestScore);
Debug.Log($"High score updated: {BestScore}");
}
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0d78b5c289c877040943bc5a1e563dfc