12#include "scene/GameObject.h"
13#include "scene/Scene.h"
14#include "scene/components/CameraComponent.h"
23 t = std::clamp(t, 0.0F, 1.0F);
24 return a * (1.0F - t) + b * t;
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);
37void HealthComponent::DrawHealthBar()
39 if (!m_showBar ||
m_owner ==
nullptr)
43 if (m_hideAtFull && m_current >= m_max && m_displayedFill >= 0.999F)
49 auto *window = engine.GetWindow();
50 auto *scene = engine.GetScene();
51 if (window ==
nullptr || scene ==
nullptr)
56 auto *cameraObject = scene->GetMainCamera();
57 if (cameraObject ==
nullptr)
61 auto *camera = cameraObject->GetComponent<CameraComponent>();
62 if (camera ==
nullptr)
69 glfwGetFramebufferSize(window, &winW, &winH);
70 if (winW <= 0 || winH <= 0)
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);
80 const vec4 clip = proj * view *
vec4(worldPos, 1.0F);
86 const vec3 ndc =
vec3(clip) / clip.w;
87 if (ndc.z < -1.0F || ndc.z > 1.0F)
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);
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));
105 const f32 halfW = width * 0.5F;
106 const f32 halfH = height * 0.5F;
108 auto &sprites = engine.GetSpriteRenderer();
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),
117 sprites.DrawRect(
vec2(screenX - halfW - border, screenY - halfH - border),
118 vec2(width + border * 2.0F, height + border * 2.0F),
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);
127 const f32 fill = std::clamp(m_displayedFill, 0.0F, 1.0F);
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);
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));
142 sprites.DrawRect(
vec2(bgPos.x, bgPos.y + height * 0.10F),
143 vec2(fillW, height * 0.28F),
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);
164 if (json.contains(
"max"))
166 m_max = json[
"max"].get<
int>();
168 m_current = json.value(
"current", m_max);
169 m_current = std::clamp(m_current, 0, m_max);
171 m_invulnerable = json.value(
"invulnerable",
false);
172 m_destroyOnDeath = json.value(
"destroyOnDeath",
false);
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);
184 m_displayedFill = m_max > 0 ?
static_cast<f32>(m_current) /
static_cast<f32>(m_max) : 0.0F;
189 m_max = std::max(1, hp);
190 m_current = std::min(m_current, m_max);
195 m_current = std::clamp(hp, 0, m_max);
200 if (
IsDead() || amount <= 0)
204 m_current = std::min(m_max, m_current + amount);
219 const int dmg = std::max(1,
static_cast<int>(info.
amount));
220 m_current = std::max(0, m_current - dmg);
233 if (m_destroyOnDeath &&
m_owner !=
nullptr)
Universal HP sink: damageable + killable game objects.
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
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).
glm::mat4 mat4
4×4 column-major float matrix (model/view/projection).
glm::vec4 vec4
4-component float vector (e.g. RGBA colour, homogeneous coords).
glm::vec3 vec3
3-component float vector (e.g. world position, RGB colour, normals).
float f32
32-bit IEEE float — the standard GL scalar type.