33#include < algorithm>
44#include < application.hpp>
55#include < cmath>
6- #include < components/camera.hpp>
76#include < ecs/world.hpp>
87#include < glm/common.hpp>
98#include < glm/geometric.hpp>
10- #include < glm/gtc/matrix_transform.hpp>
119#include < glm/vec2.hpp>
1210#include < json/json.hpp>
13- #include < material/material.hpp>
14- #include < mesh/mesh.hpp>
15- #include < shader/shader.hpp>
16- #include < vector>
11+ #include < systems/ui-renderer.hpp>
1712
1813#include " ../components/collider.hpp"
1914#include " ../components/enemy.hpp"
@@ -24,14 +19,9 @@ namespace gameplay {
2419 class EnemyHealthBarSystem {
2520 struct Config {
2621 bool enabled = true ;
27- bool showWhenDamagedOnly = true ;
2822 float maxDistance = 24 .0f ;
2923 } config;
3024
31- our::Mesh* rectangle = nullptr ;
32- our::ShaderProgram* shader = nullptr ;
33- our::TintedMaterial* material = nullptr ;
34-
3525 static glm::vec4 healthColor (float ratio) {
3626 ratio = glm::clamp (ratio, 0 .0f , 1 .0f );
3727 glm::vec3 low = {0 .84f , 0 .18f , 0 .24f };
@@ -53,33 +43,6 @@ namespace gameplay {
5343 return std::max (colliderHeight, scaleHeight);
5444 }
5545
56- static our::CameraComponent* findCamera (our::World* world) {
57- for (our::Entity* entity : world->getEntities ())
58- if (auto * camera = entity->getComponent <our::CameraComponent>(); camera) return camera;
59-
60- return nullptr ;
61- }
62-
63- static our::Vertex makeVertex (const glm::vec3& position, const glm::vec2& texCoord) {
64- our::Vertex vertex{};
65- vertex.position = position;
66- vertex.color = {255 , 255 , 255 , 255 };
67- vertex.tex_coord = texCoord;
68- vertex.normal = {0 .0f , 0 .0f , 1 .0f };
69- return vertex;
70- }
71-
72- void drawRect (const glm::mat4& projection, const glm::vec2& position, const glm::vec2& size,
73- const glm::vec4& tint) const {
74- if (!(rectangle && material && shader) || size.x <= 0 .0f || size.y <= 0 .0f || tint.a <= 0 .0f ) return ;
75-
76- material->tint = tint;
77- material->setup ();
78- shader->set (" transform" , projection * glm::translate (glm::mat4 (1 .0f ), glm::vec3 (position, 0 .0f )) *
79- glm::scale (glm::mat4 (1 .0f ), glm::vec3 (size, 1 .0f )));
80- rectangle->draw ();
81- }
82-
8346 public:
8447 void deserialize (const nlohmann::json& sceneConfig) {
8548 if (!(sceneConfig.contains (" game" ) && sceneConfig[" game" ].contains (" enemyHealthBars" ))) return ;
@@ -89,39 +52,14 @@ namespace gameplay {
8952
9053 config = {};
9154 config.enabled = healthBarConfig.value (" enabled" , config.enabled );
92- config.showWhenDamagedOnly = healthBarConfig.value (" showWhenDamagedOnly" , config.showWhenDamagedOnly );
9355 config.maxDistance = healthBarConfig.value (" maxDistance" , config.maxDistance );
56+ config.maxDistance = std::max (0 .0f , config.maxDistance );
9457 }
9558
96- void initialize () {
97- shader = new our::ShaderProgram ();
98- shader->attach (" assets/shaders/tinted.vert" , GL_VERTEX_SHADER );
99- shader->attach (" assets/shaders/tinted.frag" , GL_FRAGMENT_SHADER );
100- shader->link ();
101-
102- material = new our::TintedMaterial ();
103- material->shader = shader;
104- material->pipelineState .blending .enabled = true ;
105- material->pipelineState .blending .sourceFactor = GL_SRC_ALPHA ;
106- material->pipelineState .blending .destinationFactor = GL_ONE_MINUS_SRC_ALPHA ;
107- material->pipelineState .depthTesting .enabled = false ;
108- material->pipelineState .faceCulling .enabled = false ;
109- material->pipelineState .depthMask = false ;
110-
111- std::vector<our::Vertex> vertices = {
112- makeVertex ({0 .0f , 0 .0f , 0 .0f }, {0 .0f , 1 .0f }),
113- makeVertex ({1 .0f , 0 .0f , 0 .0f }, {1 .0f , 1 .0f }),
114- makeVertex ({1 .0f , 1 .0f , 0 .0f }, {1 .0f , 0 .0f }),
115- makeVertex ({0 .0f , 1 .0f , 0 .0f }, {0 .0f , 0 .0f }),
116- };
117- std::vector<unsigned int > elements = {0 , 1 , 2 , 2 , 3 , 0 };
118- rectangle = new our::Mesh (vertices, elements);
119- }
120-
121- void render (our::World* world, our::Application* app) const {
122- if (!(config.enabled && world && app && rectangle && material && shader)) return ;
59+ void render (our::World* world, our::Application* app, const our::UIRenderer& ui) const {
60+ if (!(config.enabled && world && app)) return ;
12361
124- our::CameraComponent* camera = findCamera (world);
62+ our::CameraComponent* camera = our::UIRenderer::findActiveCamera (world);
12563 if (!camera) return ;
12664
12765 glm::ivec2 framebufferSize = app->getFrameBufferSize ();
@@ -130,72 +68,61 @@ namespace gameplay {
13068 glm::mat4 view = camera->getViewMatrix ();
13169 glm::mat4 proj = camera->getProjectionMatrix (framebufferSize);
13270 glm::mat4 viewProj = proj * view;
133- glm::mat4 overlayProj = glm::ortho (0 .0f , static_cast <float >(framebufferSize.x ),
134- static_cast <float >(framebufferSize.y ), 0 .0f , 1 .0f , -1 .0f );
71+ glm::mat4 overlayProj = ui.overlayProjection (framebufferSize);
13572 glm::vec3 cameraPos = glm::vec3 (camera->getOwner ()->getLocalToWorldMatrix () * glm::vec4 (0 , 0 , 0 , 1 ));
13673
13774 for (our::Entity* entity : world->getEntities ()) {
13875 auto * health = entity->getComponent <HealthComponent>();
13976 if (!(entity->getComponent <EnemyComponent>() && health) || health->isDead || health->maxHealth <= 0 .0f )
14077 continue ;
14178
142- float healthRatio = glm::clamp (health->currentHealth / health->maxHealth , 0 .0f , 1 .0f );
143- if (config.showWhenDamagedOnly && healthRatio >= 0 .999f ) continue ;
144-
79+ float healthRatio = health->getHealthRatio ();
14580 glm::mat4 localToWorld = entity->getLocalToWorldMatrix ();
14681 glm::vec3 enemyPosition = glm::vec3 (localToWorld * glm::vec4 (0 , 0 , 0 , 1 ));
14782 float distance = glm::distance (enemyPosition, cameraPos);
148- if (distance > config.maxDistance ) continue ;
14983
150- ColliderComponent* collider = entity-> getComponent <ColliderComponent>() ;
151- float barHeightOffset = estimateBarHeight (entity, collider) + 0.35 ;
84+ bool visibleWhenDamaged = health-> damageRevealTimer > 0 . 0f ;
85+ bool visibleWhenClose = distance <= 8 . 0f ;
15286
153- glm::vec4 clipSpacePosition = viewProj * localToWorld * glm::vec4 ( 0 . 0f , barHeightOffset, 0 . 0f , 1 . 0f ) ;
154- if (clipSpacePosition. w <= 0 . 0f ) continue ;
87+ if (!visibleWhenDamaged && distance > config. maxDistance ) continue ;
88+ if (!(visibleWhenDamaged || visibleWhenClose) ) continue ;
15589
156- glm::vec3 ndc = glm::vec3 (clipSpacePosition) / clipSpacePosition.w ;
157- if (ndc.z < -1 .0f || ndc.z > 1 .0f || ndc.x < -1 .1f || ndc.x > 1 .1f || ndc.y < -1 .1f || ndc.y > 1 .1f )
158- continue ;
90+ ColliderComponent* collider = entity->getComponent <ColliderComponent>();
91+ float barHeightOffset = estimateBarHeight (entity, collider) + 0 .35f ;
92+
93+ glm::vec3 barWorldPos = glm::vec3 (localToWorld * glm::vec4 (0 .0f , barHeightOffset, 0 .0f , 1 .0f ));
94+ our::ScreenPoint screen = our::UIRenderer::worldToScreen (barWorldPos, viewProj, framebufferSize);
95+ if (!screen.visible ) continue ;
15996
16097 float dist = glm::clamp ((distance - 6 .0f ) / glm::max (1 .0f , config.maxDistance - 6 .0f ), 0 .0f , 1 .0f );
161- float alpha = 1 .0f - glm::smoothstep (config.maxDistance * 0 .7f , config.maxDistance , distance);
98+ float alpha = visibleWhenDamaged
99+ ? 1 .0f
100+ : 1 .0f - glm::smoothstep (config.maxDistance * 0 .7f , config.maxDistance , distance);
162101 if (alpha <= 0 .0f ) continue ;
163102
164103 float barWidth = glm::mix (86 .0f , 48 .0f , dist);
165104 float barHeight = glm::mix (10 .0f , 6 .0f , dist);
166- glm::vec2 screenCenter = {
167- (ndc.x * 0 .5f + 0 .5f ) * framebufferSize.x ,
168- (1 .0f - (ndc.y * 0 .5f + 0 .5f )) * framebufferSize.y ,
169- };
170105
171- glm::vec2 barPosition = {screenCenter. x - barWidth * 0 .5f , screenCenter .y - barHeight};
106+ glm::vec2 barPosition = {screen. position . x - barWidth * 0 .5f , screen. position .y - barHeight};
172107 glm::vec2 outerSize = {barWidth + 2 .0f , barHeight + 2 .0f };
173108 glm::vec2 innerPosition = barPosition + glm::vec2 (2 .0f , 2 .0f );
174109 glm::vec2 innerSize = {barWidth - 4 .0f , barHeight - 4 .0f };
175110 glm::vec2 fillSize = {innerSize.x * healthRatio, innerSize.y };
176111
177- drawRect (overlayProj, barPosition - glm::vec2 (1 .0f ), outerSize, {0 .02f , 0 .03f , 0 .05f , 0 .92f * alpha});
178- drawRect (overlayProj, barPosition, {barWidth, barHeight}, {0 .12f , 0 .14f , 0 .18f , 0 .88f * alpha});
112+ ui.drawRect (overlayProj, barPosition - glm::vec2 (1 .0f ), outerSize,
113+ {0 .02f , 0 .03f , 0 .05f , 0 .92f * alpha});
114+ ui.drawRect (overlayProj, barPosition, {barWidth, barHeight}, {0 .12f , 0 .14f , 0 .18f , 0 .88f * alpha});
179115
180116 glm::vec4 fillColor = healthColor (healthRatio);
181117 fillColor.a = 0 .96f * alpha;
182- drawRect (overlayProj, innerPosition, fillSize, fillColor);
118+ ui. drawRect (overlayProj, innerPosition, fillSize, fillColor);
183119
184120 if (fillSize.x > 6 .0f ) {
185- drawRect (overlayProj, innerPosition + glm::vec2 (0 .0f , 1 .0f ),
186- {fillSize.x , std::max (1 .0f , innerSize.y * 0 .28f )}, {1 .0f , 1 .0f , 1 .0f , 0 .12f * alpha});
121+ ui. drawRect (overlayProj, innerPosition + glm::vec2 (0 .0f , 1 .0f ),
122+ {fillSize.x , std::max (1 .0f , innerSize.y * 0 .28f )}, {1 .0f , 1 .0f , 1 .0f , 0 .12f * alpha});
187123 }
188124 }
189125 }
190-
191- void destroy () {
192- delete rectangle;
193- delete material;
194- delete shader;
195- rectangle = nullptr ;
196- material = nullptr ;
197- shader = nullptr ;
198- }
199126 };
200127
201128} // namespace gameplay
0 commit comments