feat(字体渲染): 添加字体管理和文本渲染功能

- 新增FontManager类用于管理字体资源
- 添加TextSprite类实现文本渲染功能
- 更新构建配置添加SDL2_ttf依赖
- 添加多种字体资源文件
- 修改窗口和相机初始化逻辑
- 更新Switch平台构建配置
This commit is contained in:
2026-03-29 11:46:25 +08:00
parent 890211431e
commit dc41475b87
16 changed files with 349 additions and 8 deletions

View File

@@ -0,0 +1,49 @@
#pragma once
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/types/type_color.h>
#include <frostbite2D/types/type_math.h>
#include <string>
namespace frostbite2D {
class TextSprite : public Sprite {
public:
enum class Align {
Left,
Center,
Right
};
TextSprite();
~TextSprite();
static Ptr<TextSprite> create();
static Ptr<TextSprite> create(const std::string& text, const std::string& fontName);
void SetText(const std::string& text);
const std::string& GetText() const { return text_; }
void SetFont(const std::string& fontName);
const std::string& GetFontName() const { return fontName_; }
void SetTextColor(const Color& color);
void SetTextColor(float r, float g, float b, float a = 1.0f);
const Color& GetTextColor() const { return textColor_; }
void SetAlign(Align align);
Align GetAlign() const { return align_; }
void RenderText();
Vec2 GetTextSize() const;
private:
void updateTexture();
std::string text_;
std::string fontName_;
Color textColor_ = Colors::White;
Align align_ = Align::Left;
};
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include <SDL2/SDL_ttf.h>
#include <frostbite2D/types/type_alias.h>
#include <optional>
#include <string>
#include <unordered_map>
namespace frostbite2D {
class FontManager {
public:
static FontManager& get();
bool init();
void shutdown();
bool registerFont(const std::string& name, const std::string& path, int fontSize);
TTF_Font* getFont(const std::string& name);
bool hasFont(const std::string& name) const;
void unregisterFont(const std::string& name);
void unloadAll();
struct FontInfo {
std::string name;
std::string path;
int size = 0;
};
std::optional<FontInfo> getFontInfo(const std::string& name) const;
FontManager(const FontManager&) = delete;
FontManager& operator=(const FontManager&) = delete;
private:
FontManager() = default;
~FontManager();
std::unordered_map<std::string, TTF_Font*> fonts_;
std::unordered_map<std::string, FontInfo> fontInfos_;
};
}

View File

@@ -0,0 +1,141 @@
#include <frostbite2D/2d/text_sprite.h>
#include <frostbite2D/graphics/font_manager.h>
#include <frostbite2D/graphics/texture.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
namespace frostbite2D {
TextSprite::TextSprite()
: Sprite() {
}
TextSprite::~TextSprite() {
}
Ptr<TextSprite> TextSprite::create() {
return MakePtr<TextSprite>();
}
Ptr<TextSprite> TextSprite::create(const std::string& text, const std::string& fontName) {
auto textSprite = MakePtr<TextSprite>();
textSprite->SetFont(fontName);
textSprite->SetText(text);
return textSprite;
}
void TextSprite::SetText(const std::string& text) {
if (text_ != text) {
text_ = text;
RenderText();
}
}
void TextSprite::SetFont(const std::string& fontName) {
if (fontName_ != fontName) {
fontName_ = fontName;
RenderText();
}
}
void TextSprite::SetTextColor(const Color& color) {
textColor_ = color;
RenderText();
}
void TextSprite::SetTextColor(float r, float g, float b, float a) {
textColor_ = Color(r, g, b, a);
RenderText();
}
void TextSprite::SetAlign(Align align) {
if (align_ != align) {
align_ = align;
RenderText();
}
}
void TextSprite::RenderText() {
if (text_.empty() || fontName_.empty()) {
return;
}
FontManager& fontMgr = FontManager::get();
TTF_Font* font = fontMgr.getFont(fontName_);
if (!font) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Font '%s' not found for text rendering", fontName_.c_str());
return;
}
SDL_Color sdlColor;
sdlColor.r = static_cast<Uint8>(textColor_.r * 255.0f);
sdlColor.g = static_cast<Uint8>(textColor_.g * 255.0f);
sdlColor.b = static_cast<Uint8>(textColor_.b * 255.0f);
sdlColor.a = static_cast<Uint8>(textColor_.a * 255.0f);
SDL_Surface* surface = TTF_RenderUTF8_Blended(font, text_.c_str(), sdlColor);
if (!surface) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to render text: %s", TTF_GetError());
return;
}
SDL_PixelFormat* rgbaFormat = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);
SDL_Surface* converted = SDL_ConvertSurface(surface, rgbaFormat, 0);
SDL_FreeSurface(surface);
surface = converted;
SDL_FreeFormat(rgbaFormat);
if (!surface) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to convert text surface: %s", SDL_GetError());
return;
}
int width = surface->w;
int height = surface->h;
std::vector<uint8> rgbaData(width * height * 4);
uint8* pixels = static_cast<uint8*>(surface->pixels);
for (int i = 0; i < width * height; i++) {
rgbaData[i * 4 + 0] = pixels[i * 4 + 0];
rgbaData[i * 4 + 1] = pixels[i * 4 + 1];
rgbaData[i * 4 + 2] = pixels[i * 4 + 2];
rgbaData[i * 4 + 3] = pixels[i * 4 + 3];
}
SDL_FreeSurface(surface);
Ptr<Texture> texture = Texture::createFromMemory(
rgbaData.data(),
width,
height,
4
);
if (texture) {
SetTexture(texture);
SetSizeToTexture();
}
}
Vec2 TextSprite::GetTextSize() const {
if (text_.empty() || fontName_.empty()) {
return Vec2::Zero();
}
FontManager& fontMgr = FontManager::get();
TTF_Font* font = fontMgr.getFont(fontName_);
if (!font) {
return Vec2::Zero();
}
int w = 0;
int h = 0;
TTF_SizeUTF8(font, text_.c_str(), &w, &h);
return Vec2(static_cast<float>(w), static_cast<float>(h));
}
void TextSprite::updateTexture() {
RenderText();
}
}

