diff --git a/examples/hello_world/main.cpp b/examples/hello_world/main.cpp index 5cb2dce..982d9e0 100644 --- a/examples/hello_world/main.cpp +++ b/examples/hello_world/main.cpp @@ -1,8 +1,52 @@ #include +#include +#include #include +#include +#include using namespace extra2d; +static bool writeTestWav(const std::string &path) { + const int sampleRate = 22050; + const int channels = 1; + const int bitsPerSample = 16; + const int durationMs = 250; + const int sampleCount = sampleRate * durationMs / 1000; + const int byteRate = sampleRate * channels * bitsPerSample / 8; + const int blockAlign = channels * bitsPerSample / 8; + const int dataSize = sampleCount * blockAlign; + const int riffSize = 36 + dataSize; + + std::ofstream file(path, std::ios::binary); + if (!file.is_open()) { + return false; + } + + file.write("RIFF", 4); + file.write(reinterpret_cast(&riffSize), 4); + file.write("WAVE", 4); + file.write("fmt ", 4); + const int fmtChunkSize = 16; + const short audioFormat = 1; + const short numChannels = static_cast(channels); + const short bps = static_cast(bitsPerSample); + file.write(reinterpret_cast(&fmtChunkSize), 4); + file.write(reinterpret_cast(&audioFormat), 2); + file.write(reinterpret_cast(&numChannels), 2); + file.write(reinterpret_cast(&sampleRate), 4); + file.write(reinterpret_cast(&byteRate), 4); + const short ba = static_cast(blockAlign); + file.write(reinterpret_cast(&ba), 2); + file.write(reinterpret_cast(&bps), 2); + file.write("data", 4); + file.write(reinterpret_cast(&dataSize), 4); + + std::vector samples(static_cast(sampleCount), 0); + file.write(reinterpret_cast(samples.data()), dataSize); + return file.good(); +} + int main(int argc, char **argv) { // 创建应用(自动创建窗口) auto app = Application::create(); @@ -18,6 +62,30 @@ int main(int argc, char **argv) { } printf("窗口: %dx%d\n", app->getWindowWidth(), app->getWindowHeight()); + + AssetsModule *assets = getAssets(); + if (assets) { + Handle font = assets->load("assets/font.ttf"); + if (font.isValid()) { + Font *fontPtr = assets->get(font); + float lineHeight = fontPtr ? fontPtr->lineHeight(32.0f) : 0.0f; + printf("字体加载成功,32px 行高: %.2f\n", lineHeight); + } else { + printf("字体加载失败\n"); + } + + const std::string testMusicPath = "assets/generated_test.wav"; + if (writeTestWav(testMusicPath)) { + Handle music = assets->load(testMusicPath); + if (music.isValid()) { + printf("音乐加载成功: %s\n", testMusicPath.c_str()); + } else { + printf("音乐加载失败: %s\n", testMusicPath.c_str()); + } + } else { + printf("测试音频文件生成失败\n"); + } + } // 运行应用 app->run(); diff --git a/examples/scene_graph_demo/romfs/assets/test.png b/examples/scene_graph_demo/romfs/assets/test.png new file mode 100644 index 0000000..3a2e8a5 Binary files /dev/null and b/examples/scene_graph_demo/romfs/assets/test.png differ diff --git a/include/assets/assets_module.h b/include/assets/assets_module.h index 0765953..659e464 100644 --- a/include/assets/assets_module.h +++ b/include/assets/assets_module.h @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -240,6 +242,8 @@ public: struct Stats { size_t textureCount = 0; size_t shaderCount = 0; + size_t fontCount = 0; + size_t musicCount = 0; size_t materialCount = 0; size_t meshCount = 0; }; @@ -250,12 +254,16 @@ private: // 资源存储 AssetStorage textures_; AssetStorage shaders_; + AssetStorage fonts_; + AssetStorage musics_; AssetStorage materials_; AssetStorage meshes_; // 加载器 std::unique_ptr> textureLoader_; std::unique_ptr> shaderLoader_; + std::unique_ptr> fontLoader_; + std::unique_ptr> musicLoader_; // 事件监听器 std::unique_ptr onShowListener_; @@ -346,6 +354,10 @@ Handle AssetsModule::load(const std::string &path); template <> Handle AssetsModule::load(const std::string &path); +template <> Handle AssetsModule::load(const std::string &path); + +template <> Handle AssetsModule::load(const std::string &path); + template <> Handle AssetsModule::load(const std::string &vertPath, const std::string &fragPath); @@ -361,6 +373,11 @@ Handle AssetsModule::loadFromMemory(const std::string &key, const uint8_t *data, size_t size); +template <> +Handle AssetsModule::loadFromMemory(const std::string &key, + const uint8_t *data, + size_t size); + template T *AssetsModule::get(Handle handle) { return nullptr; } template <> inline Texture *AssetsModule::get(Handle handle) { @@ -371,6 +388,14 @@ template <> inline Shader *AssetsModule::get(Handle handle) { return shaders_.get(handle); } +template <> inline Font *AssetsModule::get(Handle handle) { + return fonts_.get(handle); +} + +template <> inline Music *AssetsModule::get(Handle handle) { + return musics_.get(handle); +} + template <> inline Material *AssetsModule::get(Handle handle) { return materials_.get(handle); @@ -394,6 +419,15 @@ inline Ptr AssetsModule::getPtr(Handle handle) { return shaders_.getPtr(handle); } +template <> inline Ptr AssetsModule::getPtr(Handle handle) { + return fonts_.getPtr(handle); +} + +template <> +inline Ptr AssetsModule::getPtr(Handle handle) { + return musics_.getPtr(handle); +} + template <> inline Ptr AssetsModule::getPtr(Handle handle) { return materials_.getPtr(handle); @@ -417,6 +451,15 @@ inline bool AssetsModule::isValid(Handle handle) const { return shaders_.isValid(handle); } +template <> inline bool AssetsModule::isValid(Handle handle) const { + return fonts_.isValid(handle); +} + +template <> +inline bool AssetsModule::isValid(Handle handle) const { + return musics_.isValid(handle); +} + template <> inline bool AssetsModule::isValid(Handle handle) const { return materials_.isValid(handle); @@ -440,6 +483,16 @@ inline bool AssetsModule::isLoading(Handle handle) const { return shaders_.isLoading(handle); } +template <> +inline bool AssetsModule::isLoading(Handle handle) const { + return fonts_.isLoading(handle); +} + +template <> +inline bool AssetsModule::isLoading(Handle handle) const { + return musics_.isLoading(handle); +} + template <> inline bool AssetsModule::isLoading(Handle handle) const { return materials_.isLoading(handle); @@ -463,6 +516,16 @@ inline bool AssetsModule::isLoaded(Handle handle) const { return shaders_.isLoaded(handle); } +template <> +inline bool AssetsModule::isLoaded(Handle handle) const { + return fonts_.isLoaded(handle); +} + +template <> +inline bool AssetsModule::isLoaded(Handle handle) const { + return musics_.isLoaded(handle); +} + template <> inline bool AssetsModule::isLoaded(Handle handle) const { return materials_.isLoaded(handle); @@ -486,6 +549,16 @@ inline AssetLoadState AssetsModule::getLoadState(Handle handle) return shaders_.getLoadState(handle); } +template <> +inline AssetLoadState AssetsModule::getLoadState(Handle handle) const { + return fonts_.getLoadState(handle); +} + +template <> +inline AssetLoadState AssetsModule::getLoadState(Handle handle) const { + return musics_.getLoadState(handle); +} + template <> inline AssetLoadState AssetsModule::getLoadState(Handle handle) const { return materials_.getLoadState(handle); @@ -505,6 +578,14 @@ template <> inline void AssetsModule::remove(Handle handle) { shaders_.remove(handle); } +template <> inline void AssetsModule::remove(Handle handle) { + fonts_.remove(handle); +} + +template <> inline void AssetsModule::remove(Handle handle) { + musics_.remove(handle); +} + template <> inline void AssetsModule::remove(Handle handle) { materials_.remove(handle); @@ -528,6 +609,16 @@ inline uint32_t AssetsModule::getRefCount(Handle handle) const { return shaders_.getRefCount(handle); } +template <> +inline uint32_t AssetsModule::getRefCount(Handle handle) const { + return fonts_.getRefCount(handle); +} + +template <> +inline uint32_t AssetsModule::getRefCount(Handle handle) const { + return musics_.getRefCount(handle); +} + template <> inline uint32_t AssetsModule::getRefCount(Handle handle) const { return materials_.getRefCount(handle); @@ -555,6 +646,18 @@ AssetsModule::findUnloadableResources(int maxAgeSeconds) const { return shaders_.findUnloadableResources(maxAgeSeconds); } +template <> +inline std::vector> +AssetsModule::findUnloadableResources(int maxAgeSeconds) const { + return fonts_.findUnloadableResources(maxAgeSeconds); +} + +template <> +inline std::vector> +AssetsModule::findUnloadableResources(int maxAgeSeconds) const { + return musics_.findUnloadableResources(maxAgeSeconds); +} + template <> inline std::vector> AssetsModule::findUnloadableResources(int maxAgeSeconds) const { @@ -581,6 +684,16 @@ inline bool AssetsModule::unloadResource(Handle handle) { return shaders_.unloadResource(handle); } +template <> +inline bool AssetsModule::unloadResource(Handle handle) { + return fonts_.unloadResource(handle); +} + +template <> +inline bool AssetsModule::unloadResource(Handle handle) { + return musics_.unloadResource(handle); +} + template <> inline bool AssetsModule::unloadResource(Handle handle) { return materials_.unloadResource(handle); @@ -606,6 +719,16 @@ inline size_t AssetsModule::unloadOldResources(int maxCount, int maxAgeS return shaders_.unloadOldResources(maxCount, maxAgeSeconds); } +template <> +inline size_t AssetsModule::unloadOldResources(int maxCount, int maxAgeSeconds) { + return fonts_.unloadOldResources(maxCount, maxAgeSeconds); +} + +template <> +inline size_t AssetsModule::unloadOldResources(int maxCount, int maxAgeSeconds) { + return musics_.unloadOldResources(maxCount, maxAgeSeconds); +} + template <> inline size_t AssetsModule::unloadOldResources(int maxCount, int maxAgeSeconds) { return materials_.unloadOldResources(maxCount, maxAgeSeconds); @@ -651,4 +774,12 @@ template <> void AssetsModule::registerLoader( std::unique_ptr> loader); +template <> +void AssetsModule::registerLoader( + std::unique_ptr> loader); + +template <> +void AssetsModule::registerLoader( + std::unique_ptr> loader); + } // namespace extra2d diff --git a/include/assets/core/asset_system.h b/include/assets/core/asset_system.h index ad9854c..2fd630b 100644 --- a/include/assets/core/asset_system.h +++ b/include/assets/core/asset_system.h @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -22,14 +24,19 @@ public: struct Stats { size_t textureCount = 0; size_t shaderCount = 0; + size_t fontCount = 0; + size_t musicCount = 0; size_t materialCount = 0; size_t meshCount = 0; }; AssetSystem(AssetStorage &textures, AssetStorage &shaders, + AssetStorage &fonts, AssetStorage &musics, AssetStorage &materials, AssetStorage &meshes, std::unique_ptr> &textureLoader, - std::unique_ptr> &shaderLoader); + std::unique_ptr> &shaderLoader, + std::unique_ptr> &fontLoader, + std::unique_ptr> &musicLoader); Handle loadTexture(const std::string &path); Handle loadTextureFromMemory(const std::string &key, @@ -40,6 +47,10 @@ public: Handle loadShader(const std::string &path); Handle loadShader(const std::string &vertPath, const std::string &fragPath); + Handle loadFont(const std::string &path); + Handle loadFontFromMemory(const std::string &key, const uint8_t *data, + size_t size); + Handle loadMusic(const std::string &path); void loadTextureAsync(const std::string &path, std::function)> callback); @@ -51,6 +62,8 @@ public: void registerTextureLoader(std::unique_ptr> loader); void registerShaderLoader(std::unique_ptr> loader); + void registerFontLoader(std::unique_ptr> loader); + void registerMusicLoader(std::unique_ptr> loader); bool createDefaults(); void destroyDefaults(); @@ -77,14 +90,20 @@ private: AssetStorage &textures_; AssetStorage &shaders_; + AssetStorage &fonts_; + AssetStorage &musics_; AssetStorage &materials_; AssetStorage &meshes_; std::unique_ptr> &textureLoader_; std::unique_ptr> &shaderLoader_; + std::unique_ptr> &fontLoader_; + std::unique_ptr> &musicLoader_; AssetCache textureCache_; AssetCache shaderCache_; + AssetCache fontCache_; + AssetCache musicCache_; AssetDependencyGraph dependencyGraph_; AssetAsyncRuntime asyncRuntime_; AssetHotReloadRuntime hotReloadRuntime_; diff --git a/include/assets/font.h b/include/assets/font.h new file mode 100644 index 0000000..63e520b --- /dev/null +++ b/include/assets/font.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +class Font : public RefCounted { +public: + Font(); + ~Font() override; + + bool loadFromFile(const std::string &path); + bool loadFromMemory(const uint8_t *data, size_t size); + + bool isLoaded() const { return loaded_; } + int ascent() const { return ascent_; } + int descent() const { return descent_; } + int lineGap() const { return lineGap_; } + float scaleForPixelHeight(float pixelHeight) const; + float lineHeight(float pixelHeight) const; + +private: + std::vector data_; + bool loaded_ = false; + int ascent_ = 0; + int descent_ = 0; + int lineGap_ = 0; +}; + +} // namespace extra2d diff --git a/include/assets/loaders/font_loader.h b/include/assets/loaders/font_loader.h new file mode 100644 index 0000000..596caaa --- /dev/null +++ b/include/assets/loaders/font_loader.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +class FontLoader : public AssetLoader { +public: + FontLoader() = default; + explicit FontLoader(AssetFileSystem fileSystem); + + Ptr load(const std::string &path) override; + Ptr loadFromMemory(const uint8_t *data, size_t size) override; + std::vector getExtensions() const override { + return {".ttf", ".otf", ".ttc"}; + } + +private: + AssetFileSystem fileSystem_; +}; + +} // namespace extra2d diff --git a/include/assets/loaders/music_loader.h b/include/assets/loaders/music_loader.h new file mode 100644 index 0000000..df00bb3 --- /dev/null +++ b/include/assets/loaders/music_loader.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +class MusicLoader : public AssetLoader { +public: + MusicLoader() = default; + explicit MusicLoader(AssetFileSystem fileSystem); + + Ptr load(const std::string &path) override; + Ptr loadFromMemory(const uint8_t *data, size_t size) override; + std::vector getExtensions() const override { + return {".mp3", ".ogg", ".wav", ".flac", ".mod"}; + } + +private: + AssetFileSystem fileSystem_; +}; + +} // namespace extra2d diff --git a/include/assets/music.h b/include/assets/music.h new file mode 100644 index 0000000..59ee8f8 --- /dev/null +++ b/include/assets/music.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +struct Mix_Music; + +namespace extra2d { + +class Music : public RefCounted { +public: + Music(); + ~Music() override; + + bool loadFromFile(const std::string &path); + void unload(); + + bool play(int loops = -1); + void stop(); + void pause(); + void resume(); + + bool isLoaded() const { return music_ != nullptr; } + bool isPlaying() const; + + static bool initAudio(); + static void shutdownAudio(); + static bool isAudioReady(); + +private: + Mix_Music *music_ = nullptr; +}; + +} // namespace extra2d diff --git a/include/extra2d.h b/include/extra2d.h index f49fb43..5586050 100644 --- a/include/extra2d.h +++ b/include/extra2d.h @@ -42,6 +42,9 @@ #include // Renderer System +#include +#include +#include #include #include #include diff --git a/src/assets/assets_module.cpp b/src/assets/assets_module.cpp index 0ee9190..52d69d0 100644 --- a/src/assets/assets_module.cpp +++ b/src/assets/assets_module.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -20,8 +22,11 @@ bool AssetsModule::init() { E2D_INFO("资源模块正在初始化..."); textureLoader_ = std::make_unique(); shaderLoader_ = std::make_unique(); - system_ = std::make_unique(textures_, shaders_, materials_, meshes_, - textureLoader_, shaderLoader_); + fontLoader_ = std::make_unique(); + musicLoader_ = std::make_unique(); + system_ = std::make_unique( + textures_, shaders_, fonts_, musics_, materials_, meshes_, textureLoader_, + shaderLoader_, fontLoader_, musicLoader_); onShowListener_ = std::make_unique(); onShowListener_->bind([this]() { this->onGLContextReady(); }); E2D_INFO("资源模块初始化成功 (V2)"); @@ -37,10 +42,15 @@ void AssetsModule::shutdown() { } textures_.clear(); shaders_.clear(); + fonts_.clear(); + musics_.clear(); materials_.clear(); meshes_.clear(); textureLoader_.reset(); shaderLoader_.reset(); + fontLoader_.reset(); + musicLoader_.reset(); + Music::shutdownAudio(); defaultResourcesCreated_ = false; E2D_INFO("资源模块关闭完成"); } @@ -82,6 +92,20 @@ template <> Handle AssetsModule::load(const std::string &path) { return system_->loadShader(path); } +template <> Handle AssetsModule::load(const std::string &path) { + if (!system_) { + return Handle::invalid(); + } + return system_->loadFont(path); +} + +template <> Handle AssetsModule::load(const std::string &path) { + if (!system_) { + return Handle::invalid(); + } + return system_->loadMusic(path); +} + template <> Handle AssetsModule::load(const std::string &vertPath, const std::string &fragPath) { @@ -91,6 +115,16 @@ Handle AssetsModule::load(const std::string &vertPath, return system_->loadShader(vertPath, fragPath); } +template <> +Handle AssetsModule::loadFromMemory(const std::string &key, + const uint8_t *data, + size_t size) { + if (!system_) { + return Handle::invalid(); + } + return system_->loadFontFromMemory(key, data, size); +} + template <> std::vector> AssetsModule::loadDir(const std::string &directory, @@ -140,6 +174,25 @@ void AssetsModule::registerLoader( system_->registerShaderLoader(std::move(loader)); } +template <> +void AssetsModule::registerLoader(std::unique_ptr> loader) { + if (!system_) { + fontLoader_ = std::move(loader); + return; + } + system_->registerFontLoader(std::move(loader)); +} + +template <> +void AssetsModule::registerLoader( + std::unique_ptr> loader) { + if (!system_) { + musicLoader_ = std::move(loader); + return; + } + system_->registerMusicLoader(std::move(loader)); +} + bool AssetsModule::createDefaultResources() { if (!system_) { return false; @@ -271,10 +324,11 @@ AssetsModule::Stats AssetsModule::getStats() const { AssetSystem::Stats systemStats = system_->stats(); stats.textureCount = systemStats.textureCount; stats.shaderCount = systemStats.shaderCount; + stats.fontCount = systemStats.fontCount; + stats.musicCount = systemStats.musicCount; stats.materialCount = systemStats.materialCount; stats.meshCount = systemStats.meshCount; return stats; } } // namespace extra2d - diff --git a/src/assets/core/asset_system.cpp b/src/assets/core/asset_system.cpp index 57312e4..a20e0ce 100644 --- a/src/assets/core/asset_system.cpp +++ b/src/assets/core/asset_system.cpp @@ -7,12 +7,18 @@ namespace extra2d { AssetSystem::AssetSystem(AssetStorage &textures, AssetStorage &shaders, + AssetStorage &fonts, + AssetStorage &musics, AssetStorage &materials, AssetStorage &meshes, std::unique_ptr> &textureLoader, - std::unique_ptr> &shaderLoader) - : textures_(textures), shaders_(shaders), materials_(materials), meshes_(meshes), - textureLoader_(textureLoader), shaderLoader_(shaderLoader), + std::unique_ptr> &shaderLoader, + std::unique_ptr> &fontLoader, + std::unique_ptr> &musicLoader) + : textures_(textures), shaders_(shaders), fonts_(fonts), musics_(musics), + materials_(materials), meshes_(meshes), textureLoader_(textureLoader), + shaderLoader_(shaderLoader), fontLoader_(fontLoader), + musicLoader_(musicLoader), hotReloadRuntime_(fileSystem_), builtinFactory_(textures, shaders, materials, meshes, fileSystem_) {} @@ -139,6 +145,58 @@ Handle AssetSystem::loadShader(const std::string &vertPath, return handle; } +Handle AssetSystem::loadFont(const std::string &path) { + Handle cached = fontCache_.find(path); + if (cached.isValid() && fonts_.isValid(cached)) { + return cached; + } + if (!fontLoader_) { + return Handle::invalid(); + } + Ptr font = fontLoader_->load(path); + if (!font) { + return Handle::invalid(); + } + Handle handle = fonts_.insert(font); + fontCache_.set(path, handle); + return handle; +} + +Handle AssetSystem::loadFontFromMemory(const std::string &key, + const uint8_t *data, size_t size) { + Handle cached = fontCache_.find(key); + if (cached.isValid() && fonts_.isValid(cached)) { + return cached; + } + if (!fontLoader_) { + return Handle::invalid(); + } + Ptr font = fontLoader_->loadFromMemory(data, size); + if (!font) { + return Handle::invalid(); + } + Handle handle = fonts_.insert(font); + fontCache_.set(key, handle); + return handle; +} + +Handle AssetSystem::loadMusic(const std::string &path) { + Handle cached = musicCache_.find(path); + if (cached.isValid() && musics_.isValid(cached)) { + return cached; + } + if (!musicLoader_) { + return Handle::invalid(); + } + Ptr music = musicLoader_->load(path); + if (!music) { + return Handle::invalid(); + } + Handle handle = musics_.insert(music); + musicCache_.set(path, handle); + return handle; +} + void AssetSystem::initAsync(uint32_t threadCount) { if (asyncRuntime_.running()) { return; @@ -198,6 +256,14 @@ void AssetSystem::registerShaderLoader(std::unique_ptr> load shaderLoader_ = std::move(loader); } +void AssetSystem::registerFontLoader(std::unique_ptr> loader) { + fontLoader_ = std::move(loader); +} + +void AssetSystem::registerMusicLoader(std::unique_ptr> loader) { + musicLoader_ = std::move(loader); +} + bool AssetSystem::createDefaults() { return builtinFactory_.create(); } void AssetSystem::destroyDefaults() { builtinFactory_.destroy(); } @@ -299,12 +365,16 @@ void AssetSystem::clear() { dependencyGraph_.clear(); textureCache_.clear(); shaderCache_.clear(); + fontCache_.clear(); + musicCache_.clear(); } AssetSystem::Stats AssetSystem::stats() const { Stats s; s.textureCount = textures_.count(); s.shaderCount = shaders_.count(); + s.fontCount = fonts_.count(); + s.musicCount = musics_.count(); s.materialCount = materials_.count(); s.meshCount = meshes_.count(); return s; diff --git a/src/assets/font.cpp b/src/assets/font.cpp new file mode 100644 index 0000000..e1f2c64 --- /dev/null +++ b/src/assets/font.cpp @@ -0,0 +1,81 @@ +#include +#include +#define STB_TRUETYPE_IMPLEMENTATION +#include +#include + +namespace extra2d { + +Font::Font() = default; + +Font::~Font() = default; + +bool Font::loadFromFile(const std::string &path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + E2D_ERROR("Font: 打开字体文件失败: {}", path); + return false; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + if (size <= 0) { + E2D_ERROR("Font: 字体文件为空: {}", path); + return false; + } + + std::vector buffer(static_cast(size)); + if (!file.read(reinterpret_cast(buffer.data()), size)) { + E2D_ERROR("Font: 读取字体文件失败: {}", path); + return false; + } + + return loadFromMemory(buffer.data(), buffer.size()); +} + +bool Font::loadFromMemory(const uint8_t *data, size_t size) { + if (!data || size == 0) { + E2D_ERROR("Font: 无效的内存数据"); + return false; + } + + stbtt_fontinfo info; + if (!stbtt_InitFont(&info, data, stbtt_GetFontOffsetForIndex(data, 0))) { + E2D_ERROR("Font: 初始化字体失败"); + return false; + } + + int ascent = 0; + int descent = 0; + int lineGap = 0; + stbtt_GetFontVMetrics(&info, &ascent, &descent, &lineGap); + + data_.assign(data, data + size); + loaded_ = true; + ascent_ = ascent; + descent_ = descent; + lineGap_ = lineGap; + return true; +} + +float Font::scaleForPixelHeight(float pixelHeight) const { + if (!loaded_ || pixelHeight <= 0.0f) { + return 0.0f; + } + stbtt_fontinfo info; + if (!stbtt_InitFont(&info, data_.data(), + stbtt_GetFontOffsetForIndex(data_.data(), 0))) { + return 0.0f; + } + return stbtt_ScaleForPixelHeight(&info, pixelHeight); +} + +float Font::lineHeight(float pixelHeight) const { + float scale = scaleForPixelHeight(pixelHeight); + if (scale <= 0.0f) { + return 0.0f; + } + return static_cast(ascent_ - descent_ + lineGap_) * scale; +} + +} // namespace extra2d diff --git a/src/assets/loaders/font_loader.cpp b/src/assets/loaders/font_loader.cpp new file mode 100644 index 0000000..a9a0ce4 --- /dev/null +++ b/src/assets/loaders/font_loader.cpp @@ -0,0 +1,30 @@ +#include +#include + +namespace extra2d { + +FontLoader::FontLoader(AssetFileSystem fileSystem) + : fileSystem_(std::move(fileSystem)) {} + +Ptr FontLoader::load(const std::string &path) { + std::string resolved = fileSystem_.assetPath(path); + Ptr font = makePtr(); + if (!font->loadFromFile(resolved)) { + E2D_ERROR("FontLoader: 加载字体失败: {}", resolved); + return Ptr(); + } + E2D_DEBUG("FontLoader: 已加载字体 {}", resolved); + return font; +} + +Ptr FontLoader::loadFromMemory(const uint8_t *data, size_t size) { + Ptr font = makePtr(); + if (!font->loadFromMemory(data, size)) { + E2D_ERROR("FontLoader: 从内存加载字体失败"); + return Ptr(); + } + E2D_DEBUG("FontLoader: 已从内存加载字体"); + return font; +} + +} // namespace extra2d diff --git a/src/assets/loaders/music_loader.cpp b/src/assets/loaders/music_loader.cpp new file mode 100644 index 0000000..8dac71f --- /dev/null +++ b/src/assets/loaders/music_loader.cpp @@ -0,0 +1,27 @@ +#include +#include + +namespace extra2d { + +MusicLoader::MusicLoader(AssetFileSystem fileSystem) + : fileSystem_(std::move(fileSystem)) {} + +Ptr MusicLoader::load(const std::string &path) { + std::string resolved = fileSystem_.assetPath(path); + Ptr music = makePtr(); + if (!music->loadFromFile(resolved)) { + E2D_ERROR("MusicLoader: 加载音乐失败: {}", resolved); + return Ptr(); + } + E2D_DEBUG("MusicLoader: 已加载音乐 {}", resolved); + return music; +} + +Ptr MusicLoader::loadFromMemory(const uint8_t *data, size_t size) { + (void)data; + (void)size; + E2D_ERROR("MusicLoader: loadFromMemory 暂不支持"); + return Ptr(); +} + +} // namespace extra2d diff --git a/src/assets/music.cpp b/src/assets/music.cpp new file mode 100644 index 0000000..edd3418 --- /dev/null +++ b/src/assets/music.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include + +namespace extra2d { +namespace { +std::mutex g_audioMutex; +bool g_audioReady = false; +} + +Music::Music() = default; + +Music::~Music() { unload(); } + +bool Music::initAudio() { + std::lock_guard lock(g_audioMutex); + if (g_audioReady) { + return true; + } + + if ((SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO) == 0) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { + E2D_ERROR("Music: 初始化 SDL 音频子系统失败: {}", SDL_GetError()); + return false; + } + } + + int initFlags = MIX_INIT_OGG | MIX_INIT_MP3 | MIX_INIT_FLAC | MIX_INIT_MOD; + Mix_Init(initFlags); + + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { + E2D_ERROR("Music: 打开音频设备失败: {}", Mix_GetError()); + return false; + } + + g_audioReady = true; + return true; +} + +void Music::shutdownAudio() { + std::lock_guard lock(g_audioMutex); + if (!g_audioReady) { + return; + } + Mix_CloseAudio(); + Mix_Quit(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + g_audioReady = false; +} + +bool Music::isAudioReady() { + std::lock_guard lock(g_audioMutex); + return g_audioReady; +} + +bool Music::loadFromFile(const std::string &path) { + unload(); + if (!initAudio()) { + return false; + } + music_ = Mix_LoadMUS(path.c_str()); + if (!music_) { + E2D_ERROR("Music: 加载音乐失败: {} ({})", path, Mix_GetError()); + return false; + } + return true; +} + +void Music::unload() { + if (music_) { + Mix_FreeMusic(music_); + music_ = nullptr; + } +} + +bool Music::play(int loops) { + if (!music_) { + return false; + } + if (Mix_PlayMusic(music_, loops) == -1) { + E2D_ERROR("Music: 播放失败: {}", Mix_GetError()); + return false; + } + return true; +} + +void Music::stop() { Mix_HaltMusic(); } + +void Music::pause() { Mix_PauseMusic(); } + +void Music::resume() { Mix_ResumeMusic(); } + +bool Music::isPlaying() const { return Mix_PlayingMusic() != 0; } + +} // namespace extra2d