From dece09c45ab1480e8853599dc75fb08da1bc7b17 Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Thu, 18 Dec 2025 12:05:29 +0100 Subject: [PATCH] Working airplane ability revamp --- .../Foreground/Bushes2.prefab | 2 +- .../ParalaxBackground/Middle/House 7.prefab | 47 ++++++++ .../MiniGames/ValentineNoteDelivery.unity | 81 +++++++++++++- .../Airplane/Abilities/AbilityFactory.cs | 10 +- .../Airplane/Abilities/BaseAirplaneAbility.cs | 87 +++++++++++++-- .../Airplane/Abilities/BobbingAbility.cs | 7 +- .../Airplane/Abilities/DropAbility.cs | 79 ++++++-------- .../Airplane/Abilities/JetAbility.cs | 31 ++++-- .../Airplane/Core/AirplaneController.cs | 9 +- .../Core/Spawning/PositioningUtility.cs | 32 +++--- .../Airplane/Data/AirplaneAbilityConfig.cs | 19 +++- .../Airplane/UI/AirplaneAbilityButton.cs | 102 ++++++++++++++++-- 12 files changed, 407 insertions(+), 99 deletions(-) diff --git a/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Foreground/Bushes2.prefab b/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Foreground/Bushes2.prefab index df6c2905..cd3da6fb 100644 --- a/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Foreground/Bushes2.prefab +++ b/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Foreground/Bushes2.prefab @@ -231,7 +231,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 90239fb003214b4087d0717f6f417161, type: 3} m_Name: m_EditorClassIdentifier: AppleHillsScripts::Minigames.Airplane.Data.PrefabSpawnEntryComponent - spawnPositionMode: 0 + spawnPositionMode: 2 specifiedY: 0 randomYMin: -5 randomYMax: 5 diff --git a/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Middle/House 7.prefab b/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Middle/House 7.prefab index efa3c38c..8220db25 100644 --- a/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Middle/House 7.prefab +++ b/Assets/Prefabs/Minigames/Airplane/ParalaxBackground/Middle/House 7.prefab @@ -11,6 +11,7 @@ GameObject: - component: {fileID: 6489938372873184282} - component: {fileID: 1053150720430733673} - component: {fileID: -1118722040340468723} + - component: {fileID: 6386565729048525216} m_Layer: 0 m_Name: House 7 m_TagString: Untagged @@ -108,3 +109,49 @@ MonoBehaviour: specifiedY: 0 randomYMin: -5 randomYMax: 5 +--- !u!61 &6386565729048525216 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3707466564348580456} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: -0.32072258, y: 2.7431228} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.4} + oldSize: {x: 26.25, y: 17.275} + newSize: {x: 7.65, y: 6.075} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 19.621716, y: 12.998686} + m_EdgeRadius: 0 diff --git a/Assets/Scenes/MiniGames/ValentineNoteDelivery.unity b/Assets/Scenes/MiniGames/ValentineNoteDelivery.unity index 4ff17efe..86c01cbd 100644 --- a/Assets/Scenes/MiniGames/ValentineNoteDelivery.unity +++ b/Assets/Scenes/MiniGames/ValentineNoteDelivery.unity @@ -1361,6 +1361,7 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: + - {fileID: 1821240619} - {fileID: 2111947704} - {fileID: 1723844487} - {fileID: 2022736810} @@ -1387,6 +1388,7 @@ MonoBehaviour: abilityIcon: {fileID: 2111947705} cooldownFill: {fileID: 1723844489} cooldownText: {fileID: 2022736812} + activeStateVisual: {fileID: 1821240618} showDebugLogs: 1 --- !u!1 &1017291798 GameObject: @@ -3460,7 +3462,7 @@ Camera: far clip plane: 1000 field of view: 60 orthographic: 1 - orthographic size: 20 + orthographic size: 25 m_Depth: -1 m_CullingMask: serializedVersion: 2 @@ -3485,12 +3487,87 @@ Transform: m_GameObject: {fileID: 1810521056} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -18.900146, y: 10, z: -10} + m_LocalPosition: {x: -9.599976, y: 15, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1821240618 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1821240619} + - component: {fileID: 1821240621} + - component: {fileID: 1821240620} + m_Layer: 0 + m_Name: ActiveStateVisual + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1821240619 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1821240618} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1.7, y: 1.7, z: 1.7} + m_ConstrainProportionsScale: 1 + m_Children: [] + m_Father: {fileID: 839468744} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1821240620 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1821240618} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: -8836962644236845764, guid: c5cc7367a37a7944abb3876352b0e0ff, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1821240621 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1821240618} + m_CullTransparentMesh: 1 --- !u!1 &1842736649 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Minigames/Airplane/Abilities/AbilityFactory.cs b/Assets/Scripts/Minigames/Airplane/Abilities/AbilityFactory.cs index f85cc73f..b3184168 100644 --- a/Assets/Scripts/Minigames/Airplane/Abilities/AbilityFactory.cs +++ b/Assets/Scripts/Minigames/Airplane/Abilities/AbilityFactory.cs @@ -1,5 +1,4 @@ -using AppleHills.Core.Settings; -using Core; +using Core; using Core.Settings; using Minigames.Airplane.Data; @@ -38,7 +37,9 @@ namespace Minigames.Airplane.Abilities config.abilityIcon, config.cooldownDuration, config.jetSpeed, - config.jetAngle + config.jetAngle, + config.maxActiveDuration, + config.cooldownMultiplier ); } @@ -61,7 +62,8 @@ namespace Minigames.Airplane.Abilities config.abilityIcon, config.cooldownDuration, config.dropForce, - config.dropDistance, + config.maxActiveDuration, + config.cooldownMultiplier, config.zeroHorizontalVelocity ); } diff --git a/Assets/Scripts/Minigames/Airplane/Abilities/BaseAirplaneAbility.cs b/Assets/Scripts/Minigames/Airplane/Abilities/BaseAirplaneAbility.cs index 2ef878cd..f5191ef9 100644 --- a/Assets/Scripts/Minigames/Airplane/Abilities/BaseAirplaneAbility.cs +++ b/Assets/Scripts/Minigames/Airplane/Abilities/BaseAirplaneAbility.cs @@ -21,6 +21,10 @@ namespace Minigames.Airplane.Abilities protected readonly bool canReuse; protected bool showDebugLogs; + // Active duration configuration + protected readonly float maxActiveDuration; + protected readonly bool hasActiveDuration; + #endregion #region Constructor @@ -28,13 +32,15 @@ namespace Minigames.Airplane.Abilities /// /// Base constructor for abilities. Called by subclasses. /// - protected BaseAirplaneAbility(string name, Sprite icon, float cooldown, bool reusable = true) + protected BaseAirplaneAbility(string name, Sprite icon, float cooldown, bool reusable = true, float activeDuration = 0f) { abilityName = name; abilityIcon = icon; cooldownDuration = cooldown; canReuse = reusable; showDebugLogs = false; + maxActiveDuration = activeDuration; + hasActiveDuration = activeDuration > 0f; } #endregion @@ -45,6 +51,8 @@ namespace Minigames.Airplane.Abilities public Sprite AbilityIcon => abilityIcon; public float CooldownDuration => cooldownDuration; public bool CanReuse => canReuse; + public bool HasActiveDuration => hasActiveDuration; + public float MaxActiveDuration => maxActiveDuration; #endregion @@ -54,12 +62,22 @@ namespace Minigames.Airplane.Abilities protected bool isActive; protected bool isOnCooldown; protected float cooldownTimer; + protected float initialCooldownDuration; // Track initial cooldown for fill calculation + + // Active duration tracking + protected float activeDurationTimer; + protected float activeDurationUsed; // Track how long ability was active public bool IsActive => isActive; public bool IsOnCooldown => isOnCooldown; public float CooldownRemaining => cooldownTimer; + public float InitialCooldownDuration => initialCooldownDuration; public bool CanActivate => !isOnCooldown && !isActive && currentAirplane != null && currentAirplane.IsFlying; + // Active duration state + public float ActiveDurationRemaining => activeDurationTimer; + public float ActiveProgress => hasActiveDuration && maxActiveDuration > 0f ? activeDurationTimer / maxActiveDuration : 0f; + #endregion #region Events @@ -67,6 +85,7 @@ namespace Minigames.Airplane.Abilities public event Action OnAbilityActivated; public event Action OnAbilityDeactivated; public event Action OnCooldownChanged; // (remaining, total) + public event Action OnActiveDurationChanged; // (remaining, total) #endregion @@ -82,6 +101,9 @@ namespace Minigames.Airplane.Abilities isActive = false; isOnCooldown = false; cooldownTimer = 0f; + initialCooldownDuration = 0f; + activeDurationTimer = 0f; + activeDurationUsed = 0f; if (showDebugLogs) { @@ -109,10 +131,37 @@ namespace Minigames.Airplane.Abilities } } - OnCooldownChanged?.Invoke(cooldownTimer, cooldownDuration); + OnCooldownChanged?.Invoke(cooldownTimer, initialCooldownDuration); } } + /// + /// Update active duration timer. Called every frame by ability manager when ability is active. + /// Auto-deactivates when duration expires. + /// + public virtual void UpdateActiveDuration(float deltaTime) + { + if (!isActive || !hasActiveDuration) return; + + activeDurationTimer -= deltaTime; + activeDurationUsed += deltaTime; + + if (activeDurationTimer <= 0f) + { + activeDurationTimer = 0f; + + if (showDebugLogs) + { + Logging.Debug($"[{abilityName}] Active duration expired, auto-deactivating"); + } + + // Auto-deactivate when duration expires + Deactivate(); + } + + OnActiveDurationChanged?.Invoke(activeDurationTimer, maxActiveDuration); + } + /// /// Cleanup when airplane is destroyed or flight ends. /// @@ -127,6 +176,9 @@ namespace Minigames.Airplane.Abilities isActive = false; isOnCooldown = false; cooldownTimer = 0f; + initialCooldownDuration = 0f; + activeDurationTimer = 0f; + activeDurationUsed = 0f; if (showDebugLogs) { @@ -155,12 +207,26 @@ namespace Minigames.Airplane.Abilities isActive = false; OnAbilityDeactivated?.Invoke(this); + // Calculate and start dynamic cooldown + float dynamicCooldown = CalculateDynamicCooldown(); + StartCooldown(dynamicCooldown); + if (showDebugLogs) { Logging.Debug($"[{abilityName}] Deactivated"); } } + /// + /// Calculate cooldown duration based on ability usage. + /// Override in subclasses for custom dynamic cooldown logic. + /// Default: returns base cooldown duration. + /// + protected virtual float CalculateDynamicCooldown() + { + return cooldownDuration; + } + #endregion #region Protected Helpers @@ -180,6 +246,14 @@ namespace Minigames.Airplane.Abilities } isActive = true; + + // Reset active duration timer if ability has active duration + if (hasActiveDuration) + { + activeDurationTimer = maxActiveDuration; + activeDurationUsed = 0f; + } + OnAbilityActivated?.Invoke(this); if (showDebugLogs) @@ -191,15 +265,16 @@ namespace Minigames.Airplane.Abilities /// /// Start cooldown timer (called by subclasses after execution). /// - protected virtual void StartCooldown() + protected virtual void StartCooldown(float? customCooldown = null) { isOnCooldown = true; - cooldownTimer = cooldownDuration; - OnCooldownChanged?.Invoke(cooldownTimer, cooldownDuration); + cooldownTimer = customCooldown ?? cooldownDuration; + initialCooldownDuration = cooldownTimer; // Store the initial cooldown value + OnCooldownChanged?.Invoke(cooldownTimer, initialCooldownDuration); if (showDebugLogs) { - Logging.Debug($"[{abilityName}] Cooldown started: {cooldownDuration}s"); + Logging.Debug($"[{abilityName}] Cooldown started: {cooldownTimer}s"); } } diff --git a/Assets/Scripts/Minigames/Airplane/Abilities/BobbingAbility.cs b/Assets/Scripts/Minigames/Airplane/Abilities/BobbingAbility.cs index f876d35d..34264ecf 100644 --- a/Assets/Scripts/Minigames/Airplane/Abilities/BobbingAbility.cs +++ b/Assets/Scripts/Minigames/Airplane/Abilities/BobbingAbility.cs @@ -1,4 +1,4 @@ -using Core; +using Core; using UnityEngine; namespace Minigames.Airplane.Abilities @@ -23,7 +23,7 @@ namespace Minigames.Airplane.Abilities /// Create bobbing ability with configuration from settings. /// public BobbingAbility(string name, Sprite icon, float cooldown, Vector2 force) - : base(name, icon, cooldown) + : base(name, icon, cooldown, reusable: true, activeDuration: 0f) { bobForce = force; } @@ -52,9 +52,8 @@ namespace Minigames.Airplane.Abilities } } - // Instant ability - deactivate immediately and start cooldown + // Instant ability - deactivate immediately (base.Deactivate starts cooldown) base.Deactivate(); - StartCooldown(); } #endregion diff --git a/Assets/Scripts/Minigames/Airplane/Abilities/DropAbility.cs b/Assets/Scripts/Minigames/Airplane/Abilities/DropAbility.cs index 56560294..5cdf8e59 100644 --- a/Assets/Scripts/Minigames/Airplane/Abilities/DropAbility.cs +++ b/Assets/Scripts/Minigames/Airplane/Abilities/DropAbility.cs @@ -1,12 +1,12 @@ -using System.Collections; -using Core; +using Core; using UnityEngine; namespace Minigames.Airplane.Abilities { /// - /// Drop Plane Ability: Swipe down to drop straight down. - /// Sustained ability - drops for fixed duration/distance. + /// Drop Plane Ability: Press to drop straight down with strong downward force. + /// Sustained ability - active for up to N seconds, can be interrupted early. + /// Cooldown is proportional to usage time (full duration = N*2 cooldown). /// Good for precision strikes on targets. /// Configuration loaded from settings at runtime. /// @@ -15,8 +15,8 @@ namespace Minigames.Airplane.Abilities #region Configuration private readonly float dropForce; - private readonly float dropDistance; private readonly bool zeroHorizontalVelocity; + private readonly float cooldownMultiplier; #endregion @@ -25,12 +25,12 @@ namespace Minigames.Airplane.Abilities /// /// Create drop ability with configuration from settings. /// - public DropAbility(string name, Sprite icon, float cooldown, float force, float distance, bool zeroHorizontal = true) - : base(name, icon, cooldown) + public DropAbility(string name, Sprite icon, float cooldown, float force, float maxActiveDuration, float cooldownMult, bool zeroHorizontal = true) + : base(name, icon, cooldown, reusable: true, activeDuration: maxActiveDuration) { dropForce = force; - dropDistance = distance; zeroHorizontalVelocity = zeroHorizontal; + cooldownMultiplier = cooldownMult; } #endregion @@ -38,8 +38,6 @@ namespace Minigames.Airplane.Abilities #region State private float originalXVelocity; - private Vector3 dropStartPosition; - private Coroutine dropCoroutine; #endregion @@ -66,17 +64,11 @@ namespace Minigames.Airplane.Abilities // Apply strong downward force rb.AddForce(Vector2.down * dropForce, ForceMode2D.Impulse); - - // Track drop distance - dropStartPosition = currentAirplane.transform.position; - - // Start monitoring drop distance - dropCoroutine = currentAirplane.StartCoroutine(MonitorDropDistance()); } if (showDebugLogs) { - Logging.Debug($"[DropAbility] Activated - Force: {dropForce}, Distance: {dropDistance}"); + Logging.Debug($"[DropAbility] Activated - Force: {dropForce}, Max Duration: {maxActiveDuration}s"); } } @@ -84,13 +76,6 @@ namespace Minigames.Airplane.Abilities { if (!isActive) return; - // Stop monitoring - if (dropCoroutine != null && currentAirplane != null) - { - currentAirplane.StopCoroutine(dropCoroutine); - dropCoroutine = null; - } - // Restore horizontal velocity (optional) if (currentAirplane != null) { @@ -102,36 +87,32 @@ namespace Minigames.Airplane.Abilities } } - base.Deactivate(); + if (showDebugLogs) + { + Logging.Debug($"[DropAbility] Deactivating after {activeDurationUsed:F2}s usage"); + } - // Start cooldown - StartCooldown(); + // Base.Deactivate() will call CalculateDynamicCooldown and start cooldown + base.Deactivate(); + } + + /// + /// Calculate dynamic cooldown based on usage time. + /// Full duration (maxActiveDuration) = maxActiveDuration * cooldownMultiplier cooldown. + /// Partial usage = proportional cooldown. + /// + protected override float CalculateDynamicCooldown() + { + // Calculate proportional cooldown: usedTime * multiplier + // Example: 3s max, 2x multiplier -> full use = 6s cooldown, 1.5s use = 3s cooldown + float dynamicCooldown = activeDurationUsed * cooldownMultiplier; if (showDebugLogs) { - Logging.Debug("[DropAbility] Deactivated, cooldown started"); - } - } - - #endregion - - #region Drop Monitoring - - private IEnumerator MonitorDropDistance() - { - while (isActive && currentAirplane != null) - { - float distanceDropped = Mathf.Abs(dropStartPosition.y - currentAirplane.transform.position.y); - - if (distanceDropped >= dropDistance) - { - // Drop distance reached - deactivate - Deactivate(); - yield break; - } - - yield return null; + Logging.Debug($"[DropAbility] CalculateDynamicCooldown: used={activeDurationUsed:F2}s, multiplier={cooldownMultiplier}, result={dynamicCooldown:F2}s"); } + + return dynamicCooldown; } #endregion diff --git a/Assets/Scripts/Minigames/Airplane/Abilities/JetAbility.cs b/Assets/Scripts/Minigames/Airplane/Abilities/JetAbility.cs index e1f27c3e..8f18ffc9 100644 --- a/Assets/Scripts/Minigames/Airplane/Abilities/JetAbility.cs +++ b/Assets/Scripts/Minigames/Airplane/Abilities/JetAbility.cs @@ -13,6 +13,7 @@ namespace Minigames.Airplane.Abilities private readonly float jetSpeed; private readonly float jetAngle; + private readonly float cooldownMultiplier; #endregion @@ -21,11 +22,12 @@ namespace Minigames.Airplane.Abilities /// /// Create jet ability with configuration from settings. /// - public JetAbility(string name, Sprite icon, float cooldown, float speed, float angle) - : base(name, icon, cooldown) + public JetAbility(string name, Sprite icon, float cooldown, float speed, float angle, float maxActiveDuration, float cooldownMult) + : base(name, icon, cooldown, reusable: true, activeDuration: maxActiveDuration) { jetSpeed = speed; jetAngle = angle; + cooldownMultiplier = cooldownMult; } #endregion @@ -87,15 +89,32 @@ namespace Minigames.Airplane.Abilities currentAirplane.RotateToVelocity = originalRotateToVelocity; } - base.Deactivate(); + if (showDebugLogs) + { + Logging.Debug($"[JetAbility] Deactivating after {activeDurationUsed:F2}s usage"); + } - // Start cooldown after deactivation - StartCooldown(); + // Base.Deactivate() will call CalculateDynamicCooldown and start cooldown + base.Deactivate(); + } + + /// + /// Calculate dynamic cooldown based on usage time. + /// Full duration (maxActiveDuration) = maxActiveDuration * cooldownMultiplier cooldown. + /// Partial usage = proportional cooldown. + /// + protected override float CalculateDynamicCooldown() + { + // Calculate proportional cooldown: usedTime * multiplier + // Example: 5s max, 2x multiplier -> full use = 10s cooldown, 2.5s use = 5s cooldown + float dynamicCooldown = activeDurationUsed * cooldownMultiplier; if (showDebugLogs) { - Logging.Debug("[JetAbility] Deactivated, cooldown started"); + Logging.Debug($"[JetAbility] CalculateDynamicCooldown: used={activeDurationUsed:F2}s, multiplier={cooldownMultiplier}, result={dynamicCooldown:F2}s"); } + + return dynamicCooldown; } #endregion diff --git a/Assets/Scripts/Minigames/Airplane/Core/AirplaneController.cs b/Assets/Scripts/Minigames/Airplane/Core/AirplaneController.cs index 9dbb7360..f6d1eaa4 100644 --- a/Assets/Scripts/Minigames/Airplane/Core/AirplaneController.cs +++ b/Assets/Scripts/Minigames/Airplane/Core/AirplaneController.cs @@ -176,9 +176,16 @@ namespace Minigames.Airplane.Core transform.rotation = Quaternion.Euler(0, 0, angle); } - // Update ability cooldown + // Update ability timers if (currentAbility != null) { + // Update active duration if ability is active + if (currentAbility.IsActive) + { + currentAbility.UpdateActiveDuration(Time.deltaTime); + } + + // Update cooldown if on cooldown currentAbility.UpdateCooldown(Time.deltaTime); } diff --git a/Assets/Scripts/Minigames/Airplane/Core/Spawning/PositioningUtility.cs b/Assets/Scripts/Minigames/Airplane/Core/Spawning/PositioningUtility.cs index 2150f36b..159f30e5 100644 --- a/Assets/Scripts/Minigames/Airplane/Core/Spawning/PositioningUtility.cs +++ b/Assets/Scripts/Minigames/Airplane/Core/Spawning/PositioningUtility.cs @@ -136,7 +136,8 @@ namespace Minigames.Airplane.Core.Spawning } /// - /// Get the bounds of an object from its Renderer or Collider. + /// Get the bounds of an object from its Collider or Renderer. + /// Prioritizes 2D colliders first for accurate bounds, then falls back to Renderer. /// Searches child objects if no component is found on the root. /// public static Bounds GetObjectBounds(GameObject obj) @@ -146,14 +147,14 @@ namespace Minigames.Airplane.Core.Spawning return new Bounds(Vector3.zero, Vector3.one); } - // Try renderer first (most common for sprites) - Renderer objRenderer = obj.GetComponentInChildren(); - if (objRenderer != null) return objRenderer.bounds; - - // Try 2D collider + // Try 2D collider first (most accurate for ground snapping) Collider2D objCollider2D = obj.GetComponentInChildren(); if (objCollider2D != null) return objCollider2D.bounds; + // Try renderer (common for sprites without colliders) + Renderer objRenderer = obj.GetComponentInChildren(); + if (objRenderer != null) return objRenderer.bounds; + // Try 3D collider Collider objCollider3D = obj.GetComponentInChildren(); if (objCollider3D != null) return objCollider3D.bounds; @@ -165,6 +166,7 @@ namespace Minigames.Airplane.Core.Spawning /// /// Draw debug visualization for object bounds after positioning. /// Shows the actual bounds in world space at the object's final position. + /// Matches GetObjectBounds priority: 2D Collider → Renderer → 3D Collider. /// public static void DrawObjectBoundsDebug(GameObject obj, SpawnPositionMode mode) { @@ -173,20 +175,20 @@ namespace Minigames.Airplane.Core.Spawning Bounds bounds = default; string sourceType = "none"; - // Get bounds from whatever component is available - Renderer objRenderer = obj.GetComponentInChildren(); - if (objRenderer != null) + // Get bounds from whatever component is available (matching GetObjectBounds priority) + Collider2D objCollider2D = obj.GetComponentInChildren(); + if (objCollider2D != null) { - bounds = objRenderer.bounds; - sourceType = "Renderer"; + bounds = objCollider2D.bounds; + sourceType = "Collider2D"; } else { - Collider2D objCollider2D = obj.GetComponentInChildren(); - if (objCollider2D != null) + Renderer objRenderer = obj.GetComponentInChildren(); + if (objRenderer != null) { - bounds = objCollider2D.bounds; - sourceType = "Collider2D"; + bounds = objRenderer.bounds; + sourceType = "Renderer"; } else { diff --git a/Assets/Scripts/Minigames/Airplane/Data/AirplaneAbilityConfig.cs b/Assets/Scripts/Minigames/Airplane/Data/AirplaneAbilityConfig.cs index 5a6d9633..a3d43c02 100644 --- a/Assets/Scripts/Minigames/Airplane/Data/AirplaneAbilityConfig.cs +++ b/Assets/Scripts/Minigames/Airplane/Data/AirplaneAbilityConfig.cs @@ -15,7 +15,7 @@ namespace Minigames.Airplane.Data [Tooltip("Icon for ability button")] public Sprite abilityIcon; - [Tooltip("Cooldown duration in seconds")] + [Tooltip("Base cooldown duration in seconds (multiplied by cooldownMultiplier based on usage)")] public float cooldownDuration = 5f; [Tooltip("Speed while ability is active")] @@ -23,6 +23,13 @@ namespace Minigames.Airplane.Data [Tooltip("Direction angle (0 = right, 90 = up)")] public float jetAngle = 0f; + + [Header("Active Duration Settings")] + [Tooltip("Maximum duration ability can be active (seconds). Full usage = maxActiveDuration * cooldownMultiplier cooldown")] + public float maxActiveDuration = 5f; + + [Tooltip("Cooldown multiplier based on usage time. Full duration = cooldownMultiplier * maxActiveDuration cooldown")] + public float cooldownMultiplier = 2f; } /// @@ -58,14 +65,18 @@ namespace Minigames.Airplane.Data [Tooltip("Icon for ability button")] public Sprite abilityIcon; - [Tooltip("Cooldown duration in seconds")] + [Tooltip("Base cooldown duration in seconds (multiplied by cooldownMultiplier based on usage)")] public float cooldownDuration = 4f; [Tooltip("Downward force applied")] public float dropForce = 20f; - [Tooltip("Distance to drop before returning to normal flight")] - public float dropDistance = 5f; + [Header("Active Duration Settings")] + [Tooltip("Maximum duration ability can be active (seconds). Full usage = maxActiveDuration * cooldownMultiplier cooldown")] + public float maxActiveDuration = 3f; + + [Tooltip("Cooldown multiplier based on usage time. Full duration = cooldownMultiplier * maxActiveDuration cooldown")] + public float cooldownMultiplier = 2f; [Tooltip("Should horizontal velocity be zeroed during drop?")] public bool zeroHorizontalVelocity = true; diff --git a/Assets/Scripts/Minigames/Airplane/UI/AirplaneAbilityButton.cs b/Assets/Scripts/Minigames/Airplane/UI/AirplaneAbilityButton.cs index 12b2c5fd..d3259854 100644 --- a/Assets/Scripts/Minigames/Airplane/UI/AirplaneAbilityButton.cs +++ b/Assets/Scripts/Minigames/Airplane/UI/AirplaneAbilityButton.cs @@ -23,6 +23,10 @@ namespace Minigames.Airplane.UI [SerializeField] private Image cooldownFill; [SerializeField] private TextMeshProUGUI cooldownText; + [Header("Active State Visual")] + [Tooltip("Visual element shown when ability is active (e.g., glowing border, pulsing overlay)")] + [SerializeField] private GameObject activeStateVisual; + [Header("Debug")] [SerializeField] private bool showDebugLogs; @@ -46,6 +50,12 @@ namespace Minigames.Airplane.UI button.onClick.AddListener(OnButtonClick); } + // Hide active state visual by default + if (activeStateVisual != null) + { + activeStateVisual.SetActive(false); + } + // Hide by default gameObject.SetActive(false); } @@ -54,11 +64,29 @@ namespace Minigames.Airplane.UI { if (currentAbility == null) return; - // Update cooldown display - if (currentAbility.IsOnCooldown) + // Priority 1: Show active duration if ability is active + if (currentAbility.IsActive && currentAbility.HasActiveDuration) { - // Fill starts at 1 and reduces to 0 over cooldown duration - float fillAmount = currentAbility.CooldownRemaining / currentAbility.CooldownDuration; + // NO fill during active duration - only text and active visual + if (cooldownFill != null) + { + cooldownFill.fillAmount = 0f; + } + + // Show remaining active time + if (cooldownText != null) + { + cooldownText.text = $"{currentAbility.ActiveDurationRemaining:F1}s"; + } + } + // Priority 2: Show cooldown if on cooldown + else if (currentAbility.IsOnCooldown) + { + // Fill always starts at 1 (full) and reduces to 0, regardless of actual cooldown duration + float fillAmount = currentAbility.InitialCooldownDuration > 0f + ? currentAbility.CooldownRemaining / currentAbility.InitialCooldownDuration + : 0f; + if (cooldownFill != null) { cooldownFill.fillAmount = fillAmount; @@ -72,7 +100,7 @@ namespace Minigames.Airplane.UI } else { - // Cooldown complete - fill at 0, no text + // Normal state - fill at 0, no text if (cooldownFill != null) cooldownFill.fillAmount = 0f; @@ -89,6 +117,7 @@ namespace Minigames.Airplane.UI currentAbility.OnAbilityActivated -= HandleAbilityActivated; currentAbility.OnAbilityDeactivated -= HandleAbilityDeactivated; currentAbility.OnCooldownChanged -= HandleCooldownChanged; + currentAbility.OnActiveDurationChanged -= HandleActiveDurationChanged; } // Unregister from input system @@ -129,8 +158,8 @@ namespace Minigames.Airplane.UI cooldownText.text = ""; } - // Check if this is a hold ability (Jet) - isHoldAbility = ability is JetAbility; + // Check if this is a hold ability (Jet or Drop - abilities with active duration that can be interrupted) + isHoldAbility = ability is JetAbility || ability is DropAbility; // Subscribe to ability events if (ability != null) @@ -138,6 +167,7 @@ namespace Minigames.Airplane.UI ability.OnAbilityActivated += HandleAbilityActivated; ability.OnAbilityDeactivated += HandleAbilityDeactivated; ability.OnCooldownChanged += HandleCooldownChanged; + ability.OnActiveDurationChanged += HandleActiveDurationChanged; if (showDebugLogs) { @@ -164,6 +194,7 @@ namespace Minigames.Airplane.UI currentAbility.OnAbilityActivated -= HandleAbilityActivated; currentAbility.OnAbilityDeactivated -= HandleAbilityDeactivated; currentAbility.OnCooldownChanged -= HandleCooldownChanged; + currentAbility.OnActiveDurationChanged -= HandleActiveDurationChanged; } // Unregister from input system @@ -172,6 +203,9 @@ namespace Minigames.Airplane.UI InputManager.Instance.UnregisterOverrideConsumer(this); } + // Hide active state visual + HideActiveState(); + currentAbility = null; currentAirplane = null; isHolding = false; @@ -216,6 +250,12 @@ namespace Minigames.Airplane.UI private void HandleAbilityActivated(BaseAirplaneAbility ability) { + // Show active state visual if ability has active duration + if (ability.HasActiveDuration) + { + ShowActiveState(); + } + if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] Ability activated: {ability.AbilityName}"); @@ -224,6 +264,9 @@ namespace Minigames.Airplane.UI private void HandleAbilityDeactivated(BaseAirplaneAbility ability) { + // Hide active state visual + HideActiveState(); + if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] Ability deactivated: {ability.AbilityName}"); @@ -249,6 +292,14 @@ namespace Minigames.Airplane.UI } } + private void HandleActiveDurationChanged(float remaining, float total) + { + if (showDebugLogs) + { + Logging.Debug($"[AirplaneAbilityButton] OnActiveDurationChanged: remaining={remaining:F2}, total={total:F2}"); + } + } + #endregion #region ITouchInputConsumer Implementation @@ -295,6 +346,43 @@ namespace Minigames.Airplane.UI } #endregion + + #region Active State Visual API + + /// + /// Show active state visual (placeholder API). + /// Override or assign activeStateVisual GameObject in inspector for custom visuals. + /// + private void ShowActiveState() + { + if (activeStateVisual != null) + { + activeStateVisual.SetActive(true); + + if (showDebugLogs) + { + Logging.Debug("[AirplaneAbilityButton] Active state visual shown"); + } + } + } + + /// + /// Hide active state visual (placeholder API). + /// + private void HideActiveState() + { + if (activeStateVisual != null) + { + activeStateVisual.SetActive(false); + + if (showDebugLogs) + { + Logging.Debug("[AirplaneAbilityButton] Active state visual hidden"); + } + } + } + + #endregion } }