feat(utils): 添加二进制文件读取器 BinaryReader

refactor(assets): 将着色器路径从 "shaders" 改为 "assets/shaders"
refactor(build): 统一各平台资源目录复制逻辑为 assets 目录

style: 更新 .gitignore 忽略参考代码目录和 .pvf 文件
This commit is contained in:
2026-03-18 03:12:39 +08:00
parent 57a96a0cc5
commit a4883b433e
12 changed files with 535 additions and 67 deletions

2
.gitignore vendored
View File

@@ -12,3 +12,5 @@ build/
*.json
.vscode/compile_commands.json
参考代码/
*.pvf

View File

@@ -0,0 +1,269 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
namespace frostbite2D {
/**
* @brief 二进制文件读取器
*
* 将整个二进制文件加载到内存中,提供便捷的读取接口。
* 支持各种基本类型的读取、定位操作和 CRC 解码。
*
* @example
* BinaryReader reader("data.bin");
* if (reader.isOpen()) {
* int32 value = reader.read<int32>();
* std::string str = reader.readString(10);
* }
*/
class BinaryReader {
public:
/**
* @brief 默认构造函数
*/
BinaryReader() = default;
/**
* @brief 从文件构造
* @param filePath 文件路径
*/
explicit BinaryReader(const std::string& filePath);
/**
* @brief 从内存数据构造
* @param data 数据指针
* @param size 数据大小
*/
BinaryReader(const char* data, size_t size);
/**
* @brief 析构函数
*/
~BinaryReader() = default;
// ---------------------------------------------------------------------------
// 文件操作
// ---------------------------------------------------------------------------
/**
* @brief 打开文件
* @param filePath 文件路径
* @return 打开成功返回 true
*/
bool open(const std::string& filePath);
/**
* @brief 从内存加载数据
* @param data 数据指针
* @param size 数据大小
*/
void loadFromMemory(const char* data, size_t size);
/**
* @brief 关闭并清空数据
*/
void close();
/**
* @brief 检查文件是否成功打开
* @return 已打开返回 true
*/
bool isOpen() const;
// ---------------------------------------------------------------------------
// 位置操作
// ---------------------------------------------------------------------------
/**
* @brief 获取当前读取位置
* @return 当前位置(字节偏移)
*/
size_t tell() const;
/**
* @brief 设置读取位置
* @param pos 目标位置(字节偏移)
*/
void seek(size_t pos);
/**
* @brief 跳过指定字节数
* @param count 要跳过的字节数
*/
void skip(size_t count);
/**
* @brief 检查是否已到达文件末尾
* @return 到达末尾返回 true
*/
bool eof() const;
// ---------------------------------------------------------------------------
// 信息获取
// ---------------------------------------------------------------------------
/**
* @brief 获取数据总大小
* @return 数据大小(字节)
*/
size_t size() const;
/**
* @brief 获取剩余可读取的字节数
* @return 剩余字节数
*/
size_t remaining() const;
/**
* @brief 获取上一次读取的字节数
* @return 上次读取的字节数
*/
size_t lastReadCount() const;
// ---------------------------------------------------------------------------
// 原始数据读取
// ---------------------------------------------------------------------------
/**
* @brief 读取原始字节数据
* @param buffer 输出缓冲区
* @param size 要读取的字节数
* @return 实际读取的字节数
*/
size_t read(char* buffer, size_t size);
/**
* @brief 读取原始字节数据到 vector
* @param size 要读取的字节数
* @return 读取的数据
*/
std::vector<uint8> readBytes(size_t size);
/**
* @brief 获取数据指针(不移动读取位置)
* @return 当前位置的数据指针
*/
const char* data() const;
/**
* @brief 获取当前位置的数据指针(不移动读取位置)
* @return 当前位置的数据指针
*/
const char* currentData() const;
// ---------------------------------------------------------------------------
// 类型化读取(模板方法)
// ---------------------------------------------------------------------------
/**
* @brief 读取指定类型的值
* @tparam T 要读取的类型
* @return 读取的值
*/
template <typename T>
T read() {
T value;
read(reinterpret_cast<char*>(&value), sizeof(T));
return value;
}
/**
* @brief 读取 int8
* @return 读取的值
*/
int8 readInt8();
/**
* @brief 读取 int16
* @return 读取的值
*/
int16 readInt16();
/**
* @brief 读取 int32
* @return 读取的值
*/
int32 readInt32();
/**
* @brief 读取 int64
* @return 读取的值
*/
int64 readInt64();
/**
* @brief 读取 uint8
* @return 读取的值
*/
uint8 readUInt8();
/**
* @brief 读取 uint16
* @return 读取的值
*/
uint16 readUInt16();
/**
* @brief 读取 uint32
* @return 读取的值
*/
uint32 readUInt32();
/**
* @brief 读取 uint64
* @return 读取的值
*/
uint64 readUInt64();
/**
* @brief 读取 float
* @return 读取的值
*/
float readFloat();
/**
* @brief 读取 double
* @return 读取的值
*/
double readDouble();
// ---------------------------------------------------------------------------
// 字符串读取
// ---------------------------------------------------------------------------
/**
* @brief 读取指定长度的字符串
* @param length 字符串长度(字节)
* @return 读取的字符串
*/
std::string readString(size_t length);
/**
* @brief 读取以 null 结尾的字符串
* @return 读取的字符串
*/
std::string readNullTerminatedString();
// ---------------------------------------------------------------------------
// CRC 解码
// ---------------------------------------------------------------------------
/**
* @brief CRC 解码
* @param length 要解码的数据长度(字节)
* @param crc32 CRC32 值
*/
void crcDecode(size_t length, uint32 crc32);
private:
std::vector<char> data_; ///< 存储的数据
size_t position_ = 0; ///< 当前读取位置
size_t lastReadCount_ = 0; ///< 上一次读取的字节数
};
} // namespace frostbite2D

