Files
esengine/docs/guide/platform-adapter/wechat-minigame.md
yhh ad96edfad0 fix: 恢复 @esengine/ecs-framework 包名
上一个提交错误地将 npm 包名也改了,这里恢复正确的包名。
只更新 GitHub 仓库 URL,不改变 npm 包名。
2025-12-08 21:26:35 +08:00

14 KiB
Raw Blame History

微信小游戏适配器

概述

微信小游戏平台适配器专为微信小游戏环境设计处理微信小游戏的特殊限制和API。

特性支持

特性 支持情况 说明
Worker 支持 需要使用预编译文件,配置 workerScriptPath
SharedArrayBuffer 不支持 微信小游戏环境不支持
Transferable Objects 不支持 只支持可序列化对象
高精度时间 支持 使用 wx.getPerformance()
设备信息 支持 完整的微信小游戏设备信息

WorkerEntitySystem 使用方式

重要:微信小游戏 Worker 限制

微信小游戏的 Worker 有以下限制:

  • Worker 脚本必须在代码包内,不能动态生成
  • 必须在 game.json 中配置 workers 目录
  • 最多只能创建 1 个 Worker

因此,使用 WorkerEntitySystem 时有两种方式:

  1. 推荐:使用 CLI 工具自动生成 Worker 文件
  2. 手动创建 Worker 文件

方式一:使用 CLI 工具自动生成(推荐)

我们提供了 @esengine/worker-generator 工具,可以自动从你的 TypeScript 代码中提取 workerProcess 函数并生成微信小游戏兼容的 Worker 文件。

安装

pnpm add -D @esengine/worker-generator
# 或
npm install --save-dev @esengine/worker-generator

使用

# 扫描 src 目录,生成 Worker 文件到 workers 目录
npx esengine-worker-gen --src ./src --out ./workers --wechat

# 查看帮助
npx esengine-worker-gen --help

参数说明

参数 说明 默认值
-s, --src <dir> 源代码目录 ./src
-o, --out <dir> 输出目录 ./workers
-w, --wechat 生成微信小游戏兼容代码 false
-m, --mapping 生成 worker-mapping.json true
-t, --tsconfig <path> TypeScript 配置文件路径 自动查找
-v, --verbose 详细输出 false

示例输出

🔧 ESEngine Worker Generator

Source directory: /project/src
Output directory: /project/workers
WeChat mode: Yes

Scanning for WorkerEntitySystem classes...

✓ Found 1 WorkerEntitySystem class(es):
  - PhysicsSystem (src/systems/PhysicsSystem.ts)

Generating Worker files...

✓ Successfully generated 1 Worker file(s):
  - PhysicsSystem -> workers/physics-system-worker.js

📝 Usage:
1. Copy the generated files to your project's workers/ directory
2. Configure game.json (WeChat): { "workers": "workers" }
3. In your System constructor, add:
   workerScriptPath: 'workers/physics-system-worker.js'

在构建流程中集成

// package.json
{
  "scripts": {
    "build:workers": "esengine-worker-gen --src ./src --out ./workers --wechat",
    "build": "pnpm build:workers && your-build-command"
  }
}

方式二:手动创建 Worker 文件

如果你不想使用 CLI 工具,也可以手动创建 Worker 文件。

在项目中创建 workers/entity-worker.js

// workers/entity-worker.js
// 微信小游戏 WorkerEntitySystem 通用 Worker 模板

let sharedFloatArray = null;

worker.onMessage(function(e) {
    const { type, id, entities, deltaTime, systemConfig, startIndex, endIndex, sharedBuffer } = e.data;

    try {
        // 处理 SharedArrayBuffer 初始化
        if (type === 'init' && sharedBuffer) {
            sharedFloatArray = new Float32Array(sharedBuffer);
            worker.postMessage({ type: 'init', success: true });
            return;
        }

        // 处理 SharedArrayBuffer 数据
        if (type === 'shared' && sharedFloatArray) {
            processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig);
            worker.postMessage({ id, result: null });
            return;
        }

        // 传统处理方式
        if (entities) {
            const result = workerProcess(entities, deltaTime, systemConfig);

            // 处理 Promise 返回值
            if (result && typeof result.then === 'function') {
                result.then(function(finalResult) {
                    worker.postMessage({ id, result: finalResult });
                }).catch(function(error) {
                    worker.postMessage({ id, error: error.message });
                });
            } else {
                worker.postMessage({ id, result: result });
            }
        }
    } catch (error) {
        worker.postMessage({ id, error: error.message });
    }
});

