app.hypergames.hypersdk 1.3.4 → 1.4.1

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 (86) hide show
  1. package/CHANGELOG.md +15 -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/Tools/HyperBuildAssistant.cs +53 -3
  22. package/Editor/Scripts/Windows/HyperCoreSettingsWindow.cs +0 -8
  23. package/Editor/Scripts/Windows/HyperGameContentWindow.cs +0 -8
  24. package/Editor/Scripts/Windows/HyperSettingsWindow.cs +3 -3
  25. package/HyperLoader.unity +4 -49
  26. package/Runtime/Internal/AssemblyInfo.cs +1 -1
  27. package/Runtime/Internal/Managers/EventManager.cs +4 -0
  28. package/Runtime/Internal/Managers/GameManager.cs +173 -5
  29. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +16 -10
  30. package/Runtime/Internal/UI/Animation/BlinkingText.cs +1 -1
  31. package/Runtime/Internal/UI/Animation/HyperTween.cs +88 -0
  32. package/Runtime/Internal/UI/Components/ControlBar.cs +220 -40
  33. package/Runtime/Internal/UI/Components/HealthBarDisplay.cs +269 -0
  34. package/Runtime/Internal/UI/Components/HealthBarDisplay.cs.meta +2 -0
  35. package/Runtime/Internal/UI/Components/LivesDisplay.cs +327 -0
  36. package/Runtime/Internal/UI/Components/LivesDisplay.cs.meta +2 -0
  37. package/Runtime/Internal/UI/Modals/ExitGameMenuModal.cs +46 -0
  38. package/Runtime/Internal/UI/Modals/ExitGameMenuModal.cs.meta +11 -0
  39. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +202 -3
  40. package/Runtime/Internal/Utils/HyperConstants.cs +27 -0
  41. package/Runtime/Public/Core/HyperSDK.cs +68 -0
  42. package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys + Hyper BIG.mat +1 -1
  43. package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys + Shadows.mat +1 -1
  44. package/Runtime/Resources/HyperSDKSettings.asset +3 -2
  45. package/Runtime/Resources/Sprites/Big Icons/Exit.png +0 -0
  46. package/Runtime/Resources/Sprites/Big Icons/Exit.png.meta +195 -0
  47. package/Runtime/Resources/Sprites/Control Bar/Ellipse 3.png +0 -0
  48. package/Runtime/Resources/Sprites/Control Bar/Ellipse 3.png.meta +169 -0
  49. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Green Fill.png +0 -0
  50. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Green Fill.png.meta +169 -0
  51. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Underlay.png +0 -0
  52. package/Runtime/Resources/Sprites/Control Bar/Health Bar - Underlay.png.meta +169 -0
  53. package/Runtime/Resources/Sprites/Control Bar/Health Bar.png +0 -0
  54. package/Runtime/Resources/Sprites/Control Bar/Health Bar.png.meta +169 -0
  55. package/Runtime/Resources/Sprites/Control Bar/heart.empty.png +0 -0
  56. package/Runtime/Resources/Sprites/Control Bar/heart.empty.png.meta +169 -0
  57. package/Runtime/Resources/Sprites/Control Bar/heart.fill.png +0 -0
  58. package/Runtime/Resources/Sprites/Control Bar/heart.fill.png.meta +169 -0
  59. package/Runtime/Resources/Sprites/Frames/{Game Over P.png.meta → Game Over.png.meta } +4 -4
  60. package/Runtime/Resources/Sprites/HYPERGAMES_Logo.png.meta +3 -3
  61. package/Runtime/Resources/Sprites/Hyper Atlas.spriteatlasv2 +6 -0
  62. package/Runtime/Resources/Sprites/Start CTAs/Hand.PNG +0 -0
  63. package/Runtime/Resources/Sprites/Start CTAs/Hand.PNG.meta +169 -0
  64. package/Runtime/Resources/Sprites/Start CTAs/Line .PNG +0 -0
  65. package/Runtime/Resources/Sprites/Start CTAs/Line .PNG.meta +169 -0
  66. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture GIF.controller +130 -0
  67. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture GIF.controller.meta +8 -0
  68. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture.anim +158 -0
  69. package/Runtime/Resources/Sprites/Start CTAs/Swipe Gesture.anim.meta +8 -0
  70. package/Runtime/Resources/Sprites/Start CTAs.meta +8 -0
  71. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Health Bar Display.prefab +361 -0
  72. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Health Bar Display.prefab.meta +7 -0
  73. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Lives Display .prefab +246 -0
  74. package/Runtime/Resources/UIPrefabs/Control Bar Objects/Lives Display .prefab.meta +7 -0
  75. package/Runtime/Resources/UIPrefabs/Control Bar Objects.meta +8 -0
  76. package/Runtime/Resources/UIPrefabs/ControlBar.prefab +131 -17
  77. package/Runtime/Resources/UIPrefabs/ExitGameMenuModal.prefab +1043 -0
  78. package/Runtime/Resources/UIPrefabs/ExitGameMenuModal.prefab.meta +7 -0
  79. package/Runtime/Resources/UIPrefabs/GamePausedModal_Battle.prefab +1 -1
  80. package/Runtime/Resources/UIPrefabs/StartingInstructionModal.prefab +271 -57
  81. package/Runtime/Resources/UIPrefabs/TutorialLandscapeModal.prefab +1 -1
  82. package/Runtime/Resources/UIPrefabs/TutorialPortraitModal.prefab +1 -1
  83. package/Runtime/Shared/DTOs/Enums.cs +22 -6
  84. package/package.json +1 -1
  85. /package/Editor/{HyperSDK.Editor.asmdef.meta → Hyper.Editor.asmdef.meta} +0 -0
  86. /package/Runtime/Resources/Sprites/Frames/{Game Over P.png → Game Over.png} +0 -0
