feat(资源加载): 替换stb_image为SDL_image并添加脚本解析功能

- 将stb_image替换为SDL_image以解决Switch平台兼容性问题
- 添加PVF资源包解析器和脚本解析器功能
- 修改各平台配置文件添加SDL_image依赖
- 更新纹理加载逻辑使用SDL_image API
- 新增脚本解析相关类用于处理游戏脚本数据
This commit is contained in:
2026-03-18 04:18:57 +08:00
parent a4883b433e
commit cb9f497fbb
17 changed files with 1647 additions and 32 deletions

View File

@@ -0,0 +1,46 @@
# 清理 Xmake 缓存并重新构建计划
## 问题描述
- 已在 `sprite.cpp` 中添加 `printf("LoadA")` 调试代码
- 但运行时该调试语句未执行
- 说明 xmake 仍在使用旧的编译缓存
## 解决方案
### 步骤 1: 清理 Xmake 缓存
运行以下命令清理所有编译缓存:
```bash
xmake clean -a
```
或者更彻底的清理(删除整个 build 目录):
```bash
xmake clean
rmdir /s /q build # Windows
# 或者
rm -rf build # Linux/Mac
```
### 步骤 2: 重新配置项目(可选)
为了确保配置也是最新的:
```bash
xmake f -c
```
### 步骤 3: 重新构建 Switch 版本
```bash
xmake build -p switch
```
## 验证步骤
1. 清理完成后,查看 build 目录是否被删除
2. 重新构建时,应该能看到所有源文件被重新编译
3. 运行新构建的 NRO 文件,验证 `printf("LoadA")` 是否输出
## 预期结果
- 所有源文件被重新编译
- 新的修改生效
- `printf("LoadA")` 能够正常输出
- 可以进一步定位真正的崩溃位置

View File

@@ -0,0 +1,66 @@
# 修复 SDL2_image 链接错误计划
## 问题描述
链接时出现大量 undefined reference 错误,主要是关于 libjpeg 的函数:
- `jpeg_calc_output_dimensions`
- `jpeg_CreateDecompress`
- `jpeg_destroy_decompress`
- `jpeg_finish_decompress`
等等
## 问题原因
SDL2_image 依赖多个第三方库来支持不同的图片格式:
- libjpeg - JPEG 格式支持
- libpng - PNG 格式支持
- libtiff - TIFF 格式支持
- libwebp - WebP 格式支持
等等
需要在 switch.lua 中添加这些依赖库的链接。
## 修复方案
### 修改 platform/switch.lua
`add_syslinks` 中添加 SDL2_image 所需的依赖库。
根据 devkitPro/portlibs 的通常配置,需要添加:
- libpng
- libjpeg
- zlib (libpng 依赖)
- 可能还需要其他库
## 具体修复代码
修改 `platform/switch.lua` 中的 `add_syslinks` 部分:
```lua
-- 修改前
add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true})
-- 修改后
add_syslinks("SDL2_mixer", "SDL2_image", "SDL2",
"png", "jpeg", "z", -- SDL2_image 依赖
"opusfile", "opus", "vorbisidec", "ogg",
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true})
```
## 库的顺序说明
链接顺序很重要,被依赖的库要放在后面:
- SDL2_image 依赖 png、jpeg、z
- png 依赖 z
- 所以顺序是: SDL2_image → png → jpeg → z
## 备选方案
如果上面的库还不够,可以尝试添加更多库:
- `tiff` - TIFF 支持
- `webp` - WebP 支持
- `lzma` - 某些图片格式可能需要
## 测试步骤
1. 应用修复
2. 清理缓存
3. 重新编译
4. 验证链接成功

View File

@@ -0,0 +1,169 @@
# 将 stb_image 替换为 SDL_image 完整计划
## 概述
将项目中的 stb_image 完全替换为 SDL_image以解决 Switch 平台兼容性问题。
## 修改文件清单
### 1. 平台配置文件3个文件
- `platform/mingw.lua` - Windows/Mingw 配置
- `platform/linux.lua` - Linux 配置
- `platform/switch.lua` - Switch 配置
### 2. 核心代码文件1个文件
- `Frostbite2D/src/frostbite2D/graphics/texture.cpp` - 纹理加载实现
## 详细修改方案
### 修改 1: platform/mingw.lua
**变更**: 添加 SDL_image 依赖
```lua
-- 修改前
add_requires("libsdl2", {configs = {shared = true}})
add_requires("glm")
-- 修改后
add_requires("libsdl2", {configs = {shared = true}})
add_requires("libsdl2_image", {configs = {shared = true}})
add_requires("glm")
-- 在 add_packages 部分添加
add_packages("libsdl2")
add_packages("libsdl2_image")
add_packages("glm")
```
### 修改 2: platform/linux.lua
**变更**: 添加 SDL_image 依赖
```lua
-- 修改前
add_requires("libsdl2", {configs = {shared = true,wayland = true}})
add_requires("glm")
-- 修改后
add_requires("libsdl2", {configs = {shared = true,wayland = true}})
add_requires("libsdl2_image")
add_requires("glm")
-- 在 add_packages 部分添加
add_packages("libsdl2")
add_packages("libsdl2_image")
add_packages("glm")
```
### 修改 3: platform/switch.lua
**变更**: 添加 SDL2_image 库链接
```lua
-- 修改前
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true})
-- 修改后
add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true})
```
### 修改 4: texture.cpp
**变更**: 完全重写 `loadFromFile` 函数,使用 SDL_image
```cpp
#include <cstdio>
#include <cstdlib>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
// 移除所有 stb_image 相关的定义和 include
// #define STB_IMAGE_IMPLEMENTATION
// #include <stb/stb_image.h>
namespace frostbite2D {
Ptr<Texture> Texture::loadFromFile(const std::string& path) {
Asset& asset = Asset::get();
std::string resolvedPath = asset.resolveAssetPath(path);
std::vector<uint8> fileData;
SDL_Log("LoadQ");
if (!asset.readBinaryFile(path, fileData)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read texture file: %s", resolvedPath.c_str());
return nullptr;
}
SDL_Log("LoadQ1: file size = %zu bytes", fileData.size());
if (fileData.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Texture file is empty: %s", resolvedPath.c_str());
return nullptr;
}
// 使用 SDL_image 从内存加载
SDL_RWops* rw = SDL_RWFromConstMem(fileData.data(), static_cast<int>(fileData.size()));
if (!rw) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops: %s", SDL_GetError());
return nullptr;
}
SDL_Surface* surface = IMG_Load_RW(rw, 1);
if (!surface) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image: %s", IMG_GetError());
SDL_FreeRW(rw);
return nullptr;
}
SDL_Log("LoadQ2: %dx%d, format: %d", surface->w, surface->h, surface->format->format);
// 确定格式
GLenum format;
int channels;
if (surface->format->BytesPerPixel == 4) {
format = GL_RGBA;
channels = 4;
} else {
format = GL_RGB;
channels = 3;
}
int width = surface->w;
int height = surface->h;
uint32_t textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, surface->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
SDL_FreeSurface(surface);
auto texture = Ptr<Texture>(new Texture(width, height, textureID));
texture->path_ = resolvedPath;
texture->channels_ = channels;
SDL_Log("Loaded texture: %s (%dx%d, %d channels)", resolvedPath.c_str(), width, height, channels);
return texture;
}
// ... 其余函数保持不变
}
```
## 实施步骤
1. 修改 3 个平台配置文件,添加 SDL_image 依赖
2. 修改 texture.cpp替换 stb_image 为 SDL_image
3. 清理 xmake 缓存
4. 重新编译项目
5. 测试所有平台
## 注意事项
- SDL_image 支持的图片格式包括: PNG, JPEG, BMP, GIF, TIFF, WebP 等
- 确保所有平台都能正确链接 SDL_image 库
- Switch 平台的 devkitPro/portlibs 应该已经包含 SDL2_image

