diff --git a/src/saland/Game.cpp b/src/saland/Game.cpp index 4b78efb..7319e86 100644 --- a/src/saland/Game.cpp +++ b/src/saland/Game.cpp @@ -405,14 +405,17 @@ void Game::Draw(SDL_Renderer* target) { MiscItem* m = dynamic_cast (p.get()); if (m) { DrawMiscEntity(target, globalData.spriteHolder.get(), m, SDL_GetTicks(), data->topx, data->topy, globalData.debugDrawCollision, &globalData.logicalResize); + DrawDamageNumbers(target, m, data->topx, data->topy, &globalData.logicalResize); } Human* h = dynamic_cast (p.get()); if (h) { DrawHumanEntity(target, globalData.spriteHolder.get(), h, SDL_GetTicks(), data->topx, data->topy, globalData.debugDrawCollision, &globalData.logicalResize); + DrawDamageNumbers(target, h, data->topx, data->topy, &globalData.logicalResize); } Monster* monster = dynamic_cast (p.get()); if (monster) { DrawMonster(target, globalData.spriteHolder.get(), monster, SDL_GetTicks(), data->topx, data->topy, globalData.debugDrawCollision, &globalData.logicalResize); + DrawDamageNumbers(target, monster, data->topx, data->topy, &globalData.logicalResize); } Projectile* projectile = dynamic_cast (p.get()); if (projectile) { @@ -613,10 +616,13 @@ void Game::Update() { data->human->moveX = deltaX; data->human->moveY = deltaY; UpdateHuman(data->human.get(), deltaTime); + UpdateDamageNumbers(data->human.get()); for (std::shared_ptr& entity : data->gameRegion.placeables) { if (entity->removeMe) { continue; } + // Update damage numbers for all entities + UpdateDamageNumbers(entity.get()); Projectile* projectile = dynamic_cast (entity.get()); if (projectile) { UpdateProjectile(projectile, deltaTime); diff --git a/src/saland/GameDraw.cpp b/src/saland/GameDraw.cpp index fc9081e..4b982ed 100644 --- a/src/saland/GameDraw.cpp +++ b/src/saland/GameDraw.cpp @@ -322,4 +322,55 @@ void DrawRectWhite(SDL_Renderer* target, int topx, int topy, int height, int wid void DrawRectYellow(SDL_Renderer* target, int topx, int topy, int height, int width, sago::SagoLogicalResize* resize) { std::string name = "ui_rect_yellow_"; DrawRect(target, topx, topy, height, width, name, resize); +} + +void DrawDamageNumbers(SDL_Renderer* target, Placeable* entity, int offsetX, int offsetY, sago::SagoLogicalResize* resize) { + Uint32 currentTime = SDL_GetTicks(); + + for (const auto& dmg : entity->damageNumbers) { + Uint32 age = currentTime - dmg.createdAt; + if (age >= dmg.duration) { + continue; // Skip expired damage numbers + } + + // Calculate position (move upward over time) + float progress = static_cast(age) / dmg.duration; + float yOffset = progress * 30.0f; // Move up 30 pixels over lifetime + int drawX = static_cast(dmg.X) - offsetX; + int drawY = static_cast(dmg.Y - yOffset) - offsetY; + + // Calculate alpha (fade out) + Uint8 alpha = static_cast(255 * (1.0f - progress)); + + // Draw the damage number + sago::SagoTextField textField; + textField.SetHolder(globalData.dataHolder); + textField.SetFont("freeserif"); + textField.SetFontSize(20); + + // Red color with fading alpha + SDL_Color textColor = {255, 50, 50, alpha}; + SDL_Color outlineColor = {0, 0, 0, alpha}; + textField.SetColor(textColor); + textField.SetOutline(2, outlineColor); + + // Format damage as integer + int damageInt = static_cast(dmg.damage + 0.5f); + textField.SetText(std::to_string(damageInt)); + + textField.Draw(target, drawX, drawY, sago::SagoTextField::Alignment::center, sago::SagoTextField::VerticalAlignment::center, resize); + } +} + +void UpdateDamageNumbers(Placeable* entity) { + Uint32 currentTime = SDL_GetTicks(); + + // Remove expired damage numbers + entity->damageNumbers.erase( + std::remove_if(entity->damageNumbers.begin(), entity->damageNumbers.end(), + [currentTime](const DamageNumber& dmg) { + return (currentTime - dmg.createdAt) >= dmg.duration; + }), + entity->damageNumbers.end() + ); } \ No newline at end of file diff --git a/src/saland/GameDraw.hpp b/src/saland/GameDraw.hpp index 3d2f025..9368667 100644 --- a/src/saland/GameDraw.hpp +++ b/src/saland/GameDraw.hpp @@ -41,6 +41,8 @@ void DrawProjectile(SDL_Renderer* target, sago::SagoSpriteHolder* sHolder, const void DrawRectWhite(SDL_Renderer* target, int topx, int topy, int height, int width, sago::SagoLogicalResize* resize = nullptr); void DrawRectYellow(SDL_Renderer* target, int topx, int topy, int height, int width, sago::SagoLogicalResize* resize = nullptr); void DrawTile(SDL_Renderer* renderer, sago::SagoSpriteHolder* sHolder, const sago::tiled::TileMap& tm, uint32_t gid, int x, int y, sago::SagoLogicalResize* resize = nullptr); +void DrawDamageNumbers(SDL_Renderer* target, Placeable* entity, int offsetX, int offsetY, sago::SagoLogicalResize* resize = nullptr); +void UpdateDamageNumbers(Placeable* entity); #endif /* GAMEDRAW_HPP */ diff --git a/src/saland/GameUpdates.cpp b/src/saland/GameUpdates.cpp index c1cb974..47a406c 100644 --- a/src/saland/GameUpdates.cpp +++ b/src/saland/GameUpdates.cpp @@ -128,7 +128,16 @@ void UpdateProjectile(Projectile* entity, float fDeltaTime) { void ProjectileHit(Projectile* p, Placeable* target) { //Monster* monster = dynamic_cast (target); if (target->destructible) { - target->health -= p->damage.getDamage(); + float damageAmount = p->damage.getDamage(); + target->health -= damageAmount; p->removeMe = true; + + // Create damage number + DamageNumber dmg; + dmg.X = target->X; + dmg.Y = target->Y - target->Radius; // Above the entity + dmg.damage = damageAmount; + dmg.createdAt = SDL_GetTicks(); + target->damageNumbers.push_back(dmg); } } \ No newline at end of file diff --git a/src/saland/model/placeables.hpp b/src/saland/model/placeables.hpp index 627014a..51b23cb 100644 --- a/src/saland/model/placeables.hpp +++ b/src/saland/model/placeables.hpp @@ -27,9 +27,19 @@ https://github.com/sago007/saland #include #include #include +#include +#include const float pixel2unit = 32.0f; +struct DamageNumber { + float X = 0.0f; + float Y = 0.0f; + float damage = 0.0f; + uint32_t createdAt = 0; + uint32_t duration = 1000; // milliseconds +}; + class Damage { public: @@ -53,6 +63,7 @@ public: bool removeMe = false; bool destructible = true; b2Body* body = nullptr; + std::vector damageNumbers; virtual bool isStatic() { return true; }