/**
 * 实体处理函数 - 根据你的业务逻辑修改此函数
 * @param {Array} entities - 实体数据数组
 * @param {number} deltaTime - 帧间隔时间
 * @param {Object} systemConfig - 系统配置
 * @returns {Array} 处理后的实体数据
 */
function workerProcess(entities, deltaTime, systemConfig) {
    // ====== 在这里编写你的处理逻辑 ======
    // 示例:物理计算
    return entities.map(function(entity) {
        // 应用重力
        entity.vy += (systemConfig.gravity || 100) * deltaTime;

        // 更新位置
        entity.x += entity.vx * deltaTime;
        entity.y += entity.vy * deltaTime;

        // 应用摩擦力
        entity.vx *= (systemConfig.friction || 0.95);
        entity.vy *= (systemConfig.friction || 0.95);

        return entity;
    });
}

/**
 * SharedArrayBuffer 处理函数(可选)
 */
function processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig) {
    if (!sharedFloatArray) return;

    // ====== 根据需要实现 SharedArrayBuffer 处理逻辑 ======
    // 注意:微信小游戏不支持 SharedArrayBuffer此函数通常不会被调用
}

步骤 2配置 game.json

game.json 中添加 workers 配置:

{
  "deviceOrientation": "portrait",
  "showStatusBar": false,
  "workers": "workers"
}

步骤 3使用 WorkerEntitySystem

import { WorkerEntitySystem, Matcher, Entity } from '@esengine/ecs-framework';

interface PhysicsData {
    id: number;
    x: number;
    y: number;
    vx: number;
    vy: number;
    mass: number;
}

class PhysicsSystem extends WorkerEntitySystem<PhysicsData> {
    constructor() {
        super(Matcher.all(Transform, Velocity), {
            enableWorker: true,
            workerCount: 1,  // 微信小游戏限制只能创建 1 个 Worker
            workerScriptPath: 'workers/entity-worker.js',  // 指定预编译的 Worker 文件
            systemConfig: {
                gravity: 100,
                friction: 0.95
            }
        });
    }

    protected getDefaultEntityDataSize(): number {
        return 6;
    }

    protected extractEntityData(entity: Entity): PhysicsData {
        const transform = entity.getComponent(Transform);
        const velocity = entity.getComponent(Velocity);
        const physics = entity.getComponent(Physics);

        return {
            id: entity.id,
            x: transform.x,
            y: transform.y,
            vx: velocity.x,
            vy: velocity.y,
            mass: physics.mass
        };
    }

    // 注意:在微信小游戏中,此方法不会被使用
    // Worker 的处理逻辑在 workers/entity-worker.js 中的 workerProcess 函数里
    protected workerProcess(entities: PhysicsData[], deltaTime: number, config: any): PhysicsData[] {
        return entities.map(entity => {
            entity.vy += config.gravity * deltaTime;
            entity.x += entity.vx * deltaTime;
            entity.y += entity.vy * deltaTime;
            entity.vx *= config.friction;
            entity.vy *= config.friction;
            return entity;
        });
    }

    protected applyResult(entity: Entity, result: PhysicsData): void {
        const transform = entity.getComponent(Transform);
        const velocity = entity.getComponent(Velocity);

        transform.x = result.x;
        transform.y = result.y;
        velocity.x = result.vx;
        velocity.y = result.vy;
    }

    // SharedArrayBuffer 相关方法(微信小游戏不支持,可省略)
    protected writeEntityToBuffer(data: PhysicsData, offset: number): void {}
    protected readEntityFromBuffer(offset: number): PhysicsData | null { return null; }
}

临时禁用 Worker降级到同步模式

如果遇到问题,可以临时禁用 Worker

class PhysicsSystem extends WorkerEntitySystem<PhysicsData> {
    constructor() {
        super(Matcher.all(Transform, Velocity), {
            enableWorker: false,  // 禁用 Worker使用主线程同步处理
            // ... 其他配置
        });
    }
}

完整适配器实现

import type {
    IPlatformAdapter,
    PlatformWorker,
    WorkerCreationOptions,
    PlatformConfig
} from '@esengine/ecs-framework';

/**
 * 微信小游戏平台适配器
 */
export class WeChatMiniGameAdapter implements IPlatformAdapter {
    public readonly name = 'wechat-minigame';
    public readonly version: string;
    private systemInfo: any;

