feat: 添加Squirrel脚本支持并完善平台相关功能

- 新增SquirrelVM类实现脚本引擎功能
- 添加Windows平台初始化代码以支持UTF-8控制台输出
- 在各平台构建配置中添加squirrel依赖
- 创建测试脚本main.nut验证中文支持
- 更新.gitignore排除nul文件
This commit is contained in:
2026-03-30 22:09:59 +08:00
parent d97b4e69b9
commit d25ff7655b
11 changed files with 251 additions and 2 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ build/
.vscode/compile_commands.json
参考代码/
*.pvf
nul

View File

@@ -0,0 +1,13 @@
#pragma once
#ifdef _WIN32
#include <windows.h>
namespace frostbite2D {
void windowsInit();
} // namespace frostbite2D
#endif

View File

@@ -0,0 +1,50 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/asset.h>
#include <json/json.hpp>
#include <squirrel.h> // Squirrel核心头文件
#include <sqstdio.h> // Squirrel标准IO库
#include <sqstdaux.h> // 新增:包含 sqstd_seterrorhandlers 等辅助函数
#include <sqstdblob.h> // 新增:包含 sqstd_register_bloblib 函数
#include <sqstdsystem.h> // 新增:包含 sqstd_register_systemlib 函数
#include <sqstdmath.h> // 新增:包含 sqstd_register_mathlib 函数
#include <sqstdstring.h> // 新增:包含 sqstd_register_stringlib 函数
#include <string>
namespace frostbite2D {
class SquirrelVM {
public:
static SquirrelVM &get();
void setScriptDirectory(const std::string &path);
void setDebugMode(bool enable);
bool init();
void shutdown();
void loadScript();
HSQUIRRELVM getVM() const { return vm_; }
bool isValid() const { return vm_ != nullptr; }
private:
SquirrelVM() = default;
~SquirrelVM() = default;
SquirrelVM(const SquirrelVM &) = delete;
SquirrelVM &operator=(const SquirrelVM &) = delete;
static void printCallback(HSQUIRRELVM vm, const char *s, ...);
static void errorCallback(HSQUIRRELVM vm, const char *s, ...);
bool loadScriptFile(const std::string &path);
HSQUIRRELVM vm_ = nullptr;
bool initialized_ = false;
std::string scriptDirectory_;
bool debugMode_ = false;
};
} // namespace frostbite2D

View File

@@ -15,6 +15,7 @@
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/platform/switch.h>
#include <frostbite2D/platform/windows.h>
#include <frostbite2D/resource/asset.h>
#include <frostbite2D/resource/npk_archive.h>
#include <frostbite2D/resource/pvf_archive.h>
@@ -47,6 +48,10 @@ bool Application::init(const AppConfig& config) {
switchInit();
#endif
#ifdef _WIN32
windowsInit();
#endif
// 初始化核心模块
if (!initCoreModules()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize core modules");

View File

@@ -0,0 +1,14 @@
#include <frostbite2D/platform/windows.h>
#ifdef _WIN32
namespace frostbite2D {
void windowsInit() {
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
}
} // namespace frostbite2D
#endif

View File

@@ -0,0 +1,155 @@
#include <frostbite2D/script/squirrel_vm.h>
#include <SDL2/SDL.h>
#include <squirrel.h>
namespace frostbite2D {
SquirrelVM &SquirrelVM::get() {
static SquirrelVM instance;
return instance;
}
void SquirrelVM::setScriptDirectory(const std::string &path) {
scriptDirectory_ = path;
}
void SquirrelVM::setDebugMode(bool enable) { debugMode_ = enable; }
bool SquirrelVM::init() {
if (initialized_) {
return true;
}
vm_ = sq_open(1024);
if (!vm_) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[Squirrel] Failed to create VM");
return false;
}
sqstd_seterrorhandlers(vm_);
sq_pushroottable(vm_);
sqstd_register_bloblib(vm_);
sqstd_register_iolib(vm_);
sqstd_register_systemlib(vm_);
sqstd_register_mathlib(vm_);
sqstd_register_stringlib(vm_);
sq_setprintfunc(vm_, printCallback, errorCallback);
loadScript();
initialized_ = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"[Squirrel] VM initialized (Squirrel %s)", SQUIRREL_VERSION);
return true;
}
void SquirrelVM::loadScript() {
std::string scriptDir =
scriptDirectory_.empty() ? "scripts" : scriptDirectory_;
std::string configPath = scriptDir + "/scripts.json";
Asset &asset = Asset::get();
std::optional<std::string> jsonContent = asset.readFileToString(configPath);
if (!jsonContent) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR,
"[Squirrel] Failed to load scripts config: %s",
configPath.c_str());
return;
}
try {
auto json = nlohmann::json::parse(*jsonContent);
auto files = json["files"];
for (const auto &filePath : files) {
std::string fullPath = scriptDir + "/" + std::string(filePath);
loadScriptFile(fullPath);
}
} catch (const std::exception &e) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR,
"[Squirrel] Failed to parse scripts.json: %s", e.what());
}
}
bool SquirrelVM::loadScriptFile(const std::string &path) {
Asset &asset = Asset::get();
std::optional<std::string> content = asset.readFileToString(path);
if (!content) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[Squirrel] Failed to read script: %s",
path.c_str());
return false;
}
const SQChar *script = content->c_str();
SQInteger size = static_cast<SQInteger>(content->size());
if (SQ_FAILED(sq_compilebuffer(vm_, script, size, path.c_str(), SQTrue))) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR,
"[Squirrel] Failed to compile script: %s", path.c_str());
return false;
}
sq_pushroottable(vm_);
if (SQ_FAILED(sq_call(vm_, 1, SQTrue, SQTrue))) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR,
"[Squirrel] Failed to execute script: %s", path.c_str());
sq_pop(vm_, 2);
return false;
}
sq_pop(vm_, 2);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[Squirrel] Loaded script: %s",
path.c_str());
return true;
}
void SquirrelVM::shutdown() {
if (vm_) {
sq_close(vm_);
vm_ = nullptr;
}
initialized_ = false;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[Squirrel] VM shutdown");
}
void SquirrelVM::printCallback(HSQUIRRELVM vm, const char *s, ...) {
va_list args;
va_start(args, s);
int size = vsnprintf(nullptr, 0, s, args);
va_end(args);
if (size > 0) {
std::vector<char> buffer(size + 1);
va_start(args, s);
vsnprintf(buffer.data(), buffer.size(), s, args);
va_end(args);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[Squirrel] %s", buffer.data());
}
}
void SquirrelVM::errorCallback(HSQUIRRELVM vm, const char *s, ...) {
va_list args;
va_start(args, s);
int size = vsnprintf(nullptr, 0, s, args);
va_end(args);
if (size > 0) {
std::vector<char> buffer(size + 1);
va_start(args, s);
vsnprintf(buffer.data(), buffer.size(), s, args);
va_end(args);
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[Squirrel Error] %s", buffer.data());
}
}
} // namespace frostbite2D

