feat: implement modular script system (ScriptModule)
This commit is contained in:
parent
b4036cd8dd
commit
360a209a5c
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
请确认此方案后,我将开始
|
||||
|
|
@ -4,7 +4,9 @@
|
|||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/platform/window.h>
|
||||
#include <extra2d/base/module.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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<ModulePtr>& getModules() const { return modules_; }
|
||||
|
||||
private:
|
||||
Application() = default;
|
||||
~Application();
|
||||
|
|
@ -117,6 +132,9 @@ private:
|
|||
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||
UniquePtr<Camera> camera_;
|
||||
|
||||
// 模块系统
|
||||
std::vector<ModulePtr> modules_;
|
||||
|
||||
// 状态
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
|
|
|
|||
|
|
@ -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<Module>;
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
#include "../base/module.h"
|
||||
#include "../core/string.h"
|
||||
#include "../core/types.h"
|
||||
#include <squirrel.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 脚本配置结构
|
||||
// ============================================================================
|
||||
struct ScriptConfig {
|
||||
std::vector<String> 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<String>& 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<ScriptModule>;
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -178,6 +178,19 @@ template <typename T> T *getSingleton(HSQUIRRELVM vm, SQInteger idx) {
|
|||
return static_cast<T *>(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
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/script/sq_binding.h>
|
||||
|
||||
namespace extra2d {
|
||||
namespace sq {
|
||||
|
||||
// 注册 Stage 相关绑定
|
||||
void registerStageBindings(HSQUIRRELVM vm);
|
||||
|
||||
} // namespace sq
|
||||
} // namespace extra2d
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
|
@ -48,6 +49,9 @@ template <typename T> inline std::string to_string_arg(const T &value) {
|
|||
} else {
|
||||
return std::to_string(value);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, String>) {
|
||||
// 处理 extra2d::String
|
||||
return value.c_str();
|
||||
} else {
|
||||
std::ostringstream oss;
|
||||
oss << value;
|
||||
|
|
|
|||
|
|
@ -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> 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
#include "extra2d/base/module.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// Module 基类的实现
|
||||
// 目前所有方法都是虚函数,在头文件中已有默认实现
|
||||
// 如果后续需要添加通用逻辑,可以在这里实现
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -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 <cstdarg>
|
||||
#include <fstream>
|
||||
#include <sqstdaux.h>
|
||||
#include <sqstdblob.h>
|
||||
#include <sqstdio.h>
|
||||
#include <sqstdmath.h>
|
||||
#include <sqstdstring.h>
|
||||
#include <sstream>
|
||||
|
||||
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, "<string>");
|
||||
}
|
||||
|
||||
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<String>& 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<SQInteger>(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
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/scene/sprite.h>
|
||||
#include <extra2d/script/sq_binding_stage.h>
|
||||
#include <extra2d/script/sq_binding_types.h>
|
||||
|
||||
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<SceneManager*>(getUserPointer(vm, 1));
|
||||
if (!sceneManager)
|
||||
return sq_throwerror(vm, "null director");
|
||||
|
||||
auto scene = getPtr<Scene>(vm, 2);
|
||||
if (!scene)
|
||||
return sq_throwerror(vm, "null scene");
|
||||
|
||||
auto& app = Application::instance();
|
||||
scene->setViewportSize(static_cast<float>(app.window().getWidth()),
|
||||
static_cast<float>(app.window().getHeight()));
|
||||
sceneManager->enterScene(scene);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SQInteger directorReplaceScene(HSQUIRRELVM vm) {
|
||||
auto* sceneManager = static_cast<SceneManager*>(getUserPointer(vm, 1));
|
||||
if (!sceneManager)
|
||||
return sq_throwerror(vm, "null director");
|
||||
|
||||
auto scene = getPtr<Scene>(vm, 2);
|
||||
if (!scene)
|
||||
return sq_throwerror(vm, "null scene");
|
||||
|
||||
auto& app = Application::instance();
|
||||
scene->setViewportSize(static_cast<float>(app.window().getWidth()),
|
||||
static_cast<float>(app.window().getHeight()));
|
||||
sceneManager->replaceScene(scene);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SQInteger directorGetCurrentScene(HSQUIRRELVM vm) {
|
||||
auto* sceneManager = static_cast<SceneManager*>(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<SceneManager*>(getUserPointer(vm, 1));
|
||||
if (!sceneManager)
|
||||
return sq_throwerror(vm, "null director");
|
||||
|
||||
sceneManager->popScene();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Scene (舞台/场景)
|
||||
// ============================================================================
|
||||
|
||||
static SQInteger sceneCreate(HSQUIRRELVM vm) {
|
||||
auto scene = makePtr<Scene>();
|
||||
pushPtr(vm, scene);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static SQInteger sceneSetBackgroundColor(HSQUIRRELVM vm) {
|
||||
auto *scene = getRawPtr<Scene>(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<float>(getFloat(vm, 2));
|
||||
float g = static_cast<float>(getFloat(vm, 3));
|
||||
float b = static_cast<float>(getFloat(vm, 4));
|
||||
float a = static_cast<float>(getFloat(vm, 5));
|
||||
scene->setBackgroundColor(Color(r, g, b, a));
|
||||
} else if (argc >= 2) {
|
||||
// Color instance
|
||||
Color *c = getValueInstance<Color>(vm, 2);
|
||||
if (c)
|
||||
scene->setBackgroundColor(*c);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SQInteger sceneGetBackgroundColor(HSQUIRRELVM vm) {
|
||||
auto *scene = getRawPtr<Scene>(vm, 1);
|
||||
if (!scene)
|
||||
return sq_throwerror(vm, "null scene");
|
||||
pushValueInstance(vm, scene->getBackgroundColor());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static SQInteger sceneAddChild(HSQUIRRELVM vm) {
|
||||
auto scene = getPtr<Scene>(vm, 1);
|
||||
auto node = getPtr<Node>(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<Scene>(vm, 1);
|
||||
auto node = getPtr<Node>(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<Scene>(vm, 1);
|
||||
if (!scene)
|
||||
return sq_throwerror(vm, "null scene");
|
||||
scene->removeAllChildren();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SQInteger sceneGetChildByName(HSQUIRRELVM vm) {
|
||||
auto *scene = getRawPtr<Scene>(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<Scene>(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<Scene>(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<Scene>())
|
||||
.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
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
# Extra2D ScriptModule 使用指南
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 在 C++ 中注册 ScriptModule
|
||||
|
||||
```cpp
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
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<ScriptModule>();
|
||||
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>();
|
||||
|
||||
// 加载配置
|
||||
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/` 目录获取完整示例。
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Core 层初始化脚本
|
||||
// 基础工具类和函数
|
||||
|
||||
print("Core layer initialized");
|
||||
|
|
@ -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");
|
||||
|
|
@ -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");
|
||||
|
|
@ -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");
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Engine 层初始化脚本
|
||||
// 引擎封装类的基础设置
|
||||
|
||||
print("Engine layer initialized");
|
||||
|
|
@ -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");
|
||||
|
|
@ -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");
|
||||
|
|
@ -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([]);
|
||||
Loading…
Reference in New Issue