fix(worker-generator): 映射文件不再放入 workers 目录避免微信编译错误
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generatedAt": "2025-12-08T08:57:32.415Z",
|
"generatedAt": "2025-12-08T09:16:10.529Z",
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"PhysicsWorkerSystem": "physics-worker.js"
|
"PhysicsWorkerSystem": "physics-worker.js"
|
||||||
}
|
}
|
||||||
|
|||||||
30
examples/wechat-worker-demo/build.js
Normal file
30
examples/wechat-worker-demo/build.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 构建脚本 - 打包为微信小游戏可用的 JS 文件
|
||||||
|
* Build script - bundle for WeChat Mini Game
|
||||||
|
*/
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const outDir = path.join(__dirname, 'dist');
|
||||||
|
|
||||||
|
// 确保输出目录存在
|
||||||
|
if (!fs.existsSync(outDir)) {
|
||||||
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打包主程序
|
||||||
|
esbuild.buildSync({
|
||||||
|
entryPoints: ['src/index.ts'],
|
||||||
|
bundle: true,
|
||||||
|
outfile: 'dist/game-bundle.js',
|
||||||
|
format: 'iife',
|
||||||
|
globalName: 'GameDemo',
|
||||||
|
target: ['es2015'],
|
||||||
|
platform: 'browser',
|
||||||
|
external: [], // 不排除任何依赖,全部打包
|
||||||
|
minify: false,
|
||||||
|
sourcemap: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Build complete: dist/game-bundle.js');
|
||||||
173
examples/wechat-worker-demo/deploy.js
Normal file
173
examples/wechat-worker-demo/deploy.js
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* 部署脚本 - 复制文件到微信小游戏项目
|
||||||
|
* Deploy script - copy files to WeChat Mini Game project
|
||||||
|
*/
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// 微信小游戏项目路径
|
||||||
|
const WECHAT_PROJECT = 'F:/MiniGame';
|
||||||
|
|
||||||
|
// 需要复制的文件
|
||||||
|
const filesToCopy = [
|
||||||
|
// Worker 文件
|
||||||
|
{ src: 'workers/physics-worker.js', dest: 'workers/physics-worker.js' },
|
||||||
|
// 注意:worker-mapping.json 不要放在 workers 目录,微信会把它当 JS 编译
|
||||||
|
// Note: Don't put worker-mapping.json in workers dir, WeChat will try to compile it as JS
|
||||||
|
];
|
||||||
|
|
||||||
|
// ECS 框架库
|
||||||
|
const ecsFrameworkSrc = path.join(__dirname, '../../packages/core/dist/index.umd.js');
|
||||||
|
const ecsFrameworkDest = path.join(WECHAT_PROJECT, 'libs/ecs-framework.js');
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
function ensureDir(filePath) {
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Deploying to WeChat Mini Game project:', WECHAT_PROJECT);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// 复制 ECS 框架
|
||||||
|
ensureDir(ecsFrameworkDest);
|
||||||
|
if (fs.existsSync(ecsFrameworkSrc)) {
|
||||||
|
fs.copyFileSync(ecsFrameworkSrc, ecsFrameworkDest);
|
||||||
|
console.log('Copied: ecs-framework.js');
|
||||||
|
} else {
|
||||||
|
console.warn('Warning: ECS framework not found at', ecsFrameworkSrc);
|
||||||
|
console.warn('Please run "pnpm build" in packages/core first');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制 Worker 文件
|
||||||
|
for (const file of filesToCopy) {
|
||||||
|
const srcPath = path.join(__dirname, file.src);
|
||||||
|
const destPath = path.join(WECHAT_PROJECT, file.dest);
|
||||||
|
|
||||||
|
if (fs.existsSync(srcPath)) {
|
||||||
|
ensureDir(destPath);
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
console.log('Copied:', file.dest);
|
||||||
|
} else {
|
||||||
|
console.warn('Warning: File not found:', srcPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 game.js
|
||||||
|
const gameJs = `/**
|
||||||
|
* ESEngine Worker System 微信小游戏测试
|
||||||
|
* ESEngine Worker System WeChat Mini Game Test
|
||||||
|
*/
|
||||||
|
|
||||||
|
console.log('====================================');
|
||||||
|
console.log('ESEngine Worker 微信小游戏测试');
|
||||||
|
console.log('====================================');
|
||||||
|
|
||||||
|
// 检查 Worker API
|
||||||
|
console.log('\\n[1] 检查环境...');
|
||||||
|
console.log('wx.createWorker:', typeof wx.createWorker);
|
||||||
|
|
||||||
|
// 创建 Worker
|
||||||
|
console.log('\\n[2] 创建 Worker...');
|
||||||
|
var worker = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
worker = wx.createWorker('workers/physics-worker.js', {
|
||||||
|
useExperimentalWorker: true
|
||||||
|
});
|
||||||
|
console.log('Worker 创建成功!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Worker 创建失败:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (worker) {
|
||||||
|
// 设置消息处理
|
||||||
|
worker.onMessage(function(res) {
|
||||||
|
console.log('\\n[4] 收到 Worker 响应!');
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
console.error('Worker 错误:', res.error);
|
||||||
|
} else if (res.result) {
|
||||||
|
console.log('Worker 处理成功!');
|
||||||
|
console.log('实体数量:', res.result.length);
|
||||||
|
|
||||||
|
// 显示前 3 个实体
|
||||||
|
for (var i = 0; i < Math.min(3, res.result.length); i++) {
|
||||||
|
var e = res.result[i];
|
||||||
|
console.log(' 实体 ' + e.id + ': (' + e.x.toFixed(1) + ', ' + e.y.toFixed(1) + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\\n========== Worker 测试成功! ==========');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建测试数据
|
||||||
|
console.log('\\n[3] 发送测试数据...');
|
||||||
|
|
||||||
|
var entities = [];
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
entities.push({
|
||||||
|
id: i + 1,
|
||||||
|
x: Math.random() * 300 + 37,
|
||||||
|
y: Math.random() * 400 + 100,
|
||||||
|
dx: (Math.random() - 0.5) * 100,
|
||||||
|
dy: (Math.random() - 0.5) * 50,
|
||||||
|
mass: 1 + Math.random(),
|
||||||
|
bounce: 0.8,
|
||||||
|
friction: 0.98,
|
||||||
|
radius: 5 + Math.random() * 5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
id: Date.now(),
|
||||||
|
entities: entities,
|
||||||
|
deltaTime: 0.016,
|
||||||
|
systemConfig: {
|
||||||
|
gravity: 200,
|
||||||
|
canvasWidth: 375,
|
||||||
|
canvasHeight: 667,
|
||||||
|
groundFriction: 0.98
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('已发送 ' + entities.length + ' 个实体');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 Canvas 显示
|
||||||
|
var canvas = wx.createCanvas();
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
ctx.fillStyle = '#1a1a2e';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.font = '18px Arial';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('ESEngine Worker 测试', canvas.width / 2, 50);
|
||||||
|
|
||||||
|
ctx.font = '14px Arial';
|
||||||
|
ctx.fillStyle = '#aaaaaa';
|
||||||
|
ctx.fillText('查看控制台日志', canvas.width / 2, 80);
|
||||||
|
|
||||||
|
ctx.fillStyle = worker ? '#00ff00' : '#ff0000';
|
||||||
|
ctx.fillText('Worker: ' + (worker ? '已创建' : '创建失败'), canvas.width / 2, 110);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const gameJsPath = path.join(WECHAT_PROJECT, 'game.js');
|
||||||
|
fs.writeFileSync(gameJsPath, gameJs);
|
||||||
|
console.log('Created: game.js');
|
||||||
|
|
||||||
|
// 确保 game.json 配置正确
|
||||||
|
const gameJsonPath = path.join(WECHAT_PROJECT, 'game.json');
|
||||||
|
const gameJson = {
|
||||||
|
deviceOrientation: 'portrait',
|
||||||
|
workers: 'workers'
|
||||||
|
};
|
||||||
|
fs.writeFileSync(gameJsonPath, JSON.stringify(gameJson, null, 2));
|
||||||
|
console.log('Updated: game.json');
|
||||||
|
|
||||||
|
console.log('\\nDeploy complete!');
|
||||||
|
console.log('Open WeChat DevTools and load:', WECHAT_PROJECT);
|
||||||
15
examples/wechat-worker-demo/package.json
Normal file
15
examples/wechat-worker-demo/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "wechat-worker-demo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "ESEngine Worker System WeChat Mini Game Demo",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.js",
|
||||||
|
"generate-worker": "npx esengine-worker-gen --src ./src --wechat --verbose",
|
||||||
|
"deploy": "node deploy.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@esengine/ecs-framework": "^2.3.2",
|
||||||
|
"@esengine/worker-generator": "^1.0.1",
|
||||||
|
"esbuild": "^0.19.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
65
examples/wechat-worker-demo/src/components.ts
Normal file
65
examples/wechat-worker-demo/src/components.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 组件定义
|
||||||
|
* Component definitions
|
||||||
|
*/
|
||||||
|
import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@ECSComponent('Position')
|
||||||
|
export class Position extends Component {
|
||||||
|
x: number = 0;
|
||||||
|
y: number = 0;
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(x: number, y: number): void {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
export class Velocity extends Component {
|
||||||
|
dx: number = 0;
|
||||||
|
dy: number = 0;
|
||||||
|
|
||||||
|
constructor(dx: number = 0, dy: number = 0) {
|
||||||
|
super();
|
||||||
|
this.dx = dx;
|
||||||
|
this.dy = dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(dx: number, dy: number): void {
|
||||||
|
this.dx = dx;
|
||||||
|
this.dy = dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Physics')
|
||||||
|
export class Physics extends Component {
|
||||||
|
mass: number = 1;
|
||||||
|
bounce: number = 0.8;
|
||||||
|
friction: number = 0.95;
|
||||||
|
|
||||||
|
constructor(mass: number = 1, bounce: number = 0.8, friction: number = 0.95) {
|
||||||
|
super();
|
||||||
|
this.mass = mass;
|
||||||
|
this.bounce = bounce;
|
||||||
|
this.friction = friction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Renderable')
|
||||||
|
export class Renderable extends Component {
|
||||||
|
color: string = '#ffffff';
|
||||||
|
size: number = 5;
|
||||||
|
|
||||||
|
constructor(color: string = '#ffffff', size: number = 5) {
|
||||||
|
super();
|
||||||
|
this.color = color;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
examples/wechat-worker-demo/src/index.ts
Normal file
6
examples/wechat-worker-demo/src/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* ESEngine Worker System 微信小游戏示例
|
||||||
|
* ESEngine Worker System WeChat Mini Game Example
|
||||||
|
*/
|
||||||
|
export { Position, Velocity, Physics, Renderable } from './components';
|
||||||
|
export { PhysicsWorkerSystem } from './systems/PhysicsWorkerSystem';
|
||||||
212
examples/wechat-worker-demo/src/systems/PhysicsWorkerSystem.ts
Normal file
212
examples/wechat-worker-demo/src/systems/PhysicsWorkerSystem.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* 物理 Worker 系统
|
||||||
|
* Physics Worker System
|
||||||
|
*
|
||||||
|
* 这个系统会被 worker-generator CLI 扫描,
|
||||||
|
* 自动提取 workerProcess 方法生成 Worker 文件
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
WorkerEntitySystem,
|
||||||
|
Matcher,
|
||||||
|
Entity,
|
||||||
|
ECSSystem
|
||||||
|
} from '@esengine/ecs-framework';
|
||||||
|
import { Position, Velocity, Physics, Renderable } from '../components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物理实体数据
|
||||||
|
* Physics entity data
|
||||||
|
*/
|
||||||
|
export interface PhysicsEntityData {
|
||||||
|
id: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
dx: number;
|
||||||
|
dy: number;
|
||||||
|
mass: number;
|
||||||
|
bounce: number;
|
||||||
|
friction: number;
|
||||||
|
radius: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物理系统配置
|
||||||
|
* Physics system config
|
||||||
|
*/
|
||||||
|
export interface PhysicsConfig {
|
||||||
|
gravity: number;
|
||||||
|
canvasWidth: number;
|
||||||
|
canvasHeight: number;
|
||||||
|
groundFriction: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSSystem('PhysicsWorkerSystem')
|
||||||
|
export class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsEntityData> {
|
||||||
|
constructor(canvasWidth: number = 375, canvasHeight: number = 667) {
|
||||||
|
super(
|
||||||
|
Matcher.empty().all(Position, Velocity, Physics),
|
||||||
|
{
|
||||||
|
enableWorker: true,
|
||||||
|
workerCount: 1,
|
||||||
|
// 重要:这个路径会被 CLI 工具读取,生成 Worker 文件到此位置
|
||||||
|
// Important: CLI tool reads this path to generate Worker file
|
||||||
|
workerScriptPath: 'workers/physics-worker.js',
|
||||||
|
systemConfig: {
|
||||||
|
gravity: 200,
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
groundFriction: 0.98
|
||||||
|
} as PhysicsConfig
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取实体数据
|
||||||
|
* Extract entity data
|
||||||
|
*/
|
||||||
|
protected extractEntityData(entity: Entity): PhysicsEntityData {
|
||||||
|
const position = entity.getComponent(Position)!;
|
||||||
|
const velocity = entity.getComponent(Velocity)!;
|
||||||
|
const physics = entity.getComponent(Physics)!;
|
||||||
|
const renderable = entity.getComponent(Renderable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
dx: velocity.dx,
|
||||||
|
dy: velocity.dy,
|
||||||
|
mass: physics.mass,
|
||||||
|
bounce: physics.bounce,
|
||||||
|
friction: physics.friction,
|
||||||
|
radius: renderable?.size || 5
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker 处理函数 - 会被 CLI 工具提取
|
||||||
|
* Worker process function - will be extracted by CLI tool
|
||||||
|
*
|
||||||
|
* 注意:这个函数必须是纯函数,不能使用 this 或外部变量
|
||||||
|
* Note: This function must be pure, cannot use this or external variables
|
||||||
|
*/
|
||||||
|
protected workerProcess(
|
||||||
|
entities: PhysicsEntityData[],
|
||||||
|
deltaTime: number,
|
||||||
|
config: PhysicsConfig
|
||||||
|
): PhysicsEntityData[] {
|
||||||
|
const gravity = config.gravity;
|
||||||
|
const canvasWidth = config.canvasWidth;
|
||||||
|
const canvasHeight = config.canvasHeight;
|
||||||
|
const groundFriction = config.groundFriction;
|
||||||
|
|
||||||
|
// 复制实体数组避免修改原数据
|
||||||
|
// Copy entity array to avoid modifying original data
|
||||||
|
const result = entities.map(e => ({ ...e }));
|
||||||
|
|
||||||
|
// 物理更新
|
||||||
|
// Physics update
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
const entity = result[i];
|
||||||
|
|
||||||
|
// 应用重力
|
||||||
|
// Apply gravity
|
||||||
|
entity.dy += gravity * deltaTime;
|
||||||
|
|
||||||
|
// 更新位置
|
||||||
|
// Update position
|
||||||
|
entity.x += entity.dx * deltaTime;
|
||||||
|
entity.y += entity.dy * deltaTime;
|
||||||
|
|
||||||
|
// 边界碰撞
|
||||||
|
// Boundary collision
|
||||||
|
if (entity.x <= entity.radius) {
|
||||||
|
entity.x = entity.radius;
|
||||||
|
entity.dx = -entity.dx * entity.bounce;
|
||||||
|
} else if (entity.x >= canvasWidth - entity.radius) {
|
||||||
|
entity.x = canvasWidth - entity.radius;
|
||||||
|
entity.dx = -entity.dx * entity.bounce;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.y <= entity.radius) {
|
||||||
|
entity.y = entity.radius;
|
||||||
|
entity.dy = -entity.dy * entity.bounce;
|
||||||
|
} else if (entity.y >= canvasHeight - entity.radius) {
|
||||||
|
entity.y = canvasHeight - entity.radius;
|
||||||
|
entity.dy = -entity.dy * entity.bounce;
|
||||||
|
entity.dx *= groundFriction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空气阻力
|
||||||
|
// Air friction
|
||||||
|
entity.dx *= entity.friction;
|
||||||
|
entity.dy *= entity.friction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单碰撞检测
|
||||||
|
// Simple collision detection
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
for (let j = i + 1; j < result.length; j++) {
|
||||||
|
const ball1 = result[i];
|
||||||
|
const ball2 = result[j];
|
||||||
|
|
||||||
|
const dx = ball2.x - ball1.x;
|
||||||
|
const dy = ball2.y - ball1.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
const minDistance = ball1.radius + ball2.radius;
|
||||||
|
|
||||||
|
if (distance < minDistance && distance > 0) {
|
||||||
|
// 分离两个球
|
||||||
|
// Separate two balls
|
||||||
|
const nx = dx / distance;
|
||||||
|
const ny = dy / distance;
|
||||||
|
const overlap = minDistance - distance;
|
||||||
|
|
||||||
|
ball1.x -= nx * overlap * 0.5;
|
||||||
|
ball1.y -= ny * overlap * 0.5;
|
||||||
|
ball2.x += nx * overlap * 0.5;
|
||||||
|
ball2.y += ny * overlap * 0.5;
|
||||||
|
|
||||||
|
// 弹性碰撞
|
||||||
|
// Elastic collision
|
||||||
|
const relVx = ball2.dx - ball1.dx;
|
||||||
|
const relVy = ball2.dy - ball1.dy;
|
||||||
|
const velAlongNormal = relVx * nx + relVy * ny;
|
||||||
|
|
||||||
|
if (velAlongNormal > 0) continue;
|
||||||
|
|
||||||
|
const restitution = (ball1.bounce + ball2.bounce) * 0.5;
|
||||||
|
const impulse = -(1 + restitution) * velAlongNormal / (1/ball1.mass + 1/ball2.mass);
|
||||||
|
|
||||||
|
ball1.dx -= impulse * nx / ball1.mass;
|
||||||
|
ball1.dy -= impulse * ny / ball1.mass;
|
||||||
|
ball2.dx += impulse * nx / ball2.mass;
|
||||||
|
ball2.dy += impulse * ny / ball2.mass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用处理结果
|
||||||
|
* Apply processing result
|
||||||
|
*/
|
||||||
|
protected applyResult(entity: Entity, result: PhysicsEntityData): void {
|
||||||
|
if (!entity?.enabled) return;
|
||||||
|
|
||||||
|
const position = entity.getComponent(Position);
|
||||||
|
const velocity = entity.getComponent(Velocity);
|
||||||
|
|
||||||
|
if (position && velocity) {
|
||||||
|
position.set(result.x, result.y);
|
||||||
|
velocity.set(result.dx, result.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getDefaultEntityDataSize(): number {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
examples/wechat-worker-demo/tsconfig.json
Normal file
17
examples/wechat-worker-demo/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2015",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"declaration": false,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
175
examples/wechat-worker-demo/workers/physics-worker.js
Normal file
175
examples/wechat-worker-demo/workers/physics-worker.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* Auto-generated Worker file for PhysicsWorkerSystem
|
||||||
|
* 自动生成的 Worker 文件
|
||||||
|
*
|
||||||
|
* Source: F:/ecs-framework/examples/wechat-worker-demo/src/systems/PhysicsWorkerSystem.ts
|
||||||
|
* Generated by @esengine/worker-generator
|
||||||
|
*
|
||||||
|
* 使用方式 | Usage:
|
||||||
|
* 1. 将此文件放入 workers/ 目录
|
||||||
|
* 2. 在 game.json 中配置 "workers": "workers"
|
||||||
|
* 3. 在 System 中配置 workerScriptPath: 'workers/physics-worker-system-worker.js'
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 微信小游戏 Worker 环境
|
||||||
|
// WeChat Mini Game Worker environment
|
||||||
|
let sharedFloatArray = null;
|
||||||
|
const ENTITY_DATA_SIZE = 9;
|
||||||
|
|
||||||
|
worker.onMessage(function(res) {
|
||||||
|
// 微信小游戏 Worker 消息直接传递数据,不需要 .data
|
||||||
|
// WeChat Mini Game Worker passes data directly, no .data wrapper
|
||||||
|
var type = res.type;
|
||||||
|
var id = res.id;
|
||||||
|
var entities = res.entities;
|
||||||
|
var deltaTime = res.deltaTime;
|
||||||
|
var systemConfig = res.systemConfig;
|
||||||
|
var startIndex = res.startIndex;
|
||||||
|
var endIndex = res.endIndex;
|
||||||
|
var sharedBuffer = res.sharedBuffer;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 处理 SharedArrayBuffer 初始化
|
||||||
|
// Handle SharedArrayBuffer initialization
|
||||||
|
if (type === 'init' && sharedBuffer) {
|
||||||
|
sharedFloatArray = new Float32Array(sharedBuffer);
|
||||||
|
worker.postMessage({ type: 'init', success: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 SharedArrayBuffer 数据
|
||||||
|
// Handle SharedArrayBuffer data
|
||||||
|
if (type === 'shared' && sharedFloatArray) {
|
||||||
|
processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig);
|
||||||
|
worker.postMessage({ id: id, result: null });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传统处理方式
|
||||||
|
// Traditional processing
|
||||||
|
if (entities) {
|
||||||
|
var result = workerProcess(entities, deltaTime, systemConfig);
|
||||||
|
|
||||||
|
// 处理 Promise 返回值
|
||||||
|
// Handle Promise return value
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
result.then(function(finalResult) {
|
||||||
|
worker.postMessage({ id: id, result: finalResult });
|
||||||
|
}).catch(function(error) {
|
||||||
|
worker.postMessage({ id: id, error: error.message });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
worker.postMessage({ id: id, result: result });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
worker.postMessage({ id: id, error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体处理函数 - 从 PhysicsWorkerSystem.workerProcess 提取
|
||||||
|
* Entity processing function - extracted from PhysicsWorkerSystem.workerProcess
|
||||||
|
*/
|
||||||
|
function workerProcess(entities, deltaTime, config) {
|
||||||
|
var __assign = (this && this.__assign) || function () {
|
||||||
|
__assign = Object.assign || function(t) {
|
||||||
|
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||||
|
s = arguments[i];
|
||||||
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||||
|
t[p] = s[p];
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
return __assign.apply(this, arguments);
|
||||||
|
};
|
||||||
|
var gravity = config.gravity;
|
||||||
|
var canvasWidth = config.canvasWidth;
|
||||||
|
var canvasHeight = config.canvasHeight;
|
||||||
|
var groundFriction = config.groundFriction;
|
||||||
|
// 复制实体数组避免修改原数据
|
||||||
|
// Copy entity array to avoid modifying original data
|
||||||
|
var result = entities.map(function (e) { return (__assign({}, e)); });
|
||||||
|
// 物理更新
|
||||||
|
// Physics update
|
||||||
|
for (var i = 0; i < result.length; i++) {
|
||||||
|
var entity = result[i];
|
||||||
|
// 应用重力
|
||||||
|
// Apply gravity
|
||||||
|
entity.dy += gravity * deltaTime;
|
||||||
|
// 更新位置
|
||||||
|
// Update position
|
||||||
|
entity.x += entity.dx * deltaTime;
|
||||||
|
entity.y += entity.dy * deltaTime;
|
||||||
|
// 边界碰撞
|
||||||
|
// Boundary collision
|
||||||
|
if (entity.x <= entity.radius) {
|
||||||
|
entity.x = entity.radius;
|
||||||
|
entity.dx = -entity.dx * entity.bounce;
|
||||||
|
}
|
||||||
|
else if (entity.x >= canvasWidth - entity.radius) {
|
||||||
|
entity.x = canvasWidth - entity.radius;
|
||||||
|
entity.dx = -entity.dx * entity.bounce;
|
||||||
|
}
|
||||||
|
if (entity.y <= entity.radius) {
|
||||||
|
entity.y = entity.radius;
|
||||||
|
entity.dy = -entity.dy * entity.bounce;
|
||||||
|
}
|
||||||
|
else if (entity.y >= canvasHeight - entity.radius) {
|
||||||
|
entity.y = canvasHeight - entity.radius;
|
||||||
|
entity.dy = -entity.dy * entity.bounce;
|
||||||
|
entity.dx *= groundFriction;
|
||||||
|
}
|
||||||
|
// 空气阻力
|
||||||
|
// Air friction
|
||||||
|
entity.dx *= entity.friction;
|
||||||
|
entity.dy *= entity.friction;
|
||||||
|
}
|
||||||
|
// 简单碰撞检测
|
||||||
|
// Simple collision detection
|
||||||
|
for (var i = 0; i < result.length; i++) {
|
||||||
|
for (var j = i + 1; j < result.length; j++) {
|
||||||
|
var ball1 = result[i];
|
||||||
|
var ball2 = result[j];
|
||||||
|
var dx = ball2.x - ball1.x;
|
||||||
|
var dy = ball2.y - ball1.y;
|
||||||
|
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
var minDistance = ball1.radius + ball2.radius;
|
||||||
|
if (distance < minDistance && distance > 0) {
|
||||||
|
// 分离两个球
|
||||||
|
// Separate two balls
|
||||||
|
var nx = dx / distance;
|
||||||
|
var ny = dy / distance;
|
||||||
|
var overlap = minDistance - distance;
|
||||||
|
ball1.x -= nx * overlap * 0.5;
|
||||||
|
ball1.y -= ny * overlap * 0.5;
|
||||||
|
ball2.x += nx * overlap * 0.5;
|
||||||
|
ball2.y += ny * overlap * 0.5;
|
||||||
|
// 弹性碰撞
|
||||||
|
// Elastic collision
|
||||||
|
var relVx = ball2.dx - ball1.dx;
|
||||||
|
var relVy = ball2.dy - ball1.dy;
|
||||||
|
var velAlongNormal = relVx * nx + relVy * ny;
|
||||||
|
if (velAlongNormal > 0)
|
||||||
|
continue;
|
||||||
|
var restitution = (ball1.bounce + ball2.bounce) * 0.5;
|
||||||
|
var impulse = -(1 + restitution) * velAlongNormal / (1 / ball1.mass + 1 / ball2.mass);
|
||||||
|
ball1.dx -= impulse * nx / ball1.mass;
|
||||||
|
ball1.dy -= impulse * ny / ball1.mass;
|
||||||
|
ball2.dx += impulse * nx / ball2.mass;
|
||||||
|
ball2.dy += impulse * ny / ball2.mass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SharedArrayBuffer 处理函数
|
||||||
|
* SharedArrayBuffer processing function
|
||||||
|
*/
|
||||||
|
function processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig) {
|
||||||
|
if (!sharedFloatArray) return;
|
||||||
|
// No SharedArrayBuffer processing defined
|
||||||
|
}
|
||||||
6
examples/wechat-worker-demo/workers/worker-mapping.json
Normal file
6
examples/wechat-worker-demo/workers/worker-mapping.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"generatedAt": "2025-12-08T09:57:08.855Z",
|
||||||
|
"mappings": {
|
||||||
|
"PhysicsWorkerSystem": "physics-worker.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@esengine/worker-generator",
|
"name": "@esengine/worker-generator",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "CLI tool to generate Worker files from WorkerEntitySystem classes for WeChat Mini Game and other platforms",
|
"description": "CLI tool to generate Worker files from WorkerEntitySystem classes for WeChat Mini Game and other platforms",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -263,6 +263,9 @@ function processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig)
|
|||||||
/**
|
/**
|
||||||
* 生成映射文件
|
* 生成映射文件
|
||||||
* Generate mapping file
|
* Generate mapping file
|
||||||
|
*
|
||||||
|
* 注意:映射文件不能放在 workers 目录,微信小游戏会把它当 JS 编译
|
||||||
|
* Note: Mapping file should NOT be in workers dir, WeChat will try to compile it as JS
|
||||||
*/
|
*/
|
||||||
function generateMappingFile(
|
function generateMappingFile(
|
||||||
success: Array<{ className: string; outputPath: string }>,
|
success: Array<{ className: string; outputPath: string }>,
|
||||||
@@ -274,13 +277,15 @@ function generateMappingFile(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const item of success) {
|
for (const item of success) {
|
||||||
// 使用相对于输出目录的路径
|
// 使用相对于项目根目录的路径
|
||||||
// Use path relative to output directory
|
// Use path relative to project root
|
||||||
const relativePath = path.relative(config.outDir, item.outputPath).replace(/\\/g, '/');
|
const relativePath = path.relative(process.cwd(), item.outputPath).replace(/\\/g, '/');
|
||||||
mapping.mappings[item.className] = relativePath;
|
mapping.mappings[item.className] = relativePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappingPath = path.join(config.outDir, 'worker-mapping.json');
|
// 映射文件放在项目根目录,而不是 workers 目录
|
||||||
|
// Put mapping file in project root, not in workers directory
|
||||||
|
const mappingPath = path.join(process.cwd(), 'worker-mapping.json');
|
||||||
fs.writeFileSync(mappingPath, JSON.stringify(mapping, null, 2), 'utf8');
|
fs.writeFileSync(mappingPath, JSON.stringify(mapping, null, 2), 'utf8');
|
||||||
|
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -1574,13 +1574,13 @@ importers:
|
|||||||
ts-morph:
|
ts-morph:
|
||||||
specifier: ^21.0.1
|
specifier: ^21.0.1
|
||||||
version: 21.0.1
|
version: 21.0.1
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.3.0
|
||||||
|
version: 5.9.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.10.0
|
specifier: ^20.10.0
|
||||||
version: 20.19.25
|
version: 20.19.25
|
||||||
typescript:
|
|
||||||
specifier: ^5.3.0
|
|
||||||
version: 5.9.3
|
|
||||||
|
|
||||||
packages/world-streaming:
|
packages/world-streaming:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user