View File

@@ -28,7 +28,7 @@ bool Renderer::init() {
}
//初始化着色器管理器
if (!shaderManager_.init("shaders")) {
if (!shaderManager_.init("assets/shaders")) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize shader manager");
return false;
}

View File

@@ -0,0 +1,235 @@
#include <frostbite2D/utils/binary_reader.h>
#include <frostbite2D/utils/asset.h>
#include <fstream>
#include <SDL.h>
namespace frostbite2D {
BinaryReader::BinaryReader(const std::string& filePath) {
open(filePath);
}
BinaryReader::BinaryReader(const char* data, size_t size) {
loadFromMemory(data, size);
}
bool BinaryReader::open(const std::string& filePath) {
close();
Asset& asset = Asset::get();
auto dataOpt = asset.readFileToBytes(filePath);
if (!dataOpt.has_value()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "BinaryReader: 无法打开文件: %s", filePath.c_str());
return false;
}
const std::vector<uint8>& bytes = dataOpt.value();
data_.resize(bytes.size());
std::memcpy(data_.data(), bytes.data(), bytes.size());
position_ = 0;
lastReadCount_ = 0;
return true;
}
void BinaryReader::loadFromMemory(const char* data, size_t size) {
close();
if (data && size > 0) {
data_.resize(size);
std::memcpy(data_.data(), data, size);
}
position_ = 0;
lastReadCount_ = 0;
}
void BinaryReader::close() {
data_.clear();
position_ = 0;
lastReadCount_ = 0;
}
bool BinaryReader::isOpen() const {
return !data_.empty();
}
size_t BinaryReader::tell() const {
return position_;
}
void BinaryReader::seek(size_t pos) {
position_ = std::clamp(pos, static_cast<size_t>(0), data_.size());
}
void BinaryReader::skip(size_t count) {
seek(position_ + count);
}
bool BinaryReader::eof() const {
return position_ >= data_.size();
}
size_t BinaryReader::size() const {
return data_.size();
}
size_t BinaryReader::remaining() const {
if (position_ >= data_.size()) {
return 0;
}
return data_.size() - position_;
}
size_t BinaryReader::lastReadCount() const {
return lastReadCount_;
}
size_t BinaryReader::read(char* buffer, size_t size) {
if (!buffer || size == 0 || eof()) {
lastReadCount_ = 0;
return 0;
}
size_t bytesToRead = std::min(size, remaining());
std::memcpy(buffer, data_.data() + position_, bytesToRead);
position_ += bytesToRead;
lastReadCount_ = bytesToRead;
if (bytesToRead != size) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "BinaryReader: 读取数据不完整,期望 %zu 字节,实际读取 %zu 字节", size, bytesToRead);
}
return bytesToRead;
}
std::vector<uint8> BinaryReader::readBytes(size_t size) {
std::vector<uint8> result;
size_t bytesToRead = std::min(size, remaining());
if (bytesToRead > 0) {
result.resize(bytesToRead);
read(reinterpret_cast<char*>(result.data()), bytesToRead);
}
return result;
}
const char* BinaryReader::data() const {
return data_.data();
}
const char* BinaryReader::currentData() const {
if (eof()) {
return nullptr;
}
return data_.data() + position_;
}
int8 BinaryReader::readInt8() {
return read<int8>();
}
int16 BinaryReader::readInt16() {
return read<int16>();
}
int32 BinaryReader::readInt32() {
return read<int32>();
}
int64 BinaryReader::readInt64() {
return read<int64>();
}
uint8 BinaryReader::readUInt8() {
return read<uint8>();
}
uint16 BinaryReader::readUInt16() {
return read<uint16>();
}
uint32 BinaryReader::readUInt32() {
return read<uint32>();
}
uint64 BinaryReader::readUInt64() {
return read<uint64>();
}
float BinaryReader::readFloat() {
return read<float>();
}
double BinaryReader::readDouble() {
return read<double>();
}
std::string BinaryReader::readString(size_t length) {
if (length == 0 || eof()) {
return "";
}
size_t bytesToRead = std::min(length, remaining());
std::string result(data_.data() + position_, bytesToRead);
position_ += bytesToRead;
lastReadCount_ = bytesToRead;
if (bytesToRead != length) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "BinaryReader: 读取字符串不完整,期望 %zu 字节,实际读取 %zu 字节", length, bytesToRead);
}
return result;
}
std::string BinaryReader::readNullTerminatedString() {
if (eof()) {
return "";
}
size_t startPos = position_;
while (position_ < data_.size() && data_[position_] != '\0') {
++position_;
}
size_t length = position_ - startPos;
std::string result(data_.data() + startPos, length);
if (position_ < data_.size()) {
++position_;
}
lastReadCount_ = length + 1;
return result;
}
void BinaryReader::crcDecode(size_t length, uint32 crc32) {
if (length == 0 || eof()) {
return;
}
const uint32 key = 0x81A79011;
size_t originalPos = position_;
size_t bytesToProcess = std::min(length, remaining());
for (size_t i = 0; i < bytesToProcess; i += 4) {
size_t pos = position_;
uint32 value = read<uint32>();
uint32 decoded = (value ^ key ^ crc32);
decoded = (decoded >> 6) | ((decoded << (32 - 6)) & 0xFFFFFFFF);
if (pos + 3 < data_.size()) {
data_[pos] = static_cast<char>((decoded >> 0) & 0xFF);
data_[pos + 1] = static_cast<char>((decoded >> 8) & 0xFF);
data_[pos + 2] = static_cast<char>((decoded >> 16) & 0xFF);
data_[pos + 3] = static_cast<char>((decoded >> 24) & 0xFF);
}
}
seek(originalPos + bytesToProcess);
}
} // namespace frostbite2D

