Skip to content

微信小游戏适配器

概述

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

特性支持

  • Worker: 支持(通过 wx.createWorker 创建,需要配置 game.json)
  • SharedArrayBuffer: 不支持
  • Transferable Objects: 不支持(只支持可序列化对象)
  • 高精度时间: 使用 Date.now()wx.getPerformance()
  • 设备信息: 完整的微信小游戏设备信息

完整实现

typescript
import type {
    IPlatformAdapter,
    PlatformWorker,
    WorkerCreationOptions,
    PlatformConfig,
    WeChatDeviceInfo
} 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.version || 'unknown';
    }

    /**
     * 检查是否支持Worker
     */
    public isWorkerSupported(): boolean {
        // 微信小游戏支持Worker,通过wx.createWorker创建
        return typeof wx !== 'undefined' && typeof wx.createWorker === 'function';
    }

    /**
     * 检查是否支持SharedArrayBuffer(不支持)
     */
    public isSharedArrayBufferSupported(): boolean {
        return false; // 微信小游戏不支持SharedArrayBuffer
    }

    /**
     * 获取硬件并发数
     */
    public getHardwareConcurrency(): number {
        // 微信小游戏官方限制:最多只能创建 1 个 Worker
        return 1;
    }

    /**
     * 创建Worker
     * @param script 脚本内容或文件路径
     * @param options Worker创建选项
     */
    public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
        if (!this.isWorkerSupported()) {
            throw new Error('微信小游戏不支持Worker');
        }

        try {
            return new WeChatWorker(script, options);
        } catch (error) {
            throw new Error(`创建微信Worker失败: ${(error as Error).message}`);
        }
    }

    /**
     * 创建SharedArrayBuffer(不支持)
     */
    public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
        return null; // 微信小游戏不支持SharedArrayBuffer
    }

    /**
     * 获取高精度时间戳
     */
    public getHighResTimestamp(): number {
        // 尝试使用微信的性能API,否则使用Date.now()
        if (typeof wx !== 'undefined' && wx.getPerformance) {
            const performance = wx.getPerformance();
            return performance.now();
        }
        return Date.now();
    }

    /**
     * 获取平台配置
     */
    public getPlatformConfig(): PlatformConfig {
        return {
            maxWorkerCount: 1, // 微信小游戏最多支持 1 个 Worker
            supportsModuleWorker: false, // 不支持模块Worker
            supportsTransferableObjects: this.checkTransferableObjectsSupport(),
            maxSharedArrayBufferSize: 0,
            workerScriptPrefix: '',
            limitations: {
                noEval: true, // 微信小游戏限制eval使用
                requiresWorkerInit: false,
                memoryLimit: this.getMemoryLimit(),
                workerNotSupported: false,
                workerLimitations: [
                    '最多只能创建 1 个 Worker',
                    '创建新Worker前必须先调用 Worker.terminate()',
                    'Worker脚本必须为项目内相对路径',
                    '需要在 game.json 中配置 workers 路径',
                    '使用 worker.onMessage() 而不是 self.onmessage',
                    '需要基础库 1.9.90 及以上版本'
                ]
            },
            extensions: {
                platform: 'wechat-minigame',
                systemInfo: this.systemInfo,
                appId: this.systemInfo.host?.appId || 'unknown'
            }
        };
    }

    /**
     * 获取微信小游戏设备信息
     */
    public getDeviceInfo(): WeChatDeviceInfo {
        return {
            // 设备基础信息
            brand: this.systemInfo.brand,
            model: this.systemInfo.model,
            platform: this.systemInfo.platform,
            system: this.systemInfo.system,
            benchmarkLevel: this.systemInfo.benchmarkLevel,
            cpuType: this.systemInfo.cpuType,
            memorySize: this.systemInfo.memorySize,
            deviceAbi: this.systemInfo.deviceAbi,
            abi: this.systemInfo.abi,

            // 窗口信息
            screenWidth: this.systemInfo.screenWidth,
            screenHeight: this.systemInfo.screenHeight,
            screenTop: this.systemInfo.screenTop,
            windowWidth: this.systemInfo.windowWidth,
            windowHeight: this.systemInfo.windowHeight,
            pixelRatio: this.systemInfo.pixelRatio,
            statusBarHeight: this.systemInfo.statusBarHeight,
            safeArea: this.systemInfo.safeArea,

            // 应用信息
            version: this.systemInfo.version,
            language: this.systemInfo.language,
            theme: this.systemInfo.theme,
            SDKVersion: this.systemInfo.SDKVersion,
            enableDebug: this.systemInfo.enableDebug,
            fontSizeSetting: this.systemInfo.fontSizeSetting,
            host: this.systemInfo.host
        };
    }

    /**
     * 异步获取完整的平台配置
     */
    public async getPlatformConfigAsync(): Promise<PlatformConfig> {
        // 可以在这里添加异步获取设备性能信息的逻辑
        const baseConfig = this.getPlatformConfig();

        // 尝试获取设备性能信息
        try {
            const benchmarkLevel = await this.getBenchmarkLevel();
            baseConfig.extensions = {
                ...baseConfig.extensions,
                benchmarkLevel
            };
        } catch (error) {
            console.warn('获取性能基准失败:', error);
        }

        return baseConfig;
    }

    /**
     * 检查是否支持Transferable Objects
     */
    private checkTransferableObjectsSupport(): boolean {
        // 微信小游戏不支持 Transferable Objects
        // 基础库 2.20.2 之前只支持可序列化的 key-value 对象
        // 2.20.2 之后支持任意类型数据,但仍然不支持 Transferable Objects
        return false;
    }

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

    /**
     * 获取内存限制
     */
    private getMemoryLimit(): number {
        // 微信小游戏通常有内存限制
        const memorySize = this.systemInfo.memorySize;
        if (memorySize) {
            // 解析内存大小字符串(如 "4GB")
            const match = memorySize.match(/(\d+)([GM]B)?/i);
            if (match) {
                const value = parseInt(match[1], 10);
                const unit = match[2]?.toUpperCase();

                if (unit === 'GB') {
                    return value * 1024 * 1024 * 1024;
                } else if (unit === 'MB') {
                    return value * 1024 * 1024;
                }
            }
        }

        // 默认限制为512MB
        return 512 * 1024 * 1024;
    }

    /**
     * 异步获取设备性能基准
     */
    private async getBenchmarkLevel(): Promise<number> {
        return new Promise((resolve) => {
            if (typeof wx !== 'undefined' && wx.getDeviceInfo) {
                wx.getDeviceInfo({
                    success: (res: any) => {
                        resolve(res.benchmarkLevel || 0);
                    },
                    fail: () => {
                        resolve(0);
                    }
                });
            } else {
                resolve(this.systemInfo.benchmarkLevel || 0);
            }
        });
    }
}

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

    constructor(script: string, options: WorkerCreationOptions = {}) {
        if (typeof wx === 'undefined' || typeof wx.createWorker !== 'function') {
            throw new Error('微信小游戏不支持Worker');
        }

        try {
            // 判断 script 是文件路径还是脚本内容
            if (this.isFilePath(script)) {
                // 直接使用文件路径
                this.scriptPath = script;
                this.isTemporaryFile = false;
                this.worker = wx.createWorker(this.scriptPath, {
                    useExperimentalWorker: true // 启用实验性Worker获得更好性能
                });
            } else {
                // 微信小游戏不支持动态脚本内容,只能使用文件路径
                // 将脚本内容写入文件系统
                this.scriptPath = this.writeScriptToFile(script, options.name);
                this.isTemporaryFile = true;
                this.worker = wx.createWorker(this.scriptPath, {
                    useExperimentalWorker: true
                });
            }
        } catch (error) {
            throw new Error(`创建微信Worker失败: ${(error as Error).message}`);
        }
    }

    /**
     * 判断是否为文件路径
     */
    private isFilePath(script: string): boolean {
        // 简单判断:如果包含 .js 后缀且不包含换行符或分号,认为是文件路径
        return script.endsWith('.js') &&
               !script.includes('\n') &&
               !script.includes(';') &&
               script.length < 200; // 文件路径通常不会太长
    }

    /**
     * 将脚本内容写入文件系统
     * 注意:微信小游戏不支持blob URL,只能使用文件系统
     */
    private writeScriptToFile(script: string, name?: string): string {
        const fs = wx.getFileSystemManager();
        const fileName = name ? `worker-${name}.js` : `worker-${Date.now()}.js`;
        const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`;

        try {
            fs.writeFileSync(filePath, script, 'utf8');
            return filePath;
        } catch (error) {
            throw new Error(`写入Worker脚本文件失败: ${(error as Error).message}`);
        }
    }

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

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

        try {
            // 微信小游戏 Worker 只支持可序列化对象,忽略 transfer 参数
            this.worker.postMessage(message);
        } catch (error) {
            throw new Error(`发送消息到微信Worker失败: ${(error as Error).message}`);
        }
    }

    public onMessage(handler: (event: { data: any }) => void): void {
        // 微信小游戏使用 onMessage 方法,不是 onmessage 属性
        this.worker.onMessage((res: any) => {
            handler({ data: res });
        });
    }

    public onError(handler: (error: ErrorEvent) => void): void {
        // 注意:微信小游戏 Worker 的错误处理可能与标准不同
        if (this.worker.onError) {
            this.worker.onError(handler);
        }
    }

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

                // 清理临时脚本文件
                this.cleanupScriptFile();
            } catch (error) {
                console.error('终止微信Worker失败:', error);
            }
        }
    }

    /**
     * 清理临时脚本文件
     */
    private cleanupScriptFile(): void {
        // 只清理临时创建的文件,不清理用户提供的文件路径
        if (this.scriptPath && this.isTemporaryFile) {
            try {
                const fs = wx.getFileSystemManager();
                fs.unlinkSync(this.scriptPath);
            } catch (error) {
                console.warn('清理Worker脚本文件失败:', error);
            }
        }
    }
}

使用方法

1. 复制代码

将上述代码复制到你的项目中,例如 src/platform/WeChatMiniGameAdapter.ts

2. 注册适配器

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

// 检查是否在微信小游戏环境
if (typeof wx !== 'undefined') {
    const wechatAdapter = new WeChatMiniGameAdapter();
    PlatformManager.getInstance().registerAdapter(wechatAdapter);
}

3. WorkerEntitySystem 使用方式

微信小游戏适配器与 WorkerEntitySystem 配合使用,自动处理 Worker 脚本创建:

基本使用方式(推荐)

typescript
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
            systemConfig: { gravity: 100, friction: 0.95 }
        });
    }

    protected getDefaultEntityDataSize(): number {
        return 6; // id, x, y, vx, vy, mass
    }

    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
        };
    }

    // WorkerEntitySystem 会自动将此函数序列化并写入临时文件
    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;
    }
}

使用预先创建的 Worker 文件(可选)

如果你希望使用预先创建的 Worker 文件:

typescript
// 1. 在 game.json 中配置 Worker 路径
/*
{
  "workers": "workers"
}
*/

// 2. 创建 workers/physics.js 文件
// workers/physics.js 内容:
/*
// 微信小游戏 Worker 使用标准的 self.onmessage
self.onmessage = function(e) {
    const { type, id, entities, deltaTime, systemConfig } = e.data;

    if (entities) {
        // 处理物理计算
        const results = entities.map(entity => {
            entity.vy += systemConfig.gravity * deltaTime;
            entity.x += entity.vx * deltaTime;
            entity.y += entity.vy * deltaTime;
            return entity;
        });

        self.postMessage({ id, result: results });
    }
};
*/

// 3. 通过平台适配器直接创建(不推荐,WorkerEntitySystem会自动处理)
const adapter = PlatformManager.getInstance().getAdapter();
const worker = adapter.createWorker('workers/physics.js');

4. 获取设备信息

typescript
const manager = PlatformManager.getInstance();
if (manager.hasAdapter()) {
    const adapter = manager.getAdapter();
    console.log('微信设备信息:', adapter.getDeviceInfo());
}

官方文档参考

在使用微信小游戏 Worker 之前,建议先阅读官方文档:

重要注意事项

Worker 限制和配置

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

  • 数量限制: 最多只能创建 1 个 Worker
  • 版本要求: 需要基础库 1.9.90 及以上版本
  • 脚本支持: 不支持 blob URL,只能使用文件路径或写入文件系统
  • 文件路径: Worker 脚本路径必须为绝对路径,但不能以 "/" 开头
  • 生命周期: 创建新 Worker 前必须先调用 Worker.terminate() 终止当前 Worker
  • 消息处理: Worker 内使用标准的 self.onmessage,主线程使用 worker.onMessage()
  • 实验性功能: 支持 useExperimentalWorker 选项获得更好的 iOS 性能

Worker 配置(可选)

如果使用预先创建的 Worker 文件,需要在 game.json 中添加 workers 配置:

json
{
  "deviceOrientation": "portrait",
  "showStatusBar": false,
  "workers": "workers",
  "subpackages": []
}

注意: 使用 WorkerEntitySystem 时无需此配置,框架会自动将脚本写入临时文件。

内存限制

微信小游戏有严格的内存限制:

  • 通常限制在 256MB - 512MB
  • 需要及时释放不用的资源
  • 避免内存泄漏

API 限制

  • 不支持 eval() 函数
  • 不支持 Function 构造器
  • DOM API 受限
  • 文件系统 API 受限

性能优化建议

1. 分帧处理

typescript
class FramedProcessor {
    private tasks: (() => void)[] = [];
    private isProcessing = false;

    public addTask(task: () => void): void {
        this.tasks.push(task);
        if (!this.isProcessing) {
            this.processNextFrame();
        }
    }

    private processNextFrame(): void {
        this.isProcessing = true;
        const startTime = Date.now();
        const frameTime = 16; // 16ms per frame

        while (this.tasks.length > 0 && Date.now() - startTime < frameTime) {
            const task = this.tasks.shift();
            if (task) task();
        }

        if (this.tasks.length > 0) {
            setTimeout(() => this.processNextFrame(), 0);
        } else {
            this.isProcessing = false;
        }
    }
}

2. 内存管理

typescript
class MemoryManager {
    private static readonly MAX_MEMORY = 256 * 1024 * 1024; // 256MB

    public static checkMemoryUsage(): void {
        if (typeof wx !== 'undefined' && wx.getPerformance) {
            const performance = wx.getPerformance();
            const memoryInfo = performance.getEntries().find(
                (entry: any) => entry.entryType === 'memory'
            );

            if (memoryInfo && memoryInfo.usedJSHeapSize > this.MAX_MEMORY * 0.8) {
                console.warn('内存使用率过高,建议清理资源');
                // 触发垃圾回收或资源清理
            }
        }
    }
}

调试技巧

typescript
// 检查微信小游戏环境
if (typeof wx !== 'undefined') {
    const adapter = new WeChatMiniGameAdapter();

    console.log('微信版本:', adapter.version);
    console.log('设备信息:', adapter.getDeviceInfo());
    console.log('平台配置:', adapter.getPlatformConfig());

    // 检查功能支持
    console.log('Worker支持:', adapter.isWorkerSupported());
    console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported());
}

微信小游戏特殊API

typescript
// 获取设备性能等级
if (typeof wx !== 'undefined' && wx.getDeviceInfo) {
    wx.getDeviceInfo({
        success: (res) => {
            console.log('设备性能等级:', res.benchmarkLevel);
        }
    });
}

// 监听内存警告
if (typeof wx !== 'undefined' && wx.onMemoryWarning) {
    wx.onMemoryWarning(() => {
        console.warn('收到内存警告,开始清理资源');
        // 清理不必要的资源
    });
}

Released under the MIT License.