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
}
}