feat(字体渲染): 添加字体管理和文本渲染功能
- 新增FontManager类用于管理字体资源 - 添加TextSprite类实现文本渲染功能 - 更新构建配置添加SDL2_ttf依赖 - 添加多种字体资源文件 - 修改窗口和相机初始化逻辑 - 更新Switch平台构建配置
This commit is contained in:
49
Frostbite2D/include/frostbite2D/2d/text_sprite.h
Normal file
49
Frostbite2D/include/frostbite2D/2d/text_sprite.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
43
Frostbite2D/include/frostbite2D/graphics/font_manager.h
Normal file
43
Frostbite2D/include/frostbite2D/graphics/font_manager.h
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
141
Frostbite2D/src/frostbite2D/2d/text_sprite.cpp
Normal file
141
Frostbite2D/src/frostbite2D/2d/text_sprite.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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_);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
98
Frostbite2D/src/frostbite2D/graphics/font_manager.cpp
Normal file
98
Frostbite2D/src/frostbite2D/graphics/font_manager.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Game/assets/Fonts/NotoSansCJKsc-Regular.otf
Normal file
BIN
Game/assets/Fonts/NotoSansCJKsc-Regular.otf
Normal file
Binary file not shown.
BIN
Game/assets/Fonts/NotoSansSC-Light.otf
Normal file
BIN
Game/assets/Fonts/NotoSansSC-Light.otf
Normal file
Binary file not shown.
BIN
Game/assets/Fonts/NotoSansSC-Regular.otf
Normal file
BIN
Game/assets/Fonts/NotoSansSC-Regular.otf
Normal file
Binary file not shown.
BIN
Game/assets/Fonts/ShinGoPr6-Medium.ttf
Normal file
BIN
Game/assets/Fonts/ShinGoPr6-Medium.ttf
Normal file
Binary file not shown.
BIN
Game/assets/Fonts/VonwaonBitmap-12px.ttf
Normal file
BIN
Game/assets/Fonts/VonwaonBitmap-12px.ttf
Normal file
Binary file not shown.
BIN
Game/assets/Fonts/calibri.ttf
Normal file
BIN
Game/assets/Fonts/calibri.ttf
Normal file
Binary file not shown.
BIN
Game/assets/Fonts/msgothic_0.ttc
Normal file
BIN
Game/assets/Fonts/msgothic_0.ttc
Normal file
Binary file not shown.
@@ -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");
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user