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/core/types.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
#include <extra2d/platform/window.h>
|
#include <extra2d/platform/window.h>
|
||||||
|
#include <extra2d/base/module.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -96,6 +98,19 @@ public:
|
||||||
// 获取配置
|
// 获取配置
|
||||||
const AppConfig &getConfig() const { return config_; }
|
const AppConfig &getConfig() const { return config_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 模块系统 (新增)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 注册模块
|
||||||
|
void use(ModulePtr module);
|
||||||
|
|
||||||
|
// 获取模块
|
||||||
|
ModulePtr getModule(const char* name);
|
||||||
|
|
||||||
|
// 获取所有模块
|
||||||
|
const std::vector<ModulePtr>& getModules() const { return modules_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application() = default;
|
Application() = default;
|
||||||
~Application();
|
~Application();
|
||||||
|
|
@ -117,6 +132,9 @@ private:
|
||||||
UniquePtr<EventDispatcher> eventDispatcher_;
|
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||||
UniquePtr<Camera> camera_;
|
UniquePtr<Camera> camera_;
|
||||||
|
|
||||||
|
// 模块系统
|
||||||
|
std::vector<ModulePtr> modules_;
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
bool running_ = 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);
|
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
|
// 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 <cstdarg>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -48,6 +49,9 @@ template <typename T> inline std::string to_string_arg(const T &value) {
|
||||||
} else {
|
} else {
|
||||||
return std::to_string(value);
|
return std::to_string(value);
|
||||||
}
|
}
|
||||||
|
} else if constexpr (std::is_same_v<T, String>) {
|
||||||
|
// 处理 extra2d::String
|
||||||
|
return value.c_str();
|
||||||
} else {
|
} else {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << value;
|
oss << value;
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,15 @@ void Application::shutdown() {
|
||||||
sceneManager_->end();
|
sceneManager_->end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 销毁所有模块(逆序销毁)
|
||||||
|
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
|
||||||
|
if (*it) {
|
||||||
|
(*it)->destroyModule();
|
||||||
|
(*it)->setInitialized(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modules_.clear();
|
||||||
|
|
||||||
// 清理子系统
|
// 清理子系统
|
||||||
sceneManager_.reset();
|
sceneManager_.reset();
|
||||||
resourceManager_.reset();
|
resourceManager_.reset();
|
||||||
|
|
@ -311,6 +320,13 @@ void Application::update() {
|
||||||
timerManager_->update(deltaTime_);
|
timerManager_->update(deltaTime_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新所有启用的模块
|
||||||
|
for (const auto& module : modules_) {
|
||||||
|
if (module && module->isEnabled()) {
|
||||||
|
module->onUpdate(deltaTime_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
sceneManager_->update(deltaTime_);
|
sceneManager_->update(deltaTime_);
|
||||||
}
|
}
|
||||||
|
|
@ -324,6 +340,13 @@ void Application::render() {
|
||||||
|
|
||||||
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
|
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
|
||||||
|
|
||||||
|
// 渲染所有启用的模块
|
||||||
|
for (const auto& module : modules_) {
|
||||||
|
if (module && module->isEnabled()) {
|
||||||
|
module->onRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
sceneManager_->render(*renderer_);
|
sceneManager_->render(*renderer_);
|
||||||
} else {
|
} 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
|
} // 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