View File

@@ -0,0 +1,74 @@
# stbi_load_from_memory 失败调试和修复计划
## 问题描述
`stbi_load_from_memory()` 执行失败,但不知道具体失败原因。
## 修复方案
### 1. 添加详细的调试信息
修改 `texture.cpp`,添加以下调试信息:
- 输出读取到的文件大小
- 输出 stbi 的失败原因(使用 `stbi_failure_reason()`
### 2. 修改代码
**文件**: `Frostbite2D/src/frostbite2D/graphics/texture.cpp`
**变更**:
```cpp
Ptr<Texture> Texture::loadFromFile(const std::string& path) {
Asset& asset = Asset::get();
std::string resolvedPath = asset.resolveAssetPath(path);
std::vector<uint8> fileData;
SDL_Log("LoadQ");
if (!asset.readBinaryFile(path, fileData)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read texture file: %s", resolvedPath.c_str());
return nullptr;
}
SDL_Log("LoadQ1: file size = %zu bytes", fileData.size());
// 检查文件是否为空
if (fileData.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Texture file is empty: %s", resolvedPath.c_str());
return nullptr;
}
int width, height, channels;
uint8* data = stbi_load_from_memory(fileData.data(), static_cast<int>(fileData.size()), &width, &height, &channels, 0);
SDL_Log("LoadQ2");
if (!data) {
const char* failureReason = stbi_failure_reason();
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s, reason: %s", resolvedPath.c_str(), failureReason ? failureReason : "unknown");
return nullptr;
}
SDL_Log("LoadQ3: %dx%d, %d channels", width, height, channels);
// ... 其余代码保持不变
}
```
## 调试步骤
1. 应用上述修改
2. 重新编译并运行
3. 查看日志输出,特别注意:
- `file size` 的值是多少
- `stbi_failure_reason()` 输出的失败原因是什么
## 根据失败原因的可能解决方案
### 如果失败原因是 "png" 相关
- 检查 PNG 文件是否损坏
- 尝试用其他图片格式(如 BMP测试
### 如果失败原因是 "out of memory"
- 检查图片是否太大
- 考虑调整 stbi 的内存分配设置
### 如果文件大小为 0
- 检查文件路径是否正确
- 检查 Asset 类的读取是否有问题
- 检查文件是否存在于 Switch 上
### 如果是其他原因
- 根据具体的失败原因进一步分析

View File

@@ -0,0 +1,68 @@
# Switch 平台 stbi_load_from_memory 崩溃修复计划
## 问题描述
- 文件读取成功129244 字节
- 但调用 `stbi_load_from_memory()` 时直接崩溃
- 说明 stbi_image 在 Switch 平台上有兼容性问题
## 可能的原因
1. **stbi_image 的内存分配问题** - stbi 默认使用 malloc/free在 Switch 上可能有问题
2. **缺少 stbi_image 的平台特定配置**
3. **SIMD 优化问题** - stbi_image 的 ARM NEON 优化在 Switch 上可能有问题
4. **栈溢出** - stbi_image 可能在栈上分配了太大的结构
## 修复方案
### 方案 1: 配置 stbi_image 使用自定义内存分配(推荐)
修改 texture.cpp在 include stb_image.h 之前定义自定义的内存分配函数。
### 方案 2: 禁用 stbi_image 的 SIMD 优化
定义 `STBI_NO_SIMD` 来禁用 NEON 优化。
### 方案 3: 两者结合(最稳妥)
同时使用自定义内存分配和禁用 SIMD 优化。
## 具体修复代码
修改 `Frostbite2D/src/frostbite2D/graphics/texture.cpp` 的开头部分:
```cpp
#include <cstdio>
#include <cstdlib>
// 自定义 stbi_image 的内存分配函数
#define STBI_MALLOC(sz) malloc(sz)
#define STBI_REALLOC(p,sz) realloc(p,sz)
#define STBI_FREE(p) free(p)
// 禁用 SIMD 优化,避免 Switch 平台兼容性问题
#define STBI_NO_SIMD
#define STB_IMAGE_IMPLEMENTATION
#include "SDL_log.h"
#include <SDL2/SDL.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/utils/asset.h>
#include <stb/stb_image.h>
#include <glad/glad.h>
```
## 额外调试建议
如果上述修复后仍然崩溃,可以添加更多调试来定位具体位置:
1. 在 stbi_load_from_memory 调用前后添加 printf
2. 尝试用最简单的图片(小尺寸 BMP测试
3. 检查 stbi_image 的版本
## 测试步骤
1. 应用修复
2. 清理缓存并重新编译
3. 运行测试
4. 查看是否仍然崩溃
## 预期结果
- stbi_load_from_memory 不再崩溃
- 图片能够正常加载
- LoadQ2 和 LoadQ3 日志能够正常输出

View File

@@ -0,0 +1,74 @@
# Switch 平台 Asset 类崩溃修复计划
## 问题描述
调用 `Sprite::createFromFile("assets/player.png")` 时崩溃,甚至连 printf 都没执行。崩溃发生在 `Asset::readBinaryFile()` 内部的 `fs::exists()` 调用上。
## 问题根因分析
Switch 平台上的 `std::filesystem` 实现可能不稳定或不完整,导致 `fs::exists()` 调用直接崩溃。
## 修复方案
修改 `Asset::readBinaryFile()` 函数,**移除文件存在性检查**,直接尝试打开文件。这样可以避免调用有问题的 `fs::exists()`
### 修复: 修改 asset.cpp
**文件**: `Frostbite2D/src/frostbite2D/utils/asset.cpp`
**变更**:
- 移除 `readBinaryFile()` 中的 `exists()` 检查
- 直接尝试打开文件,通过 `file.is_open()` 来判断是否成功
**代码修改**:
```cpp
bool Asset::readBinaryFile(const std::string &path,
std::vector<uint8> &outData) {
std::string fullPath = resolveFullPath(path);
// 移除 fs::exists() 检查,直接尝试打开文件
std::ifstream file(toPath(fullPath), std::ios::in | std::ios::binary);
if (!file.is_open()) {
return false;
}
file.seekg(0, std::ios::end);
auto size = file.tellg();
file.seekg(0, std::ios::beg);
outData.resize(static_cast<size_t>(size));
file.read(reinterpret_cast<char *>(outData.data()), size);
file.close();
return true;
}
```
### 可选: 同时修复 readTextFile
为了保持一致性,也可以对 `readTextFile()` 做同样的修改:
```cpp
bool Asset::readTextFile(const std::string &path, std::string &outContent) {
std::string fullPath = resolveFullPath(path);
// 移除 fs::exists() 检查,直接尝试打开文件
std::ifstream file(toPath(fullPath), std::ios::in | std::ios::binary);
if (!file.is_open()) {
return false;
}
std::ostringstream ss;
ss << file.rdbuf();
outContent = ss.str();
file.close();
return true;
}
```
## 测试步骤
1. 应用修复
2. 重新构建 Switch 版本: `xmake build -p switch`
3. 在 Switch 上运行生成的 NRO 文件
4. 验证图片能正常加载
## 预期结果
- 不再崩溃
- 文件能正常读取
- 图片能正常加载和显示

View File

@@ -0,0 +1,51 @@
# Switch 平台 stbi_load 崩溃修复计划
## 问题描述
在 Switch 平台运行时,调用 `Sprite::createFromFile("assets/player.png")` 会在 `stbi_load` 这一行崩溃。
## 修复方案(简化版)
只修改一个文件:使用 Asset 类读取文件到内存,再用 `stbi_load_from_memory` 加载。
### 修复: 修改 texture.cpp
**文件**: `Frostbite2D/src/frostbite2D/graphics/texture.cpp`
**变更**:
- 使用 `Asset::readBinaryFile()` 读取文件到内存
- 使用 `stbi_load_from_memory()` 替代 `stbi_load()`
**代码修改**:
```cpp
Ptr<Texture> Texture::loadFromFile(const std::string& path) {
Asset& asset = Asset::get();
std::vector<uint8> fileData;
if (!asset.readBinaryFile(path, fileData)) {
std::string resolvedPath = asset.resolveAssetPath(path);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read texture file: %s", resolvedPath.c_str());
return nullptr;
}
int width, height, channels;
uint8* data = stbi_load_from_memory(fileData.data(), static_cast<int>(fileData.size()), &width, &height, &channels, 0);
if (!data) {
std::string resolvedPath = asset.resolveAssetPath(path);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", resolvedPath.c_str());
return nullptr;
}
// ... 其余代码保持不变
}
```
## 测试步骤
1. 应用修复
2. 重新构建 Switch 版本: `xmake build -p switch`
3. 在 Switch 上运行生成的 NRO 文件
4. 验证图片能正常加载
## 预期结果
- stbi_load 不再崩溃
- 图片能正常加载和显示
- 程序能正常运行

View File

@@ -0,0 +1,222 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/utils/binary_reader.h>
#include <optional>
#include <string>
#include <map>
#include <vector>
namespace frostbite2D {
/**
* @brief PVF 文件信息结构体
*/
struct PvfFileInfo {
size_t offset = 0; ///< 相对于数据起始位置的偏移
uint32 crc32 = 0; ///< CRC32 校验值
size_t length = 0; ///< 文件长度(字节)
bool decoded = false; ///< 是否已解码
};
/**
* @brief 原始文件数据结构体
*/
struct RawData {
std::unique_ptr<char[]> data; ///< 原始数据指针(智能指针自动管理内存)
size_t size; ///< 数据大小(字节)
};
/**
* @brief PVF 资源包归档类
*
* 用于读取和解析 PVF 格式的游戏资源包文件。
* 提供文件索引管理、字符串资源访问和文件内容读取功能。
*
* @example
* auto& archive = PvfArchive::get();
* if (archive.open("Script.pvf")) {
* archive.init();
* auto content = archive.getFileContent("script/example.txt");
* }
*/
class PvfArchive {
public:
/**
* @brief 获取单例实例
* @return PVF 归档实例引用
*/
static PvfArchive& get();
PvfArchive(const PvfArchive&) = delete;
PvfArchive& operator=(const PvfArchive&) = delete;
PvfArchive(PvfArchive&&) = delete;
PvfArchive& operator=(PvfArchive&&) = delete;
// ---------------------------------------------------------------------------
// 文件操作
// ---------------------------------------------------------------------------
/**
* @brief 打开 PVF 文件
* @param filePath 文件路径
* @return 打开成功返回 true
*/
bool open(const std::string& filePath = "Script.pvf");
/**
* @brief 关闭并清空所有数据
*/
void close();
/**
* @brief 检查是否已打开
* @return 已打开返回 true
*/
bool isOpen() const;
// ---------------------------------------------------------------------------
// 初始化
// ---------------------------------------------------------------------------
/**
* @brief 完整初始化(解析头部、加载字符串表)
*/
void init();
/**
* @brief 解析 PVF 文件头部并建立文件索引
*/
void initHeader();
/**
* @brief 加载二进制字符串表 (stringtable.bin)
*/
void initBinStringTable();
/**
* @brief 加载本地化字符串 (n_string.lst)
*/
void initLoadStrings();
// ---------------------------------------------------------------------------
// 文件信息查询
// ---------------------------------------------------------------------------
/**
* @brief 检查文件是否存在
* @param path 文件路径
* @return 存在返回 true
*/
bool hasFile(const std::string& path) const;
/**
* @brief 获取文件信息
* @param path 文件路径
* @return 文件信息,不存在返回 std::nullopt
*/
std::optional<PvfFileInfo> getFileInfo(const std::string& path) const;
/**
* @brief 获取所有文件路径列表
* @return 文件路径列表
*/
std::vector<std::string> listFiles() const;
// ---------------------------------------------------------------------------
// 文件内容读取
// ---------------------------------------------------------------------------
/**
* @brief 获取文件内容作为字符串
* @param path 文件路径
* @return 文件内容,失败返回 std::nullopt
*/
std::optional<std::string> getFileContent(const std::string& path);
/**
* @brief 获取文件内容作为字节数组
* @param path 文件路径
* @return 字节数组,失败返回 std::nullopt
*/
std::optional<std::vector<uint8>> getFileBytes(const std::string& path);
/**
* @brief 获取文件原始数据(使用智能指针管理内存,已包含 CRC 解密)
*
* 与原始 GetFileContentChar 功能相同,但使用智能指针避免内存泄漏。
* 数据会在首次访问时自动进行 CRC 解密。
*
* @param path 文件路径
* @return RawData 结构体,包含数据指针和大小;失败返回 std::nullopt
*/
std::optional<RawData> getFileRawData(const std::string& path);
// ---------------------------------------------------------------------------
// 字符串资源访问
// ---------------------------------------------------------------------------
/**
* @brief 获取二进制字符串
* @param key 字符串键
* @return 字符串,不存在返回 std::nullopt
*/
std::optional<std::string> getBinString(int key) const;
/**
* @brief 获取本地化字符串
* @param type 字符串类型
* @param key 字符串键
* @return 字符串,不存在返回 std::nullopt
*/
std::optional<std::string> getLoadString(const std::string& type, const std::string& key) const;
/**
* @brief 检查二进制字符串是否存在
* @param key 字符串键
* @return 存在返回 true
*/
bool hasBinString(int key) const;
/**
* @brief 检查本地化字符串是否存在
* @param type 字符串类型
* @param key 字符串键
* @return 存在返回 true
*/
bool hasLoadString(const std::string& type, const std::string& key) const;
private:
PvfArchive() = default;
~PvfArchive() = default;
/**
* @brief 规范化路径(转为小写)
* @param path 原始路径
* @return 规范化后的路径
*/
std::string normalizePath(const std::string& path) const;
/**
* @brief 分割字符串
* @param str 要分割的字符串
* @param delimiter 分隔符
* @return 分割后的字符串列表
*/
std::vector<std::string> splitString(const std::string& str, const std::string& delimiter) const;
/**
* @brief 解码文件内容(内部使用)
* @param info 文件信息(会修改 decoded 标志)
* @return 成功返回 true
*/
bool decodeFile(PvfFileInfo& info);
BinaryReader reader_; ///< 二进制读取器
size_t dataStartPos_ = 0; ///< 数据起始位置
std::map<std::string, PvfFileInfo> fileInfo_; ///< 文件信息映射
std::map<int, std::string> binStringTable_; ///< 二进制字符串表
std::map<std::string, std::map<std::string, std::string>> loadStrings_; ///< 本地化字符串
};
} // namespace frostbite2D

View File

@@ -0,0 +1,208 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/utils/pvf_archive.h>
#include <optional>
#include <string>
#include <vector>
#include <cstring>
namespace frostbite2D {
/**
* @brief 脚本指令操作码枚举
*/
enum class ScriptOpcode : uint8 {
Integer = 2, ///< 整数值
Float = 4, ///< 浮点值
StringRef5 = 5, ///< 二进制字符串引用类型5
StringRef6 = 6, ///< 二进制字符串引用类型6
StringRef7 = 7, ///< 二进制字符串引用类型7
StringRef8 = 8, ///< 二进制字符串引用类型8
ExtendedString9 = 9, ///< 扩展字符串(带额外字节)
ExtendedString10 = 10, ///< 扩展字符串
};
/**
* @brief 脚本值类型
*/
enum class ScriptValueType {
Empty, ///< 空值
Integer, ///< 整数值
Float, ///< 浮点值
String, ///< 字符串值
};
/**
* @brief 脚本解析结果值
*/
struct ScriptValue {
ScriptValueType type = ScriptValueType::Empty; ///< 值类型
int32 intValue = 0; ///< 整数值type == Integer 时有效)
double floatValue = 0.0; ///< 浮点值type == Float 时有效)
std::string stringValue; ///< 字符串值type == String 时有效)
/**
* @brief 判断是否为空值
*/
bool isEmpty() const { return type == ScriptValueType::Empty; }
/**
* @brief 转换为字符串表示
*/
std::string toString() const;
};
/**
* @brief 脚本二进制数据解析器
*
* 用于解析 PVF 存档中的脚本二进制数据。
* 每条指令为 5 字节1 字节操作码 + 4 字节数据。
* 支持与 PvfArchive 交互获取字符串资源。
*
* @example
* auto& archive = PvfArchive::get();
* if (auto rawData = archive.getFileRawData("script/example.bin")) {
* ScriptParser parser(*rawData, "script/example.bin");
*
* // 迭代解析
* while (!parser.isEnd()) {
* if (auto value = parser.next()) {
* // 处理 value
* }
* }
*
* // 或者一次性解析所有
* auto allValues = parser.parseAll();
* }
*/
class ScriptParser {
public:
/**
* @brief 从 RawData 构造
* @param data 原始数据
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const RawData& data, const std::string& filePath);
/**
* @brief 从字节数组构造
* @param data 字节数组
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const std::vector<uint8>& data, const std::string& filePath);
/**
* @brief 从指针和大小构造
* @param data 数据指针
* @param size 数据大小
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const char* data, size_t size, const std::string& filePath);
// ---------------------------------------------------------------------------
// 迭代器风格 API
// ---------------------------------------------------------------------------
/**
* @brief 获取下一个值
* @return 解析的值,失败或结束返回 std::nullopt
*/
std::optional<ScriptValue> next();
/**
* @brief 回退一个指令
*/
void back();
/**
* @brief 检查是否已到达数据末尾
* @return 到达末尾返回 true
*/
bool isEnd() const;
/**
* @brief 重置解析位置到开头
*/
void reset();
// ---------------------------------------------------------------------------
// 批量解析
// ---------------------------------------------------------------------------
/**
* @brief 解析所有值
* @return 所有解析值的列表
*/
std::vector<ScriptValue> parseAll();
// ---------------------------------------------------------------------------
// 状态查询
// ---------------------------------------------------------------------------
/**
* @brief 获取当前解析位置
* @return 当前位置(字节偏移)
*/
size_t position() const;
/**
* @brief 获取数据总大小
* @return 数据大小(字节)
*/
size_t size() const;
/**
* @brief 获取文件路径
* @return 文件路径
*/
const std::string& filePath() const;
/**
* @brief 获取文件类型(从路径提取)
* @return 文件类型
*/
const std::string& fileType() const;
/**
* @brief 检查解析器是否有效(有数据)
* @return 有效返回 true
*/
bool isValid() const;
private:
/**
* @brief 从文件路径提取文件类型
* @param filePath 文件路径
* @return 文件类型
*/
std::string extractFileType(const std::string& filePath) const;
/**
* @brief 读取 1 字节
* @param offset 偏移位置
* @return 字节值
*/
uint8 readByte(size_t offset) const;
/**
* @brief 读取 4 字节整数
* @param offset 偏移位置
* @return 整数值
*/
int32 readInt32(size_t offset) const;
/**
* @brief 解析单个值
* @param offset 偏移位置
* @return 解析的值
*/
std::optional<ScriptValue> parseValueAt(size_t offset) const;
std::vector<char> data_; ///< 数据副本
size_t position_ = 2; ///< 当前解析位置(从第 2 字节开始)
std::string filePath_; ///< 文件路径
std::string fileType_; ///< 文件类型
};
} // namespace frostbite2D

View File

@@ -18,9 +18,9 @@ Sprite::Sprite(Ptr<Texture> texture)
Sprite::~Sprite() { Sprite::~Sprite() {
} }
Ptr<Sprite> Sprite::createFromFile(const std::string& path) { Ptr<Sprite> Sprite::createFromFile(const std::string &path) {
Ptr<Texture> texture = Texture::loadFromFile(path); Ptr<Texture> texture = Texture::loadFromFile(path);
if (!texture) { if (!texture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", path.c_str()); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", path.c_str());
return nullptr; return nullptr;
} }

View File

@@ -1,9 +1,10 @@
#define STB_IMAGE_IMPLEMENTATION
#include "SDL_log.h" #include "SDL_log.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <cstdio>
#include <cstdlib>
#include <frostbite2D/graphics/texture.h> #include <frostbite2D/graphics/texture.h>
#include <frostbite2D/utils/asset.h> #include <frostbite2D/utils/asset.h>
#include <stb/stb_image.h>
#include <glad/glad.h> #include <glad/glad.h>
namespace frostbite2D { namespace frostbite2D {
@@ -21,22 +22,55 @@ Texture::~Texture() {
Ptr<Texture> Texture::loadFromFile(const std::string& path) { Ptr<Texture> Texture::loadFromFile(const std::string& path) {
Asset& asset = Asset::get(); Asset& asset = Asset::get();
std::string resolvedPath = asset.resolveAssetPath(path); std::string resolvedPath = asset.resolveAssetPath(path);
std::vector<uint8> fileData;
int width, height, channels; if (!asset.readBinaryFile(path, fileData)) {
uint8* data = stbi_load(resolvedPath.c_str(), &width, &height, &channels, 0); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to read texture file: %s", resolvedPath.c_str());
if (!data) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", resolvedPath.c_str());
return nullptr; return nullptr;
} }
uint32_t format = channels == 4 ? GL_RGBA : GL_RGB; if (fileData.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Texture file is empty: %s",
resolvedPath.c_str());
return nullptr;
}
SDL_RWops *rw =
SDL_RWFromConstMem(fileData.data(), static_cast<int>(fileData.size()));
if (!rw) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops: %s",
SDL_GetError());
return nullptr;
}
SDL_Surface *surface = IMG_Load_RW(rw, 1);
if (!surface) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image: %s",
IMG_GetError());
SDL_FreeRW(rw);
return nullptr;
}
GLenum format;
int channels;
if (surface->format->BytesPerPixel == 4) {
format = GL_RGBA;
channels = 4;
} else {
format = GL_RGB;
channels = 3;
}
int width = surface->w;
int height = surface->h;
uint32_t textureID; uint32_t textureID;
glGenTextures(1, &textureID); glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID); glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format,
GL_UNSIGNED_BYTE, surface->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@@ -44,14 +78,13 @@ Ptr<Texture> Texture::loadFromFile(const std::string& path) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
stbi_image_free(data);
SDL_FreeSurface(surface);
auto texture = Ptr<Texture>(new Texture(width, height, textureID)); auto texture = Ptr<Texture>(new Texture(width, height, textureID));
texture->path_ = resolvedPath; texture->path_ = resolvedPath;
texture->channels_ = channels; texture->channels_ = channels;
SDL_Log("Loaded texture: %s (%dx%d, %d channels)", resolvedPath.c_str(), width, height, channels);
return texture; return texture;
} }

