app.hypergames.hypersdk 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/Editor/{HyperSDK.Editor.asmdef → Hyper.Editor.asmdef} +1 -1
  3. package/Editor/Resources/ControlBarPreviews/Dark/Default.png +0 -0
  4. package/Editor/Resources/ControlBarPreviews/Dark/Default.png.meta +143 -0
  5. package/Editor/Resources/ControlBarPreviews/Dark/HealthBarLeft.png +0 -0
  6. package/Editor/Resources/ControlBarPreviews/Dark/HealthBarLeft.png.meta +143 -0
  7. package/Editor/Resources/ControlBarPreviews/Dark/HeartsLeft.png +0 -0
  8. package/Editor/Resources/ControlBarPreviews/Dark/HeartsLeft.png.meta +169 -0
  9. package/Editor/Resources/ControlBarPreviews/Dark.meta +8 -0
  10. package/Editor/Resources/ControlBarPreviews/Light/Default.png +0 -0
  11. package/Editor/Resources/ControlBarPreviews/Light/Default.png.meta +143 -0
  12. package/Editor/Resources/ControlBarPreviews/Light/HealthBarLeft.png +0 -0
  13. package/Editor/Resources/ControlBarPreviews/Light/HealthBarLeft.png.meta +143 -0
  14. package/Editor/Resources/ControlBarPreviews/Light/HeartsLeft.png +0 -0
  15. package/Editor/Resources/ControlBarPreviews/Light/HeartsLeft.png.meta +143 -0
  16. package/Editor/Resources/ControlBarPreviews/Light.meta +8 -0
  17. package/Editor/Resources/ControlBarPreviews.meta +8 -0
  18. package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs +206 -0
  19. package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs.meta +2 -0
  20. package/Editor/Scripts/Inspectors.meta +8 -0
  21. package/Editor/Scripts/Windows/HyperCoreSettingsWindow.cs +0 -8
  22. package/Editor/Scripts/Windows/HyperGameContentWindow.cs +0 -8
  23. package/Editor/Scripts/Windows/HyperSettingsWindow.cs +3 -3
  24. package/HyperLoader.unity +4 -49
  25. package/Runtime/Internal/AssemblyInfo.cs +1 -1
  26. package/Runtime/Internal/Managers/EventManager.cs +4 -0
  27. package/Runtime/Internal/Managers/GameManager.cs +135 -2
  28. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +16 -10
  29. package/Runtime/Internal/UI/Animation/BlinkingText.cs +1 -1
  30. package/Runtime/Internal/UI/Animation/HyperTween.cs +88 -0
  31. package/Runtime/Internal/UI/Components/ControlBar.cs +220 -40
  32. package/Runtime/Internal/UI/Components/HealthBarDisplay.cs +269 -0
  33. package/Runtime/Internal/UI/Components/HealthBarDisplay.cs.meta +2 -0
  34. package/Runtime/Internal/UI/Components/LivesDisplay.cs +327 -0
  35. package/Runtime/Internal/UI/Components/LivesDisplay.cs.meta +2 -0
  36. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +202 -3
  37. package/Runtime/Internal/Utils/HyperConstants.cs +27 -0
  38. package/Runtime/Public/Core/HyperSDK.cs +68 -0
  39. package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys + Hyper BIG.mat +1 -1
  40. package/Runtime/Resources/HyperSDKSettings.asset +3 -2
  41. package/Runtime/Resources/Sprites/Control Bar/Ellipse 3.png +0 -0
  42. package/Runtime/Resources/Sprites/Control Bar/Ellipse 3.png.meta +169 -0
  43. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Green Fill.png +0 -0
  44. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Green Fill.png.meta +169 -0
  45. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Underlay.png +0 -0
  46. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Underlay.png.meta +169 -0
  47. package/Runtime/Resources/Sprites/Control Bar/Health Bar.png +0 -0
  48. package/Runtime/Resources/Sprites/Control Bar/Health Bar.png.meta +169 -0
  49. package/Runtime/Resources/Sprites/Control Bar/heart.empty.png +0 -0
  50. package/Runtime/Resources/Sprites/Control Bar/heart.empty.png.meta +169 -0
  51. package/Runtime/Resources/Sprites/Control Bar/heart.fill.png +0 -0
  52. package/Runtime/Resources/Sprites/Control Bar/heart.fill.png.meta +169 -0
  53. package/Runtime/Resources/Sprites/Frames/{Game Over P.png.meta → Game Over.png.meta } +4 -4
  54. package/Runtime/Resources/Sprites/HYPERGAMES_Logo.png.meta +3 -3
  55. package/Runtime/Resources/Sprites/Hyper Atlas.spriteatlasv2 +6 -0
  56. package/Runtime/Resources/Sprites/Start CTAs/Hand.PNG +0 -0
  57. package/Runtime/Resources/Sprites/Start CTAs/Hand.PNG.meta +169 -0
  58. package/Runtime/Resources/Sprites/Start CTAs/Line .PNG +0 -0
  59. package/Runtime/Resources/Sprites/Start CTAs/Line .PNG.meta +169 -0
  60. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture GIF.controller +130 -0
  61. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture GIF.controller.meta +8 -0
  62. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture.anim +158 -0
  63. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture.anim.meta +8 -0
  64. package/Runtime/Resources/Sprites/Start CTAs.meta +8 -0
  65. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Health Bar Display.prefab +361 -0
  66. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Health Bar Display.prefab.meta +7 -0
  67. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Lives Display .prefab +246 -0
  68. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Lives Display .prefab.meta +7 -0
  69. package/Runtime/Resources/UIPrefabs/Control Bar Objects.meta +8 -0
  70. package/Runtime/Resources/UIPrefabs/ControlBar.prefab +131 -17
  71. package/Runtime/Resources/UIPrefabs/StartingInstructionModal.prefab +271 -57
  72. package/Runtime/Resources/UIPrefabs/TutorialLandscapeModal.prefab +1 -1
  73. package/Runtime/Resources/UIPrefabs/TutorialPortraitModal.prefab +1 -1
  74. package/Runtime/Shared/DTOs/Enums.cs +22 -6
  75. package/package.json +1 -1
  76. /package/Editor/{HyperSDK.Editor.asmdef.meta → Hyper.Editor.asmdef.meta} +0 -0
  77. /package/Runtime/Resources/Sprites/Frames/{Game Over P.png → Game Over.png} +0 -0