View File

@@ -158,11 +158,11 @@ bool Application::initCoreModules() {
// 设置窗口清除颜色和视口
renderer_->setClearColor(0.0f, 0.0f, 0.0f);
renderer_->setViewport(0, 0, window_->width(), window_->height());
renderer_->setViewport(0, 0, config_.windowConfig.width, config_.windowConfig.height);
// 创建并设置相机
camera_ = new Camera();
camera_->setViewport(window_->width(), window_->height());
camera_->setViewport(config_.windowConfig.width, config_.windowConfig.height);
camera_->setFlipY(true); // 启用 Y 轴翻转,(0,0) 在左上角
renderer_->setCamera(camera_);

View File

@@ -10,9 +10,8 @@ namespace frostbite2D {
bool Window::create(const WindowConfig &cfg) {
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
#ifdef __SWITCH__
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
#else
#ifndef __SWITCH__
if (cfg.fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else if (cfg.borderless) {

View File

@@ -0,0 +1,98 @@
#include <frostbite2D/graphics/font_manager.h>
#include <frostbite2D/resource/asset.h>
#include <SDL2/SDL.h>
namespace frostbite2D {
FontManager::~FontManager() {
unloadAll();
}
FontManager& FontManager::get() {
static FontManager instance;
return instance;
}
bool FontManager::init() {
if (TTF_WasInit() == 0) {
if (TTF_Init() != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize TTF: %s", TTF_GetError());
return false;
}
}
return true;
}
void FontManager::shutdown() {
unloadAll();
if (TTF_WasInit() != 0) {
TTF_Quit();
}
}
bool FontManager::registerFont(const std::string& name, const std::string& path, int fontSize) {
if (fonts_.find(name) != fonts_.end()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Font '%s' already registered, replacing", name.c_str());
unregisterFont(name);
}
Asset& asset = Asset::get();
std::string fullPath = asset.resolveAssetPath(path);
TTF_Font* font = TTF_OpenFont(fullPath.c_str(), fontSize);
if (!font) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load font '%s': %s", fullPath.c_str(), TTF_GetError());
return false;
}
fonts_[name] = font;
FontInfo info;
info.name = name;
info.path = path;
info.size = fontSize;
fontInfos_[name] = info;
SDL_Log("Font '%s' loaded: %s, size %d", name.c_str(), fullPath.c_str(), fontSize);
return true;
}
TTF_Font* FontManager::getFont(const std::string& name) {
auto it = fonts_.find(name);
if (it == fonts_.end()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Font '%s' not found", name.c_str());
return nullptr;
}
return it->second;
}
bool FontManager::hasFont(const std::string& name) const {
return fonts_.find(name) != fonts_.end();
}
void FontManager::unregisterFont(const std::string& name) {
auto it = fonts_.find(name);
if (it != fonts_.end()) {
TTF_CloseFont(it->second);
fonts_.erase(it);
fontInfos_.erase(name);
}
}
void FontManager::unloadAll() {
for (auto& pair : fonts_) {
TTF_CloseFont(pair.second);
}
fonts_.clear();
fontInfos_.clear();
}
std::optional<FontManager::FontInfo> FontManager::getFontInfo(const std::string& name) const {
auto it = fontInfos_.find(name);
if (it != fontInfos_.end()) {
return it->second;
}
return std::nullopt;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -26,6 +26,9 @@
#include <frostbite2D/animation/animation.h>
#include <frostbite2D/2d/text_sprite.h>
#include <frostbite2D/graphics/font_manager.h>
using namespace frostbite2D;
int main(int argc, char **argv) {
@@ -59,6 +62,14 @@ int main(int argc, char **argv) {
auto menuScene = MakePtr<Scene>();
SceneManager::get().PushScene(menuScene);
FontManager::get().init();
FontManager::get().registerFont("default",
"assets/Fonts/VonwaonBitmap-12px.ttf", 12);
auto text = TextSprite::create("你好世界", "default");
text->SetPosition(100, 100);
text->SetTextColor(Colors::Red);
menuScene->AddChild(text);
auto ani = MakePtr<Animation>(
"monster/event/bluemarble/goblin/animation_goblin2/move.ani");

View File

@@ -4,6 +4,7 @@ set_toolchains("mingw")
add_requires("libsdl2", {configs = {shared = true}})
add_requires("libsdl2_image", {configs = {shared = true}})
add_requires("libsdl2_mixer", {configs = {shared = true}})
add_requires("libsdl2_ttf", {configs = {shared = true}})
add_requires("glm")
add_requires("zlib")
@@ -19,6 +20,7 @@ target("Frostbite2D")
add_packages("libsdl2")
add_packages("libsdl2_image")
add_packages("libsdl2_mixer")
add_packages("libsdl2_ttf")
add_packages("glm")
add_packages("zlib")

View File

@@ -40,9 +40,7 @@ target("Frostbite2D")
add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2"))
add_linkdirs(path.join(devkitPro, "portlibs/switch/lib"))
add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", "webp", "png", "jpeg", "z", "opusfile", "opus", "vorbisidec", "ogg",
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true})
add_syslinks("SDL2_mixer", "SDL2_image", "SDL2_ttf", "SDL2", "freetype", "harfbuzz" , "bz2" , "webp", "png", "jpeg", "z", "opusfile", "opus", "vorbisidec", "ogg","modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",{public = true})
add_syslinks("nx", "m")