View File

@@ -0,0 +1,2 @@
print("中文测试!")
error("中文测试错误!")

View File

@@ -29,6 +29,8 @@
#include <frostbite2D/2d/text_sprite.h>
#include <frostbite2D/graphics/font_manager.h>
#include <frostbite2D/script/squirrel_vm.h>
using namespace frostbite2D;
int main(int argc, char **argv) {
@@ -45,8 +47,10 @@ int main(int argc, char **argv) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize application!");
return -1;
}
// SDL_Log("Starting main loop...");
SquirrelVM::get().setScriptDirectory("assets/scripts");
SquirrelVM::get().init();
auto &pvf = PvfArchive::get();
if (pvf.open("assets/Script.pvf")) {

View File

@@ -4,6 +4,7 @@ add_requires("libsdl2_image")
add_requires("libsdl2_mixer")
add_requires("glm")
add_requires("zlib")
add_requires("squirrel")
target("Frostbite2D")
set_kind("binary")
@@ -19,6 +20,7 @@ target("Frostbite2D")
add_packages("libsdl2_mixer")
add_packages("glm")
add_packages("zlib")
add_packages("squirrel")
-- 复制着色器文件到输出目录
after_build(function (target)

View File

@@ -7,6 +7,7 @@ add_requires("libsdl2_mixer", {configs = {shared = true}})
add_requires("libsdl2_ttf", {configs = {shared = true}})
add_requires("glm")
add_requires("zlib")
add_requires("squirrel")
target("Frostbite2D")
set_kind("binary")
@@ -23,6 +24,7 @@ target("Frostbite2D")
add_packages("libsdl2_ttf")
add_packages("glm")
add_packages("zlib")
add_packages("squirrel")
-- 复制 assets 目录到输出目录
after_build(function (target)

View File

@@ -1,4 +1,5 @@
target("Frostbite2D")
set_kind("binary")
add_files(path.join(os.projectdir(), "Frostbite2D/src/**.cpp"))
@@ -40,7 +41,7 @@ target("Frostbite2D")
add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2"))
add_linkdirs(path.join(devkitPro, "portlibs/switch/lib"))
add_syslinks("SDL2_mixer", "SDL2_image", "SDL2_ttf", "SDL2", "freetype", "harfbuzz" , "bz2" , "webp", "png", "jpeg", "z", "opusfile", "opus", "vorbisidec", "ogg","modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",{public = true})
add_syslinks("SDL2_mixer", "SDL2_image", "SDL2_ttf", "SDL2", "freetype", "harfbuzz" , "squirrel_static", "sqstdlib_static", "bz2" , "webp", "png", "jpeg", "z", "opusfile", "opus", "vorbisidec", "ogg","modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",{public = true})
add_syslinks("nx", "m")