diff --git a/Assets/Scripts/Runtime/Gameplay/GameplaySession.cs b/Assets/Scripts/Runtime/Gameplay/GameplaySession.cs index 81a32b6..1807ea6 100644 --- a/Assets/Scripts/Runtime/Gameplay/GameplaySession.cs +++ b/Assets/Scripts/Runtime/Gameplay/GameplaySession.cs @@ -31,6 +31,11 @@ namespace DanroJump.Gameplay public string SessionId { get; private set; } = CreateSessionId(); + /// + /// Текущий комбо-множитель очков (увеличивается при сборе монет/врагах, сбрасывается на платформах). + /// + public int CurrentCombo { get; private set; } = 1; + /// /// Итоговые очки текущего забега: игровые начисления плюс бонус за максимальную высоту. /// @@ -122,11 +127,33 @@ namespace DanroJump.Gameplay { this.player.Died += HandlePlayerDied; this.player.Respawned += HandlePlayerRespawned; + this.player.LandedOnNormalPlatform += ResetCombo; } } /// - /// Добавляет очки в текущий забег. + /// Увеличивает комбо-множитель. + /// + public void IncrementCombo() + { + CurrentCombo++; + Changed?.Invoke(); + } + + /// + /// Сбрасывает комбо-множитель в 1. + /// + public void ResetCombo() + { + if (CurrentCombo != 1) + { + CurrentCombo = 1; + Changed?.Invoke(); + } + } + + /// + /// Добавляет очки в текущий забег (с учетом комбо-множителя). /// public void AddScore(int amount) { @@ -135,7 +162,8 @@ namespace DanroJump.Gameplay return; } - baseScore += amount; + baseScore += amount * CurrentCombo; + CurrentCombo++; Changed?.Invoke(); } @@ -165,6 +193,7 @@ namespace DanroJump.Gameplay DeathCount = 0; RemainingLives = startingLives; State = GameplaySessionState.Running; + CurrentCombo = 1; player?.ResetRun(); Changed?.Invoke(); } @@ -186,6 +215,7 @@ namespace DanroJump.Gameplay sceneFlow?.CancelGameOverReturn(); RemainingLives = ContinueGameLives; State = GameplaySessionState.Respawning; + CurrentCombo = 1; if (player != null) { @@ -215,6 +245,7 @@ namespace DanroJump.Gameplay player.Died -= HandlePlayerDied; player.Respawned -= HandlePlayerRespawned; + player.LandedOnNormalPlatform -= ResetCombo; } /// @@ -226,6 +257,8 @@ namespace DanroJump.Gameplay DeathCount++; RemainingLives = Mathf.Max(0, RemainingLives - 1); State = RemainingLives > 0 ? GameplaySessionState.Respawning : GameplaySessionState.GameOver; + CurrentCombo = 1; + if (State == GameplaySessionState.GameOver) { audioService?.Play(GameAudioEvent.GameOver); diff --git a/Assets/Scripts/Runtime/Gameplay/IPlayerController.cs b/Assets/Scripts/Runtime/Gameplay/IPlayerController.cs index b4f04a4..8e136fb 100644 --- a/Assets/Scripts/Runtime/Gameplay/IPlayerController.cs +++ b/Assets/Scripts/Runtime/Gameplay/IPlayerController.cs @@ -9,6 +9,7 @@ namespace DanroJump.Gameplay { event System.Action Died; event System.Action Respawned; + event System.Action LandedOnNormalPlatform; Transform Transform { get; } float HighestY { get; } diff --git a/Assets/Scripts/Runtime/Gameplay/JumpPlayerController.cs b/Assets/Scripts/Runtime/Gameplay/JumpPlayerController.cs index e16798c..1462166 100644 --- a/Assets/Scripts/Runtime/Gameplay/JumpPlayerController.cs +++ b/Assets/Scripts/Runtime/Gameplay/JumpPlayerController.cs @@ -186,6 +186,7 @@ namespace DanroJump.Gameplay public event System.Action Died; public event System.Action Respawned; + public event System.Action LandedOnNormalPlatform; public Transform Transform => transform; @@ -949,6 +950,7 @@ namespace DanroJump.Gameplay if (!suppressAutoJump) { Bounce(autoJumpVelocity); + LandedOnNormalPlatform?.Invoke(); } } diff --git a/Assets/Scripts/Runtime/UI/Gameplay/GameplayHudController.cs b/Assets/Scripts/Runtime/UI/Gameplay/GameplayHudController.cs index 1bd26bd..99961b7 100644 --- a/Assets/Scripts/Runtime/UI/Gameplay/GameplayHudController.cs +++ b/Assets/Scripts/Runtime/UI/Gameplay/GameplayHudController.cs @@ -52,6 +52,7 @@ namespace DanroJump.UI.Gameplay private Label coinsLabel; private Label deathsLabel; private Label stateLabel; + private Label comboLabel; private CancellationTokenSource refreshCancellation; private CancellationTokenSource qrTimeoutCancellation; private CancellationTokenSource physicalDispenseCancellation; @@ -185,6 +186,7 @@ namespace DanroJump.UI.Gameplay coinsLabel = FindLabel(root, coinsLabelName); deathsLabel = FindLabel(root, deathsLabelName); stateLabel = FindLabel(root, stateLabelName); + comboLabel = FindLabel(root, "ComboValue"); } private void BindPrizeOverlay() @@ -248,9 +250,48 @@ namespace DanroJump.UI.Gameplay SetLabel(coinsLabel, session.Coins.ToString()); SetLabel(deathsLabel, session.DeathCount.ToString()); SetLabel(stateLabel, FormatState(session.State)); + + if (comboLabel != null) + { + if (session.CurrentCombo > 1) + { + var comboText = $"x{session.CurrentCombo} COMBO!"; + if (comboLabel.text != comboText) + { + comboLabel.text = comboText; + comboLabel.style.display = DisplayStyle.Flex; + PulseComboLabel(); + } + } + else + { + comboLabel.style.display = DisplayStyle.None; + } + } + RefreshContinueOverlay(); } + private void PulseComboLabel() + { + if (comboLabel == null) + { + return; + } + + comboLabel.style.scale = new StyleScale(new Scale(new Vector3(1.4f, 1.4f, 1f))); + ResetComboScaleAsync().Forget(); + } + + private async UniTaskVoid ResetComboScaleAsync() + { + await UniTask.Delay(TimeSpan.FromMilliseconds(80)); + if (comboLabel != null) + { + comboLabel.style.scale = new StyleScale(new Scale(new Vector3(1.0f, 1.0f, 1f))); + } + } + /// /// Безопасно обновляет текст label, если элемент найден в UXML. /// diff --git a/Assets/UI/Gameplay/GameplayHud.uxml b/Assets/UI/Gameplay/GameplayHud.uxml index ed985a0..777c914 100644 --- a/Assets/UI/Gameplay/GameplayHud.uxml +++ b/Assets/UI/Gameplay/GameplayHud.uxml @@ -17,6 +17,7 @@ +