Add Hunter NPC and Teleporter features with associated prefabs and effects
This commit is contained in:
@@ -9,11 +9,13 @@ namespace Core.Domain
|
||||
private const string HighScoreKey = "HighScore";
|
||||
private const float NpcSpawnTime = 4f;
|
||||
private const float PowerUpSpawnInterval = 25f;
|
||||
|
||||
|
||||
public int Score { get; private set; }
|
||||
public int HighScore { get; private set; }
|
||||
public bool IsGameOver { get; private set; }
|
||||
|
||||
|
||||
public float TimeDilation { get; private set; } = 1.0f;
|
||||
|
||||
public event Action<int> OnScoreChanged;
|
||||
public event Action<string> OnOrbSpawned;
|
||||
public event Action OnOrbReset;
|
||||
@@ -29,14 +31,21 @@ namespace Core.Domain
|
||||
private bool _npcSpawned;
|
||||
private float _powerUpTimer;
|
||||
|
||||
private float _timeSlowTimer;
|
||||
|
||||
// Combo System
|
||||
private float _lastOrbTime;
|
||||
public int ComboMultiplier { get; private set; } = 1;
|
||||
public event Action<int> OnComboUpdated;
|
||||
|
||||
public GameSession(List<Tile> tiles, IPersistenceService persistenceService)
|
||||
{
|
||||
_tiles = tiles;
|
||||
_persistenceService = persistenceService;
|
||||
|
||||
|
||||
Score = 0;
|
||||
IsGameOver = false;
|
||||
|
||||
|
||||
HighScore = _persistenceService.Load(HighScoreKey, 0);
|
||||
}
|
||||
|
||||
@@ -45,56 +54,88 @@ namespace Core.Domain
|
||||
_timeSinceStart = 0f;
|
||||
_powerUpTimer = 0f;
|
||||
_npcSpawned = false;
|
||||
|
||||
TimeDilation = 1.0f;
|
||||
|
||||
ComboMultiplier = 1;
|
||||
_lastOrbTime = -999f;
|
||||
|
||||
SpawnNextOrb();
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (IsGameOver) return;
|
||||
|
||||
|
||||
_timeSinceStart += deltaTime;
|
||||
if (!_npcSpawned && _timeSinceStart >= NpcSpawnTime)
|
||||
{
|
||||
_npcSpawned = true;
|
||||
OnSpawnNpc?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
_powerUpTimer += deltaTime;
|
||||
if (_powerUpTimer >= PowerUpSpawnInterval)
|
||||
{
|
||||
_powerUpTimer = 0f;
|
||||
SpawnRandomPowerUp();
|
||||
}
|
||||
|
||||
if (_timeSlowTimer > 0)
|
||||
{
|
||||
_timeSlowTimer -= deltaTime;
|
||||
if (_timeSlowTimer <= 0)
|
||||
{
|
||||
TimeDilation = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateTimeSlow(float duration)
|
||||
{
|
||||
_timeSlowTimer = duration;
|
||||
TimeDilation = 0.3f; // Slow down to 30%
|
||||
}
|
||||
|
||||
public void OrbCollected()
|
||||
{
|
||||
if (IsGameOver) return;
|
||||
|
||||
Score += 10;
|
||||
// Combo Check (2 second window)
|
||||
if (_timeSinceStart - _lastOrbTime < 2.0f)
|
||||
{
|
||||
ComboMultiplier++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ComboMultiplier = 1;
|
||||
}
|
||||
_lastOrbTime = _timeSinceStart;
|
||||
|
||||
OnComboUpdated?.Invoke(ComboMultiplier);
|
||||
|
||||
Score += 10 * ComboMultiplier;
|
||||
OnScoreChanged?.Invoke(Score);
|
||||
SpawnNextOrb();
|
||||
}
|
||||
|
||||
private void SpawnNextOrb()
|
||||
{
|
||||
var validTiles = _tiles.FindAll(t =>
|
||||
t.CurrentState == TileState.Stable &&
|
||||
{
|
||||
var validTiles = _tiles.FindAll(t =>
|
||||
t.CurrentState == TileState.Stable &&
|
||||
t.Floor == _playerFloorIndex
|
||||
);
|
||||
|
||||
|
||||
if (validTiles.Count == 0)
|
||||
{
|
||||
validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable);
|
||||
}
|
||||
|
||||
|
||||
if (validTiles.Count == 0)
|
||||
{
|
||||
EndGame();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var pick = validTiles[_rng.Next(validTiles.Count)];
|
||||
OnOrbSpawned?.Invoke(pick.Id);
|
||||
}
|
||||
@@ -102,39 +143,56 @@ namespace Core.Domain
|
||||
public void EndGame()
|
||||
{
|
||||
if (IsGameOver) return;
|
||||
|
||||
|
||||
IsGameOver = true;
|
||||
|
||||
|
||||
if (Score > HighScore)
|
||||
{
|
||||
HighScore = Score;
|
||||
_persistenceService.Save(HighScoreKey, HighScore);
|
||||
}
|
||||
|
||||
|
||||
OnGameOver?.Invoke();
|
||||
}
|
||||
|
||||
public void SetPlayerFloor(int floorIndex)
|
||||
{
|
||||
if (_playerFloorIndex == floorIndex) return;
|
||||
|
||||
|
||||
_playerFloorIndex = floorIndex;
|
||||
|
||||
if (IsGameOver) return;
|
||||
|
||||
|
||||
OnOrbReset?.Invoke();
|
||||
SpawnNextOrb();
|
||||
}
|
||||
|
||||
private void SpawnRandomPowerUp()
|
||||
{
|
||||
var validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable);
|
||||
var validTiles = _tiles.FindAll(t =>
|
||||
t.CurrentState == TileState.Stable &&
|
||||
t.Floor == _playerFloorIndex
|
||||
);
|
||||
|
||||
if (validTiles.Count == 0)
|
||||
{
|
||||
validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable);
|
||||
}
|
||||
|
||||
if (validTiles.Count == 0) return;
|
||||
|
||||
|
||||
var tile = validTiles[_rng.Next(validTiles.Count)];
|
||||
|
||||
var type = _rng.Next(0, 2) == 0 ? PowerUpType.LightFooted : PowerUpType.SpeedBoost;
|
||||
|
||||
|
||||
var rand = _rng.Next(0, 4);
|
||||
var type = PowerUpType.LightFooted;
|
||||
switch (rand)
|
||||
{
|
||||
case 0: type = PowerUpType.LightFooted; break;
|
||||
case 1: type = PowerUpType.SpeedBoost; break;
|
||||
case 2: type = PowerUpType.Hover; break;
|
||||
case 3: type = PowerUpType.TimeSlow; break;
|
||||
}
|
||||
|
||||
OnSpawnPowerUp?.Invoke(type, tile.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,7 @@ namespace Core.Domain
|
||||
{
|
||||
LightFooted,
|
||||
SpeedBoost,
|
||||
Hover,
|
||||
TimeSlow
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,7 @@ namespace Core.Domain.Status.Effects
|
||||
{
|
||||
public static readonly Color LightFootedColor = new Color(0.8f, 0.8f, 0.8f);
|
||||
public static readonly Color SpeedBoostColor = new Color(1f, 0.5f, 0f);
|
||||
public static readonly Color HoverColor = new Color(0.5f, 1f, 0.5f); // Green
|
||||
public static readonly Color TimeSlowColor = new Color(0.2f, 0.2f, 1f); // Blue
|
||||
}
|
||||
}
|
||||
32
Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs
Normal file
32
Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Core.Domain.Status.Effects
|
||||
{
|
||||
public class HoverEffect : IStatusEffect
|
||||
{
|
||||
private float _duration;
|
||||
|
||||
public bool IsExpired => _duration <= 0;
|
||||
|
||||
public HoverEffect(float duration)
|
||||
{
|
||||
_duration = duration;
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
_duration -= deltaTime;
|
||||
}
|
||||
|
||||
public void ModifyCapabilities(ref PlayerCapabilities caps)
|
||||
{
|
||||
caps.CanHover = true;
|
||||
}
|
||||
|
||||
public void OnApply()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnRemove()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a774d5cade7c04beba69252a04501099
|
||||
@@ -4,11 +4,13 @@ namespace Core.Domain.Status
|
||||
{
|
||||
public bool CanTriggerDecay;
|
||||
public float SpeedMultiplier;
|
||||
|
||||
public bool CanHover;
|
||||
|
||||
public static PlayerCapabilities Default => new PlayerCapabilities
|
||||
{
|
||||
CanTriggerDecay = true,
|
||||
SpeedMultiplier = 1f
|
||||
SpeedMultiplier = 1f,
|
||||
CanHover = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,23 @@ namespace Core.Domain
|
||||
{
|
||||
public string Id { get; }
|
||||
public int Floor { get; }
|
||||
|
||||
public TileType Type { get; }
|
||||
|
||||
public TileState CurrentState { get; private set; }
|
||||
|
||||
private readonly float _warningDuration;
|
||||
private readonly float _fallingDuration;
|
||||
|
||||
|
||||
private float _stateTimer;
|
||||
|
||||
|
||||
public event Action<Tile> OnStateChanged;
|
||||
|
||||
public Tile(string id, int floor, float warningDuration, float fallingDuration)
|
||||
public Tile(string id, int floor, float warningDuration, float fallingDuration, TileType type = TileType.Normal)
|
||||
{
|
||||
Id = id;
|
||||
Floor = floor;
|
||||
|
||||
Type = type;
|
||||
|
||||
_warningDuration = warningDuration;
|
||||
_fallingDuration = fallingDuration;
|
||||
CurrentState = TileState.Stable;
|
||||
@@ -30,16 +32,23 @@ namespace Core.Domain
|
||||
{
|
||||
if (CurrentState == TileState.Stable)
|
||||
{
|
||||
TransitionTo(TileState.Warning);
|
||||
if (Type == TileType.Fragile)
|
||||
{
|
||||
TransitionTo(TileState.Falling);
|
||||
}
|
||||
else
|
||||
{
|
||||
TransitionTo(TileState.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (CurrentState == TileState.Stable || CurrentState == TileState.Destroyed) return;
|
||||
|
||||
|
||||
_stateTimer += deltaTime;
|
||||
|
||||
|
||||
if (CurrentState == TileState.Warning && _stateTimer >= _warningDuration)
|
||||
{
|
||||
TransitionTo(TileState.Falling);
|
||||
|
||||
8
Assets/Scripts/Core/Domain/TileType.cs
Normal file
8
Assets/Scripts/Core/Domain/TileType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Core.Domain
|
||||
{
|
||||
public enum TileType
|
||||
{
|
||||
Normal,
|
||||
Fragile
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Core/Domain/TileType.cs.meta
Normal file
2
Assets/Scripts/Core/Domain/TileType.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ac9ceadb8e587481aa83f4f78997207
|
||||
Reference in New Issue
Block a user