@@ -4,32 +4,82 @@ using TMPro;
4
4
  using UnityEngine;
5
5
  using UnityEngine.UI;
6
6
 
7
-
8
7
  namespace Hyper.Internal
9
8
  {
10
9
  internal class ControlBar : MonoBehaviour
11
10
  {
11
+ [Header("UI Components")]
12
+ public Image roundedBG;
12
13
  [SerializeField] TextMeshProUGUI scoreText;
13
14
  [SerializeField] private RectTransform scoreTextRect;
14
15
  [SerializeField] private RectTransform timerText;
15
16
  [SerializeField] private Image safeAreaFillerBG;
16
- public Image roundedBG;
17
17
  [SerializeField] private Image pauseButtonImage;
18
18
  [SerializeField] private Image pauseBarsImage;
19
+ [SerializeField] private Button connectionStatusIcon;
20
+
21
+ [Header("Sprites")]
19
22
  [SerializeField] Sprite portraitBG;
20
23
  [SerializeField] Sprite landscapeBG;
21
24
  [SerializeField] Sprite pauseBarsLight;
22
25
  [SerializeField] Sprite pauseBarsDark;
23
26
 
27
+ [Header("Lives Display")]
28
+ [SerializeField] private GameObject livesDisplayPrefab;
29
+
30
+ [Header("Health Bar Display")]
31
+ [SerializeField] private GameObject healthBarDisplayPrefab;
32
+
24
33
  private GameManager _gameManager;
34
+ private TextMeshProUGUI _scoreTextComponent;
35
+ private TextMeshProUGUI _timerTextComponent;
36
+ private LivesDisplay _livesDisplay;
37
+ private HealthBarDisplay _healthBarDisplay;
38
+
39
+ // Layout constants
40
+ private const float CENTERED_LAYOUT_SCORE_Y = 31f;
41
+ private const float CENTERED_LAYOUT_TIMER_Y = -13f;
42
+ private const float CENTERED_LAYOUT_SCORE_FONT_SIZE = 100f;
43
+ private const float CENTERED_LAYOUT_TIMER_FONT_SIZE = 50f;
44
+
45
+ private const float DEFAULT_LAYOUT_SCORE_Y = 10f;
46
+ private const float DEFAULT_LAYOUT_TIMER_X = 35f;
47
+ private const float DEFAULT_LAYOUT_TIMER_Y = 10f;
48
+ private const float DEFAULT_LAYOUT_SCORE_FONT_SIZE = 100f;
49
+ private const float DEFAULT_LAYOUT_TIMER_FONT_SIZE = 60f;
50
+
51
+ // Theme constants
52
+ private const string LIGHT_THEME_BG_COLOR = "#F5F5F5";
53
+ private const string LIGHT_THEME_TEXT_COLOR = "#434343";
54
+ private const float DARK_THEME_PAUSE_BUTTON_ALPHA = 0.25f;
55
+ private const float LIGHT_THEME_PAUSE_BUTTON_ALPHA = 1f;
25
56
 
26
57
  void Awake()
27
58
  {
28
59
  _gameManager = HyperRuntime.GameManager;
60
+ CacheTextComponents();
29
61
 
30
62
  RectTransform rect = transform.GetComponent<RectTransform>();
63
+ ConfigureOrientation(rect);
64
+ ConfigureLayout();
65
+ ConfigureTheme();
66
+
67
+ connectionStatusIcon.onClick.AddListener(ShowConnectionLostStatus);
68
+ }
31
69
 
32
- // Choose correct sprite based on Orientation
70
+ private void OnDestroy()
71
+ {
72
+ connectionStatusIcon.onClick.RemoveListener(ShowConnectionLostStatus);
73
+ }
74
+
75
+ private void CacheTextComponents()
76
+ {
77
+ _scoreTextComponent = scoreTextRect.GetComponent<TextMeshProUGUI>();
78
+ _timerTextComponent = timerText.GetComponent<TextMeshProUGUI>();
79
+ }
80
+
81
+ private void ConfigureOrientation(RectTransform rect)
82
+ {
33
83
  if (_gameManager.IsPortrait())
34
84
  {
35
85
  roundedBG.sprite = portraitBG;
@@ -40,62 +90,174 @@ namespace Hyper.Internal
40
90
  roundedBG.sprite = landscapeBG;
41
91
  rect.sizeDelta = new Vector2(rect.sizeDelta.x, 180);
42
92
  }
93
+ }
43
94
 
95
+ private void ConfigureLayout()
96
+ {
97
+ switch (_gameManager.ControlBarLayout)
98
+ {
99
+ case ControlBarLayout.HeartsLeft:
100
+ ApplyCenteredLayout();
101
+ SetupLivesDisplay();
102
+ break;
103
+ case ControlBarLayout.HealthBarLeft:
104
+ ApplyCenteredLayout();
105
+ HideLivesDisplay();
106
+ SetupHealthBarDisplay();
107
+ break;
108
+ case ControlBarLayout.Default:
109
+ ApplyDefaultLayout();
110
+ HideLivesDisplay();
111
+ HideHealthBarDisplay();
112
+ break;
113
+ }
114
+ }
44
115
 
45
- // Configure correct positioning based on the selected Layout
46
- if (_gameManager.ControlBarLayout == ControlBarLayout.CustomLeft)
116
+ private void SetupLivesDisplay()
117
+ {
118
+ if (_livesDisplay == null && livesDisplayPrefab != null)
47
119
  {
48
- scoreTextRect.anchoredPosition = new Vector2(0, 31);
49
- scoreTextRect.GetComponent<TextMeshProUGUI>().fontSize = 100;
120
+ GameObject livesInstance = Instantiate(livesDisplayPrefab, roundedBG.transform);
121
+ _livesDisplay = livesInstance.GetComponent<LivesDisplay>();
50
122
 
51
- timerText.anchoredPosition = new Vector2(0, -13);
52
- timerText.anchorMin = new Vector2(0.5f, 0.5f);
53
- timerText.anchorMax = new Vector2(0.5f, 0.5f);
54
- timerText.pivot = new Vector2(0.5f, 0.5f);
55
- timerText.GetComponent<TextMeshProUGUI>().fontSize = 50;
123
+ if (_livesDisplay == null)
124
+ {
125
+ Debug.LogError("[ControlBar] LivesDisplay prefab does not have LivesDisplay component!");
126
+ Destroy(livesInstance);
127
+ return;
128
+ }
129
+
130
+ // Position on the left side where timer would be in default layout
131
+ RectTransform livesRect = livesInstance.GetComponent<RectTransform>();
132
+ if (livesRect != null)
133
+ {
134
+ livesRect.anchoredPosition = new Vector2(DEFAULT_LAYOUT_TIMER_X, DEFAULT_LAYOUT_TIMER_Y);
135
+ livesRect.anchorMin = new Vector2(0f, 0.5f);
136
+ livesRect.anchorMax = new Vector2(0f, 0.5f);
137
+ livesRect.pivot = new Vector2(0, 0.5f);
138
+ }
56
139
  }
57
- else if (_gameManager.ControlBarLayout == ControlBarLayout.Default)
140
+
141
+ if (_livesDisplay != null)
58
142
  {
59
- scoreTextRect.anchoredPosition = new Vector2(0, 10);
60
- scoreTextRect.GetComponent<TextMeshProUGUI>().fontSize = 100;
143
+ _livesDisplay.gameObject.SetActive(true);
144
+ }
145
+ }
61
146
 
62
- timerText.anchoredPosition = new Vector2(40, 10);
63
- timerText.anchorMin = new Vector2(0f, 0.5f);
64
- timerText.anchorMax = new Vector2(0f, 0.5f);
65
- timerText.pivot = new Vector2(0, 0.5f);
66
- timerText.GetComponent<TextMeshProUGUI>().fontSize = 60;
67
- timerText.GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.MidlineLeft;
147
+ private void HideLivesDisplay()
148
+ {
149
+ if (_livesDisplay != null)
150
+ {
151
+ _livesDisplay.gameObject.SetActive(false);
68
152
  }
153
+ }
69
154
 
70
- //Configure correct coloring based on selected theme
71
- if (_gameManager.ControlBarTheme == ControlBarTheme.Dark)
155
+ private void SetupHealthBarDisplay()
156
+ {
157
+ if (_healthBarDisplay == null && healthBarDisplayPrefab != null)
72
158
  {
73
- roundedBG.color = Color.black;
74
- safeAreaFillerBG.color = Color.black;
159
+ GameObject healthBarInstance = Instantiate(healthBarDisplayPrefab, roundedBG.transform);
160
+ _healthBarDisplay = healthBarInstance.GetComponent<HealthBarDisplay>();
75
161
 
76
- scoreTextRect.GetComponent<TextMeshProUGUI>().color = Color.white;
77
- timerText.GetComponent<TextMeshProUGUI>().color = Color.white;
162
+ if (_healthBarDisplay == null)
163
+ {
164
+ Debug.LogError("[ControlBar] HealthBarDisplay prefab does not have HealthBarDisplay component!");
165
+ Destroy(healthBarInstance);
166
+ return;
167
+ }
78
168
 
79
- //pauseButtonImage.sprite = pauseButtonDark;
80
- pauseButtonImage.color = new Color(1, 1, 1, 0.25f);
81
- pauseBarsImage.sprite = pauseBarsDark;
169
+ // Position on the left side where timer would be in default layout
170
+ RectTransform healthBarRect = healthBarInstance.GetComponent<RectTransform>();
171
+ if (healthBarRect != null)
172
+ {
173
+ healthBarRect.anchoredPosition = new Vector2(DEFAULT_LAYOUT_TIMER_X, DEFAULT_LAYOUT_TIMER_Y);
174
+ healthBarRect.anchorMin = new Vector2(0f, 0.5f);
175
+ healthBarRect.anchorMax = new Vector2(0f, 0.5f);
176
+ healthBarRect.pivot = new Vector2(0, 0.5f);
177
+ }
178
+ }
179
+
180
+ if (_healthBarDisplay != null)
181
+ {
182
+ _healthBarDisplay.gameObject.SetActive(true);
82
183
  }
83
- else if (_gameManager.ControlBarTheme == ControlBarTheme.Light)
184
+ }
185
+
186
+ private void HideHealthBarDisplay()
187
+ {
188
+ if (_healthBarDisplay != null)
84
189
  {
85
- roundedBG.color = HexToColor("#F5F5F5");
86
- safeAreaFillerBG.color = HexToColor("#F5F5F5");
190
+ _healthBarDisplay.gameObject.SetActive(false);
191
+ }
192
+ }
193
+
194
+ private void ApplyCenteredLayout()
195
+ {
196
+ scoreTextRect.anchoredPosition = new Vector2(0, CENTERED_LAYOUT_SCORE_Y);
197
+ _scoreTextComponent.fontSize = CENTERED_LAYOUT_SCORE_FONT_SIZE;
87
198
 
88
- scoreTextRect.GetComponent<TextMeshProUGUI>().color = HexToColor("#434343");
89
- timerText.GetComponent<TextMeshProUGUI>().color = HexToColor("#434343");
199
+ timerText.anchoredPosition = new Vector2(0, CENTERED_LAYOUT_TIMER_Y);
200
+ timerText.anchorMin = new Vector2(0.5f, 0.5f);
201
+ timerText.anchorMax = new Vector2(0.5f, 0.5f);
202
+ timerText.pivot = new Vector2(0.5f, 0.5f);
203
+ _timerTextComponent.fontSize = CENTERED_LAYOUT_TIMER_FONT_SIZE;
204
+ }
205
+
206
+ private void ApplyDefaultLayout()
207
+ {
208
+ scoreTextRect.anchoredPosition = new Vector2(0, DEFAULT_LAYOUT_SCORE_Y);
209
+ _scoreTextComponent.fontSize = DEFAULT_LAYOUT_SCORE_FONT_SIZE;
90
210
 
91
- //pauseButtonImage.sprite = pauseButtonLight;
92
- pauseButtonImage.color = new Color(1, 1, 1, 1);
93
- pauseBarsImage.sprite = pauseBarsLight;
94
- }
211
+ timerText.anchoredPosition = new Vector2(DEFAULT_LAYOUT_TIMER_X, DEFAULT_LAYOUT_TIMER_Y);
212
+ timerText.anchorMin = new Vector2(0f, 0.5f);
213
+ timerText.anchorMax = new Vector2(0f, 0.5f);
214
+ timerText.pivot = new Vector2(0, 0.5f);
215
+ _timerTextComponent.fontSize = DEFAULT_LAYOUT_TIMER_FONT_SIZE;
216
+ _timerTextComponent.alignment = TextAlignmentOptions.MidlineLeft;
95
217
  }
96
218
 
97
- public void ShowConnectionLostStatus()
219
+ private void ConfigureTheme()
98
220
  {
221
+ switch (_gameManager.ControlBarTheme)
222
+ {
223
+ case ControlBarTheme.Dark:
224
+ ApplyDarkTheme();
225
+ break;
226
+ case ControlBarTheme.Light:
227
+ ApplyLightTheme();
228
+ break;
229
+ }
230
+ }
231
+
232
+ private void ApplyDarkTheme()
233
+ {
234
+ roundedBG.color = Color.black;
235
+ safeAreaFillerBG.color = Color.black;
236
+
237
+ _scoreTextComponent.color = Color.white;
238
+ _timerTextComponent.color = Color.white;
239
+
240
+ pauseButtonImage.color = new Color(1, 1, 1, DARK_THEME_PAUSE_BUTTON_ALPHA);
241
+ pauseBarsImage.sprite = pauseBarsDark;
242
+ }
243
+
244
+ private void ApplyLightTheme()
245
+ {
246
+ Color bgColor = HexToColor(LIGHT_THEME_BG_COLOR);
247
+ Color textColor = HexToColor(LIGHT_THEME_TEXT_COLOR);
248
+
249
+ roundedBG.color = bgColor;
250
+ safeAreaFillerBG.color = bgColor;
251
+
252
+ _scoreTextComponent.color = textColor;
253
+ _timerTextComponent.color = textColor;
254
+
255
+ pauseButtonImage.color = new Color(1, 1, 1, LIGHT_THEME_PAUSE_BUTTON_ALPHA);
256
+ pauseBarsImage.sprite = pauseBarsLight;
257
+ }
258
+
259
+ private void ShowConnectionLostStatus()
260
+ {
99
261
  // Show the connection lost status
100
262
  _gameManager.ShowConnectionLost();
101
263
  }
@@ -105,6 +267,24 @@ namespace Hyper.Internal
105
267
  scoreText.SetText(HyperRuntime.DataManager.CurrentScore.ToString("N0"));
106
268
  }
107
269
 
270
+ /// <summary>
271
+ /// Gets the LivesDisplay component if available.
272
+ /// Only available when ControlBarLayout is set to HeartsLeft.
273
+ /// </summary>
274
+ public LivesDisplay GetLivesDisplay()
275
+ {
276
+ return _livesDisplay;
277
+ }
278
+
279
+ /// <summary>
280
+ /// Gets the HealthBarDisplay component if available.
281
+ /// Only available when ControlBarLayout is set to HealthBarGreenLeft.
282
+ /// </summary>
283
+ public HealthBarDisplay GetHealthBarDisplay()
284
+ {
285
+ return _healthBarDisplay;
286
+ }
287
+
108
288
  Color HexToColor(string hex)
109
289
  {
110
290
  if (ColorUtility.TryParseHtmlString(hex, out Color color))
@@ -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