View File

@@ -6,9 +6,11 @@
#include <frostbite2D/graphics/texture.h>
#include <glad/glad.h>
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/scene/scene.h>
#include <frostbite2D/scene/scene_manager.h>
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/utils/binary_reader.h>
using namespace frostbite2D;
@@ -36,7 +38,7 @@ int main(int argc, char **argv) {
SDL_Log("Testing colored quad...");
// 尝试加载精灵
auto sprite = Sprite::createFromFile("assets\\player.png");
auto sprite = Sprite::createFromFile("assets/player.png");
if (sprite) {
sprite->SetPosition(100, 100);
menuScene->AddActor(sprite);
@@ -45,6 +47,7 @@ int main(int argc, char **argv) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from file!");
}
app.run();
app.shutdown();

View File

@@ -16,39 +16,15 @@ target("Frostbite2D")
-- 复制着色器文件到输出目录
after_build(function (target)
-- 复制 shaders 目录
local shaders_dir = path.join(os.projectdir(), "Game/shaders")
-- 复制 assets 目录
local assets_dir = path.join(os.projectdir(), "Game/assets")
local output_dir = target:targetdir()
local target_shaders_dir = path.join(output_dir, "shaders")
local target_assets_dir = path.join(output_dir, "assets")
if os.isdir(shaders_dir) then
-- 确保目标目录存在
if not os.isdir(target_shaders_dir) then
os.mkdir(target_shaders_dir)
end
-- 复制所有着色器文件
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
local filename = path.filename(file)
local target_file = path.join(target_shaders_dir, filename)
os.cp(file, target_file)
end
end
-- 复制图标文件到输出目录
local icons_dir = path.join(os.projectdir(), "assets/icons")
local target_icons_dir = path.join(output_dir, "assets/icons")
if os.isdir(icons_dir) then
if not os.isdir(target_icons_dir) then
os.mkdir(target_icons_dir)
end
for _, file in ipairs(os.files(path.join(icons_dir, "*.*"))) do
local filename = path.filename(file)
local target_file = path.join(target_icons_dir, filename)
os.cp(file, target_file)
print("Copy icon: " .. filename)
end
if os.isdir(assets_dir) then
os.rm(target_assets_dir)
os.cp(assets_dir, output_dir)
print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir)
end
end)
target_end()

