2025-12-08 18:33:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 部署脚本 - 复制文件到微信小游戏项目
|
|
|
|
|
|
* 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// 创建 game.js - 完整物理球可视化演示
|
|
|
|
|
|
// Create game.js - Full physics ball visualization demo
|
2025-12-08 18:33:23 +08:00
|
|
|
|
const gameJs = `/**
|
2025-12-09 11:07:44 +08:00
|
|
|
|
* ESEngine Worker System 微信小游戏物理演示
|
|
|
|
|
|
* ESEngine Worker System WeChat Mini Game Physics Demo
|
|
|
|
|
|
*
|
|
|
|
|
|
* 演示 Worker 线程处理物理计算,主线程渲染
|
|
|
|
|
|
* Demonstrates Worker thread physics + main thread rendering
|
2025-12-08 18:33:23 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// ============ 配置 | Configuration ============
|
|
|
|
|
|
var CONFIG = {
|
|
|
|
|
|
BALL_COUNT: 20, // 球数量 | Number of balls
|
|
|
|
|
|
GRAVITY: 400, // 重力 | Gravity
|
|
|
|
|
|
GROUND_FRICTION: 0.98, // 地面摩擦 | Ground friction
|
|
|
|
|
|
BALL_BOUNCE: 0.85, // 弹性系数 | Bounce factor
|
|
|
|
|
|
MIN_RADIUS: 8, // 最小半径 | Min radius
|
|
|
|
|
|
MAX_RADIUS: 20, // 最大半径 | Max radius
|
|
|
|
|
|
COLORS: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F']
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 全局状态 | Global State ============
|
|
|
|
|
|
var canvas = wx.createCanvas();
|
|
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
|
|
var worker = null;
|
|
|
|
|
|
var entities = [];
|
|
|
|
|
|
var lastTime = Date.now();
|
|
|
|
|
|
var frameCount = 0;
|
|
|
|
|
|
var fps = 0;
|
|
|
|
|
|
var workerReady = false;
|
|
|
|
|
|
var pendingRequest = false;
|
|
|
|
|
|
|
2025-12-08 18:33:23 +08:00
|
|
|
|
console.log('====================================');
|
2025-12-09 11:07:44 +08:00
|
|
|
|
console.log('ESEngine Worker 物理演示');
|
|
|
|
|
|
console.log('Canvas:', canvas.width, 'x', canvas.height);
|
2025-12-08 18:33:23 +08:00
|
|
|
|
console.log('====================================');
|
|
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// ============ 初始化实体 | Initialize Entities ============
|
|
|
|
|
|
function initEntities() {
|
|
|
|
|
|
entities = [];
|
|
|
|
|
|
for (var i = 0; i < CONFIG.BALL_COUNT; i++) {
|
|
|
|
|
|
var radius = CONFIG.MIN_RADIUS + Math.random() * (CONFIG.MAX_RADIUS - CONFIG.MIN_RADIUS);
|
|
|
|
|
|
entities.push({
|
|
|
|
|
|
id: i + 1,
|
|
|
|
|
|
x: radius + Math.random() * (canvas.width - radius * 2),
|
|
|
|
|
|
y: radius + Math.random() * (canvas.height * 0.5), // 上半部分生成
|
|
|
|
|
|
dx: (Math.random() - 0.5) * 200,
|
|
|
|
|
|
dy: Math.random() * 100,
|
|
|
|
|
|
mass: radius * 0.1,
|
|
|
|
|
|
bounce: CONFIG.BALL_BOUNCE,
|
|
|
|
|
|
friction: CONFIG.GROUND_FRICTION,
|
|
|
|
|
|
radius: radius,
|
|
|
|
|
|
color: CONFIG.COLORS[i % CONFIG.COLORS.length]
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('Created', entities.length, 'balls');
|
|
|
|
|
|
}
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// ============ 创建 Worker | Create Worker ============
|
|
|
|
|
|
function createWorker() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
worker = wx.createWorker('workers/physics-worker.js', {
|
|
|
|
|
|
useExperimentalWorker: true
|
|
|
|
|
|
});
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
worker.onMessage(function(res) {
|
|
|
|
|
|
pendingRequest = false;
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
if (res.error) {
|
|
|
|
|
|
console.error('Worker error:', res.error);
|
|
|
|
|
|
return;
|
2025-12-08 18:33:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
if (res.result && Array.isArray(res.result)) {
|
|
|
|
|
|
// 更新实体位置(保留颜色等渲染属性)
|
|
|
|
|
|
// Update entity positions (keep rendering properties like color)
|
|
|
|
|
|
for (var i = 0; i < res.result.length; i++) {
|
|
|
|
|
|
var updated = res.result[i];
|
|
|
|
|
|
var entity = entities[i];
|
|
|
|
|
|
if (entity && updated) {
|
|
|
|
|
|
entity.x = updated.x;
|
|
|
|
|
|
entity.y = updated.y;
|
|
|
|
|
|
entity.dx = updated.dx;
|
|
|
|
|
|
entity.dy = updated.dy;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
workerReady = true;
|
|
|
|
|
|
console.log('Worker created successfully!');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Worker creation failed:', error.message);
|
|
|
|
|
|
workerReady = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// ============ 发送物理更新到 Worker | Send Physics Update to Worker ============
|
|
|
|
|
|
function sendToWorker(deltaTime) {
|
|
|
|
|
|
if (!worker || !workerReady || pendingRequest) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 准备发送数据(只发送物理相关属性)
|
|
|
|
|
|
// Prepare data (only physics-related properties)
|
|
|
|
|
|
var physicsData = [];
|
|
|
|
|
|
for (var i = 0; i < entities.length; i++) {
|
|
|
|
|
|
var e = entities[i];
|
|
|
|
|
|
physicsData.push({
|
|
|
|
|
|
id: e.id,
|
|
|
|
|
|
x: e.x,
|
|
|
|
|
|
y: e.y,
|
|
|
|
|
|
dx: e.dx,
|
|
|
|
|
|
dy: e.dy,
|
|
|
|
|
|
mass: e.mass,
|
|
|
|
|
|
bounce: e.bounce,
|
|
|
|
|
|
friction: e.friction,
|
|
|
|
|
|
radius: e.radius
|
2025-12-08 18:33:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
pendingRequest = true;
|
2025-12-08 18:33:23 +08:00
|
|
|
|
worker.postMessage({
|
|
|
|
|
|
id: Date.now(),
|
2025-12-09 11:07:44 +08:00
|
|
|
|
entities: physicsData,
|
|
|
|
|
|
deltaTime: deltaTime,
|
2025-12-08 18:33:23 +08:00
|
|
|
|
systemConfig: {
|
2025-12-09 11:07:44 +08:00
|
|
|
|
gravity: CONFIG.GRAVITY,
|
|
|
|
|
|
canvasWidth: canvas.width,
|
|
|
|
|
|
canvasHeight: canvas.height,
|
|
|
|
|
|
groundFriction: CONFIG.GROUND_FRICTION
|
2025-12-08 18:33:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-12-09 11:07:44 +08:00
|
|
|
|
}
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// ============ 渲染 | Render ============
|
|
|
|
|
|
function render() {
|
|
|
|
|
|
// 清屏 - 深色背景
|
|
|
|
|
|
// Clear screen - dark background
|
|
|
|
|
|
ctx.fillStyle = '#1a1a2e';
|
|
|
|
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制地面
|
|
|
|
|
|
// Draw ground
|
|
|
|
|
|
ctx.fillStyle = '#2d3436';
|
|
|
|
|
|
ctx.fillRect(0, canvas.height - 10, canvas.width, 10);
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制所有球
|
|
|
|
|
|
// Draw all balls
|
|
|
|
|
|
for (var i = 0; i < entities.length; i++) {
|
|
|
|
|
|
var e = entities[i];
|
|
|
|
|
|
|
|
|
|
|
|
// 球体渐变效果
|
|
|
|
|
|
// Ball gradient effect
|
|
|
|
|
|
var gradient = ctx.createRadialGradient(
|
|
|
|
|
|
e.x - e.radius * 0.3, e.y - e.radius * 0.3, 0,
|
|
|
|
|
|
e.x, e.y, e.radius
|
|
|
|
|
|
);
|
|
|
|
|
|
gradient.addColorStop(0, '#ffffff');
|
|
|
|
|
|
gradient.addColorStop(0.3, e.color);
|
|
|
|
|
|
gradient.addColorStop(1, shadeColor(e.color, -30));
|
|
|
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
ctx.arc(e.x, e.y, e.radius, 0, Math.PI * 2);
|
|
|
|
|
|
ctx.fillStyle = gradient;
|
|
|
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
|
|
|
|
// 球体边框
|
|
|
|
|
|
// Ball border
|
|
|
|
|
|
ctx.strokeStyle = shadeColor(e.color, -50);
|
|
|
|
|
|
ctx.lineWidth = 1;
|
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制 UI
|
|
|
|
|
|
// Draw UI
|
|
|
|
|
|
ctx.fillStyle = '#ffffff';
|
|
|
|
|
|
ctx.font = '14px Arial';
|
|
|
|
|
|
ctx.textAlign = 'left';
|
|
|
|
|
|
ctx.fillText('ESEngine Worker Physics Demo', 10, 25);
|
|
|
|
|
|
ctx.fillText('FPS: ' + fps + ' | Balls: ' + entities.length, 10, 45);
|
|
|
|
|
|
ctx.fillText('Worker: ' + (workerReady ? 'Active' : 'Failed'), 10, 65);
|
|
|
|
|
|
|
|
|
|
|
|
// 提示文字
|
|
|
|
|
|
// Hint text
|
|
|
|
|
|
ctx.textAlign = 'center';
|
|
|
|
|
|
ctx.fillStyle = '#888888';
|
|
|
|
|
|
ctx.font = '12px Arial';
|
|
|
|
|
|
ctx.fillText('Physics calculated in Worker thread', canvas.width / 2, canvas.height - 20);
|
2025-12-08 18:33:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// 颜色加深/减淡工具函数
|
|
|
|
|
|
// Color shade utility function
|
|
|
|
|
|
function shadeColor(color, percent) {
|
|
|
|
|
|
var num = parseInt(color.replace('#', ''), 16);
|
|
|
|
|
|
var amt = Math.round(2.55 * percent);
|
|
|
|
|
|
var R = (num >> 16) + amt;
|
|
|
|
|
|
var G = (num >> 8 & 0x00FF) + amt;
|
|
|
|
|
|
var B = (num & 0x0000FF) + amt;
|
|
|
|
|
|
R = Math.max(0, Math.min(255, R));
|
|
|
|
|
|
G = Math.max(0, Math.min(255, G));
|
|
|
|
|
|
B = Math.max(0, Math.min(255, B));
|
|
|
|
|
|
return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 游戏循环 | Game Loop ============
|
|
|
|
|
|
function gameLoop() {
|
|
|
|
|
|
var now = Date.now();
|
|
|
|
|
|
var deltaTime = (now - lastTime) / 1000;
|
|
|
|
|
|
lastTime = now;
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// 限制 deltaTime 防止跳帧
|
|
|
|
|
|
// Clamp deltaTime to prevent frame skip
|
|
|
|
|
|
if (deltaTime > 0.1) deltaTime = 0.1;
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// FPS 计算
|
|
|
|
|
|
// FPS calculation
|
|
|
|
|
|
frameCount++;
|
|
|
|
|
|
if (frameCount >= 30) {
|
|
|
|
|
|
fps = Math.round(30 / ((now - (lastTime - deltaTime * 1000 * 30)) / 1000));
|
|
|
|
|
|
frameCount = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送物理计算到 Worker
|
|
|
|
|
|
// Send physics calculation to Worker
|
|
|
|
|
|
sendToWorker(deltaTime);
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染
|
|
|
|
|
|
// Render
|
|
|
|
|
|
render();
|
|
|
|
|
|
|
|
|
|
|
|
// 下一帧
|
|
|
|
|
|
// Next frame
|
|
|
|
|
|
requestAnimationFrame(gameLoop);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 启动 | Start ============
|
|
|
|
|
|
initEntities();
|
|
|
|
|
|
createWorker();
|
|
|
|
|
|
|
|
|
|
|
|
// 简单的 FPS 计算变量
|
|
|
|
|
|
var fpsLastTime = Date.now();
|
|
|
|
|
|
var fpsFrameCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 覆盖 FPS 计算逻辑
|
|
|
|
|
|
setInterval(function() {
|
|
|
|
|
|
var now = Date.now();
|
|
|
|
|
|
fps = Math.round(fpsFrameCount * 1000 / (now - fpsLastTime));
|
|
|
|
|
|
fpsLastTime = now;
|
|
|
|
|
|
fpsFrameCount = 0;
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
// 修改 gameLoop 中的帧计数
|
|
|
|
|
|
var originalGameLoop = gameLoop;
|
|
|
|
|
|
gameLoop = function() {
|
|
|
|
|
|
fpsFrameCount++;
|
|
|
|
|
|
|
|
|
|
|
|
var now = Date.now();
|
|
|
|
|
|
var deltaTime = (now - lastTime) / 1000;
|
|
|
|
|
|
lastTime = now;
|
|
|
|
|
|
|
|
|
|
|
|
if (deltaTime > 0.1) deltaTime = 0.1;
|
|
|
|
|
|
|
|
|
|
|
|
sendToWorker(deltaTime);
|
|
|
|
|
|
render();
|
|
|
|
|
|
requestAnimationFrame(gameLoop);
|
|
|
|
|
|
};
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// 开始游戏循环
|
|
|
|
|
|
// Start game loop
|
|
|
|
|
|
console.log('Starting game loop...');
|
|
|
|
|
|
requestAnimationFrame(gameLoop);
|
2025-12-08 18:33:23 +08:00
|
|
|
|
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// 触摸重置
|
|
|
|
|
|
// Touch to reset
|
|
|
|
|
|
wx.onTouchStart(function(e) {
|
|
|
|
|
|
console.log('Touch detected - resetting balls');
|
|
|
|
|
|
initEntities();
|
|
|
|
|
|
});
|
2025-12-08 18:33:23 +08:00
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
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);
|