diff --git a/Frostbite2D/include/frostbite2D/2d/text_sprite.h b/Frostbite2D/include/frostbite2D/2d/text_sprite.h new file mode 100644 index 0000000..eccd426 --- /dev/null +++ b/Frostbite2D/include/frostbite2D/2d/text_sprite.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include + +namespace frostbite2D { + +class TextSprite : public Sprite { +public: + enum class Align { + Left, + Center, + Right + }; + + TextSprite(); + ~TextSprite(); + + static Ptr create(); + static Ptr 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; +}; + +} diff --git a/Frostbite2D/include/frostbite2D/graphics/font_manager.h b/Frostbite2D/include/frostbite2D/graphics/font_manager.h new file mode 100644 index 0000000..68f24d6 --- /dev/null +++ b/Frostbite2D/include/frostbite2D/graphics/font_manager.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 getFontInfo(const std::string& name) const; + + FontManager(const FontManager&) = delete; + FontManager& operator=(const FontManager&) = delete; + +private: + FontManager() = default; + ~FontManager(); + + std::unordered_map fonts_; + std::unordered_map fontInfos_; +}; + +} diff --git a/Frostbite2D/src/frostbite2D/2d/text_sprite.cpp b/Frostbite2D/src/frostbite2D/2d/text_sprite.cpp new file mode 100644 index 0000000..ef2cc2b --- /dev/null +++ b/Frostbite2D/src/frostbite2D/2d/text_sprite.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include + +namespace frostbite2D { + +TextSprite::TextSprite() + : Sprite() { +} + +TextSprite::~TextSprite() { +} + +Ptr TextSprite::create() { + return MakePtr(); +} + +Ptr TextSprite::create(const std::string& text, const std::string& fontName) { + auto textSprite = MakePtr(); + 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(textColor_.r * 255.0f); + sdlColor.g = static_cast(textColor_.g * 255.0f); + sdlColor.b = static_cast(textColor_.b * 255.0f); + sdlColor.a = static_cast(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 rgbaData(width * height * 4); + uint8* pixels = static_cast(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::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(w), static_cast(h)); +} + +void TextSprite::updateTexture() { + RenderText(); +} + +} diff --git a/Frostbite2D/src/frostbite2D/core/application.cpp b/Frostbite2D/src/frostbite2D/core/application.cpp index 6a9a96e..8a95377 100644 --- a/Frostbite2D/src/frostbite2D/core/application.cpp +++ b/Frostbite2D/src/frostbite2D/core/application.cpp @@ -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_); diff --git a/Frostbite2D/src/frostbite2D/core/window.cpp b/Frostbite2D/src/frostbite2D/core/window.cpp index 7907c9a..400276a 100644 --- a/Frostbite2D/src/frostbite2D/core/window.cpp +++ b/Frostbite2D/src/frostbite2D/core/window.cpp @@ -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) { diff --git a/Frostbite2D/src/frostbite2D/graphics/font_manager.cpp b/Frostbite2D/src/frostbite2D/graphics/font_manager.cpp new file mode 100644 index 0000000..c11f8fb --- /dev/null +++ b/Frostbite2D/src/frostbite2D/graphics/font_manager.cpp @@ -0,0 +1,98 @@ +#include +#include +#include + +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::getFontInfo(const std::string& name) const { + auto it = fontInfos_.find(name); + if (it != fontInfos_.end()) { + return it->second; + } + return std::nullopt; +} + +} diff --git a/Game/assets/Fonts/NotoSansCJKsc-Regular.otf b/Game/assets/Fonts/NotoSansCJKsc-Regular.otf new file mode 100644 index 0000000..4c5b8bd Binary files /dev/null and b/Game/assets/Fonts/NotoSansCJKsc-Regular.otf differ diff --git a/Game/assets/Fonts/NotoSansSC-Light.otf b/Game/assets/Fonts/NotoSansSC-Light.otf new file mode 100644 index 0000000..85ccdf4 Binary files /dev/null and b/Game/assets/Fonts/NotoSansSC-Light.otf differ diff --git a/Game/assets/Fonts/NotoSansSC-Regular.otf b/Game/assets/Fonts/NotoSansSC-Regular.otf new file mode 100644 index 0000000..d350ffa Binary files /dev/null and b/Game/assets/Fonts/NotoSansSC-Regular.otf differ diff --git a/Game/assets/Fonts/ShinGoPr6-Medium.ttf b/Game/assets/Fonts/ShinGoPr6-Medium.ttf new file mode 100644 index 0000000..9e92df7 Binary files /dev/null and b/Game/assets/Fonts/ShinGoPr6-Medium.ttf differ diff --git a/Game/assets/Fonts/VonwaonBitmap-12px.ttf b/Game/assets/Fonts/VonwaonBitmap-12px.ttf new file mode 100644 index 0000000..d337c4d Binary files /dev/null and b/Game/assets/Fonts/VonwaonBitmap-12px.ttf differ diff --git a/Game/assets/Fonts/calibri.ttf b/Game/assets/Fonts/calibri.ttf new file mode 100644 index 0000000..5f9e62c Binary files /dev/null and b/Game/assets/Fonts/calibri.ttf differ diff --git a/Game/assets/Fonts/msgothic_0.ttc b/Game/assets/Fonts/msgothic_0.ttc new file mode 100644 index 0000000..7bfcdf7 Binary files /dev/null and b/Game/assets/Fonts/msgothic_0.ttc differ diff --git a/Game/src/main.cpp b/Game/src/main.cpp index 97df0f0..72a19c4 100644 --- a/Game/src/main.cpp +++ b/Game/src/main.cpp @@ -26,6 +26,9 @@ #include +#include +#include + using namespace frostbite2D; int main(int argc, char **argv) { @@ -59,6 +62,14 @@ int main(int argc, char **argv) { auto menuScene = MakePtr(); 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( "monster/event/bluemarble/goblin/animation_goblin2/move.ani"); diff --git a/platform/mingw.lua b/platform/mingw.lua index cf02dc2..47decea 100644 --- a/platform/mingw.lua +++ b/platform/mingw.lua @@ -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") diff --git a/platform/switch.lua b/platform/switch.lua index 50eeba6..51bb757 100644 --- a/platform/switch.lua +++ b/platform/switch.lua @@ -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")