From 360a209a5c8e43b59430add0ef6500b561ad226e Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Tue, 10 Feb 2026 21:46:43 +0800 Subject: [PATCH] feat: implement modular script system (ScriptModule) --- .../documents/Extra2D 脚本引擎架构升级计划.md | 321 +++++++++++++++++ Extra2D/include/extra2d/app/application.h | 18 + Extra2D/include/extra2d/base/module.h | 68 ++++ .../include/extra2d/script/script_module.h | 92 +++++ Extra2D/include/extra2d/script/sq_binding.h | 13 + .../include/extra2d/script/sq_binding_stage.h | 12 + Extra2D/include/extra2d/utils/logger.h | 4 + Extra2D/src/app/application.cpp | 59 +++ Extra2D/src/base/module.cpp | 9 + Extra2D/src/script/script_module.cpp | 341 ++++++++++++++++++ Extra2D/src/script/sq_binding_stage.cpp | 223 ++++++++++++ docs/ScriptModule_Usage.md | 193 ++++++++++ scripts/ScriptConfig.cfg | 30 ++ scripts/core/_init.nut | 4 + scripts/core/array.nut | 53 +++ scripts/core/class.nut | 43 +++ scripts/core/math.nut | 77 ++++ scripts/engine/_init.nut | 4 + scripts/engine/director.nut | 31 ++ scripts/engine/stage.nut | 68 ++++ scripts/user/main.nut | 53 +++ 21 files changed, 1716 insertions(+) create mode 100644 .trae/documents/Extra2D 脚本引擎架构升级计划.md create mode 100644 Extra2D/include/extra2d/base/module.h create mode 100644 Extra2D/include/extra2d/script/script_module.h create mode 100644 Extra2D/include/extra2d/script/sq_binding_stage.h create mode 100644 Extra2D/src/base/module.cpp create mode 100644 Extra2D/src/script/script_module.cpp create mode 100644 Extra2D/src/script/sq_binding_stage.cpp create mode 100644 docs/ScriptModule_Usage.md create mode 100644 scripts/ScriptConfig.cfg create mode 100644 scripts/core/_init.nut create mode 100644 scripts/core/array.nut create mode 100644 scripts/core/class.nut create mode 100644 scripts/core/math.nut create mode 100644 scripts/engine/_init.nut create mode 100644 scripts/engine/director.nut create mode 100644 scripts/engine/stage.nut create mode 100644 scripts/user/main.nut diff --git a/.trae/documents/Extra2D 脚本引擎架构升级计划.md b/.trae/documents/Extra2D 脚本引擎架构升级计划.md new file mode 100644 index 0000000..883f23f --- /dev/null +++ b/.trae/documents/Extra2D 脚本引擎架构升级计划.md @@ -0,0 +1,321 @@ +# Extra2D 模块化脚本系统融合方案 + +结合 **Yosin 的配置驱动脚本加载** 和 **Kiwano 的模块系统**,为 Extra2D 实现一个强大的模块化 Squirrel 脚本架构。 + +## 融合架构设计 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Extra2D.exe (C++ 引擎) │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Application (单例) │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ ModuleList (模块列表) │ │ │ +│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │ │ +│ │ │ │ ScriptModule│ │ AudioModule │ │ PhysicsModule │ │ │ │ +│ │ │ │ (脚本系统) │ │ (音频系统) │ │ (物理系统) │ │ │ │ +│ │ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ - Run() - 运行应用 │ │ +│ │ - Use(Module&) - 注册模块 │ │ +│ │ - Update(dt) - 更新所有模块 │ │ +│ │ - Render() - 渲染所有模块 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ ScriptModule (脚本模块) │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Squirrel VM │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ ScriptConfig.cfg (配置驱动) │ │ │ +│ │ │ - 定义脚本加载顺序 │ │ │ +│ │ │ - 定义模块依赖关系 │ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ 加载流程: │ │ +│ │ 1. 加载 Core 层 (基础工具类) │ │ +│ │ 2. 加载 Engine 层 (引擎封装类) │ │ +│ │ 3. 执行 User/main.nut (用户入口) │ │ +│ │ │ │ +│ │ 每帧调用: │ │ +│ │ - OnUpdate(dt) -> 调用脚本的 update 函数 │ │ +│ │ - OnRender() -> 调用脚本的 render 函数 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Squirrel 脚本层 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Core/ │ │ Engine/ │ │ User/ │ │ Modules/ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ _init.nut │ │ _init.nut │ │ main.nut │ │ ui.nut │ │ +│ │ class.nut │ │ app.nut │ │ stages/ │ │ network.nut │ │ +│ │ array.nut │ │ director.nut│ │ objects/ │ │ ... │ │ +│ │ math.nut │ │ stage.nut │ │ ui/ │ │ │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## 核心组件设计 + +### 1. Module 基类(来自 Kiwano) + +```cpp +// base/module.h +class Module { +public: + virtual void setupModule(); // 初始化 + virtual void destroyModule(); // 销毁 + virtual void onUpdate(float dt); // 更新 + virtual void onRender(); // 渲染 + virtual void handleEvent(Event* evt); // 事件 +}; +``` + +### 2. ScriptModule(脚本模块) + +```cpp +// script/script_module.h +class ScriptModule : public Module { +public: + void setupModule() override { + // 1. 初始化 Squirrel VM + vm_ = sq_open(1024); + + // 2. 注册 C++ 绑定 + registerBindings(vm_); + + // 3. 读取 ScriptConfig.cfg + auto config = loadConfig("scripts/ScriptConfig.cfg"); + + // 4. 按顺序加载脚本 + for (auto& path : config.preload) { + executeFile(path); + } + + // 5. 调用 main 函数 + callMainFunction(); + } + + void onUpdate(float dt) override { + // 调用脚本的全局 update 函数 + callScriptFunction("onUpdate", dt); + } +}; +``` + +### 3. 脚本配置系统(来自 Yosin) + +``` +# scripts/ScriptConfig.cfg +# 定义脚本加载顺序 + +# ========== Core 层(基础工具)========== +scripts/core/_init.nut +scripts/core/class.nut +scripts/core/array.nut +scripts/core/table.nut +scripts/core/math.nut +scripts/core/string.nut + +# ========== Engine 层(引擎封装)========== +scripts/engine/_init.nut +scripts/engine/application.nut +scripts/engine/director.nut +scripts/engine/stage.nut +scripts/engine/node.nut +scripts/engine/sprite.nut +scripts/engine/action.nut +scripts/engine/audio.nut +scripts/engine/input.nut + +# ========== User 层(用户逻辑)========== +scripts/user/stages/start_stage.nut +scripts/user/objects/player.nut +scripts/user/ui/main_ui.nut +scripts/user/main.nut # 入口脚本(最后加载) +``` + +### 4. 脚本类层次结构(来自 Yosin) + +```squirrel +// scripts/core/class.nut +// 基础类系统 + +// scripts/engine/node.nut +class Node { + C_Object = null; // C++ 对象指针 + + constructor() { + C_Object = Node_Create(); + Register_Destruction(C_Object, this); + } + + function setPosition(x, y) { + Node_SetPosition(C_Object, x, y); + } + + function getPosition() { + return Node_GetPosition(C_Object); + } +} + +// scripts/engine/stage.nut +class Stage extends Node { + constructor() { + C_Object = Stage_Create(); + } + + function enter() { + Director_EnterStage(C_Object); + } + + function onUpdate(dt) { + // 子类重写 + } +} + +// scripts/engine/application.nut +class Application { + title = "Extra2D Game"; + width = 1280; + height = 720; + + function run(stageClass) { + App_SetTitle(title); + App_SetSize(width, height); + App_Run(stageClass); + } +} +``` + +### 5. 用户入口脚本(main.nut) + +```squirrel +// scripts/user/main.nut +function main(args) { + // 创建应用 + local app = Application(); + app.title = "My Game"; + app.width = 800; + app.height = 600; + + // 运行开始舞台 + app.run(StartStage); +} + +// 定义开始舞台 +class StartStage extends Stage { + function onEnter() { + // 创建精灵 + local sprite = Sprite("assets/logo.png"); + sprite.setPosition(400, 300); + this.addChild(sprite); + + // 创建 UI + local ui = MainUI(); + this.addChild(ui); + } + + function onUpdate(dt) { + // 每帧更新 + } +} +``` + +## 实现步骤 + +### 阶段 1: 基础架构 + +1. 创建 `base/module.h/cpp`(Kiwano 风格) +2. 修改 `Application` 支持模块注册 +3. 实现模块生命周期管理 + +### 阶段 2: 脚本模块 + +1. 创建 `script/script_module.h/cpp` +2. 实现配置解析(`ScriptConfig.cfg`) +3. 实现脚本加载器 + +### 阶段 3: C++ 绑定扩展 + +1. 扩展 `Node` 绑定(添加 `C_Object` 指针桥接) +2. 添加 `Stage` 类绑定 +3. 添加 `Director` 类绑定 +4. 添加 `Application` 配置绑定 + +### 阶段 4: 脚本类库 + +1. 创建 `scripts/core/` 基础工具 +2. 创建 `scripts/engine/` 引擎封装 +3. 实现类继承机制 + +### 阶段 5: 入口机制 + +1. 实现 `main()` 函数自动调用 +2. 实现 `GameWindow` 封装类 +3. 创建示例项目 + +## 文件变更清单 + +### 新增文件 + +``` +src/extra2d/base/module.h +src/extra2d/base/module.cpp +src/extra2d/script/script_module.h +src/extra2d/script/script_module.cpp +src/extra2d/script/sq_binding_stage.cpp +src/extra2d/script/sq_binding_director.cpp +scripts/ScriptConfig.cfg +scripts/core/_init.nut +scripts/core/class.nut +scripts/engine/_init.nut +scripts/engine/application.nut +scripts/engine/director.nut +scripts/engine/stage.nut +scripts/user/main.nut +``` + +### 修改文件 + +``` +src/extra2d/app/application.h/cpp +src/extra2d/script/script_engine.cpp +src/extra2d/script/sq_binding_node.cpp +``` + +## 最终效果 + +用户只需编写脚本即可创建完整游戏: + +```squirrel +// 纯脚本游戏 +function main() { + local app = Application(); + app.config({ + title = "Hello World", + width = 800, + height = 600 + }); + + local director = Director(); + director.enterStage(MyStage()); + + app.run(); +} + +class MyStage extends Stage { + function onEnter() { + local label = Label("Hello Squirrel!"); + label.position = [400, 300]; + this.addChild(label); + } +} +``` + +请确认此方案后,我将开始 diff --git a/Extra2D/include/extra2d/app/application.h b/Extra2D/include/extra2d/app/application.h index 4724881..8ba5213 100644 --- a/Extra2D/include/extra2d/app/application.h +++ b/Extra2D/include/extra2d/app/application.h @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include namespace extra2d { @@ -96,6 +98,19 @@ public: // 获取配置 const AppConfig &getConfig() const { return config_; } + // ------------------------------------------------------------------------ + // 模块系统 (新增) + // ------------------------------------------------------------------------ + + // 注册模块 + void use(ModulePtr module); + + // 获取模块 + ModulePtr getModule(const char* name); + + // 获取所有模块 + const std::vector& getModules() const { return modules_; } + private: Application() = default; ~Application(); @@ -117,6 +132,9 @@ private: UniquePtr eventDispatcher_; UniquePtr camera_; + // 模块系统 + std::vector modules_; + // 状态 bool initialized_ = false; bool running_ = false; diff --git a/Extra2D/include/extra2d/base/module.h b/Extra2D/include/extra2d/base/module.h new file mode 100644 index 0000000..7f8cddd --- /dev/null +++ b/Extra2D/include/extra2d/base/module.h @@ -0,0 +1,68 @@ +#pragma once + +#include "../core/types.h" +#include "../event/event.h" + +namespace extra2d { + +// 前置声明 +class Application; + +// ============================================================================ +// 模块基类 (参考 Kiwano 设计) +// 所有功能模块的基类,提供统一的生命周期管理 +// ============================================================================ +class Module { +public: + Module() : enabled_(true), initialized_(false) {} + virtual ~Module() = default; + + // 禁用拷贝 + Module(const Module&) = delete; + Module& operator=(const Module&) = delete; + + // ============================================================================ + // 生命周期方法 + // ============================================================================ + + // 模块初始化 (在 Application::init 中调用) + virtual void setupModule() {} + + // 模块销毁 (在 Application::shutdown 中调用) + virtual void destroyModule() {} + + // 每帧更新 + virtual void onUpdate(float dt) {} + + // 每帧渲染 + virtual void onRender() {} + + // 事件处理 + virtual void handleEvent(Event* evt) {} + + // ============================================================================ + // 状态管理 + // ============================================================================ + + // 启用/禁用模块 + void setEnabled(bool enabled) { enabled_ = enabled; } + bool isEnabled() const { return enabled_; } + + // 检查是否已初始化 + bool isInitialized() const { return initialized_; } + void setInitialized(bool initialized) { initialized_ = initialized; } + + // 获取模块名称 (子类可重写) + virtual const char* getName() const { return "Module"; } + +protected: + bool enabled_; // 模块是否启用 + bool initialized_; // 模块是否已初始化 +}; + +// ============================================================================ +// 模块智能指针类型 +// ============================================================================ +using ModulePtr = Ptr; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/script/script_module.h b/Extra2D/include/extra2d/script/script_module.h new file mode 100644 index 0000000..94ac385 --- /dev/null +++ b/Extra2D/include/extra2d/script/script_module.h @@ -0,0 +1,92 @@ +#pragma once + +#include "../base/module.h" +#include "../core/string.h" +#include "../core/types.h" +#include +#include + +namespace extra2d { + +// ============================================================================ +// 脚本配置结构 +// ============================================================================ +struct ScriptConfig { + std::vector preload; // 预加载脚本列表 + String entryPoint; // 入口脚本 (默认: scripts/user/main.nut) +}; + +// ============================================================================ +// 脚本模块 - 集成 Squirrel 脚本引擎的 Module 实现 +// ============================================================================ +class ScriptModule : public Module { +public: + ScriptModule(); + ~ScriptModule() override; + + // 禁用拷贝 + ScriptModule(const ScriptModule&) = delete; + ScriptModule& operator=(const ScriptModule&) = delete; + + // Module 接口实现 + void setupModule() override; + void destroyModule() override; + void onUpdate(float dt) override; + void onRender() override; + const char* getName() const override { return "ScriptModule"; } + + // ============================================================================ + // 脚本执行 + // ============================================================================ + bool executeString(const String& code); + bool executeFile(const String& filepath); + + // ============================================================================ + // 配置加载 + // ============================================================================ + bool loadConfig(const String& configPath); + const ScriptConfig& getConfig() const { return config_; } + + // ============================================================================ + // Squirrel VM 访问 + // ============================================================================ + HSQUIRRELVM getVM() const { return vm_; } + bool isInitialized() const { return vm_ != nullptr; } + + // ============================================================================ + // 脚本调用 + // ============================================================================ + bool callFunction(const char* funcName); + bool callFunction(const char* funcName, float arg); + +private: + // 初始化 Squirrel VM + bool initVM(); + // 关闭 Squirrel VM + void closeVM(); + // 注册 C++ 绑定 + void registerBindings(); + // 加载脚本列表 + bool loadScripts(const std::vector& scripts); + // 编译并运行 + bool compileAndRun(const String& source, const String& sourceName); + + // Squirrel 回调 + static void printFunc(HSQUIRRELVM vm, const SQChar* fmt, ...); + static void errorFunc(HSQUIRRELVM vm, const SQChar* fmt, ...); + static SQInteger errorHandler(HSQUIRRELVM vm); + static void compilerError(HSQUIRRELVM vm, const SQChar* desc, + const SQChar* source, SQInteger line, + SQInteger column); + + HSQUIRRELVM vm_ = nullptr; + ScriptConfig config_; + bool configLoaded_ = false; +}; + +// ============================================================================ +// 脚本模块智能指针 +// ============================================================================ +using ScriptModulePtr = Ptr; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/script/sq_binding.h b/Extra2D/include/extra2d/script/sq_binding.h index d541ed4..8c2d0f3 100644 --- a/Extra2D/include/extra2d/script/sq_binding.h +++ b/Extra2D/include/extra2d/script/sq_binding.h @@ -178,6 +178,19 @@ template T *getSingleton(HSQUIRRELVM vm, SQInteger idx) { return static_cast(up); } +// ============================================================================ +// User pointer helpers (for non-class user pointers) +// ============================================================================ +inline void pushUserPointer(HSQUIRRELVM vm, void *ptr) { + sq_pushuserpointer(vm, ptr); +} + +inline void *getUserPointer(HSQUIRRELVM vm, SQInteger idx) { + SQUserPointer up = nullptr; + sq_getuserpointer(vm, idx, &up); + return up; +} + // ============================================================================ // ClassDef — fluent API for registering a class // ============================================================================ diff --git a/Extra2D/include/extra2d/script/sq_binding_stage.h b/Extra2D/include/extra2d/script/sq_binding_stage.h new file mode 100644 index 0000000..2948720 --- /dev/null +++ b/Extra2D/include/extra2d/script/sq_binding_stage.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace extra2d { +namespace sq { + +// 注册 Stage 相关绑定 +void registerStageBindings(HSQUIRRELVM vm); + +} // namespace sq +} // namespace extra2d diff --git a/Extra2D/include/extra2d/utils/logger.h b/Extra2D/include/extra2d/utils/logger.h index 837ca26..76aaf4e 100644 --- a/Extra2D/include/extra2d/utils/logger.h +++ b/Extra2D/include/extra2d/utils/logger.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,9 @@ template inline std::string to_string_arg(const T &value) { } else { return std::to_string(value); } + } else if constexpr (std::is_same_v) { + // 处理 extra2d::String + return value.c_str(); } else { std::ostringstream oss; oss << value; diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index 9d99aed..a0aab7a 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -176,6 +176,15 @@ void Application::shutdown() { sceneManager_->end(); } + // 销毁所有模块(逆序销毁) + for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) { + if (*it) { + (*it)->destroyModule(); + (*it)->setInitialized(false); + } + } + modules_.clear(); + // 清理子系统 sceneManager_.reset(); resourceManager_.reset(); @@ -311,6 +320,13 @@ void Application::update() { timerManager_->update(deltaTime_); } + // 更新所有启用的模块 + for (const auto& module : modules_) { + if (module && module->isEnabled()) { + module->onUpdate(deltaTime_); + } + } + if (sceneManager_) { sceneManager_->update(deltaTime_); } @@ -324,6 +340,13 @@ void Application::render() { renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight()); + // 渲染所有启用的模块 + for (const auto& module : modules_) { + if (module && module->isEnabled()) { + module->onRender(); + } + } + if (sceneManager_) { sceneManager_->render(*renderer_); } else { @@ -360,4 +383,40 @@ void Application::enterScene(Ptr scene, } } +// ============================================================================ +// 模块系统实现 +// ============================================================================ + +void Application::use(ModulePtr module) { + if (!module) { + E2D_LOG_WARN("Cannot register null module"); + return; + } + + // 检查是否已存在同名模块 + for (const auto& m : modules_) { + if (strcmp(m->getName(), module->getName()) == 0) { + E2D_LOG_WARN("Module '{}' already registered", module->getName()); + return; + } + } + + // 初始化模块 + module->setupModule(); + module->setInitialized(true); + + // 添加到模块列表 + modules_.push_back(module); + E2D_LOG_INFO("Module '{}' registered", module->getName()); +} + +ModulePtr Application::getModule(const char* name) { + for (const auto& module : modules_) { + if (strcmp(module->getName(), name) == 0) { + return module; + } + } + return nullptr; +} + } // namespace extra2d diff --git a/Extra2D/src/base/module.cpp b/Extra2D/src/base/module.cpp new file mode 100644 index 0000000..ccbc829 --- /dev/null +++ b/Extra2D/src/base/module.cpp @@ -0,0 +1,9 @@ +#include "extra2d/base/module.h" + +namespace extra2d { + +// Module 基类的实现 +// 目前所有方法都是虚函数,在头文件中已有默认实现 +// 如果后续需要添加通用逻辑,可以在这里实现 + +} // namespace extra2d diff --git a/Extra2D/src/script/script_module.cpp b/Extra2D/src/script/script_module.cpp new file mode 100644 index 0000000..bd48db0 --- /dev/null +++ b/Extra2D/src/script/script_module.cpp @@ -0,0 +1,341 @@ +#include "extra2d/script/script_module.h" +#include "extra2d/script/sq_binding_action.h" +#include "extra2d/script/sq_binding_animation.h" +#include "extra2d/script/sq_binding_audio.h" +#include "extra2d/script/sq_binding_input.h" +#include "extra2d/script/sq_binding_node.h" +#include "extra2d/script/sq_binding_stage.h" +#include "extra2d/script/sq_binding_types.h" +#include "extra2d/utils/logger.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +ScriptModule::ScriptModule() = default; + +ScriptModule::~ScriptModule() { + destroyModule(); +} + +void ScriptModule::setupModule() { + if (vm_) { + E2D_LOG_WARN("ScriptModule: VM already initialized"); + return; + } + + // 1. 初始化 Squirrel VM + if (!initVM()) { + E2D_LOG_ERROR("ScriptModule: failed to initialize VM"); + return; + } + + // 2. 注册 C++ 绑定 + registerBindings(); + + // 3. 加载配置 (如果存在) + if (!configLoaded_) { + loadConfig("scripts/ScriptConfig.cfg"); + } + + // 4. 加载预定义脚本 + if (!config_.preload.empty()) { + loadScripts(config_.preload); + } + + // 5. 执行入口脚本 + if (!config_.entryPoint.empty()) { + executeFile(config_.entryPoint); + } + + E2D_LOG_INFO("ScriptModule: setup complete"); +} + +void ScriptModule::destroyModule() { + if (vm_) { + closeVM(); + E2D_LOG_INFO("ScriptModule: destroyed"); + } +} + +void ScriptModule::onUpdate(float dt) { + if (!vm_) return; + + // 调用脚本的全局 onUpdate 函数 + callFunction("onUpdate", dt); +} + +void ScriptModule::onRender() { + if (!vm_) return; + + // 调用脚本的全局 onRender 函数 + callFunction("onRender"); +} + +bool ScriptModule::executeString(const String& code) { + if (!vm_) { + E2D_LOG_ERROR("ScriptModule: VM not initialized"); + return false; + } + return compileAndRun(code, ""); +} + +bool ScriptModule::executeFile(const String& filepath) { + if (!vm_) { + E2D_LOG_ERROR("ScriptModule: VM not initialized"); + return false; + } + + std::ifstream file(filepath.c_str()); + if (!file.is_open()) { + E2D_LOG_ERROR("ScriptModule: cannot open file '{}'", filepath); + return false; + } + + std::ostringstream ss; + ss << file.rdbuf(); + return compileAndRun(ss.str(), filepath); +} + +bool ScriptModule::loadConfig(const String& configPath) { + std::ifstream file(configPath.c_str()); + if (!file.is_open()) { + E2D_LOG_WARN("ScriptModule: config file not found '{}', using defaults", configPath); + // 使用默认配置 + config_.entryPoint = "scripts/user/main.nut"; + configLoaded_ = true; + return true; + } + + std::string stdLine; + bool inPreloadSection = false; + + while (std::getline(file, stdLine)) { + // 转换为 String 进行处理 + String line = String(stdLine.c_str()); + + // 跳过空行和注释 + line = line.trim(); + if (line.empty() || line.startsWith("#")) { + continue; + } + + // 检查是否是入口点定义 + if (line.startsWith("entry:")) { + config_.entryPoint = line.substring(6).trim(); + continue; + } + + // 检查是否是预加载脚本 + if (line.endsWith(".nut")) { + config_.preload.push_back(line); + } + } + + // 如果没有指定入口点,使用默认值 + if (config_.entryPoint.empty()) { + config_.entryPoint = "scripts/user/main.nut"; + } + + configLoaded_ = true; + E2D_LOG_INFO("ScriptModule: loaded config from '{}'", configPath); + return true; +} + +bool ScriptModule::callFunction(const char* funcName) { + if (!vm_) return false; + + SQInteger top = sq_gettop(vm_); + sq_pushroottable(vm_); + sq_pushstring(vm_, funcName, -1); + + if (SQ_SUCCEEDED(sq_get(vm_, -2))) { + sq_pushroottable(vm_); // this + if (SQ_SUCCEEDED(sq_call(vm_, 1, SQFalse, SQTrue))) { + sq_settop(vm_, top); + return true; + } + } + + sq_settop(vm_, top); + return false; +} + +bool ScriptModule::callFunction(const char* funcName, float arg) { + if (!vm_) return false; + + SQInteger top = sq_gettop(vm_); + sq_pushroottable(vm_); + sq_pushstring(vm_, funcName, -1); + + if (SQ_SUCCEEDED(sq_get(vm_, -2))) { + sq_pushroottable(vm_); // this + sq_pushfloat(vm_, arg); + if (SQ_SUCCEEDED(sq_call(vm_, 2, SQFalse, SQTrue))) { + sq_settop(vm_, top); + return true; + } + } + + sq_settop(vm_, top); + return false; +} + +bool ScriptModule::initVM() { + vm_ = sq_open(1024); + if (!vm_) { + E2D_LOG_ERROR("ScriptModule: failed to create Squirrel VM"); + return false; + } + + sq_setprintfunc(vm_, printFunc, errorFunc); + sq_setcompilererrorhandler(vm_, compilerError); + + sq_pushroottable(vm_); + + // Register standard libraries + sqstd_register_mathlib(vm_); + sqstd_register_stringlib(vm_); + sqstd_register_bloblib(vm_); + sqstd_register_iolib(vm_); + + // Set error handler + sq_newclosure(vm_, errorHandler, 0); + sq_seterrorhandler(vm_); + + sq_pop(vm_, 1); // pop root table + + E2D_LOG_INFO("ScriptModule: Squirrel VM initialized (v{})", SQUIRREL_VERSION); + return true; +} + +void ScriptModule::closeVM() { + if (vm_) { + sq_close(vm_); + vm_ = nullptr; + E2D_LOG_INFO("ScriptModule: Squirrel VM closed"); + } +} + +void ScriptModule::registerBindings() { + if (!vm_) return; + + // Register Easy2D bindings + sq::registerValueTypes(vm_); + sq::registerNodeBindings(vm_); + sq::registerStageBindings(vm_); + sq::registerInputBindings(vm_); + sq::registerActionBindings(vm_); + sq::registerAudioBindings(vm_); + sq::registerAnimationBindings(vm_); + + // Register global log function + sq_pushroottable(vm_); + sq_pushstring(vm_, "log", -1); + sq_newclosure( + vm_, + [](HSQUIRRELVM v) -> SQInteger { + const SQChar* msg = nullptr; + sq_getstring(v, 2, &msg); + if (msg) + E2D_LOG_INFO("[Script] {}", msg); + return 0; + }, + 0); + sq_newslot(vm_, -3, SQFalse); + sq_pop(vm_, 1); + + E2D_LOG_INFO("ScriptModule: C++ bindings registered"); +} + +bool ScriptModule::loadScripts(const std::vector& scripts) { + bool allSuccess = true; + for (const auto& script : scripts) { + if (!executeFile(script)) { + E2D_LOG_ERROR("ScriptModule: failed to load script '{}'", script); + allSuccess = false; + } else { + E2D_LOG_INFO("ScriptModule: loaded script '{}'", script); + } + } + return allSuccess; +} + +bool ScriptModule::compileAndRun(const String& source, const String& sourceName) { + if (!vm_) return false; + + SQInteger top = sq_gettop(vm_); + sq_pushroottable(vm_); + + if (SQ_FAILED(sq_compilebuffer(vm_, source.c_str(), + static_cast(source.length()), + sourceName.c_str(), SQTrue))) { + sq_settop(vm_, top); + return false; + } + + sq_push(vm_, -2); // push root table as 'this' + + if (SQ_FAILED(sq_call(vm_, 1, SQFalse, SQTrue))) { + sq_settop(vm_, top); + return false; + } + + sq_settop(vm_, top); + return true; +} + +void ScriptModule::printFunc(HSQUIRRELVM, const SQChar* fmt, ...) { + va_list args; + va_start(args, fmt); + char buf[2048]; + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + E2D_LOG_INFO("[Squirrel] {}", buf); +} + +void ScriptModule::errorFunc(HSQUIRRELVM, const SQChar* fmt, ...) { + va_list args; + va_start(args, fmt); + char buf[2048]; + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + E2D_LOG_ERROR("[Squirrel] {}", buf); +} + +SQInteger ScriptModule::errorHandler(HSQUIRRELVM vm) { + const SQChar* errMsg = nullptr; + if (sq_gettop(vm) >= 1) { + if (SQ_FAILED(sq_getstring(vm, 2, &errMsg))) { + errMsg = "unknown error"; + } + } + + // Print call stack + SQStackInfos si; + SQInteger level = 1; + E2D_LOG_ERROR("[Squirrel] Runtime error: {}", errMsg ? errMsg : "unknown"); + + while (SQ_SUCCEEDED(sq_stackinfos(vm, level, &si))) { + const SQChar* fn = si.funcname ? si.funcname : "unknown"; + const SQChar* src = si.source ? si.source : "unknown"; + E2D_LOG_ERROR(" [{}] {}:{} in {}", level, src, si.line, fn); + ++level; + } + + return 0; +} + +void ScriptModule::compilerError(HSQUIRRELVM, const SQChar* desc, + const SQChar* source, SQInteger line, + SQInteger column) { + E2D_LOG_ERROR("[Squirrel] Compile error: {}:{}:{}: {}", source, line, column, desc); +} + +} // namespace extra2d diff --git a/Extra2D/src/script/sq_binding_stage.cpp b/Extra2D/src/script/sq_binding_stage.cpp new file mode 100644 index 0000000..62156f4 --- /dev/null +++ b/Extra2D/src/script/sq_binding_stage.cpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include +#include + +namespace extra2d { +namespace sq { + +// ============================================================================ +// Director (场景管理器单例) +// ============================================================================ + +static SQInteger directorGetInstance(HSQUIRRELVM vm) { + // Director 是单例,返回 SceneManager 的引用 + auto& app = Application::instance(); + auto* sceneManager = &app.scenes(); + sq_pushuserpointer(vm, sceneManager); + return 1; +} + +static SQInteger directorEnterScene(HSQUIRRELVM vm) { + auto* sceneManager = static_cast(getUserPointer(vm, 1)); + if (!sceneManager) + return sq_throwerror(vm, "null director"); + + auto scene = getPtr(vm, 2); + if (!scene) + return sq_throwerror(vm, "null scene"); + + auto& app = Application::instance(); + scene->setViewportSize(static_cast(app.window().getWidth()), + static_cast(app.window().getHeight())); + sceneManager->enterScene(scene); + return 0; +} + +static SQInteger directorReplaceScene(HSQUIRRELVM vm) { + auto* sceneManager = static_cast(getUserPointer(vm, 1)); + if (!sceneManager) + return sq_throwerror(vm, "null director"); + + auto scene = getPtr(vm, 2); + if (!scene) + return sq_throwerror(vm, "null scene"); + + auto& app = Application::instance(); + scene->setViewportSize(static_cast(app.window().getWidth()), + static_cast(app.window().getHeight())); + sceneManager->replaceScene(scene); + return 0; +} + +static SQInteger directorGetCurrentScene(HSQUIRRELVM vm) { + auto* sceneManager = static_cast(getUserPointer(vm, 1)); + if (!sceneManager) + return sq_throwerror(vm, "null director"); + + auto scene = sceneManager->getCurrentScene(); + if (scene) { + pushPtr(vm, scene); + } else { + pushNull(vm); + } + return 1; +} + +static SQInteger directorPopScene(HSQUIRRELVM vm) { + auto* sceneManager = static_cast(getUserPointer(vm, 1)); + if (!sceneManager) + return sq_throwerror(vm, "null director"); + + sceneManager->popScene(); + return 0; +} + +// ============================================================================ +// Scene (舞台/场景) +// ============================================================================ + +static SQInteger sceneCreate(HSQUIRRELVM vm) { + auto scene = makePtr(); + pushPtr(vm, scene); + return 1; +} + +static SQInteger sceneSetBackgroundColor(HSQUIRRELVM vm) { + auto *scene = getRawPtr(vm, 1); + if (!scene) + return sq_throwerror(vm, "null scene"); + + SQInteger argc = sq_gettop(vm); + if (argc >= 5) { + // r, g, b, a + float r = static_cast(getFloat(vm, 2)); + float g = static_cast(getFloat(vm, 3)); + float b = static_cast(getFloat(vm, 4)); + float a = static_cast(getFloat(vm, 5)); + scene->setBackgroundColor(Color(r, g, b, a)); + } else if (argc >= 2) { + // Color instance + Color *c = getValueInstance(vm, 2); + if (c) + scene->setBackgroundColor(*c); + } + return 0; +} + +static SQInteger sceneGetBackgroundColor(HSQUIRRELVM vm) { + auto *scene = getRawPtr(vm, 1); + if (!scene) + return sq_throwerror(vm, "null scene"); + pushValueInstance(vm, scene->getBackgroundColor()); + return 1; +} + +static SQInteger sceneAddChild(HSQUIRRELVM vm) { + auto scene = getPtr(vm, 1); + auto node = getPtr(vm, 2); + if (!scene || !node) + return sq_throwerror(vm, "null scene or node"); + scene->addChild(node); + return 0; +} + +static SQInteger sceneRemoveChild(HSQUIRRELVM vm) { + auto scene = getPtr(vm, 1); + auto node = getPtr(vm, 2); + if (!scene || !node) + return sq_throwerror(vm, "null scene or node"); + scene->removeChild(node); + return 0; +} + +static SQInteger sceneRemoveAllChildren(HSQUIRRELVM vm) { + auto *scene = getRawPtr(vm, 1); + if (!scene) + return sq_throwerror(vm, "null scene"); + scene->removeAllChildren(); + return 0; +} + +static SQInteger sceneGetChildByName(HSQUIRRELVM vm) { + auto *scene = getRawPtr(vm, 1); + if (!scene) + return sq_throwerror(vm, "null scene"); + auto child = scene->getChildByName(getString(vm, 2)); + if (child) + pushPtr(vm, child); + else + pushNull(vm); + return 1; +} + +static SQInteger sceneSetSpatialIndexingEnabled(HSQUIRRELVM vm) { + auto *scene = getRawPtr(vm, 1); + if (!scene) + return sq_throwerror(vm, "null scene"); + scene->setSpatialIndexingEnabled(getBool(vm, 2)); + return 0; +} + +static SQInteger sceneIsSpatialIndexingEnabled(HSQUIRRELVM vm) { + auto *scene = getRawPtr(vm, 1); + if (!scene) + return sq_throwerror(vm, "null scene"); + push(vm, scene->isSpatialIndexingEnabled()); + return 1; +} + +void registerStageBindings(HSQUIRRELVM vm) { + // Director 单例 + sq_pushroottable(vm); + sq_pushstring(vm, "Director", -1); + + sq_newtable(vm); + + // Director.getInstance() -> 返回 SceneManager 指针 + sq_pushstring(vm, "getInstance", -1); + sq_newclosure(vm, directorGetInstance, 0); + sq_newslot(vm, -3, SQFalse); + + // Director.enterScene(scene) + sq_pushstring(vm, "enterScene", -1); + sq_newclosure(vm, directorEnterScene, 0); + sq_newslot(vm, -3, SQFalse); + + // Director.replaceScene(scene) + sq_pushstring(vm, "replaceScene", -1); + sq_newclosure(vm, directorReplaceScene, 0); + sq_newslot(vm, -3, SQFalse); + + // Director.getCurrentScene() + sq_pushstring(vm, "getCurrentScene", -1); + sq_newclosure(vm, directorGetCurrentScene, 0); + sq_newslot(vm, -3, SQFalse); + + // Director.popScene() + sq_pushstring(vm, "popScene", -1); + sq_newclosure(vm, directorPopScene, 0); + sq_newslot(vm, -3, SQFalse); + + sq_newslot(vm, -3, SQFalse); // Director -> root + sq_pop(vm, 1); // pop root + + // Scene 类 + ClassDef(vm, "Scene") + .setTypeTag(typeTag()) + .staticMethod("create", sceneCreate) + .method("setBackgroundColor", sceneSetBackgroundColor) + .method("getBackgroundColor", sceneGetBackgroundColor) + .method("addChild", sceneAddChild) + .method("removeChild", sceneRemoveChild) + .method("removeAllChildren", sceneRemoveAllChildren) + .method("getChildByName", sceneGetChildByName) + .method("setSpatialIndexingEnabled", sceneSetSpatialIndexingEnabled) + .method("isSpatialIndexingEnabled", sceneIsSpatialIndexingEnabled) + .commit(); +} + +} // namespace sq +} // namespace extra2d diff --git a/docs/ScriptModule_Usage.md b/docs/ScriptModule_Usage.md new file mode 100644 index 0000000..773caa2 --- /dev/null +++ b/docs/ScriptModule_Usage.md @@ -0,0 +1,193 @@ +# Extra2D ScriptModule 使用指南 + +## 快速开始 + +### 1. 在 C++ 中注册 ScriptModule + +```cpp +#include + +int main(int argc, char** argv) { + // 初始化日志 + Logger::init(); + + // 配置应用 + AppConfig config; + config.title = "Script Game"; + config.width = 1280; + config.height = 720; + + // 初始化应用 + auto& app = Application::instance(); + if (!app.init(config)) { + return -1; + } + + // 注册 ScriptModule + auto scriptModule = makePtr(); + app.use(scriptModule); + + // 运行应用 + app.run(); + + return 0; +} +``` + +### 2. 创建脚本配置文件 + +创建 `scripts/ScriptConfig.cfg`: + +``` +# 预加载脚本列表 +scripts/core/_init.nut +scripts/core/class.nut +scripts/core/math.nut + +# 入口脚本 +entry: scripts/user/main.nut +``` + +### 3. 编写游戏脚本 + +创建 `scripts/user/main.nut`: + +```squirrel +// 游戏入口 +function main(args) { + print("Game started!"); + + // 创建场景 + local scene = Scene(); + scene.setBackgroundColor(0.1, 0.1, 0.3, 1.0); + + // 进入场景 + Director.enterScene(scene); +} + +// 每帧更新 +function onUpdate(dt) { + // 游戏逻辑 +} + +// 每帧渲染 +function onRender() { + // 渲染逻辑 +} + +// 启动游戏 +main([]); +``` + +## 脚本类库 + +### Core 层工具 + +#### 类系统 +```squirrel +// 定义类 +local MyClass = class(null); +MyClass.value <- 0; + +MyClass.constructor <- function(v) { + this.value = v; +} + +MyClass.getValue <- function() { + return this.value; +} + +// 创建实例 +local obj = MyClass.new(42); +print(obj.getValue()); // 输出: 42 +``` + +#### 数学工具 +```squirrel +// 向量 +local v1 = Vector2(100, 200); +local v2 = Vector2(50, 50); +local v3 = v1.add(v2); + +// 插值 +local value = lerp(0, 100, 0.5); // 50 + +// 限制 +local clamped = clamp(value, 0, 100); +``` + +## 配置格式 + +### ScriptConfig.cfg + +``` +# 注释以 # 开头 + +# 预加载脚本(按顺序加载) +scripts/core/_init.nut +scripts/core/class.nut +scripts/core/array.nut +scripts/core/math.nut +scripts/core/string.nut +scripts/core/table.nut + +scripts/engine/_init.nut +scripts/engine/application.nut +scripts/engine/director.nut +scripts/engine/stage.nut +scripts/engine/node.nut +scripts/engine/sprite.nut +scripts/engine/action.nut +scripts/engine/audio.nut +scripts/engine/input.nut + +# 用户脚本 +scripts/user/stages/start_stage.nut +scripts/user/objects/player.nut +scripts/user/ui/main_ui.nut + +# 入口脚本(最后加载,必须包含 main 函数) +entry: scripts/user/main.nut +``` + +## API 参考 + +### ScriptModule C++ API + +```cpp +// 创建模块 +auto scriptModule = makePtr(); + +// 加载配置 +scriptModule->loadConfig("path/to/config.cfg"); + +// 执行脚本 +scriptModule->executeFile("script.nut"); +scriptModule->executeString("print('Hello')"); + +// 调用脚本函数 +scriptModule->callFunction("onUpdate", 0.016f); + +// 获取 Squirrel VM +HSQUIRRELVM vm = scriptModule->getVM(); +``` + +### 全局函数 + +脚本中可以使用的全局函数: + +```squirrel +// 日志 +print("message"); // 输出到控制台 +log("message"); // 输出到日志系统 + +// 数学 +sqrt(x); // 平方根 +abs(x); // 绝对值 +sin(x); cos(x); tan(x); // 三角函数 +rand(); // 随机数 +``` + +## 示例项目 + +参见 `examples/script_demo/` 目录获取完整示例。 diff --git a/scripts/ScriptConfig.cfg b/scripts/ScriptConfig.cfg new file mode 100644 index 0000000..4455496 --- /dev/null +++ b/scripts/ScriptConfig.cfg @@ -0,0 +1,30 @@ +# Extra2D 脚本配置文件 +# 定义脚本加载顺序和入口点 + +# ========== Core 层(基础工具)========== +scripts/core/_init.nut +scripts/core/class.nut +scripts/core/array.nut +scripts/core/table.nut +scripts/core/math.nut +scripts/core/string.nut + +# ========== Engine 层(引擎封装)========== +scripts/engine/_init.nut +scripts/engine/application.nut +scripts/engine/director.nut +scripts/engine/stage.nut +scripts/engine/node.nut +scripts/engine/sprite.nut +scripts/engine/action.nut +scripts/engine/audio.nut +scripts/engine/input.nut + +# ========== User 层(用户逻辑)========== +# 用户脚本将在最后加载 +# scripts/user/stages/start_stage.nut +# scripts/user/objects/player.nut +# scripts/user/ui/main_ui.nut + +# 入口脚本(最后加载) +entry: scripts/user/main.nut diff --git a/scripts/core/_init.nut b/scripts/core/_init.nut new file mode 100644 index 0000000..6df7aa5 --- /dev/null +++ b/scripts/core/_init.nut @@ -0,0 +1,4 @@ +// Core 层初始化脚本 +// 基础工具类和函数 + +print("Core layer initialized"); diff --git a/scripts/core/array.nut b/scripts/core/array.nut new file mode 100644 index 0000000..18f5f2e --- /dev/null +++ b/scripts/core/array.nut @@ -0,0 +1,53 @@ +// Core 层 - 数组工具 +// 提供数组操作的工具函数 + +// 检查数组是否包含元素 +function array_contains(arr, item) { + foreach (i, v in arr) { + if (v == item) return true; + } + return false; +} + +// 查找数组元素索引 +function array_indexOf(arr, item) { + foreach (i, v in arr) { + if (v == item) return i; + } + return -1; +} + +// 数组去重 +function array_unique(arr) { + local result = []; + local seen = {}; + foreach (v in arr) { + if (!seen.rawin(v)) { + seen[v] <- true; + result.append(v); + } + } + return result; +} + +// 数组过滤 +function array_filter(arr, predicate) { + local result = []; + foreach (v in arr) { + if (predicate(v)) { + result.append(v); + } + } + return result; +} + +// 数组映射 +function array_map(arr, mapper) { + local result = []; + foreach (v in arr) { + result.append(mapper(v)); + } + return result; +} + +print("Array utilities loaded"); diff --git a/scripts/core/class.nut b/scripts/core/class.nut new file mode 100644 index 0000000..9ee9cf8 --- /dev/null +++ b/scripts/core/class.nut @@ -0,0 +1,43 @@ +// Core 层 - 类系统 +// 提供基础的类定义工具 + +// 简单的类创建函数 +function class(base) { + local new_class = {}; + + if (base != null) { + // 继承基类 + foreach (key, value in base) { + new_class[key] <- value; + } + new_class.base <- base; + } + + // 创建实例的函数 + new_class.constructor <- function() {} + + new_class.new <- function(...) { + local instance = {}; + + // 复制类成员到实例 + foreach (key, value in this) { + if (key != "new" && key != "constructor") { + instance[key] <- value; + } + } + + // 设置元表实现继承 + instance.setdelegate(this); + + // 调用构造函数 + if (this.constructor != null) { + instance.constructor.acall([instance].concat(vargv)); + } + + return instance; + } + + return new_class; +} + +print("Class system loaded"); diff --git a/scripts/core/math.nut b/scripts/core/math.nut new file mode 100644 index 0000000..5e16546 --- /dev/null +++ b/scripts/core/math.nut @@ -0,0 +1,77 @@ +// Core 层 - 数学工具 +// 提供数学计算的工具函数 + +// 常量 +const PI <- 3.14159265359; +const DEG_TO_RAD <- PI / 180.0; +const RAD_TO_DEG <- 180.0 / PI; + +// 角度转弧度 +function degToRad(deg) { + return deg * DEG_TO_RAD; +} + +// 弧度转角度 +function radToDeg(rad) { + return rad * RAD_TO_DEG; +} + +// 限制数值在范围内 +function clamp(value, min, max) { + if (value < min) return min; + if (value > max) return max; + return value; +} + +// 线性插值 +function lerp(a, b, t) { + return a + (b - a) * t; +} + +// 获取符号 +function sign(x) { + if (x > 0) return 1; + if (x < 0) return -1; + return 0; +} + +// 向量2D类 +Vector2 <- class { + x = 0.0; + y = 0.0; + + constructor(_x = 0.0, _y = 0.0) { + x = _x; + y = _y; + } + + function length() { + return sqrt(x * x + y * y); + } + + function normalize() { + local len = length(); + if (len > 0) { + return Vector2(x / len, y / len); + } + return Vector2(0, 0); + } + + function add(v) { + return Vector2(x + v.x, y + v.y); + } + + function sub(v) { + return Vector2(x - v.x, y - v.y); + } + + function mul(s) { + return Vector2(x * s, y * s); + } + + function toString() { + return "Vector2(" + x + ", " + y + ")"; + } +}; + +print("Math utilities loaded"); diff --git a/scripts/engine/_init.nut b/scripts/engine/_init.nut new file mode 100644 index 0000000..74e5f5c --- /dev/null +++ b/scripts/engine/_init.nut @@ -0,0 +1,4 @@ +// Engine 层初始化脚本 +// 引擎封装类的基础设置 + +print("Engine layer initialized"); diff --git a/scripts/engine/director.nut b/scripts/engine/director.nut new file mode 100644 index 0000000..c0c435f --- /dev/null +++ b/scripts/engine/director.nut @@ -0,0 +1,31 @@ +// Engine 层 - Director 导演类 +// 场景管理单例 + +Director <- { + // 进入场景 + function enterScene(scene) { + // 调用 C++ Director.enterScene + local instance = this.getInstance(); + return instance.enterScene(scene); + } + + // 替换场景 + function replaceScene(scene) { + local instance = this.getInstance(); + return instance.replaceScene(scene); + } + + // 获取当前场景 + function getCurrentScene() { + local instance = this.getInstance(); + return instance.getCurrentScene(); + } + + // 弹出场景 + function popScene() { + local instance = this.getInstance(); + return instance.popScene(); + } +}; + +print("Director loaded"); diff --git a/scripts/engine/stage.nut b/scripts/engine/stage.nut new file mode 100644 index 0000000..d86c32c --- /dev/null +++ b/scripts/engine/stage.nut @@ -0,0 +1,68 @@ +// Engine 层 - Stage 场景类 +// 继承自 Node 的场景类 + +Stage <- class(Node) { + // 构造函数 + constructor() { + // 创建 C++ Scene 对象 + this.C_Object = Scene.create(); + } + + // 设置背景颜色 + function setBackgroundColor(r, g, b, a) { + if (this.C_Object) { + this.C_Object.setBackgroundColor(r, g, b, a); + } + } + + // 获取背景颜色 + function getBackgroundColor() { + if (this.C_Object) { + return this.C_Object.getBackgroundColor(); + } + return null; + } + + // 添加子节点 + function addChild(node) { + if (this.C_Object && node && node.C_Object) { + this.C_Object.addChild(node.C_Object); + } + } + + // 移除子节点 + function removeChild(node) { + if (this.C_Object && node && node.C_Object) { + this.C_Object.removeChild(node.C_Object); + } + } + + // 移除所有子节点 + function removeAllChildren() { + if (this.C_Object) { + this.C_Object.removeAllChildren(); + } + } + + // 进入场景时调用 + function onEnter() { + // 子类重写 + } + + // 退出场景时调用 + function onExit() { + // 子类重写 + } + + // 每帧更新 + function onUpdate(dt) { + // 子类重写 + } + + // 每帧渲染 + function onRender() { + // 子类重写 + } +}; + +print("Stage loaded"); diff --git a/scripts/user/main.nut b/scripts/user/main.nut new file mode 100644 index 0000000..da620a7 --- /dev/null +++ b/scripts/user/main.nut @@ -0,0 +1,53 @@ +// User 层 - 入口脚本 +// 这是用户游戏的入口点 + +function main(args) { + print("========================================"); + print(" Extra2D Script System Demo"); + print("========================================"); + + // 创建开始场景 + local startStage = StartStage(); + + // 进入场景 + Director.enterScene(startStage.C_Object); + + print("Game started! Press START to exit."); +} + +// 定义开始场景 +StartStage <- class(Stage) { + constructor() { + base.constructor(); + + // 设置背景颜色 (深蓝色) + this.setBackgroundColor(0.1, 0.1, 0.3, 1.0); + + print("StartStage created!"); + } + + function onEnter() { + print("StartStage entered!"); + } + + function onUpdate(dt) { + // 游戏逻辑 + } + + function onRender() { + // 渲染逻辑 + } +}; + +// 全局更新函数(每帧调用) +function onUpdate(dt) { + // 在这里编写每帧更新的逻辑 +} + +// 全局渲染函数(每帧调用) +function onRender() { + // 在这里编写每帧渲染的逻辑 +} + +// 启动游戏 +main([]);