View File

@@ -16,26 +16,17 @@ target("Frostbite2D")
add_packages("libsdl2")
add_packages("glm")
-- 复制着色器文件到输出目录
-- 复制 assets 目录到输出目录
after_build(function (target)
-- 复制 shaders 目录
local shaders_dir = path.join(os.projectdir(), "Game/shaders")
-- 复制 assets 目录
local assets_dir = path.join(os.projectdir(), "Game/assets")
local output_dir = target:targetdir()
local target_shaders_dir = path.join(output_dir, "shaders")
local target_assets_dir = path.join(output_dir, "assets")
if os.isdir(shaders_dir) then
-- 确保目标目录存在
if not os.isdir(target_shaders_dir) then
os.mkdir(target_shaders_dir)
end
-- 复制所有着色器文件
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
local filename = path.filename(file)
local target_file = path.join(target_shaders_dir, filename)
os.cp(file, target_file)
print("Copy shader: " .. filename)
end
if os.isdir(assets_dir) then
os.rm(target_assets_dir)
os.cp(assets_dir, output_dir)
print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir)
end
-- 复制 SDL2 DLL (Windows 平台)

View File

@@ -69,23 +69,15 @@ target("Frostbite2D")
print("Generated NRO: " .. nro_file)
end
-- 复制 shaders 目录
local shaders_dir = path.join(os.projectdir(), "Game/shaders")
local target_shaders_dir = path.join(output_dir, "shaders")
-- 复制 assets 目录
local assets_dir = path.join(os.projectdir(), "Game/assets")
local output_dir = target:targetdir()
local target_assets_dir = path.join(output_dir, "assets")
if os.isdir(shaders_dir) then
-- 确保目标目录存在
if not os.isdir(target_shaders_dir) then
os.mkdir(target_shaders_dir)
end
-- 复制所有着色器文件
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
local filename = path.filename(file)
local target_file = path.join(target_shaders_dir, filename)
os.cp(file, target_file)
print("Copy shader: " .. filename)
end
if os.isdir(assets_dir) then
os.rm(target_assets_dir)
os.cp(assets_dir, output_dir)
print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir)
end
end)
target_end()