View File

@@ -0,0 +1,334 @@
#include <frostbite2D/utils/pvf_archive.h>
#include <algorithm>
#include <cctype>
#include <SDL.h>
namespace frostbite2D {
PvfArchive& PvfArchive::get() {
static PvfArchive instance;
return instance;
}
bool PvfArchive::open(const std::string& filePath) {
close();
if (!reader_.open(filePath)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: 无法打开文件: %s", filePath.c_str());
return false;
}
return true;
}
void PvfArchive::close() {
reader_.close();
dataStartPos_ = 0;
fileInfo_.clear();
binStringTable_.clear();
loadStrings_.clear();
}
bool PvfArchive::isOpen() const {
return reader_.isOpen();
}
void PvfArchive::init() {
if (!isOpen()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: 请先打开文件再初始化");
return;
}
initHeader();
initBinStringTable();
initLoadStrings();
}
void PvfArchive::initHeader() {
if (!isOpen()) {
return;
}
reader_.seek(0);
int32 uuidLength = reader_.readInt32();
std::string uuid = reader_.readString(uuidLength);
(void)uuid;
int32 version = reader_.readInt32();
(void)version;
int32 alignedIndexHeaderSize = reader_.readInt32();
int32 indexHeaderCrc = reader_.readInt32();
int32 indexSize = reader_.readInt32();
size_t firstPos = reader_.tell();
reader_.crcDecode(alignedIndexHeaderSize, indexHeaderCrc);
dataStartPos_ = alignedIndexHeaderSize + 56;
size_t currPos = 0;
for (int32 i = 0; i < indexSize; ++i) {
reader_.seek(firstPos + currPos);
int32 fileNumber = reader_.readInt32();
(void)fileNumber;
int32 filePathLength = reader_.readInt32();
std::string fileName = normalizePath(reader_.readString(filePathLength));
int32 fileLength = reader_.readInt32();
int32 crc32 = reader_.readInt32();
int32 relativeOffset = reader_.readInt32();
if (fileLength > 0) {
int32 realFileLength = (fileLength + 3) & 0xFFFFFFFC;
PvfFileInfo info;
info.offset = relativeOffset;
info.crc32 = static_cast<uint32>(crc32);
info.length = realFileLength;
info.decoded = false;
fileInfo_[fileName] = info;
}
currPos += 20;
currPos += filePathLength;
}
}
void PvfArchive::initBinStringTable() {
std::string tablePath = "stringtable.bin";
auto infoOpt = getFileInfo(tablePath);
if (!infoOpt.has_value()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: stringtable.bin 文件不存在");
return;
}
PvfFileInfo info = infoOpt.value();
reader_.seek(dataStartPos_ + info.offset);
reader_.crcDecode(info.length, info.crc32);
reader_.seek(dataStartPos_ + info.offset);
size_t fileHeaderPos = reader_.tell();
int32 count = reader_.readInt32();
for (int32 i = 0; i < count; ++i) {
reader_.seek(fileHeaderPos + i * 4 + 4);
int32 startPos = reader_.readInt32();
int32 endPos = reader_.readInt32();
int32 len = endPos - startPos;
reader_.seek(fileHeaderPos + startPos + 4);
std::string str = reader_.readString(len);
binStringTable_[i] = str;
}
}
void PvfArchive::initLoadStrings() {
std::string lstPath = "n_string.lst";
auto infoOpt = getFileInfo(lstPath);
if (!infoOpt.has_value()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: n_string.lst 文件不存在");
return;
}
PvfFileInfo info = infoOpt.value();
reader_.seek(dataStartPos_ + info.offset);
reader_.crcDecode(info.length, info.crc32);
reader_.seek(dataStartPos_ + info.offset);
size_t fileHeaderPos = reader_.tell();
int16 flag = reader_.readInt16();
(void)flag;
size_t i = 2;
while (i < info.length) {
if ((info.length - i) >= 10) {
reader_.seek(fileHeaderPos + i + 6);
int32 findKey = reader_.readInt32();
auto binStrOpt = getBinString(findKey);
if (!binStrOpt.has_value()) {
i += 10;
continue;
}
std::string key = binStrOpt.value();
size_t slashPos = key.find('/');
std::string type = (slashPos != std::string::npos) ? normalizePath(key.substr(0, slashPos)) : "";
if (!key.empty()) {
auto fileInfoOpt = getFileInfo(key);
if (fileInfoOpt.has_value()) {
auto contentOpt = getFileContent(key);
if (contentOpt.has_value()) {
std::string content = contentOpt.value();
std::vector<std::string> lines = splitString(content, "\n");
for (const auto& line : lines) {
size_t gtPos = line.find('>');
if (gtPos != std::string::npos && gtPos + 1 < line.length()) {
std::string strKey = line.substr(0, gtPos);
std::string strValue = line.substr(gtPos + 1);
loadStrings_[type][strKey] = strValue;
}
}
}
}
}
} else {
break;
}
i += 10;
}
}
bool PvfArchive::hasFile(const std::string& path) const {
return fileInfo_.find(normalizePath(path)) != fileInfo_.end();
}
std::optional<PvfFileInfo> PvfArchive::getFileInfo(const std::string& path) const {
std::string normalizedPath = normalizePath(path);
auto it = fileInfo_.find(normalizedPath);
if (it != fileInfo_.end()) {
return it->second;
}
return std::nullopt;
}
std::vector<std::string> PvfArchive::listFiles() const {
std::vector<std::string> files;
files.reserve(fileInfo_.size());
for (const auto& pair : fileInfo_) {
files.push_back(pair.first);
}
return files;
}
std::optional<std::string> PvfArchive::getFileContent(const std::string& path) {
std::string normalizedPath = normalizePath(path);
auto it = fileInfo_.find(normalizedPath);
if (it == fileInfo_.end()) {
return std::nullopt;
}
PvfFileInfo& info = it->second;
if (!decodeFile(info)) {
return std::nullopt;
}
reader_.seek(dataStartPos_ + info.offset);
std::vector<uint8> bytes = reader_.readBytes(info.length);
return std::string(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
std::optional<std::vector<uint8>> PvfArchive::getFileBytes(const std::string& path) {
std::string normalizedPath = normalizePath(path);
auto it = fileInfo_.find(normalizedPath);
if (it == fileInfo_.end()) {
return std::nullopt;
}
PvfFileInfo& info = it->second;
if (!decodeFile(info)) {
return std::nullopt;
}
reader_.seek(dataStartPos_ + info.offset);
return reader_.readBytes(info.length);
}
std::optional<RawData> PvfArchive::getFileRawData(const std::string& path) {
std::string normalizedPath = normalizePath(path);
auto it = fileInfo_.find(normalizedPath);
if (it == fileInfo_.end()) {
return std::nullopt;
}
PvfFileInfo& info = it->second;
if (!decodeFile(info)) {
return std::nullopt;
}
reader_.seek(dataStartPos_ + info.offset);
RawData result;
result.size = info.length;
result.data = std::make_unique<char[]>(info.length);
reader_.read(result.data.get(), info.length);
return result;
}
std::optional<std::string> PvfArchive::getBinString(int key) const {
auto it = binStringTable_.find(key);
if (it != binStringTable_.end()) {
return it->second;
}
return std::nullopt;
}
std::optional<std::string> PvfArchive::getLoadString(const std::string& type, const std::string& key) const {
std::string normalizedType = normalizePath(type);
auto typeIt = loadStrings_.find(normalizedType);
if (typeIt != loadStrings_.end()) {
auto keyIt = typeIt->second.find(key);
if (keyIt != typeIt->second.end()) {
return keyIt->second;
}
}
return std::nullopt;
}
bool PvfArchive::hasBinString(int key) const {
return binStringTable_.find(key) != binStringTable_.end();
}
bool PvfArchive::hasLoadString(const std::string& type, const std::string& key) const {
std::string normalizedType = normalizePath(type);
auto typeIt = loadStrings_.find(normalizedType);
if (typeIt != loadStrings_.end()) {
return typeIt->second.find(key) != typeIt->second.end();
}
return false;
}
std::string PvfArchive::normalizePath(const std::string& path) const {
std::string result = path;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
std::vector<std::string> PvfArchive::splitString(const std::string& str, const std::string& delimiter) const {
std::vector<std::string> tokens;
size_t pos = 0;
size_t found;
while ((found = str.find(delimiter, pos)) != std::string::npos) {
tokens.push_back(str.substr(pos, found - pos));
pos = found + delimiter.length();
}
if (pos < str.length()) {
tokens.push_back(str.substr(pos));
}
return tokens;
}
bool PvfArchive::decodeFile(PvfFileInfo& info) {
if (info.decoded) {
return true;
}
reader_.seek(dataStartPos_ + info.offset);
reader_.crcDecode(info.length, info.crc32);
info.decoded = true;
return true;
}
} // namespace frostbite2D

View File

@@ -0,0 +1,222 @@
#include <frostbite2D/utils/script_parser.h>
#include <frostbite2D/utils/pvf_archive.h>
#include <algorithm>
#include <cctype>
#include <SDL.h>
namespace frostbite2D {
std::string ScriptValue::toString() const {
switch (type) {
case ScriptValueType::Integer:
return std::to_string(intValue);
case ScriptValueType::Float:
return std::to_string(floatValue);
case ScriptValueType::String:
return stringValue;
case ScriptValueType::Empty:
default:
return "";
}
}
ScriptParser::ScriptParser(const RawData& data, const std::string& filePath)
: filePath_(filePath)
, fileType_(extractFileType(filePath)) {
if (data.data && data.size > 0) {
data_.resize(data.size);
std::memcpy(data_.data(), data.data.get(), data.size);
}
if (data_.size() < 7) {
data_.clear();
}
}
ScriptParser::ScriptParser(const std::vector<uint8>& data, const std::string& filePath)
: filePath_(filePath)
, fileType_(extractFileType(filePath)) {
if (data.size() > 0) {
data_.resize(data.size());
std::memcpy(data_.data(), data.data(), data.size());
}
if (data_.size() < 7) {
data_.clear();
}
}
ScriptParser::ScriptParser(const char* data, size_t size, const std::string& filePath)
: filePath_(filePath)
, fileType_(extractFileType(filePath)) {
if (data && size > 0) {
data_.resize(size);
std::memcpy(data_.data(), data, size);
}
if (data_.size() < 7) {
data_.clear();
}
}
std::optional<ScriptValue> ScriptParser::next() {
if (!isValid() || isEnd()) {
return std::nullopt;
}
auto value = parseValueAt(position_);
if (value.has_value()) {
position_ += 5;
}
return value;
}
void ScriptParser::back() {
if (position_ >= 7) {
position_ -= 5;
}
}
bool ScriptParser::isEnd() const {
return !isValid() || position_ >= data_.size() || (data_.size() - position_) < 5;
}
void ScriptParser::reset() {
position_ = 2;
}
std::vector<ScriptValue> ScriptParser::parseAll() {
std::vector<ScriptValue> values;
reset();
while (!isEnd()) {
if (auto value = next()) {
values.push_back(*value);
}
}
return values;
}
size_t ScriptParser::position() const {
return position_;
}
size_t ScriptParser::size() const {
return data_.size();
}
const std::string& ScriptParser::filePath() const {
return filePath_;
}
const std::string& ScriptParser::fileType() const {
return fileType_;
}
bool ScriptParser::isValid() const {
return !data_.empty();
}
std::string ScriptParser::extractFileType(const std::string& filePath) const {
size_t slashPos = filePath.find_first_of("/");
if (slashPos == std::string::npos) {
return "";
}
std::string type = filePath.substr(0, slashPos);
std::transform(type.begin(), type.end(), type.begin(),
[](unsigned char c) { return std::tolower(c); });
return type;
}
uint8 ScriptParser::readByte(size_t offset) const {
if (offset >= data_.size()) {
return 0;
}
return static_cast<uint8>(data_[offset]);
}
int32 ScriptParser::readInt32(size_t offset) const {
if (offset + 4 > data_.size()) {
return 0;
}
int32 value;
std::memcpy(&value, data_.data() + offset, sizeof(int32));
return value;
}
std::optional<ScriptValue> ScriptParser::parseValueAt(size_t offset) const {
if (offset + 5 > data_.size()) {
return std::nullopt;
}
ScriptValue result;
PvfArchive& archive = PvfArchive::get();
uint8 opcode = readByte(offset);
int32 data = readInt32(offset + 1);
switch (static_cast<ScriptOpcode>(opcode)) {
case ScriptOpcode::Integer: {
result.type = ScriptValueType::Integer;
result.intValue = data;
break;
}
case ScriptOpcode::Float: {
result.type = ScriptValueType::Float;
float floatValue;
std::memcpy(&floatValue, &data, sizeof(float));
result.floatValue = floatValue;
break;
}
case ScriptOpcode::StringRef5:
case ScriptOpcode::StringRef6:
case ScriptOpcode::StringRef7:
case ScriptOpcode::StringRef8: {
result.type = ScriptValueType::String;
if (auto str = archive.getBinString(data)) {
result.stringValue = *str;
}
break;
}
case ScriptOpcode::ExtendedString9: {
result.type = ScriptValueType::String;
if (offset + 10 <= data_.size()) {
uint8 newOpcode = readByte(offset + 5);
(void)newOpcode;
int32 newData = readInt32(offset + 6);
if (auto binStr = archive.getBinString(newData)) {
if (!binStr->empty()) {
if (auto loadStr = archive.getLoadString(fileType_, *binStr)) {
result.stringValue = *loadStr;
}
}
}
}
break;
}
case ScriptOpcode::ExtendedString10: {
result.type = ScriptValueType::String;
if (auto binStr = archive.getBinString(data)) {
if (!binStr->empty()) {
if (auto loadStr = archive.getLoadString(fileType_, *binStr)) {
result.stringValue = *loadStr;
}
}
}
break;
}
default: {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ScriptParser: 未知的操作码: %d", opcode);
return std::nullopt;
}
}
return result;
}
} // namespace frostbite2D

View File

@@ -10,7 +10,8 @@
#include <frostbite2D/scene/scene.h> #include <frostbite2D/scene/scene.h>
#include <frostbite2D/scene/scene_manager.h> #include <frostbite2D/scene/scene_manager.h>
#include <frostbite2D/utils/binary_reader.h> #include <frostbite2D/utils/binary_reader.h>
#include <frostbite2D/utils/pvf_archive.h>
#include <frostbite2D/utils/script_parser.h>
using namespace frostbite2D; using namespace frostbite2D;
@@ -18,8 +19,8 @@ int main(int argc, char **argv) {
AppConfig config = AppConfig::createDefault(); AppConfig config = AppConfig::createDefault();
config.appName = "Frostbite2D Test App"; config.appName = "Frostbite2D Test App";
config.appVersion = "1.0.0"; config.appVersion = "1.0.0";
config.windowConfig.width = 800; config.windowConfig.width = 1920;
config.windowConfig.height = 600; config.windowConfig.height = 1080;
config.windowConfig.title = "Frostbite2D - Renderer Test"; config.windowConfig.title = "Frostbite2D - Renderer Test";
Application& app = Application::get(); Application& app = Application::get();
@@ -34,9 +35,6 @@ int main(int argc, char **argv) {
auto menuScene = MakePtr<Scene>(); auto menuScene = MakePtr<Scene>();
SceneManager::get().PushScene(menuScene); SceneManager::get().PushScene(menuScene);
// 先测试彩色四边形,排除纹理问题
SDL_Log("Testing colored quad...");
// 尝试加载精灵 // 尝试加载精灵
auto sprite = Sprite::createFromFile("assets/player.png"); auto sprite = Sprite::createFromFile("assets/player.png");
if (sprite) { if (sprite) {
@@ -47,6 +45,52 @@ int main(int argc, char **argv) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from file!"); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from file!");
} }
auto &archive = PvfArchive::get();
if (archive.open("assets/Script.pvf")) {
archive.init();
// 文件内容读取
if (auto rawData = archive.getFileRawData("region/balmayer_north.rgn")) {
ScriptParser parser(*rawData, "script/example.bin");
// // 方式1迭代解析
// while (!parser.isEnd()) {
// if (auto value = parser.next()) {
// switch (value->type) {
// case ScriptValueType::Integer:
// SDL_Log("Integer: %d", value->intValue);
// break;
// case ScriptValueType::Float:
// SDL_Log("Float: %f", value->floatValue);
// break;
// case ScriptValueType::String:
// SDL_Log("String: %s", value->stringValue.c_str());
// break;
// default:
// break;
// }
// }
// }
// // 方式2批量解析
// parser.reset();
auto allValues = parser.parseAll();
for (const auto &value : allValues) {
// 处理 value
switch (value.type) {
case ScriptValueType::Integer:
SDL_Log("Integer: %d", value.intValue);
break;
case ScriptValueType::Float:
SDL_Log("Float: %f", value.floatValue);
break;
case ScriptValueType::String:
SDL_Log("String: %s", value.stringValue.c_str());
break;
default:
break;
}
}
}
}
app.run(); app.run();

View File

@@ -1,5 +1,6 @@
add_requires("libsdl2", {configs = {shared = true,wayland = true}}) add_requires("libsdl2", {configs = {shared = true,wayland = true}})
add_requires("libsdl2_image")
add_requires("glm") add_requires("glm")
target("Frostbite2D") target("Frostbite2D")
@@ -12,6 +13,7 @@ target("Frostbite2D")
add_includedirs(path.join(os.projectdir(), "Game/include")) add_includedirs(path.join(os.projectdir(), "Game/include"))
add_packages("libsdl2") add_packages("libsdl2")
add_packages("libsdl2_image")
add_packages("glm") add_packages("glm")
-- 复制着色器文件到输出目录 -- 复制着色器文件到输出目录

View File

@@ -2,6 +2,7 @@
set_toolchains("mingw") set_toolchains("mingw")
add_requires("libsdl2", {configs = {shared = true}}) add_requires("libsdl2", {configs = {shared = true}})
add_requires("libsdl2_image", {configs = {shared = true}})
add_requires("glm") add_requires("glm")
target("Frostbite2D") target("Frostbite2D")
@@ -14,6 +15,7 @@ target("Frostbite2D")
add_includedirs(path.join(os.projectdir(), "Game/include")) add_includedirs(path.join(os.projectdir(), "Game/include"))
add_packages("libsdl2") add_packages("libsdl2")
add_packages("libsdl2_image")
add_packages("glm") add_packages("glm")
-- 复制 assets 目录到输出目录 -- 复制 assets 目录到输出目录

View File

@@ -40,9 +40,9 @@ target("Frostbite2D")
add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2")) add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2"))
add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) add_linkdirs(path.join(devkitPro, "portlibs/switch/lib"))
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", "webp", "png", "jpeg", "z", "opusfile", "opus", "vorbisidec", "ogg",
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true}) {public = true})
add_syslinks("nx", "m") add_syslinks("nx", "m")