@@ -0,0 +1,269 @@
1
+ using System.Collections;
2
+ using UnityEngine;
3
+ using UnityEngine.UI;
4
+ using Hyper.Internal.Animation;
5
+ using System;
6
+
7
+ namespace Hyper.Internal
8
+ {
9
+ /// <summary>
10
+ /// Manages the display of health bar in the control bar.
11
+ /// Uses Image.fillAmount to represent health from 0-1.
12
+ /// </summary>
13
+ internal class HealthBarDisplay : MonoBehaviour
14
+ {
15
+ [Header("UI Components")]
16
+ [SerializeField] private Image healthFillImage;
17
+ [SerializeField] private Image underlayFillImage;
18
+
19
+ [Header("Configuration")]
20
+ [SerializeField] private float lowHealthThreshold = 0.3f;
21
+
22
+ [Header("Animation")]
23
+ [SerializeField] private float fillDelay = 0.1f;
24
+ [SerializeField] private float fillAnimationDuration = 0.3f;
25
+ [SerializeField] private bool usePulseOnLowHealth = false;
26
+ [SerializeField] private float pulseDuration = 0.5f;
27
+
28
+ private float _currentHealth = 1f;
29
+ private float _maxHealth = 1f;
30
+ private HyperTween.TweenHandle _fillTweenHandle;
31
+ private Coroutine _pulseCoroutine;
32
+ private bool _healthExhaustedTriggered;
33
+
34
+ private void Awake()
35
+ {
36
+ if (healthFillImage == null)
37
+ {
38
+ Debug.LogError("[HealthBarDisplay] Health fill image is not assigned!");
39
+ }
40
+
41
+ // Initialize to full health
42
+ if (healthFillImage != null && underlayFillImage != null)
43
+ {
44
+ healthFillImage.fillAmount = 1f;
45
+ underlayFillImage.fillAmount = 1f;
46
+ }
47
+ }
48
+
49
+ /// <summary>
50
+ /// Initializes the health bar with a maximum health value.
51
+ /// </summary>
52
+ /// <param name="maxHealth">Maximum health value</param>
53
+ /// <param name="initialHealth">Initial health (defaults to max, or pass 0-1 for normalized)</param>
54
+ /// <param name="isNormalized">If true, initialHealth is treated as 0-1, otherwise as absolute value</param>
55
+ public void Initialize(float maxHealth, float initialHealth = -1f, bool isNormalized = false)
56
+ {
57
+ if (maxHealth <= 0)
58
+ {
59
+ Debug.LogWarning("[HealthBarDisplay] Max health must be greater than 0. Setting to 1.");
60
+ maxHealth = 1f;
61
+ }
62
+
63
+ _maxHealth = maxHealth;
64
+
65
+ if (initialHealth < 0)
66
+ {
67
+ _currentHealth = maxHealth;
68
+ }
69
+ else if (isNormalized)
70
+ {
71
+ _currentHealth = Mathf.Clamp01(initialHealth) * maxHealth;
72
+ }
73
+ else
74
+ {
75
+ _currentHealth = Mathf.Clamp(initialHealth, 0f, maxHealth);
76
+ }
77
+
78
+ _healthExhaustedTriggered = false;
79
+ UpdateDisplay();
80
+ }
81
+
82
+ /// <summary>
83
+ /// Sets the current health value.
84
+ /// </summary>
85
+ /// <param name="health">Health value (absolute)</param>
86
+ public void SetHealth(float health)
87
+ {
88
+ float previousHealth = _currentHealth;
89
+ _currentHealth = Mathf.Clamp(health, 0f, _maxHealth);
90
+ AnimateHealthChange(previousHealth, _currentHealth);
91
+
92
+ // Check if health reached 0
93
+ if (previousHealth > 0 && _currentHealth <= 0)
94
+ {
95
+ CheckHealthExhausted();
96
+ }
97
+ }
98
+
99
+ /// <summary>
100
+ /// Sets the current health as a normalized value (0-1).
101
+ /// </summary>
102
+ /// <param name="normalizedHealth">Health value from 0 to 1</param>
103
+ public void SetHealthNormalized(float normalizedHealth)
104
+ {
105
+ SetHealth(Mathf.Clamp01(normalizedHealth) * _maxHealth);
106
+ }
107
+
108
+ /// <summary>
109
+ /// Adds health.
110
+ /// </summary>
111
+ /// <param name="amount">Amount of health to add</param>
112
+ public void AddHealth(float amount)
113
+ {
114
+ if (amount > 0)
115
+ {
116
+ SetHealth(_currentHealth + amount);
117
+ }
118
+ }
119
+
120
+ /// <summary>
121
+ /// Removes health.
122
+ /// </summary>
123
+ /// <param name="amount">Amount of health to remove</param>
124
+ public void RemoveHealth(float amount)
125
+ {
126
+ if (amount > 0)
127
+ {
128
+ SetHealth(_currentHealth - amount);
129
+ }
130
+ }
131
+
132
+ /// <summary>
133
+ /// Gets the current health value.
134
+ /// </summary>
135
+ public float GetCurrentHealth() => _currentHealth;
136
+
137
+ /// <summary>
138
+ /// Gets the maximum health value.
139
+ /// </summary>
140
+ public float GetMaxHealth() => _maxHealth;
141
+
142
+ /// <summary>
143
+ /// Gets the current health as a normalized value (0-1).
144
+ /// </summary>
145
+ public float GetHealthNormalized() => _maxHealth > 0 ? _currentHealth / _maxHealth : 0f;
146
+
147
+ private void AnimateHealthChange(float fromHealth, float toHealth)
148
+ {
149
+ if (healthFillImage == null) return;
150
+
151
+ // Stop existing animation
152
+ if (_fillTweenHandle != null)
153
+ {
154
+ _fillTweenHandle.Kill();
155
+ }
156
+
157
+ float fromFill = _maxHealth > 0 ? fromHealth / _maxHealth : 0f;
158
+ float toFill = _maxHealth > 0 ? toHealth / _maxHealth : 0f;
159
+
160
+ healthFillImage.fillAmount -= Math.Abs(fromHealth - toHealth) / _maxHealth;
161
+
162
+ HyperTween.DelayedCall(fillDelay, () =>
163
+ {
164
+ // Animate fill amount
165
+ _fillTweenHandle = underlayFillImage.DOFillAmount(toFill, fillAnimationDuration)
166
+ .SetEase(HyperTween.Ease.OutQuad)
167
+ .OnComplete(() =>
168
+ {
169
+ _fillTweenHandle = null;
170
+
171
+ // Start pulse if health is low
172
+ if (usePulseOnLowHealth && toFill <= lowHealthThreshold && toFill > 0)
173
+ {
174
+ StartPulseAnimation();
175
+ }
176
+ else
177
+ {
178
+ StopPulseAnimation();
179
+ }
180
+ });
181
+ });
182
+ }
183
+
184
+ private void StartPulseAnimation()
185
+ {
186
+ if (_pulseCoroutine != null) return;
187
+ _pulseCoroutine = StartCoroutine(PulseAnimation());
188
+ }
189
+
190
+ private void StopPulseAnimation()
191
+ {
192
+ if (_pulseCoroutine != null)
193
+ {
194
+ StopCoroutine(_pulseCoroutine);
195
+ _pulseCoroutine = null;
196
+ }
197
+
198
+ if (healthFillImage != null)
199
+ {
200
+ Color color = healthFillImage.color;
201
+ color.a = 1f;
202
+ healthFillImage.color = color;
203
+ underlayFillImage.color = color;
204
+ }
205
+ }
206
+
207
+ private IEnumerator PulseAnimation()
208
+ {
209
+ if (healthFillImage == null) yield break;
210
+
211
+ while (true)
212
+ {
213
+ if (healthFillImage == null) yield break;
214
+
215
+ float normalizedHealth = GetHealthNormalized();
216
+ if (normalizedHealth > lowHealthThreshold || normalizedHealth <= 0)
217
+ {
218
+ StopPulseAnimation();
219
+ yield break;
220
+ }
221
+
222
+ // Pulse alpha
223
+ float elapsed = 0f;
224
+ Color startColor = healthFillImage.color;
225
+ Color pulseColor = new Color(startColor.r, startColor.g, startColor.b, 0.5f);
226
+
227
+ while (elapsed < pulseDuration)
228
+ {
229
+ if (healthFillImage == null) yield break;
230
+ elapsed += Time.deltaTime;
231
+ float t = Mathf.PingPong(elapsed / pulseDuration, 1f);
232
+ Color currentColor = Color.Lerp(startColor, pulseColor, t);
233
+ healthFillImage.color = currentColor;
234
+ underlayFillImage.color = currentColor;
235
+ yield return null;
236
+ }
237
+ }
238
+ }
239
+
240
+ private void UpdateDisplay()
241
+ {
242
+ if (healthFillImage == null) return;
243
+
244
+ float normalizedHealth = GetHealthNormalized();
245
+ healthFillImage.fillAmount = normalizedHealth;
246
+ underlayFillImage.fillAmount = normalizedHealth;
247
+ }
248
+
249
+ private void CheckHealthExhausted()
250
+ {
251
+ if (_currentHealth <= 0 && !_healthExhaustedTriggered)
252
+ {
253
+ _healthExhaustedTriggered = true;
254
+ HyperRuntime.EventManager?.InvokeHealthExhaustedEvent();
255
+ }
256
+ }
257
+
258
+ private void OnDestroy()
259
+ {
260
+ if (_fillTweenHandle != null)
261
+ {
262
+ _fillTweenHandle.Kill();
263
+ }
264
+ HyperTween.Kill(underlayFillImage);
265
+ StopPulseAnimation();
266
+ }
267
+ }
268
+ }
269
+
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 60de914bbc6cab74ba739af42321d40f
@@ -0,0 +1,327 @@
1
+ using System.Collections;
2
+ using UnityEngine;
3
+ using TMPro;
4
+ using UnityEngine.UI;
5
+ using Hyper.Internal.Animation;
6
+
7
+ namespace Hyper.Internal
8
+ {
9
+ /// <summary>
10
+ /// Manages the display of lives/hearts in the control bar.
11
+ /// Dynamically creates and manages heart icons based on current lives.
12
+ /// </summary>
13
+ internal class LivesDisplay : MonoBehaviour
14
+ {
15
+ [Header("Configuration")]
16
+ [SerializeField] private Sprite heartFullSprite;
17
+ [SerializeField] private Sprite heartEmptySprite;
18
+ [SerializeField] private float heartSize = 50f;
19
+ [SerializeField] private float spacing = 10f;
20
+ [SerializeField, Min(1)] private int maxVisibleHearts = 3;
21
+
22
+ [Header("Animation")]
23
+ [SerializeField] private float fadeDuration = 0.3f;
24
+ [SerializeField] private bool useScaleAnimation = false;
25
+ [SerializeField] private float scaleAmount = 0.8f;
26
+ [SerializeField] private float scaleDuration = 0.15f;
27
+
28
+ [Header("Container")]
29
+ [SerializeField] private RectTransform container;
30
+ [SerializeField] private TextMeshProUGUI overflowLabel;
31
+
32
+ private int _currentLives;
33
+ private int _maxLives;
34
+ private Image[] _heartImages;
35
+ private bool _livesExhaustedTriggered;
36
+
37
+ private void Awake()
38
+ {
39
+ if (container == null)
40
+ {
41
+ container = GetComponent<RectTransform>();
42
+ }
43
+
44
+ ConfigureLayoutGroup();
45
+
46
+ if (overflowLabel != null)
47
+ {
48
+ overflowLabel.gameObject.SetActive(false);
49
+ overflowLabel.text = string.Empty;
50
+ }
51
+ }
52
+
53
+ private void ConfigureLayoutGroup()
54
+ {
55
+ HorizontalLayoutGroup layoutGroup = container.GetComponent<HorizontalLayoutGroup>();
56
+ if (layoutGroup == null)
57
+ {
58
+ layoutGroup = container.gameObject.AddComponent<HorizontalLayoutGroup>();
59
+ }
60
+
61
+ layoutGroup.spacing = spacing;
62
+ layoutGroup.childAlignment = TextAnchor.MiddleLeft;
63
+ layoutGroup.childControlWidth = false;
64
+ layoutGroup.childControlHeight = false;
65
+ layoutGroup.childForceExpandWidth = false;
66
+ layoutGroup.childForceExpandHeight = false;
67
+ }
68
+
69
+ /// <summary>
70
+ /// Initializes the lives display with a maximum number of lives.
71
+ /// Creates heart icons based on the max value.
72
+ /// </summary>
73
+ /// <param name="maxLives">Maximum number of lives to display</param>
74
+ /// <param name="initialLives">Initial number of lives (defaults to max)</param>
75
+ public void Initialize(int maxLives, int initialLives = -1)
76
+ {
77
+ if (maxLives < 0)
78
+ {
79
+ Debug.LogWarning("[LivesDisplay] Max lives cannot be negative. Setting to 0.");
80
+ maxLives = 0;
81
+ }
82
+
83
+ _maxLives = maxLives;
84
+ _currentLives = initialLives >= 0 ? initialLives : maxLives;
85
+
86
+ if (_currentLives > _maxLives)
87
+ {
88
+ _currentLives = _maxLives;
89
+ }
90
+
91
+ _livesExhaustedTriggered = false;
92
+ CreateHeartIcons();
93
+ UpdateDisplay();
94
+ }
95
+
96
+ /// <summary>
97
+ /// Sets the current number of lives and updates the display.
98
+ /// </summary>
99
+ /// <param name="lives">Number of lives to set</param>
100
+ public void SetLives(int lives)
101
+ {
102
+ int previousLives = _currentLives;
103
+ _currentLives = Mathf.Clamp(lives, 0, _maxLives);
104
+ UpdateDisplay();
105
+
106
+ // Only check if lives decreased to 0
107
+ if (previousLives > 0 && _currentLives == 0)
108
+ {
109
+ CheckLivesExhausted();
110
+ }
111
+ }
112
+
113
+ /// <summary>
114
+ /// Adds one life (up to max).
115
+ /// </summary>
116
+ public void AddLife()
117
+ {
118
+ if (_currentLives < _maxLives)
119
+ {
120
+ _currentLives++;
121
+ UpdateDisplay();
122
+ }
123
+ }
124
+
125
+ /// <summary>
126
+ /// Removes one life (down to 0).
127
+ /// </summary>
128
+ public void RemoveLife()
129
+ {
130
+ if (_currentLives > 0)
131
+ {
132
+ int lostLifeIndex = _currentLives - 1;
133
+ _currentLives--;
134
+ UpdateDisplay();
135
+ AnimateLifeLost(lostLifeIndex);
136
+ CheckLivesExhausted();
137
+ }
138
+ }
139
+
140
+ /// <summary>
141
+ /// Gets the current number of lives.
142
+ /// </summary>
143
+ public int GetCurrentLives() => _currentLives;
144
+
145
+ /// <summary>
146
+ /// Gets the maximum number of lives.
147
+ /// </summary>
148
+ public int GetMaxLives() => _maxLives;
149
+
150
+ private void CreateHeartIcons()
151
+ {
152
+ // Clear existing hearts
153
+ ClearHearts();
154
+
155
+ if (_maxLives <= 0)
156
+ {
157
+ _heartImages = new Image[0];
158
+ return;
159
+ }
160
+
161
+ int heartSlots = maxVisibleHearts > 0
162
+ ? Mathf.Min(_maxLives, maxVisibleHearts)
163
+ : _maxLives;
164
+
165
+ _heartImages = new Image[heartSlots];
166
+
167
+ for (int i = 0; i < heartSlots; i++)
168
+ {
169
+ GameObject heartObject = new GameObject($"Heart_{i}");
170
+ heartObject.transform.SetParent(container, false);
171
+
172
+ RectTransform heartRect = heartObject.AddComponent<RectTransform>();
173
+ heartRect.sizeDelta = new Vector2(heartSize, heartSize);
174
+
175
+ Image heartImage = heartObject.AddComponent<Image>();
176
+ heartImage.sprite = heartFullSprite;
177
+ heartImage.preserveAspect = true;
178
+
179
+ _heartImages[i] = heartImage;
180
+ }
181
+ }
182
+
183
+ private void UpdateDisplay()
184
+ {
185
+ if (_heartImages == null) return;
186
+
187
+ for (int i = 0; i < _heartImages.Length; i++)
188
+ {
189
+ if (_heartImages[i] != null)
190
+ {
191
+ bool isFull = i < Mathf.Min(_currentLives, _heartImages.Length);
192
+ _heartImages[i].sprite = isFull ? heartFullSprite : heartEmptySprite;
193
+ _heartImages[i].gameObject.SetActive(true);
194
+
195
+ // Reset color to full opacity for immediate updates
196
+ Color color = _heartImages[i].color;
197
+ color.a = 1f;
198
+ _heartImages[i].color = color;
199
+ }
200
+ }
201
+
202
+ UpdateOverflowLabel();
203
+ }
204
+
205
+ private void AnimateLifeLost(int heartIndex)
206
+ {
207
+ if (_heartImages == null || heartIndex < 0 || heartIndex >= _heartImages.Length)
208
+ return;
209
+
210
+ Image heartImage = _heartImages[heartIndex];
211
+ if (heartImage == null) return;
212
+
213
+ // Update sprite immediately
214
+ Color color = heartImage.color;
215
+ color.a = 0.45f;
216
+ heartImage.color = color;
217
+ heartImage.sprite = heartEmptySprite;
218
+
219
+ // Stop any existing animations on this heart
220
+ HyperTween.Kill(heartImage);
221
+ if (heartImage.transform != null)
222
+ {
223
+ HyperTween.Kill(heartImage.transform);
224
+ }
225
+
226
+ // Fade animation: fade out then fade back in with empty sprite
227
+ AnimateHeartFade(heartImage);
228
+
229
+ // Optional scale animation
230
+ if (useScaleAnimation && heartImage.transform != null)
231
+ {
232
+ AnimateHeartScale(heartImage.transform);
233
+ }
234
+ }
235
+
236
+ private void AnimateHeartFade(Image heartImage)
237
+ {
238
+ if (heartImage == null) return;
239
+
240
+ Color startColor = heartImage.color;
241
+ Color fadeOutColor = new Color(startColor.r, startColor.g, startColor.b, 0f);
242
+ Color fadeInColor = new Color(startColor.r, startColor.g, startColor.b, 1f);
243
+ float halfDuration = fadeDuration * 0.5f;
244
+
245
+ // Fade out, then fade back in
246
+ heartImage.DOColor(fadeOutColor, halfDuration)
247
+ .SetEase(HyperTween.Ease.OutQuad)
248
+ .OnComplete(() =>
249
+ {
250
+ if (heartImage != null)
251
+ {
252
+ heartImage.DOColor(fadeInColor, halfDuration)
253
+ .SetEase(HyperTween.Ease.InQuad);
254
+ }
255
+ });
256
+ }
257
+
258
+ private void AnimateHeartScale(Transform heartTransform)
259
+ {
260
+ if (heartTransform == null) return;
261
+
262
+ Vector3 originalScale = Vector3.one;
263
+ Vector3 scaledDown = originalScale * scaleAmount;
264
+ float halfDuration = scaleDuration * 0.5f;
265
+
266
+ // Scale down, then scale back up
267
+ heartTransform.DOScale(scaledDown, halfDuration)
268
+ .SetEase(HyperTween.Ease.OutQuad)
269
+ .OnComplete(() =>
270
+ {
271
+ if (heartTransform != null)
272
+ {
273
+ heartTransform.DOScale(originalScale, halfDuration)
274
+ .SetEase(HyperTween.Ease.InQuad);
275
+ }
276
+ });
277
+ }
278
+
279
+ private void CheckLivesExhausted()
280
+ {
281
+ if (_currentLives <= 0 && !_livesExhaustedTriggered)
282
+ {
283
+ _livesExhaustedTriggered = true;
284
+ HyperRuntime.EventManager?.InvokeLivesExhaustedEvent();
285
+ }
286
+ }
287
+
288
+ private void ClearHearts()
289
+ {
290
+ if (_heartImages == null) return;
291
+
292
+ foreach (Image heart in _heartImages)
293
+ {
294
+ if (heart != null)
295
+ {
296
+ Destroy(heart.gameObject);
297
+ }
298
+ }
299
+
300
+ _heartImages = null;
301
+ }
302
+
303
+ private void OnDestroy()
304
+ {
305
+ ClearHearts();
306
+ }
307
+
308
+ private void UpdateOverflowLabel()
309
+ {
310
+ if (overflowLabel == null) return;
311
+
312
+ int heartSlots = _heartImages != null ? _heartImages.Length : 0;
313
+ int overflow = Mathf.Max(0, _currentLives - heartSlots);
314
+ if (overflow > 0)
315
+ {
316
+ overflowLabel.gameObject.SetActive(true);
317
+ overflowLabel.text = $"+{overflow}";
318
+ }
319
+ else
320
+ {
321
+ overflowLabel.text = string.Empty;
322
+ overflowLabel.gameObject.SetActive(false);
323
+ }
324
+ }
325
+ }
326
+ }
327
+
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: cb2e75c824ecc1e4f8f4461666f2ae4b