完成生命图标与得分记录
This commit is contained in:
@@ -43,15 +43,16 @@ bool HealthComponent::takeDamage(int damage_amount) {
|
||||
return true; // 造成伤害,返回true
|
||||
}
|
||||
|
||||
void HealthComponent::heal(int heal_amount) {
|
||||
int HealthComponent::heal(int heal_amount) {
|
||||
if (heal_amount <= 0 || !isAlive()) {
|
||||
return; // 不治疗或已经死亡
|
||||
return current_health_; // 不治疗或已经死亡
|
||||
}
|
||||
|
||||
current_health_ += heal_amount;
|
||||
current_health_ = std::min(max_health_, current_health_); // 防止超过最大生命值
|
||||
spdlog::debug("游戏对象 '{}' 治疗了 {} 点,当前生命值: {}/{}。",
|
||||
owner_ ? owner_->getName() : "Unknown", heal_amount, current_health_, max_health_);
|
||||
return current_health_;
|
||||
}
|
||||
|
||||
void HealthComponent::setInvincible(float duration)
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
* @return bool 如果成功造成伤害,则返回 true,否则返回 false。
|
||||
*/
|
||||
bool takeDamage(int damage_amount);
|
||||
void heal(int heal_amount); ///< @brief 治疗 GameObject,增加当前生命值(不超过最大生命值)。
|
||||
int heal(int heal_amount); ///< @brief 治疗 GameObject,增加当前生命值(不超过最大生命值),返回治疗后生命值
|
||||
|
||||
// --- Getters and Setters ---
|
||||
bool isAlive() const { return current_health_ > 0; } ///< @brief 检查 GameObject 是否存活(当前生命值大于 0)。
|
||||
|
||||
@@ -44,7 +44,7 @@ void TextRenderer::close()
|
||||
}
|
||||
|
||||
void TextRenderer::drawUIText(const std::string &text, const std::string &font_id, int font_size,
|
||||
const glm::vec2 &position, const SDL_FColor &color)
|
||||
const glm::vec2 &position, const engine::utils::FColor &color)
|
||||
{
|
||||
/* 构造函数已经保证了必要指针不会为空,这里不需要再检查 */
|
||||
TTF_Font* font = resource_manager_->getFont(font_id, font_size);
|
||||
@@ -77,7 +77,7 @@ void TextRenderer::drawUIText(const std::string &text, const std::string &font_i
|
||||
}
|
||||
|
||||
void TextRenderer::drawText(const Camera &camera, const std::string &text, const std::string &font_id, int font_size,
|
||||
const glm::vec2 &position, const SDL_FColor &color)
|
||||
const glm::vec2 &position, const engine::utils::FColor &color)
|
||||
{
|
||||
// 应用相机变换
|
||||
glm::vec2 position_screen = camera.worldToScreen(position);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <SDL3/SDL_render.h>
|
||||
#include <string>
|
||||
#include <glm/vec2.hpp>
|
||||
#include "../utils/math.h"
|
||||
|
||||
struct TTF_TextEngine;
|
||||
|
||||
@@ -48,7 +49,7 @@ public:
|
||||
* @param color 文本颜色。(默认为白色)
|
||||
*/
|
||||
void drawUIText(const std::string& text, const std::string& font_id, int font_size,
|
||||
const glm::vec2& position, const SDL_FColor& color = {1.0f, 1.0f, 1.0f, 1.0f});
|
||||
const glm::vec2& position, const engine::utils::FColor& color = {1.0f, 1.0f, 1.0f, 1.0f});
|
||||
|
||||
/**
|
||||
* @brief 绘制地图上的字符串。
|
||||
@@ -61,7 +62,7 @@ public:
|
||||
* @param color 文本颜色。
|
||||
*/
|
||||
void drawText(const Camera& camera, const std::string& text, const std::string& font_id, int font_size,
|
||||
const glm::vec2& position, const SDL_FColor& color = {1.0f, 1.0f, 1.0f, 1.0f});
|
||||
const glm::vec2& position, const engine::utils::FColor& color = {1.0f, 1.0f, 1.0f, 1.0f});
|
||||
|
||||
/**
|
||||
* @brief 获取文本的尺寸。
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "ui_image.h"
|
||||
#include "../render/renderer.h"
|
||||
#include "../render/sprite.h"
|
||||
#include "../core/context.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace engine::ui {
|
||||
|
||||
UIImage::UIImage(const std::string& texture_id,
|
||||
const glm::vec2& position,
|
||||
const glm::vec2& size,
|
||||
const std::optional<SDL_FRect>& source_rect,
|
||||
bool is_flipped)
|
||||
: UIElement(position, size),
|
||||
sprite_(texture_id, source_rect, is_flipped)
|
||||
{
|
||||
if (texture_id.empty()) {
|
||||
spdlog::warn("创建了一个空纹理ID的UIImage。");
|
||||
}
|
||||
spdlog::trace("UIImage 构造完成");
|
||||
}
|
||||
|
||||
void UIImage::render(engine::core::Context& context) {
|
||||
if (!visible_ || sprite_.getTextureId().empty()) {
|
||||
return; // 如果不可见或没有分配纹理则不渲染
|
||||
}
|
||||
|
||||
// 渲染自身
|
||||
auto position = getScreenPosition();
|
||||
if (size_.x == 0.0f && size_.y == 0.0f) { // 如果尺寸为0,则使用纹理的原始尺寸
|
||||
context.getRenderer().drawUISprite(sprite_, position);
|
||||
} else {
|
||||
context.getRenderer().drawUISprite(sprite_, position, size_);
|
||||
}
|
||||
|
||||
// 渲染子元素(调用基类方法)
|
||||
UIElement::render(context);
|
||||
}
|
||||
|
||||
} // namespace engine::ui
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include "ui_element.h"
|
||||
#include "../render/sprite.h"
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <SDL3/SDL_rect.h>
|
||||
|
||||
namespace engine::ui {
|
||||
|
||||
/**
|
||||
* @brief 一个用于显示纹理或部分纹理的UI元素。
|
||||
*
|
||||
* 继承自UIElement并添加了渲染图像的功能。
|
||||
*/
|
||||
class UIImage final : public UIElement {
|
||||
protected:
|
||||
engine::render::Sprite sprite_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 构造一个UIImage对象。
|
||||
*
|
||||
* @param texture_id 要显示的纹理ID。
|
||||
* @param position 图像的局部位置。
|
||||
* @param size 图像元素的大小。(如果为{0,0},则使用纹理的原始尺寸)
|
||||
* @param source_rect 可选:要绘制的纹理部分。(如果为空,则使用纹理的整个区域)
|
||||
* @param is_flipped 可选:精灵是否应该水平翻转。
|
||||
*/
|
||||
UIImage(const std::string& texture_id,
|
||||
const glm::vec2& position = {0.0f, 0.0f},
|
||||
const glm::vec2& size = {0.0f, 0.0f},
|
||||
const std::optional<SDL_FRect>& source_rect = std::nullopt,
|
||||
bool is_flipped = false);
|
||||
|
||||
// --- 核心方法 ---
|
||||
void render(engine::core::Context& context) override;
|
||||
|
||||
// --- Setters & Getters ---
|
||||
const engine::render::Sprite& getSprite() const { return sprite_; }
|
||||
void setSprite(const engine::render::Sprite& sprite) { sprite_ = sprite; }
|
||||
|
||||
const std::string& getTextureId() const { return sprite_.getTextureId(); }
|
||||
void setTextureId(const std::string& texture_id) { sprite_.setTextureId(texture_id); }
|
||||
|
||||
const std::optional<SDL_FRect>& getSourceRect() const { return sprite_.getSourceRect(); }
|
||||
void setSourceRect(const std::optional<SDL_FRect>& source_rect) { sprite_.setSourceRect(source_rect); }
|
||||
|
||||
bool isFlipped() const { return sprite_.isFlipped(); }
|
||||
void setFlipped(bool flipped) { sprite_.setFlipped(flipped); }
|
||||
};
|
||||
|
||||
} // namespace engine::ui
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "ui_label.h"
|
||||
#include "../core/context.h"
|
||||
#include "../render/text_renderer.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace engine::ui {
|
||||
|
||||
UILabel::UILabel(engine::render::TextRenderer& text_renderer,
|
||||
const std::string& text,
|
||||
const std::string& font_id,
|
||||
int font_size,
|
||||
const engine::utils::FColor& text_color,
|
||||
const glm::vec2& position)
|
||||
: UIElement(position),
|
||||
text_renderer_(text_renderer),
|
||||
text_(text),
|
||||
font_id_(font_id),
|
||||
font_size_(font_size),
|
||||
text_fcolor_(text_color) {
|
||||
// 获取文本渲染尺寸
|
||||
size_ = text_renderer_.getTextSize(text_, font_id_, font_size_);
|
||||
spdlog::trace("UILabel 构造完成");
|
||||
}
|
||||
|
||||
void UILabel::render(engine::core::Context& context) {
|
||||
if (!visible_ || text_.empty()) return;
|
||||
|
||||
text_renderer_.drawUIText(text_, font_id_, font_size_, getScreenPosition(), text_fcolor_);
|
||||
|
||||
// 渲染子元素(调用基类方法)
|
||||
UIElement::render(context);
|
||||
}
|
||||
|
||||
void UILabel::setText(const std::string &text)
|
||||
{
|
||||
text_ = text;
|
||||
size_ = text_renderer_.getTextSize(text_, font_id_, font_size_);
|
||||
}
|
||||
|
||||
void UILabel::setFontId(const std::string &font_id)
|
||||
{
|
||||
font_id_ = font_id;
|
||||
size_ = text_renderer_.getTextSize(text_, font_id_, font_size_);
|
||||
}
|
||||
|
||||
void UILabel::setFontSize(int font_size)
|
||||
{
|
||||
font_size_ = font_size;
|
||||
size_ = text_renderer_.getTextSize(text_, font_id_, font_size_);
|
||||
}
|
||||
|
||||
void UILabel::setTextFColor(const engine::utils::FColor &text_fcolor)
|
||||
{
|
||||
text_fcolor_ = text_fcolor;
|
||||
/* 颜色变化不影响尺寸 */
|
||||
}
|
||||
|
||||
} // namespace engine::ui
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include "ui_element.h"
|
||||
#include "../utils/math.h"
|
||||
#include "../render/text_renderer.h"
|
||||
|
||||
namespace engine::ui {
|
||||
|
||||
/**
|
||||
* @brief UILabel 类用于创建和管理用户界面中的文本标签
|
||||
*
|
||||
* UILabel 继承自 UIElement,提供了文本渲染功能。
|
||||
* 它可以设置文本内容、字体ID、字体大小和文本颜色。
|
||||
*
|
||||
* @note 需要一个文本渲染器来获取和更新文本尺寸。
|
||||
*/
|
||||
class UILabel final : public UIElement {
|
||||
private:
|
||||
engine::render::TextRenderer& text_renderer_; ///< @brief 需要文本渲染器,用于获取/更新文本尺寸
|
||||
|
||||
std::string text_; ///< @brief 文本内容
|
||||
std::string font_id_; ///< @brief 字体ID
|
||||
int font_size_; ///< @brief 字体大小
|
||||
engine::utils::FColor text_fcolor_ = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
/* 可添加其他内容,例如边框、底色 */
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 构造一个UILabel
|
||||
*
|
||||
* @param text_renderer 文本渲染器
|
||||
* @param text 文本内容
|
||||
* @param font_id 字体ID
|
||||
* @param font_size 字体大小
|
||||
* @param text_color 文本颜色
|
||||
*/
|
||||
UILabel(engine::render::TextRenderer& text_renderer,
|
||||
const std::string& text,
|
||||
const std::string& font_id,
|
||||
int font_size = 16,
|
||||
const engine::utils::FColor& text_color = {1.0f, 1.0f, 1.0f, 1.0f},
|
||||
const glm::vec2& position = {0.0f, 0.0f});
|
||||
|
||||
// --- 核心方法 ---
|
||||
void render(engine::core::Context& context) override;
|
||||
|
||||
// --- Setters & Getters ---
|
||||
const std::string& getText() const { return text_; }
|
||||
const std::string& getFontId() const { return font_id_; }
|
||||
int getFontSize() const { return font_size_; }
|
||||
const engine::utils::FColor& getTextFColor() const { return text_fcolor_; }
|
||||
|
||||
void setText(const std::string& text); ///< @brief 设置文本内容, 同时更新尺寸
|
||||
void setFontId(const std::string& font_id); ///< @brief 设置字体ID, 同时更新尺寸
|
||||
void setFontSize(int font_size); ///< @brief 设置字体大小, 同时更新尺寸
|
||||
void setTextFColor(const engine::utils::FColor& text_fcolor);
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace engine::ui
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "../../engine/audio/audio_player.h"
|
||||
#include "../../engine/ui/ui_manager.h"
|
||||
#include "../../engine/ui/ui_panel.h"
|
||||
#include "../../engine/ui/ui_label.h"
|
||||
#include "../../engine/ui/ui_image.h"
|
||||
#include "../../engine/utils/math.h"
|
||||
#include "../component/ai_component.h"
|
||||
#include "../component/ai/patrol_behavior.h"
|
||||
@@ -198,10 +200,8 @@ bool GameScene::initUI()
|
||||
{
|
||||
if (!ui_manager_->init(glm::vec2(640.0f, 360.0f))) return false;
|
||||
|
||||
// 创建一个透明的方形UIPanel
|
||||
ui_manager_->addElement(std::make_unique<engine::ui::UIPanel>(glm::vec2(100.0f, 100.0f),
|
||||
glm::vec2(200.0f, 200.0f),
|
||||
engine::utils::FColor{0.5f, 0.0f, 0.0f, 0.3f}));
|
||||
createScoreUI();
|
||||
createHealthUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -227,10 +227,10 @@ void GameScene::handleObjectCollisions()
|
||||
}
|
||||
// 处理玩家与"hazard"对象碰撞
|
||||
else if (obj1->getName() == "player" && obj2->getTag() == "hazard") {
|
||||
obj1->getComponent<game::component::PlayerComponent>()->takeDamage(1);
|
||||
handlePlayerDamage(1);
|
||||
spdlog::debug("玩家 {} 受到了 HAZARD 对象伤害", obj1->getName());
|
||||
} else if (obj2->getName() == "player" && obj1->getTag() == "hazard") {
|
||||
obj2->getComponent<game::component::PlayerComponent>()->takeDamage(1);
|
||||
handlePlayerDamage(1);
|
||||
spdlog::debug("玩家 {} 受到了 HAZARD 对象伤害", obj2->getName());
|
||||
}
|
||||
// 处理玩家与关底触发器碰撞
|
||||
@@ -269,8 +269,8 @@ void GameScene::handlePlayerDamage(int damage)
|
||||
spdlog::info("玩家 {} 死亡", player_->getName());
|
||||
// TODO: 可能的死亡逻辑处理
|
||||
}
|
||||
// 更新游戏数据(生命值)
|
||||
game_session_data_->setCurrentHealth(player_component->getHealthComponent()->getCurrentHealth());
|
||||
// 更新生命值及HealthUI
|
||||
updateHealthWithUI();
|
||||
}
|
||||
|
||||
void GameScene::playerVSEnemyCollision(engine::object::GameObject *player, engine::object::GameObject *enemy)
|
||||
@@ -301,7 +301,7 @@ void GameScene::playerVSEnemyCollision(engine::object::GameObject *player, engin
|
||||
// 播放音效 (此音效完全可以放在玩家的音频组件中,这里示例另一种用法:直接用AudioPlayer播放,传入文件路径)
|
||||
context_.getAudioPlayer().playSound("assets/audio/punch2a.mp3");
|
||||
// 加分
|
||||
game_session_data_->addScore(10);
|
||||
addScoreWithUI(10);
|
||||
}
|
||||
// 踩踏判断失败,玩家受伤
|
||||
else {
|
||||
@@ -310,12 +310,12 @@ void GameScene::playerVSEnemyCollision(engine::object::GameObject *player, engin
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::playerVSItemCollision(engine::object::GameObject * player, engine::object::GameObject * item)
|
||||
void GameScene::playerVSItemCollision(engine::object::GameObject*, engine::object::GameObject * item)
|
||||
{
|
||||
if (item->getName() == "fruit") {
|
||||
player->getComponent<engine::component::HealthComponent>()->heal(1); // 加血
|
||||
healWithUI(1); // 加血
|
||||
} else if (item->getName() == "gem") {
|
||||
game_session_data_->addScore(5);
|
||||
addScoreWithUI(5); // 加5分
|
||||
}
|
||||
item->setNeedRemove(true); // 标记道具为待删除状态
|
||||
auto item_aabb = item->getComponent<engine::component::ColliderComponent>()->getWorldAABB();
|
||||
@@ -368,4 +368,83 @@ void GameScene::createEffect(const glm::vec2& center_pos, const std::string &tag
|
||||
spdlog::debug("创建特效: {}", tag);
|
||||
}
|
||||
|
||||
void GameScene::createScoreUI() {
|
||||
// 创建得分标签
|
||||
auto score_text = "Score: " + std::to_string(game_session_data_->getCurrentScore());
|
||||
auto score_label = std::make_unique<engine::ui::UILabel>(context_.getTextRenderer(),
|
||||
score_text,
|
||||
"assets/fonts/VonwaonBitmap-16px.ttf",
|
||||
16);
|
||||
score_label_ = score_label.get(); // 成员变量赋值(获取裸指针)
|
||||
auto screen_size = ui_manager_->getRootElement()->getSize(); // 获取屏幕尺寸
|
||||
score_label_->setPosition(glm::vec2(screen_size.x - 100.0f, 10.0f));
|
||||
ui_manager_->addElement(std::move(score_label));
|
||||
}
|
||||
|
||||
void GameScene::createHealthUI() {
|
||||
int max_health = game_session_data_->getMaxHealth();
|
||||
int current_health = game_session_data_->getCurrentHealth();
|
||||
float start_x = 10.0f;
|
||||
float start_y = 10.0f;
|
||||
float icon_width = 20.0f;
|
||||
float icon_height = 18.0f;
|
||||
float spacing = 5.0f;
|
||||
std::string full_heart_tex = "assets/textures/UI/Heart.png";
|
||||
std::string empty_heart_tex = "assets/textures/UI/Heart-bg.png";
|
||||
|
||||
// 创建一个默认的UIPanel (不需要背景色,因此大小无所谓,只用于定位)
|
||||
auto health_panel = std::make_unique<engine::ui::UIPanel>();
|
||||
health_panel_ = health_panel.get(); // 成员变量赋值(获取裸指针)
|
||||
|
||||
// --- 根据最大生命值,循环创建生命值图标(添加到UIPanel中) ---
|
||||
for (int i = 0; i < max_health; ++i) { // 创建背景图标
|
||||
glm::vec2 icon_pos = {start_x + i * (icon_width + spacing), start_y};
|
||||
glm::vec2 icon_size = {icon_width, icon_height};
|
||||
|
||||
auto bg_icon = std::make_unique<engine::ui::UIImage>(empty_heart_tex, icon_pos, icon_size);
|
||||
health_panel_->addChild(std::move(bg_icon));
|
||||
}
|
||||
for (int i = 0; i < current_health; ++i) { // 创建前景图标
|
||||
glm::vec2 icon_pos = {start_x + i * (icon_width + spacing), start_y};
|
||||
glm::vec2 icon_size = {icon_width, icon_height};
|
||||
|
||||
auto fg_icon = std::make_unique<engine::ui::UIImage>(full_heart_tex, icon_pos, icon_size);
|
||||
health_panel_->addChild(std::move(fg_icon));
|
||||
}
|
||||
// 将UIPanel添加到UI管理器中
|
||||
ui_manager_->addElement(std::move(health_panel));
|
||||
}
|
||||
|
||||
void GameScene::updateHealthWithUI()
|
||||
{
|
||||
if (!player_ || !health_panel_) {
|
||||
spdlog::error("玩家对象或 HealthPanel 不存在,无法更新生命值UI");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前生命值并更新游戏数据
|
||||
auto current_health = player_->getComponent<engine::component::HealthComponent>()->getCurrentHealth();
|
||||
game_session_data_->setCurrentHealth(current_health);
|
||||
auto max_health = game_session_data_->getMaxHealth();
|
||||
|
||||
// 前景图标是后添加的,因此设置后半段的可见性即可
|
||||
for (auto i = max_health; i < max_health * 2; ++i) {
|
||||
health_panel_->getChildren()[i]->setVisible(i - max_health < current_health);
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::addScoreWithUI(int score)
|
||||
{
|
||||
game_session_data_->addScore(score);
|
||||
auto score_text = "Score: " + std::to_string(game_session_data_->getCurrentScore());
|
||||
spdlog::info("得分: {}", score_text);
|
||||
score_label_->setText(score_text);
|
||||
}
|
||||
|
||||
void GameScene::healWithUI(int amount)
|
||||
{
|
||||
player_->getComponent<engine::component::HealthComponent>()->heal(amount);
|
||||
updateHealthWithUI(); // 更新生命值与UI
|
||||
}
|
||||
|
||||
} // namespace game::scene
|
||||
@@ -12,6 +12,11 @@ namespace game::data {
|
||||
class SessionData;
|
||||
}
|
||||
|
||||
namespace engine::ui {
|
||||
class UILabel;
|
||||
class UIPanel;
|
||||
}
|
||||
|
||||
namespace game::scene {
|
||||
|
||||
/**
|
||||
@@ -19,7 +24,10 @@ namespace game::scene {
|
||||
*/
|
||||
class GameScene final: public engine::scene::Scene {
|
||||
std::shared_ptr<game::data::SessionData> game_session_data_; ///< @brief 场景间共享数据,因此用shared_ptr
|
||||
engine::object::GameObject* player_ = nullptr; ///< @brief 保存玩家对象的指针,方便访问
|
||||
engine::object::GameObject* player_ = nullptr; ///< @brief 保存玩家对象的指针,方便访问
|
||||
|
||||
engine::ui::UILabel* score_label_ = nullptr; ///< @brief 得分标签 (生命周期由UIManager管理,因此使用裸指针)
|
||||
engine::ui::UIPanel* health_panel_ = nullptr; ///< @brief 生命值图标面板
|
||||
|
||||
public:
|
||||
GameScene(engine::core::Context& context,
|
||||
@@ -56,6 +64,14 @@ private:
|
||||
* @param tag 特效标签(决定特效类型,例如"enemy","item")
|
||||
*/
|
||||
void createEffect(const glm::vec2& center_pos, const std::string& tag);
|
||||
|
||||
// --- UI 相关函数 ---
|
||||
void createScoreUI(); ///< @brief 创建得分UI
|
||||
void createHealthUI(); ///< @brief 创建生命值UI (或最大生命值改变时重设)
|
||||
void addScoreWithUI(int score); ///< @brief 增加得分,同时更新UI
|
||||
void healWithUI(int amount); ///< @brief 增加生命,同时更新UI
|
||||
void updateHealthWithUI(); ///< @brief 更新生命值UI (只适用最大生命值不变的情况)
|
||||
|
||||
};
|
||||
|
||||
} // namespace game::scene
|
||||
Reference in New Issue
Block a user