Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
HealthComponent.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <cstdio>
6#include <cstring>
7
8#include <GLFW/glfw3.h>
9
10#include "Engine.h"
12#include "scene/GameObject.h"
13#include "scene/Scene.h"
14#include "scene/components/CameraComponent.h"
15
16namespace mnd
17{
18
19namespace
20{
21vec4 LerpColor(const vec4 &a, const vec4 &b, f32 t)
22{
23 t = std::clamp(t, 0.0F, 1.0F);
24 return a * (1.0F - t) + b * t;
25}
26} // namespace
27
29{
30 const f32 target = m_max > 0 ? static_cast<f32>(m_current) / static_cast<f32>(m_max) : 0.0F;
31 const f32 t = 1.0F - std::exp(-m_barLerpSpeed * std::max(deltaTime, 0.0F));
32 m_displayedFill = std::clamp(m_displayedFill + (target - m_displayedFill) * t, 0.0F, 1.0F);
33
34 DrawHealthBar();
35}
36
37void HealthComponent::DrawHealthBar()
38{
39 if (!m_showBar || m_owner == nullptr)
40 {
41 return;
42 }
43 if (m_hideAtFull && m_current >= m_max && m_displayedFill >= 0.999F)
44 {
45 return;
46 }
47
48 auto &engine = Engine::GetInstance();
49 auto *window = engine.GetWindow();
50 auto *scene = engine.GetScene();
51 if (window == nullptr || scene == nullptr)
52 {
53 return;
54 }
55
56 auto *cameraObject = scene->GetMainCamera();
57 if (cameraObject == nullptr)
58 {
59 return;
60 }
61 auto *camera = cameraObject->GetComponent<CameraComponent>();
62 if (camera == nullptr)
63 {
64 return;
65 }
66
67 int winW = 0;
68 int winH = 0;
69 glfwGetFramebufferSize(window, &winW, &winH);
70 if (winW <= 0 || winH <= 0)
71 {
72 return;
73 }
74
75 const f32 aspect = static_cast<f32>(winW) / static_cast<f32>(winH);
76 const mat4 view = camera->GetViewMatrix();
77 const mat4 proj = camera->GetProjectionMatrix(aspect);
78
79 const vec3 worldPos = m_owner->GetWorldPosition() + vec3(0.0F, m_barOffsetY, 0.0F);
80 const vec4 clip = proj * view * vec4(worldPos, 1.0F);
81 if (clip.w <= 0.0F)
82 {
83 return;
84 }
85
86 const vec3 ndc = vec3(clip) / clip.w;
87 if (ndc.z < -1.0F || ndc.z > 1.0F)
88 {
89 return;
90 }
91
92 const f32 screenX = (ndc.x * 0.5F + 0.5F) * static_cast<f32>(winW);
93 const f32 screenY = (1.0F - (ndc.y * 0.5F + 0.5F)) * static_cast<f32>(winH);
94
95 // Perspective-correct screen width: project a world-space delta vector onto the screen.
96 // For a perspective projection, a horizontal world-space length L at clip-space depth w
97 // maps to L * proj[0][0] * (winW * 0.5) / w pixels.
98 const f32 fovScale = proj[0][0];
99 const f32 rawPixelWide = m_barWorldWidth * fovScale * (static_cast<f32>(winW) * 0.5F) / clip.w;
100 const f32 width = std::clamp(rawPixelWide, m_minScreenWidth, m_maxScreenWidth);
101 const f32 height = std::max(8.0F, width * m_barAspect);
102 const f32 border = std::max(1.0F, std::round(height * 0.18F));
103 const f32 shadowOffset = std::max(2.0F, std::round(height * 0.32F));
104
105 const f32 halfW = width * 0.5F;
106 const f32 halfH = height * 0.5F;
107
108 auto &sprites = engine.GetSpriteRenderer();
109
110 // Drop shadow.
111 sprites.DrawRect(vec2(screenX - halfW - border + shadowOffset * 0.5F,
112 screenY - halfH - border + shadowOffset),
113 vec2(width + border * 2.0F, height + border * 2.0F),
114 m_shadowColor);
115
116 // Outer border.
117 sprites.DrawRect(vec2(screenX - halfW - border, screenY - halfH - border),
118 vec2(width + border * 2.0F, height + border * 2.0F),
119 m_borderColor);
120
121 // Background — split into top/bottom halves for a subtle vertical gradient.
122 const vec2 bgPos(screenX - halfW, screenY - halfH);
123 sprites.DrawRect(bgPos, vec2(width, height * 0.5F), m_bgColorTop);
124 sprites.DrawRect(vec2(bgPos.x, bgPos.y + height * 0.5F), vec2(width, height * 0.5F), m_bgColorBottom);
125
126 // Fill.
127 const f32 fill = std::clamp(m_displayedFill, 0.0F, 1.0F);
128 if (fill > 0.0F)
129 {
130 const vec4 fillColor = (fill > 0.5F)
131 ? LerpColor(m_fillColorMid, m_fillColorHigh, (fill - 0.5F) * 2.0F)
132 : LerpColor(m_fillColorLow, m_fillColorMid, fill * 2.0F);
133
134 const f32 fillW = width * fill;
135 const vec4 fillBottom = fillColor * vec4(0.75F, 0.75F, 0.75F, 1.0F);
136 sprites.DrawRect(bgPos, vec2(fillW, height * 0.5F), fillColor);
137 sprites.DrawRect(vec2(bgPos.x, bgPos.y + height * 0.5F),
138 vec2(fillW, height * 0.5F),
139 vec4(fillBottom.x, fillBottom.y, fillBottom.z, fillColor.w));
140
141 // Gloss highlight on the upper third of the fill.
142 sprites.DrawRect(vec2(bgPos.x, bgPos.y + height * 0.10F),
143 vec2(fillW, height * 0.28F),
144 m_glossColor);
145 }
146
147 // Numeric label centered on the bar.
148 if (m_showText)
149 {
150 char buf[32];
151 std::snprintf(buf, sizeof(buf), "%d / %d", m_current, m_max);
152 const f32 textHeight = std::clamp(height * 0.85F, 10.0F, 28.0F);
153 const f32 approxCharW = textHeight * 0.5F;
154 const f32 textWidth = approxCharW * static_cast<f32>(std::strlen(buf));
155 const f32 textX = screenX - textWidth * 0.5F;
156 const f32 textY = screenY - textHeight * 0.5F;
157 sprites.DrawText(buf, vec2(textX + 1.0F, textY + 1.0F), textHeight, m_textShadowColor);
158 sprites.DrawText(buf, vec2(textX, textY), textHeight, m_textColor);
159 }
160}
161
162void HealthComponent::LoadProperties(const nlohmann::json &json)
163{
164 if (json.contains("max"))
165 {
166 m_max = json["max"].get<int>();
167 }
168 m_current = json.value("current", m_max);
169 m_current = std::clamp(m_current, 0, m_max);
170
171 m_invulnerable = json.value("invulnerable", false);
172 m_destroyOnDeath = json.value("destroyOnDeath", false);
173
174 m_showBar = json.value("showBar", m_showBar);
175 m_hideAtFull = json.value("hideAtFull", m_hideAtFull);
176 m_showText = json.value("showText", m_showText);
177 m_barOffsetY = json.value("barOffsetY", m_barOffsetY);
178 m_barWorldWidth = json.value("barWorldWidth", m_barWorldWidth);
179 m_barAspect = json.value("barAspect", m_barAspect);
180 m_minScreenWidth = json.value("minScreenWidth", m_minScreenWidth);
181 m_maxScreenWidth = json.value("maxScreenWidth", m_maxScreenWidth);
182 m_barLerpSpeed = json.value("barLerpSpeed", m_barLerpSpeed);
183
184 m_displayedFill = m_max > 0 ? static_cast<f32>(m_current) / static_cast<f32>(m_max) : 0.0F;
185}
186
188{
189 m_max = std::max(1, hp);
190 m_current = std::min(m_current, m_max);
191}
192
194{
195 m_current = std::clamp(hp, 0, m_max);
196}
197
198void HealthComponent::Heal(int amount)
199{
200 if (IsDead() || amount <= 0)
201 {
202 return;
203 }
204 m_current = std::min(m_max, m_current + amount);
205}
206
208{
209 m_current = m_max;
210}
211
213{
214 if (m_invulnerable || IsDead() || info.amount <= 0.0F)
215 {
216 return;
217 }
218
219 const int dmg = std::max(1, static_cast<int>(info.amount));
220 m_current = std::max(0, m_current - dmg);
221
222 if (m_onDamage)
223 {
224 m_onDamage(info);
225 }
226
227 if (m_current == 0)
228 {
229 if (m_onDeath)
230 {
231 m_onDeath(info);
232 }
233 if (m_destroyOnDeath && m_owner != nullptr)
234 {
236 }
237 }
238}
239
240} // namespace mnd
Universal HP sink: damageable + killable game objects.
GameObject * m_owner
Definition Component.h:57
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
Definition Engine.cpp:59
vec3 GetWorldPosition() const
Extract the world-space position from GetWorldTransform().
void MarkForDestroy()
Flag this object for removal at end of frame.
void Update(f32 deltaTime) override
void LoadProperties(const nlohmann::json &json) override
void ApplyDamage(const DamageInfo &info)
Apply damage. Routed through DamageSystem::Apply, do not call directly.
void Revive()
Reset to full HP and clear dead state.
void Heal(int amount)
Heal up to max. No-op if dead (use Revive for that).
glm::vec2 vec2
2-component float vector (e.g. UV coordinates, mouse position).
Definition Types.h:50
glm::mat4 mat4
4×4 column-major float matrix (model/view/projection).
Definition Types.h:63
glm::vec4 vec4
4-component float vector (e.g. RGBA colour, homogeneous coords).
Definition Types.h:52
glm::vec3 vec3
3-component float vector (e.g. world position, RGB colour, normals).
Definition Types.h:51
float f32
32-bit IEEE float — the standard GL scalar type.
Definition Types.h:40