    constructor() {
        this.systemInfo = this.getSystemInfo();
        this.version = this.systemInfo.SDKVersion || 'unknown';
    }

    public isWorkerSupported(): boolean {
        return typeof wx !== 'undefined' && typeof wx.createWorker === 'function';
    }

    public isSharedArrayBufferSupported(): boolean {
        return false;
    }

    public getHardwareConcurrency(): number {
        return 1;  // 微信小游戏最多 1 个 Worker
    }

    public createWorker(scriptPath: string, options: WorkerCreationOptions = {}): PlatformWorker {
        if (!this.isWorkerSupported()) {
            throw new Error('微信小游戏环境不支持 Worker');
        }

        // scriptPath 必须是代码包内的文件路径
        const worker = wx.createWorker(scriptPath, {
            useExperimentalWorker: true
        });

        return new WeChatWorker(worker);
    }

    public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
        return null;
    }

    public getHighResTimestamp(): number {
        if (typeof wx !== 'undefined' && wx.getPerformance) {
            return wx.getPerformance().now();
        }
        return Date.now();
    }

    public getPlatformConfig(): PlatformConfig {
        return {
            maxWorkerCount: 1,
            supportsModuleWorker: false,
            supportsTransferableObjects: false,
            maxSharedArrayBufferSize: 0,
            workerScriptPrefix: '',
            limitations: {
                noEval: true,  // 重要:标记不支持动态脚本
                requiresWorkerInit: false,
                memoryLimit: 512 * 1024 * 1024,
                workerNotSupported: false,
                workerLimitations: [
                    '最多只能创建 1 个 Worker',
                    'Worker 脚本必须在代码包内',
                    '需要在 game.json 中配置 workers 路径',
                    '需要使用 workerScriptPath 配置'
                ]
            },
            extensions: {
                platform: 'wechat-minigame',
                sdkVersion: this.systemInfo.SDKVersion
            }
        };
    }

    private getSystemInfo(): any {
        if (typeof wx !== 'undefined' && wx.getSystemInfoSync) {
            try {
                return wx.getSystemInfoSync();
            } catch (error) {
                console.warn('获取微信系统信息失败:', error);
            }
        }
        return {};
    }
}

/**
 * 微信 Worker 封装
 */
class WeChatWorker implements PlatformWorker {
    private _state: 'running' | 'terminated' = 'running';
    private worker: any;

    constructor(worker: any) {
        this.worker = worker;
    }

    public get state(): 'running' | 'terminated' {
        return this._state;
    }

    public postMessage(message: any, transfer?: Transferable[]): void {
        if (this._state === 'terminated') {
            throw new Error('Worker 已被终止');
        }
        this.worker.postMessage(message);
    }

    public onMessage(handler: (event: { data: any }) => void): void {
        this.worker.onMessage((res: any) => {
            handler({ data: res });
        });
    }

    public onError(handler: (error: ErrorEvent) => void): void {
        if (this.worker.onError) {
            this.worker.onError(handler);
        }
    }

    public terminate(): void {
        if (this._state === 'running') {
            this.worker.terminate();
            this._state = 'terminated';
        }
    }
}

注册适配器

import { PlatformManager } from '@esengine/ecs-framework';
import { WeChatMiniGameAdapter } from './platform/WeChatMiniGameAdapter';

// 在游戏启动时注册适配器
if (typeof wx !== 'undefined') {
    const adapter = new WeChatMiniGameAdapter();
    PlatformManager.getInstance().registerAdapter(adapter);
}

官方文档参考

重要注意事项

Worker 限制

限制项 说明
数量限制 最多只能创建 1 个 Worker
版本要求 需要基础库 1.9.90 及以上
脚本位置 必须在代码包内,不支持动态生成
生命周期 创建新 Worker 前必须先 terminate()

内存限制

  • 通常限制在 256MB - 512MB
  • 需要及时释放不用的资源
  • 建议监听内存警告:
wx.onMemoryWarning(() => {
    console.warn('收到内存警告,开始清理资源');
    // 清理不必要的资源
});

调试技巧

// 检查 Worker 配置
const adapter = PlatformManager.getInstance().getAdapter();
const config = adapter.getPlatformConfig();

console.log('Worker 支持:', adapter.isWorkerSupported());
console.log('最大 Worker 数:', config.maxWorkerCount);
console.log('平台限制:', config.limitations);