feat(assets): 添加字体和音乐资源支持

- 新增 Font 类,使用 stb_truetype 加载 TTF/OTF 字体并计算度量信息
- 新增 Music 类,基于 SDL_mixer 实现音频播放、暂停、停止等功能
- 为 AssetSystem 和 AssetsModule 添加字体和音乐的存储、加载器注册及缓存支持
- 在 hello_world 示例中演示字体和音乐的加载与使用
- 更新 extra2d.h 头文件以包含新增的资产模块头文件
This commit is contained in:
ChestnutYueyue 2026-03-16 19:38:24 +08:00
parent 1097aeae6c
commit 81bc7ae030
15 changed files with 703 additions and 7 deletions

View File

@ -1,8 +1,52 @@
#include <extra2d.h>
#include <cstdint>
#include <fstream>
#include <cstdio>
#include <string>
#include <vector>
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<const char *>(&riffSize), 4);
file.write("WAVE", 4);
file.write("fmt ", 4);
const int fmtChunkSize = 16;
const short audioFormat = 1;
const short numChannels = static_cast<short>(channels);
const short bps = static_cast<short>(bitsPerSample);
file.write(reinterpret_cast<const char *>(&fmtChunkSize), 4);
file.write(reinterpret_cast<const char *>(&audioFormat), 2);
file.write(reinterpret_cast<const char *>(&numChannels), 2);
file.write(reinterpret_cast<const char *>(&sampleRate), 4);
file.write(reinterpret_cast<const char *>(&byteRate), 4);
const short ba = static_cast<short>(blockAlign);
file.write(reinterpret_cast<const char *>(&ba), 2);
file.write(reinterpret_cast<const char *>(&bps), 2);
file.write("data", 4);
file.write(reinterpret_cast<const char *>(&dataSize), 4);
std::vector<int16_t> samples(static_cast<size_t>(sampleCount), 0);
file.write(reinterpret_cast<const char *>(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> font = assets->load<Font>("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> music = assets->load<Music>(testMusicPath);
if (music.isValid()) {
printf("音乐加载成功: %s\n", testMusicPath.c_str());
} else {
printf("音乐加载失败: %s\n", testMusicPath.c_str());
}
} else {
printf("测试音频文件生成失败\n");
}
}
// 运行应用
app->run();

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

View File

@ -3,7 +3,9 @@
#include <assets/asset_storage.h>
#include <assets/core/asset_system.h>
#include <assets/asset_loader.h>
#include <assets/font.h>
#include <assets/handle.h>
#include <assets/music.h>
#include <event/events.h>
#include <functional>
#include <memory>
@ -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<Texture> textures_;
AssetStorage<Shader> shaders_;
AssetStorage<Font> fonts_;
AssetStorage<Music> musics_;
AssetStorage<Material> materials_;
AssetStorage<Mesh> meshes_;
// 加载器
std::unique_ptr<AssetLoader<Texture>> textureLoader_;
std::unique_ptr<AssetLoader<Shader>> shaderLoader_;
std::unique_ptr<AssetLoader<Font>> fontLoader_;
std::unique_ptr<AssetLoader<Music>> musicLoader_;
// 事件监听器
std::unique_ptr<events::OnShow::Listener> onShowListener_;
@ -346,6 +354,10 @@ Handle<Texture> AssetsModule::load<Texture>(const std::string &path);
template <> Handle<Shader> AssetsModule::load<Shader>(const std::string &path);
template <> Handle<Font> AssetsModule::load<Font>(const std::string &path);
template <> Handle<Music> AssetsModule::load<Music>(const std::string &path);
template <>
Handle<Shader> AssetsModule::load<Shader>(const std::string &vertPath,
const std::string &fragPath);
@ -361,6 +373,11 @@ Handle<Texture> AssetsModule::loadFromMemory<Texture>(const std::string &key,
const uint8_t *data,
size_t size);
template <>
Handle<Font> AssetsModule::loadFromMemory<Font>(const std::string &key,
const uint8_t *data,
size_t size);
template <typename T> T *AssetsModule::get(Handle<T> handle) { return nullptr; }
template <> inline Texture *AssetsModule::get<Texture>(Handle<Texture> handle) {
@ -371,6 +388,14 @@ template <> inline Shader *AssetsModule::get<Shader>(Handle<Shader> handle) {
return shaders_.get(handle);
}
template <> inline Font *AssetsModule::get<Font>(Handle<Font> handle) {
return fonts_.get(handle);
}
template <> inline Music *AssetsModule::get<Music>(Handle<Music> handle) {
return musics_.get(handle);
}
template <>
inline Material *AssetsModule::get<Material>(Handle<Material> handle) {
return materials_.get(handle);
@ -394,6 +419,15 @@ inline Ptr<Shader> AssetsModule::getPtr<Shader>(Handle<Shader> handle) {
return shaders_.getPtr(handle);
}
template <> inline Ptr<Font> AssetsModule::getPtr<Font>(Handle<Font> handle) {
return fonts_.getPtr(handle);
}
template <>
inline Ptr<Music> AssetsModule::getPtr<Music>(Handle<Music> handle) {
return musics_.getPtr(handle);
}
template <>
inline Ptr<Material> AssetsModule::getPtr<Material>(Handle<Material> handle) {
return materials_.getPtr(handle);
@ -417,6 +451,15 @@ inline bool AssetsModule::isValid<Shader>(Handle<Shader> handle) const {
return shaders_.isValid(handle);
}
template <> inline bool AssetsModule::isValid<Font>(Handle<Font> handle) const {
return fonts_.isValid(handle);
}
template <>
inline bool AssetsModule::isValid<Music>(Handle<Music> handle) const {
return musics_.isValid(handle);
}
template <>
inline bool AssetsModule::isValid<Material>(Handle<Material> handle) const {
return materials_.isValid(handle);
@ -440,6 +483,16 @@ inline bool AssetsModule::isLoading<Shader>(Handle<Shader> handle) const {
return shaders_.isLoading(handle);
}
template <>
inline bool AssetsModule::isLoading<Font>(Handle<Font> handle) const {
return fonts_.isLoading(handle);
}
template <>
inline bool AssetsModule::isLoading<Music>(Handle<Music> handle) const {
return musics_.isLoading(handle);
}
template <>
inline bool AssetsModule::isLoading<Material>(Handle<Material> handle) const {
return materials_.isLoading(handle);
@ -463,6 +516,16 @@ inline bool AssetsModule::isLoaded<Shader>(Handle<Shader> handle) const {
return shaders_.isLoaded(handle);
}
template <>
inline bool AssetsModule::isLoaded<Font>(Handle<Font> handle) const {
return fonts_.isLoaded(handle);
}
template <>
inline bool AssetsModule::isLoaded<Music>(Handle<Music> handle) const {
return musics_.isLoaded(handle);
}
template <>
inline bool AssetsModule::isLoaded<Material>(Handle<Material> handle) const {
return materials_.isLoaded(handle);
@ -486,6 +549,16 @@ inline AssetLoadState AssetsModule::getLoadState<Shader>(Handle<Shader> handle)
return shaders_.getLoadState(handle);
}
template <>
inline AssetLoadState AssetsModule::getLoadState<Font>(Handle<Font> handle) const {
return fonts_.getLoadState(handle);
}
template <>
inline AssetLoadState AssetsModule::getLoadState<Music>(Handle<Music> handle) const {
return musics_.getLoadState(handle);
}
template <>
inline AssetLoadState AssetsModule::getLoadState<Material>(Handle<Material> handle) const {
return materials_.getLoadState(handle);
@ -505,6 +578,14 @@ template <> inline void AssetsModule::remove<Shader>(Handle<Shader> handle) {
shaders_.remove(handle);
}
template <> inline void AssetsModule::remove<Font>(Handle<Font> handle) {
fonts_.remove(handle);
}
template <> inline void AssetsModule::remove<Music>(Handle<Music> handle) {
musics_.remove(handle);
}
template <>
inline void AssetsModule::remove<Material>(Handle<Material> handle) {
materials_.remove(handle);
@ -528,6 +609,16 @@ inline uint32_t AssetsModule::getRefCount<Shader>(Handle<Shader> handle) const {
return shaders_.getRefCount(handle);
}
template <>
inline uint32_t AssetsModule::getRefCount<Font>(Handle<Font> handle) const {
return fonts_.getRefCount(handle);
}
template <>
inline uint32_t AssetsModule::getRefCount<Music>(Handle<Music> handle) const {
return musics_.getRefCount(handle);
}
template <>
inline uint32_t AssetsModule::getRefCount<Material>(Handle<Material> handle) const {
return materials_.getRefCount(handle);
@ -555,6 +646,18 @@ AssetsModule::findUnloadableResources<Shader>(int maxAgeSeconds) const {
return shaders_.findUnloadableResources(maxAgeSeconds);
}
template <>
inline std::vector<Handle<Font>>
AssetsModule::findUnloadableResources<Font>(int maxAgeSeconds) const {
return fonts_.findUnloadableResources(maxAgeSeconds);
}
template <>
inline std::vector<Handle<Music>>
AssetsModule::findUnloadableResources<Music>(int maxAgeSeconds) const {
return musics_.findUnloadableResources(maxAgeSeconds);
}
template <>
inline std::vector<Handle<Material>>
AssetsModule::findUnloadableResources<Material>(int maxAgeSeconds) const {
@ -581,6 +684,16 @@ inline bool AssetsModule::unloadResource<Shader>(Handle<Shader> handle) {
return shaders_.unloadResource(handle);
}
template <>
inline bool AssetsModule::unloadResource<Font>(Handle<Font> handle) {
return fonts_.unloadResource(handle);
}
template <>
inline bool AssetsModule::unloadResource<Music>(Handle<Music> handle) {
return musics_.unloadResource(handle);
}
template <>
inline bool AssetsModule::unloadResource<Material>(Handle<Material> handle) {
return materials_.unloadResource(handle);
@ -606,6 +719,16 @@ inline size_t AssetsModule::unloadOldResources<Shader>(int maxCount, int maxAgeS
return shaders_.unloadOldResources(maxCount, maxAgeSeconds);
}
template <>
inline size_t AssetsModule::unloadOldResources<Font>(int maxCount, int maxAgeSeconds) {
return fonts_.unloadOldResources(maxCount, maxAgeSeconds);
}
template <>
inline size_t AssetsModule::unloadOldResources<Music>(int maxCount, int maxAgeSeconds) {
return musics_.unloadOldResources(maxCount, maxAgeSeconds);
}
template <>
inline size_t AssetsModule::unloadOldResources<Material>(int maxCount, int maxAgeSeconds) {
return materials_.unloadOldResources(maxCount, maxAgeSeconds);
@ -651,4 +774,12 @@ template <>
void AssetsModule::registerLoader<Shader>(
std::unique_ptr<AssetLoader<Shader>> loader);
template <>
void AssetsModule::registerLoader<Font>(
std::unique_ptr<AssetLoader<Font>> loader);
template <>
void AssetsModule::registerLoader<Music>(
std::unique_ptr<AssetLoader<Music>> loader);
} // namespace extra2d

View File

@ -5,7 +5,9 @@
#include <assets/builtin/builtin_asset_factory.h>
#include <assets/cache/asset_cache.h>
#include <assets/dependency/asset_dependency_graph.h>
#include <assets/font.h>
#include <assets/io/asset_file_system.h>
#include <assets/music.h>
#include <assets/runtime/asset_async_runtime.h>
#include <assets/runtime/asset_hot_reload_runtime.h>
#include <functional>
@ -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<Texture> &textures, AssetStorage<Shader> &shaders,
AssetStorage<Font> &fonts, AssetStorage<Music> &musics,
AssetStorage<Material> &materials, AssetStorage<Mesh> &meshes,
std::unique_ptr<AssetLoader<Texture>> &textureLoader,
std::unique_ptr<AssetLoader<Shader>> &shaderLoader);
std::unique_ptr<AssetLoader<Shader>> &shaderLoader,
std::unique_ptr<AssetLoader<Font>> &fontLoader,
std::unique_ptr<AssetLoader<Music>> &musicLoader);
Handle<Texture> loadTexture(const std::string &path);
Handle<Texture> loadTextureFromMemory(const std::string &key,
@ -40,6 +47,10 @@ public:
Handle<Shader> loadShader(const std::string &path);
Handle<Shader> loadShader(const std::string &vertPath,
const std::string &fragPath);
Handle<Font> loadFont(const std::string &path);
Handle<Font> loadFontFromMemory(const std::string &key, const uint8_t *data,
size_t size);
Handle<Music> loadMusic(const std::string &path);
void loadTextureAsync(const std::string &path,
std::function<void(Handle<Texture>)> callback);
@ -51,6 +62,8 @@ public:
void registerTextureLoader(std::unique_ptr<AssetLoader<Texture>> loader);
void registerShaderLoader(std::unique_ptr<AssetLoader<Shader>> loader);
void registerFontLoader(std::unique_ptr<AssetLoader<Font>> loader);
void registerMusicLoader(std::unique_ptr<AssetLoader<Music>> loader);
bool createDefaults();
void destroyDefaults();
@ -77,14 +90,20 @@ private:
AssetStorage<Texture> &textures_;
AssetStorage<Shader> &shaders_;
AssetStorage<Font> &fonts_;
AssetStorage<Music> &musics_;
AssetStorage<Material> &materials_;
AssetStorage<Mesh> &meshes_;
std::unique_ptr<AssetLoader<Texture>> &textureLoader_;
std::unique_ptr<AssetLoader<Shader>> &shaderLoader_;
std::unique_ptr<AssetLoader<Font>> &fontLoader_;
std::unique_ptr<AssetLoader<Music>> &musicLoader_;
AssetCache<Texture> textureCache_;
AssetCache<Shader> shaderCache_;
AssetCache<Font> fontCache_;
AssetCache<Music> musicCache_;
AssetDependencyGraph dependencyGraph_;
AssetAsyncRuntime asyncRuntime_;
AssetHotReloadRuntime hotReloadRuntime_;

34
include/assets/font.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <types/ptr/ref_counted.h>
#include <vector>
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<uint8_t> data_;
bool loaded_ = false;
int ascent_ = 0;
int descent_ = 0;
int lineGap_ = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,24 @@
#pragma once
#include <assets/asset_loader.h>
#include <assets/font.h>
#include <assets/io/asset_file_system.h>
namespace extra2d {
class FontLoader : public AssetLoader<Font> {
public:
FontLoader() = default;
explicit FontLoader(AssetFileSystem fileSystem);
Ptr<Font> load(const std::string &path) override;
Ptr<Font> loadFromMemory(const uint8_t *data, size_t size) override;
std::vector<std::string> getExtensions() const override {
return {".ttf", ".otf", ".ttc"};
}
private:
AssetFileSystem fileSystem_;
};
} // namespace extra2d

View File

@ -0,0 +1,24 @@
#pragma once
#include <assets/asset_loader.h>
#include <assets/io/asset_file_system.h>
#include <assets/music.h>
namespace extra2d {
class MusicLoader : public AssetLoader<Music> {
public:
MusicLoader() = default;
explicit MusicLoader(AssetFileSystem fileSystem);
Ptr<Music> load(const std::string &path) override;
Ptr<Music> loadFromMemory(const uint8_t *data, size_t size) override;
std::vector<std::string> getExtensions() const override {
return {".mp3", ".ogg", ".wav", ".flac", ".mod"};
}
private:
AssetFileSystem fileSystem_;
};
} // namespace extra2d

34
include/assets/music.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <types/ptr/ref_counted.h>
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

View File

@ -42,6 +42,9 @@
#include <config/window_config.h>
// Renderer System
#include <assets/assets_module.h>
#include <assets/font.h>
#include <assets/music.h>
#include <renderer/render_types.h>
#include <renderer/renderer_module.h>
#include <renderer/shader.h>

View File

@ -1,4 +1,6 @@
#include <assets/assets_module.h>
#include <assets/loaders/font_loader.h>
#include <assets/loaders/music_loader.h>
#include <assets/loaders/shader_loader.h>
#include <assets/loaders/texture_loader.h>
#include <event/events.h>
@ -20,8 +22,11 @@ bool AssetsModule::init() {
E2D_INFO("资源模块正在初始化...");
textureLoader_ = std::make_unique<TextureLoader>();
shaderLoader_ = std::make_unique<ShaderLoader>();
system_ = std::make_unique<AssetSystem>(textures_, shaders_, materials_, meshes_,
textureLoader_, shaderLoader_);
fontLoader_ = std::make_unique<FontLoader>();
musicLoader_ = std::make_unique<MusicLoader>();
system_ = std::make_unique<AssetSystem>(
textures_, shaders_, fonts_, musics_, materials_, meshes_, textureLoader_,
shaderLoader_, fontLoader_, musicLoader_);
onShowListener_ = std::make_unique<events::OnShow::Listener>();
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<Shader> AssetsModule::load<Shader>(const std::string &path) {
return system_->loadShader(path);
}
template <> Handle<Font> AssetsModule::load<Font>(const std::string &path) {
if (!system_) {
return Handle<Font>::invalid();
}
return system_->loadFont(path);
}
template <> Handle<Music> AssetsModule::load<Music>(const std::string &path) {
if (!system_) {
return Handle<Music>::invalid();
}
return system_->loadMusic(path);
}
template <>
Handle<Shader> AssetsModule::load<Shader>(const std::string &vertPath,
const std::string &fragPath) {
@ -91,6 +115,16 @@ Handle<Shader> AssetsModule::load<Shader>(const std::string &vertPath,
return system_->loadShader(vertPath, fragPath);
}
template <>
Handle<Font> AssetsModule::loadFromMemory<Font>(const std::string &key,
const uint8_t *data,
size_t size) {
if (!system_) {
return Handle<Font>::invalid();
}
return system_->loadFontFromMemory(key, data, size);
}
template <>
std::vector<Handle<Texture>>
AssetsModule::loadDir<Texture>(const std::string &directory,
@ -140,6 +174,25 @@ void AssetsModule::registerLoader<Shader>(
system_->registerShaderLoader(std::move(loader));
}
template <>
void AssetsModule::registerLoader<Font>(std::unique_ptr<AssetLoader<Font>> loader) {
if (!system_) {
fontLoader_ = std::move(loader);
return;
}
system_->registerFontLoader(std::move(loader));
}
template <>
void AssetsModule::registerLoader<Music>(
std::unique_ptr<AssetLoader<Music>> 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

View File

@ -7,12 +7,18 @@ namespace extra2d {
AssetSystem::AssetSystem(AssetStorage<Texture> &textures,
AssetStorage<Shader> &shaders,
AssetStorage<Font> &fonts,
AssetStorage<Music> &musics,
AssetStorage<Material> &materials,
AssetStorage<Mesh> &meshes,
std::unique_ptr<AssetLoader<Texture>> &textureLoader,
std::unique_ptr<AssetLoader<Shader>> &shaderLoader)
: textures_(textures), shaders_(shaders), materials_(materials), meshes_(meshes),
textureLoader_(textureLoader), shaderLoader_(shaderLoader),
std::unique_ptr<AssetLoader<Shader>> &shaderLoader,
std::unique_ptr<AssetLoader<Font>> &fontLoader,
std::unique_ptr<AssetLoader<Music>> &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<Shader> AssetSystem::loadShader(const std::string &vertPath,
return handle;
}
Handle<Font> AssetSystem::loadFont(const std::string &path) {
Handle<Font> cached = fontCache_.find(path);
if (cached.isValid() && fonts_.isValid(cached)) {
return cached;
}
if (!fontLoader_) {
return Handle<Font>::invalid();
}
Ptr<Font> font = fontLoader_->load(path);
if (!font) {
return Handle<Font>::invalid();
}
Handle<Font> handle = fonts_.insert(font);
fontCache_.set(path, handle);
return handle;
}
Handle<Font> AssetSystem::loadFontFromMemory(const std::string &key,
const uint8_t *data, size_t size) {
Handle<Font> cached = fontCache_.find(key);
if (cached.isValid() && fonts_.isValid(cached)) {
return cached;
}
if (!fontLoader_) {
return Handle<Font>::invalid();
}
Ptr<Font> font = fontLoader_->loadFromMemory(data, size);
if (!font) {
return Handle<Font>::invalid();
}
Handle<Font> handle = fonts_.insert(font);
fontCache_.set(key, handle);
return handle;
}
Handle<Music> AssetSystem::loadMusic(const std::string &path) {
Handle<Music> cached = musicCache_.find(path);
if (cached.isValid() && musics_.isValid(cached)) {
return cached;
}
if (!musicLoader_) {
return Handle<Music>::invalid();
}
Ptr<Music> music = musicLoader_->load(path);
if (!music) {
return Handle<Music>::invalid();
}
Handle<Music> 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<AssetLoader<Shader>> load
shaderLoader_ = std::move(loader);
}
void AssetSystem::registerFontLoader(std::unique_ptr<AssetLoader<Font>> loader) {
fontLoader_ = std::move(loader);
}
void AssetSystem::registerMusicLoader(std::unique_ptr<AssetLoader<Music>> 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;

81
src/assets/font.cpp Normal file
View File

@ -0,0 +1,81 @@
#include <assets/font.h>
#include <fstream>
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb/stb_truetype.h>
#include <utils/logger.h>
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<uint8_t> buffer(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char *>(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<float>(ascent_ - descent_ + lineGap_) * scale;
}
} // namespace extra2d

View File

@ -0,0 +1,30 @@
#include <assets/loaders/font_loader.h>
#include <utils/logger.h>
namespace extra2d {
FontLoader::FontLoader(AssetFileSystem fileSystem)
: fileSystem_(std::move(fileSystem)) {}
Ptr<Font> FontLoader::load(const std::string &path) {
std::string resolved = fileSystem_.assetPath(path);
Ptr<Font> font = makePtr<Font>();
if (!font->loadFromFile(resolved)) {
E2D_ERROR("FontLoader: 加载字体失败: {}", resolved);
return Ptr<Font>();
}
E2D_DEBUG("FontLoader: 已加载字体 {}", resolved);
return font;
}
Ptr<Font> FontLoader::loadFromMemory(const uint8_t *data, size_t size) {
Ptr<Font> font = makePtr<Font>();
if (!font->loadFromMemory(data, size)) {
E2D_ERROR("FontLoader: 从内存加载字体失败");
return Ptr<Font>();
}
E2D_DEBUG("FontLoader: 已从内存加载字体");
return font;
}
} // namespace extra2d

View File

@ -0,0 +1,27 @@
#include <assets/loaders/music_loader.h>
#include <utils/logger.h>
namespace extra2d {
MusicLoader::MusicLoader(AssetFileSystem fileSystem)
: fileSystem_(std::move(fileSystem)) {}
Ptr<Music> MusicLoader::load(const std::string &path) {
std::string resolved = fileSystem_.assetPath(path);
Ptr<Music> music = makePtr<Music>();
if (!music->loadFromFile(resolved)) {
E2D_ERROR("MusicLoader: 加载音乐失败: {}", resolved);
return Ptr<Music>();
}
E2D_DEBUG("MusicLoader: 已加载音乐 {}", resolved);
return music;
}
Ptr<Music> MusicLoader::loadFromMemory(const uint8_t *data, size_t size) {
(void)data;
(void)size;
E2D_ERROR("MusicLoader: loadFromMemory 暂不支持");
return Ptr<Music>();
}
} // namespace extra2d

97
src/assets/music.cpp Normal file
View File

@ -0,0 +1,97 @@
#include <assets/music.h>
#include <SDL.h>
#include <SDL_mixer.h>
#include <mutex>
#include <utils/logger.h>
namespace extra2d {
namespace {
std::mutex g_audioMutex;
bool g_audioReady = false;
}
Music::Music() = default;
Music::~Music() { unload(); }
bool Music::initAudio() {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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