feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 (#228)

* feat: 集成Rust WASM渲染引擎与TypeScript ECS框架

* feat: 增强编辑器UI功能与跨平台支持

* fix: 修复CI测试和类型检查问题

* fix: 修复CI问题并提高测试覆盖率

* fix: 修复CI问题并提高测试覆盖率
This commit is contained in:
YHH
2025-11-21 10:03:18 +08:00
committed by GitHub
parent 8b9616837d
commit a768b890fd
107 changed files with 10221 additions and 477 deletions

View File

@@ -0,0 +1,49 @@
{
"name": "@esengine/ecs-engine-bindgen",
"version": "0.1.0",
"description": "Bridge layer between ECS Framework and Rust Engine | ECS框架与Rust引擎之间的桥接层",
"main": "bin/index.js",
"module": "bin/index.js",
"types": "bin/index.d.ts",
"exports": {
".": {
"types": "./bin/index.d.ts",
"import": "./bin/index.js",
"require": "./bin/index.js",
"default": "./bin/index.js"
}
},
"files": [
"bin",
"dist"
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"clean": "rimraf bin dist"
},
"repository": {
"type": "git",
"url": "https://github.com/esengine/ecs-framework.git",
"directory": "packages/ecs-engine-bindgen"
},
"keywords": [
"ecs",
"game-engine",
"bridge",
"wasm",
"typescript"
],
"author": "ESEngine Team",
"license": "MIT",
"dependencies": {
"@esengine/ecs-framework": "file:../core"
},
"peerDependencies": {
"es-engine": "file:../engine/pkg"
},
"devDependencies": {
"typescript": "^5.8.0",
"rimraf": "^5.0.0"
}
}

View File

@@ -0,0 +1,161 @@
/**
* Sprite component for ECS entities.
* 用于ECS实体的精灵组件。
*/
import { Component, ECSComponent } from '@esengine/ecs-framework';
/**
* Sprite component data.
* 精灵组件数据。
*
* Attach this component to entities that should be rendered as sprites.
* 将此组件附加到应作为精灵渲染的实体。
*
* @example
* ```typescript
* const entity = scene.createEntity('player');
* entity.addComponent(SpriteComponent);
* const sprite = entity.getComponent(SpriteComponent);
* sprite.textureId = 1;
* sprite.width = 64;
* sprite.height = 64;
* ```
*/
@ECSComponent('Sprite')
export class SpriteComponent extends Component {
/**
* Texture ID for this sprite.
* 此精灵的纹理ID。
*/
textureId: number = 0;
/**
* Sprite width in pixels.
* 精灵宽度(像素)。
*/
width: number = 0;
/**
* Sprite height in pixels.
* 精灵高度(像素)。
*/
height: number = 0;
/**
* UV coordinates [u0, v0, u1, v1].
* UV坐标。
* Default is full texture [0, 0, 1, 1].
* 默认为完整纹理。
*/
uv: [number, number, number, number] = [0, 0, 1, 1];
/**
* Packed RGBA color (0xAABBGGRR format for WebGL).
* 打包的RGBA颜色。
* Default is white (0xFFFFFFFF).
* 默认为白色。
*/
color: number = 0xFFFFFFFF;
/**
* Origin point X (0-1, where 0.5 is center).
* 原点X0-10.5为中心)。
*/
originX: number = 0.5;
/**
* Origin point Y (0-1, where 0.5 is center).
* 原点Y0-10.5为中心)。
*/
originY: number = 0.5;
/**
* Whether sprite is visible.
* 精灵是否可见。
*/
visible: boolean = true;
/**
* Render layer/order (higher = rendered on top).
* 渲染层级/顺序(越高越在上面)。
*/
layer: number = 0;
/**
* Flip sprite horizontally.
* 水平翻转精灵。
*/
flipX: boolean = false;
/**
* Flip sprite vertically.
* 垂直翻转精灵。
*/
flipY: boolean = false;
/**
* Set UV from a sprite atlas region.
* 从精灵图集区域设置UV。
*
* @param x - Region X in pixels | 区域X像素
* @param y - Region Y in pixels | 区域Y像素
* @param w - Region width in pixels | 区域宽度(像素)
* @param h - Region height in pixels | 区域高度(像素)
* @param atlasWidth - Atlas total width | 图集总宽度
* @param atlasHeight - Atlas total height | 图集总高度
*/
setAtlasRegion(
x: number,
y: number,
w: number,
h: number,
atlasWidth: number,
atlasHeight: number
): void {
this.uv = [
x / atlasWidth,
y / atlasHeight,
(x + w) / atlasWidth,
(y + h) / atlasHeight
];
this.width = w;
this.height = h;
}
/**
* Set color from RGBA values (0-255).
* 从RGBA值设置颜色0-255
*
* @param r - Red | 红色
* @param g - Green | 绿色
* @param b - Blue | 蓝色
* @param a - Alpha | 透明度
*/
setColorRGBA(r: number, g: number, b: number, a: number = 255): void {
this.color = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((g & 0xFF) << 8) | (r & 0xFF);
}
/**
* Set color from hex value (0xRRGGBB or 0xRRGGBBAA).
* 从十六进制值设置颜色。
*
* @param hex - Hex color value | 十六进制颜色值
*/
setColorHex(hex: number): void {
if (hex > 0xFFFFFF) {
// 0xRRGGBBAA format
const r = (hex >> 24) & 0xFF;
const g = (hex >> 16) & 0xFF;
const b = (hex >> 8) & 0xFF;
const a = hex & 0xFF;
this.color = (a << 24) | (b << 16) | (g << 8) | r;
} else {
// 0xRRGGBB format
const r = (hex >> 16) & 0xFF;
const g = (hex >> 8) & 0xFF;
const b = hex & 0xFF;
this.color = (0xFF << 24) | (b << 16) | (g << 8) | r;
}
}
}

View File

@@ -0,0 +1,335 @@
/**
* Main bridge between TypeScript ECS and Rust Engine.
* TypeScript ECS与Rust引擎之间的主桥接层。
*/
import type { SpriteRenderData, TextureLoadRequest, EngineStats } from '../types';
/**
* Engine bridge configuration.
* 引擎桥接配置。
*/
export interface EngineBridgeConfig {
/** Canvas element ID. | Canvas元素ID。 */
canvasId: string;
/** Initial canvas width. | 初始画布宽度。 */
width?: number;
/** Initial canvas height. | 初始画布高度。 */
height?: number;
/** Maximum sprites per batch. | 每批次最大精灵数。 */
maxSprites?: number;
/** Enable debug mode. | 启用调试模式。 */
debug?: boolean;
}
/**
* Bridge for communication between ECS Framework and Rust Engine.
* ECS框架与Rust引擎之间的通信桥接。
*
* This class manages data transfer between the TypeScript ECS layer
* and the WebAssembly-based Rust rendering engine.
* 此类管理TypeScript ECS层与基于WebAssembly的Rust渲染引擎之间的数据传输。
*
* @example
* ```typescript
* const bridge = new EngineBridge({ canvasId: 'game-canvas' });
* await bridge.initialize();
*
* // In game loop | 在游戏循环中
* bridge.clear(0, 0, 0, 1);
* bridge.submitSprites(spriteDataArray);
* bridge.render();
* ```
*/
export class EngineBridge {
private engine: any; // GameEngine from WASM
private config: Required<EngineBridgeConfig>;
private initialized = false;
// Pre-allocated typed arrays for batch submission
// 预分配的类型数组用于批量提交
private transformBuffer: Float32Array;
private textureIdBuffer: Uint32Array;
private uvBuffer: Float32Array;
private colorBuffer: Uint32Array;
// Statistics | 统计信息
private stats: EngineStats = {
fps: 0,
drawCalls: 0,
spriteCount: 0,
frameTime: 0
};
private lastFrameTime = 0;
private frameCount = 0;
private fpsAccumulator = 0;
/**
* Create a new engine bridge.
* 创建新的引擎桥接。
*
* @param config - Bridge configuration | 桥接配置
*/
constructor(config: EngineBridgeConfig) {
this.config = {
canvasId: config.canvasId,
width: config.width ?? 800,
height: config.height ?? 600,
maxSprites: config.maxSprites ?? 10000,
debug: config.debug ?? false
};
// Pre-allocate buffers | 预分配缓冲区
const maxSprites = this.config.maxSprites;
this.transformBuffer = new Float32Array(maxSprites * 7); // x, y, rot, sx, sy, ox, oy
this.textureIdBuffer = new Uint32Array(maxSprites);
this.uvBuffer = new Float32Array(maxSprites * 4); // u0, v0, u1, v1
this.colorBuffer = new Uint32Array(maxSprites);
}
/**
* Initialize the engine bridge with WASM module.
* 使用WASM模块初始化引擎桥接。
*
* @param wasmModule - Pre-imported WASM module | 预导入的WASM模块
*/
async initializeWithModule(wasmModule: any): Promise<void> {
if (this.initialized) {
console.warn('EngineBridge already initialized | EngineBridge已初始化');
return;
}
try {
// Initialize WASM | 初始化WASM
if (wasmModule.default) {
await wasmModule.default();
}
// Create engine instance | 创建引擎实例
this.engine = new wasmModule.GameEngine(this.config.canvasId);
this.initialized = true;
if (this.config.debug) {
console.log('EngineBridge initialized | EngineBridge初始化完成');
}
} catch (error) {
throw new Error(`Failed to initialize engine: ${error} | 引擎初始化失败: ${error}`);
}
}
/**
* Initialize the engine bridge.
* 初始化引擎桥接。
*
* Loads the WASM module and creates the engine instance.
* 加载WASM模块并创建引擎实例。
*
* @param wasmPath - Path to WASM package | WASM包路径
* @deprecated Use initializeWithModule instead | 请使用 initializeWithModule 代替
*/
async initialize(wasmPath = '@esengine/engine'): Promise<void> {
if (this.initialized) {
console.warn('EngineBridge already initialized | EngineBridge已初始化');
return;
}
try {
// Dynamic import of WASM module | 动态导入WASM模块
const wasmModule = await import(/* webpackIgnore: true */ wasmPath);
await this.initializeWithModule(wasmModule);
} catch (error) {
throw new Error(`Failed to initialize engine: ${error} | 引擎初始化失败: ${error}`);
}
}
/**
* Check if bridge is initialized.
* 检查桥接是否已初始化。
*/
get isInitialized(): boolean {
return this.initialized;
}
/**
* Get canvas width.
* 获取画布宽度。
*/
get width(): number {
return this.engine?.width ?? 0;
}
/**
* Get canvas height.
* 获取画布高度。
*/
get height(): number {
return this.engine?.height ?? 0;
}
/**
* Clear the screen.
* 清除屏幕。
*
* @param r - Red (0-1) | 红色
* @param g - Green (0-1) | 绿色
* @param b - Blue (0-1) | 蓝色
* @param a - Alpha (0-1) | 透明度
*/
clear(r: number, g: number, b: number, a: number): void {
if (!this.initialized) return;
this.engine.clear(r, g, b, a);
}
/**
* Submit sprite data for rendering.
* 提交精灵数据进行渲染。
*
* @param sprites - Array of sprite render data | 精灵渲染数据数组
*/
submitSprites(sprites: SpriteRenderData[]): void {
if (!this.initialized || sprites.length === 0) return;
const count = Math.min(sprites.length, this.config.maxSprites);
// Fill typed arrays | 填充类型数组
for (let i = 0; i < count; i++) {
const sprite = sprites[i];
const tOffset = i * 7;
const uvOffset = i * 4;
// Transform data | 变换数据
this.transformBuffer[tOffset] = sprite.x;
this.transformBuffer[tOffset + 1] = sprite.y;
this.transformBuffer[tOffset + 2] = sprite.rotation;
this.transformBuffer[tOffset + 3] = sprite.scaleX;
this.transformBuffer[tOffset + 4] = sprite.scaleY;
this.transformBuffer[tOffset + 5] = sprite.originX;
this.transformBuffer[tOffset + 6] = sprite.originY;
// Texture ID | 纹理ID
this.textureIdBuffer[i] = sprite.textureId;
// UV coordinates | UV坐标
this.uvBuffer[uvOffset] = sprite.uv[0];
this.uvBuffer[uvOffset + 1] = sprite.uv[1];
this.uvBuffer[uvOffset + 2] = sprite.uv[2];
this.uvBuffer[uvOffset + 3] = sprite.uv[3];
// Color | 颜色
this.colorBuffer[i] = sprite.color;
}
// Submit to engine (single WASM call) | 提交到引擎单次WASM调用
this.engine.submitSpriteBatch(
this.transformBuffer.subarray(0, count * 7),
this.textureIdBuffer.subarray(0, count),
this.uvBuffer.subarray(0, count * 4),
this.colorBuffer.subarray(0, count)
);
this.stats.spriteCount = count;
}
/**
* Render the current frame.
* 渲染当前帧。
*/
render(): void {
if (!this.initialized) return;
const startTime = performance.now();
this.engine.render();
const endTime = performance.now();
// Update statistics | 更新统计信息
this.stats.frameTime = endTime - startTime;
this.stats.drawCalls = 1; // Currently single batch | 当前单批次
// Calculate FPS | 计算FPS
this.frameCount++;
this.fpsAccumulator += endTime - this.lastFrameTime;
this.lastFrameTime = endTime;
if (this.fpsAccumulator >= 1000) {
this.stats.fps = this.frameCount;
this.frameCount = 0;
this.fpsAccumulator = 0;
}
}
/**
* Load a texture.
* 加载纹理。
*
* @param id - Texture ID | 纹理ID
* @param url - Image URL | 图片URL
*/
loadTexture(id: number, url: string): void {
if (!this.initialized) return;
this.engine.loadTexture(id, url);
}
/**
* Load multiple textures.
* 加载多个纹理。
*
* @param requests - Texture load requests | 纹理加载请求
*/
loadTextures(requests: TextureLoadRequest[]): void {
for (const req of requests) {
this.loadTexture(req.id, req.url);
}
}
/**
* Check if a key is pressed.
* 检查按键是否按下。
*
* @param keyCode - Key code | 键码
*/
isKeyDown(keyCode: string): boolean {
if (!this.initialized) return false;
return this.engine.isKeyDown(keyCode);
}
/**
* Update input state (call once per frame).
* 更新输入状态(每帧调用一次)。
*/
updateInput(): void {
if (!this.initialized) return;
this.engine.updateInput();
}
/**
* Get engine statistics.
* 获取引擎统计信息。
*/
getStats(): EngineStats {
return { ...this.stats };
}
/**
* Resize the viewport.
* 调整视口大小。
*
* @param width - New width | 新宽度
* @param height - New height | 新高度
*/
resize(width: number, height: number): void {
if (!this.initialized) return;
if (this.engine.resize) {
this.engine.resize(width, height);
}
}
/**
* Dispose the bridge and release resources.
* 销毁桥接并释放资源。
*/
dispose(): void {
this.engine = null;
this.initialized = false;
}
}

View File

@@ -0,0 +1,108 @@
/**
* Render batcher for collecting sprite data.
* 用于收集精灵数据的渲染批处理器。
*/
import type { SpriteRenderData } from '../types';
/**
* Collects and sorts sprite render data for batch submission.
* 收集和排序精灵渲染数据用于批量提交。
*
* This class is used to collect sprites during the ECS update loop
* and then submit them all at once to the engine.
* 此类用于在ECS更新循环中收集精灵然后一次性提交到引擎。
*
* @example
* ```typescript
* const batcher = new RenderBatcher();
*
* // During ECS update | 在ECS更新期间
* batcher.addSprite({
* x: 100, y: 200,
* rotation: 0,
* scaleX: 1, scaleY: 1,
* originX: 0.5, originY: 0.5,
* textureId: 1,
* uv: [0, 0, 1, 1],
* color: 0xFFFFFFFF
* });
*
* // At end of frame | 在帧结束时
* bridge.submitSprites(batcher.getSprites());
* batcher.clear();
* ```
*/
export class RenderBatcher {
private sprites: SpriteRenderData[] = [];
private sortByZ = false;
/**
* Create a new render batcher.
* 创建新的渲染批处理器。
*
* @param sortByZ - Whether to sort sprites by Z order | 是否按Z顺序排序精灵
*/
constructor(sortByZ = false) {
this.sortByZ = sortByZ;
}
/**
* Add a sprite to the batch.
* 将精灵添加到批处理。
*
* @param sprite - Sprite render data | 精灵渲染数据
*/
addSprite(sprite: SpriteRenderData): void {
this.sprites.push(sprite);
}
/**
* Add multiple sprites to the batch.
* 将多个精灵添加到批处理。
*
* @param sprites - Array of sprite render data | 精灵渲染数据数组
*/
addSprites(sprites: SpriteRenderData[]): void {
this.sprites.push(...sprites);
}
/**
* Get all sprites in the batch.
* 获取批处理中的所有精灵。
*
* @returns Sorted array of sprites | 排序后的精灵数组
*/
getSprites(): SpriteRenderData[] {
// Sort by texture ID for better batching (fewer texture switches)
// 按纹理ID排序以获得更好的批处理效果减少纹理切换
if (!this.sortByZ) {
this.sprites.sort((a, b) => a.textureId - b.textureId);
}
return this.sprites;
}
/**
* Get sprite count.
* 获取精灵数量。
*/
get count(): number {
return this.sprites.length;
}
/**
* Clear all sprites from the batch.
* 清除批处理中的所有精灵。
*/
clear(): void {
this.sprites.length = 0;
}
/**
* Check if batch is empty.
* 检查批处理是否为空。
*/
get isEmpty(): boolean {
return this.sprites.length === 0;
}
}

View File

@@ -0,0 +1,140 @@
/**
* Sprite render helper utilities.
* 精灵渲染辅助工具。
*/
import { Entity, Component } from '@esengine/ecs-framework';
import type { EngineBridge } from './EngineBridge';
import { RenderBatcher } from './RenderBatcher';
import { SpriteComponent } from '../components/SpriteComponent';
import type { SpriteRenderData } from '../types';
/**
* Transform component interface.
* 变换组件接口。
*
* Your transform component should implement this interface.
* 你的变换组件应该实现此接口。
*/
export interface ITransformComponent {
position: { x: number; y: number };
rotation: number;
scale: { x: number; y: number };
}
/**
* Helper class for rendering sprites (not an ECS System).
* 精灵渲染辅助类非ECS系统
*
* Use this for manual control over rendering, or use EngineRenderSystem
* for automatic ECS integration.
* 用于手动控制渲染或使用EngineRenderSystem进行自动ECS集成。
*
* @example
* ```typescript
* const bridge = new EngineBridge({ canvasId: 'canvas' });
* await bridge.initialize();
*
* const helper = new SpriteRenderHelper(bridge);
*
* // In game loop | 在游戏循环中
* helper.collectSprites(entities, Transform);
* helper.render();
* ```
*/
export class SpriteRenderHelper {
private bridge: EngineBridge;
private batcher: RenderBatcher;
/**
* Create a new sprite render helper.
* 创建新的精灵渲染辅助类。
*
* @param bridge - Engine bridge instance | 引擎桥接实例
*/
constructor(bridge: EngineBridge) {
this.bridge = bridge;
this.batcher = new RenderBatcher();
}
/**
* Collect sprite data from entities.
* 从实体收集精灵数据。
*
* @param entities - Entities to process | 要处理的实体
* @param transformType - Transform component class | 变换组件类
*/
collectSprites<T extends Component & ITransformComponent>(
entities: Entity[],
transformType: new () => T
): void {
this.batcher.clear();
for (const entity of entities) {
const sprite = entity.getComponent(SpriteComponent);
const transform = entity.getComponent(transformType);
if (!sprite || !transform || !sprite.visible) {
continue;
}
// Calculate UV with flip | 计算带翻转的UV
let uv = sprite.uv;
if (sprite.flipX || sprite.flipY) {
uv = [...sprite.uv] as [number, number, number, number];
if (sprite.flipX) {
const temp = uv[0];
uv[0] = uv[2];
uv[2] = temp;
}
if (sprite.flipY) {
const temp = uv[1];
uv[1] = uv[3];
uv[3] = temp;
}
}
const renderData: SpriteRenderData = {
x: transform.position.x,
y: transform.position.y,
rotation: transform.rotation,
scaleX: transform.scale.x,
scaleY: transform.scale.y,
originX: sprite.originX,
originY: sprite.originY,
textureId: sprite.textureId,
uv,
color: sprite.color
};
this.batcher.addSprite(renderData);
}
}
/**
* Submit batched sprites and render.
* 提交批处理的精灵并渲染。
*/
render(): void {
if (!this.batcher.isEmpty) {
this.bridge.submitSprites(this.batcher.getSprites());
}
this.bridge.render();
}
/**
* Get the number of sprites to be rendered.
* 获取要渲染的精灵数量。
*/
get spriteCount(): number {
return this.batcher.count;
}
/**
* Clear the current batch.
* 清除当前批处理。
*/
clear(): void {
this.batcher.clear();
}
}

View File

@@ -0,0 +1,13 @@
/**
* ECS Engine Bindgen - Bridge layer between ECS Framework and Rust Engine.
* ECS引擎桥接层 - ECS框架与Rust引擎之间的桥接层。
*
* @packageDocumentation
*/
export { EngineBridge, EngineBridgeConfig } from './core/EngineBridge';
export { RenderBatcher } from './core/RenderBatcher';
export { SpriteRenderHelper, ITransformComponent } from './core/SpriteRenderHelper';
export { EngineRenderSystem, type TransformComponentType } from './systems/EngineRenderSystem';
export { SpriteComponent } from './components/SpriteComponent';
export * from './types';

View File

@@ -0,0 +1,171 @@
/**
* Engine render system for ECS.
* 用于ECS的引擎渲染系统。
*/
import { EntitySystem, Matcher, Entity, ComponentType, ECSSystem, Component } from '@esengine/ecs-framework';
import type { EngineBridge } from '../core/EngineBridge';
import { RenderBatcher } from '../core/RenderBatcher';
import { SpriteComponent } from '../components/SpriteComponent';
import type { SpriteRenderData } from '../types';
import type { ITransformComponent } from '../core/SpriteRenderHelper';
/**
* Type for transform component constructor.
* 变换组件构造函数类型。
*/
export type TransformComponentType = ComponentType & (new (...args: any[]) => Component & ITransformComponent);
/**
* ECS System for rendering sprites using the Rust engine.
* 使用Rust引擎渲染精灵的ECS系统。
*
* This system extends EntitySystem and integrates with the ECS lifecycle.
* 此系统扩展EntitySystem并与ECS生命周期集成。
*
* @example
* ```typescript
* // Create transform component | 创建变换组件
* @ECSComponent('Transform')
* class Transform extends Component implements ITransformComponent {
* position = { x: 0, y: 0 };
* rotation = 0;
* scale = { x: 1, y: 1 };
* }
*
* // Initialize bridge | 初始化桥接
* const bridge = new EngineBridge({ canvasId: 'canvas' });
* await bridge.initialize();
*
* // Add system to scene | 将系统添加到场景
* const renderSystem = new EngineRenderSystem(bridge, Transform);
* scene.addSystem(renderSystem);
* ```
*/
@ECSSystem('EngineRender', { updateOrder: 1000 }) // Render system executes last | 渲染系统最后执行
export class EngineRenderSystem extends EntitySystem {
private bridge: EngineBridge;
private batcher: RenderBatcher;
private transformType: TransformComponentType;
/**
* Create a new engine render system.
* 创建新的引擎渲染系统。
*
* @param bridge - Engine bridge instance | 引擎桥接实例
* @param transformType - Transform component class (must implement ITransformComponent) | 变换组件类必须实现ITransformComponent
*/
constructor(bridge: EngineBridge, transformType: TransformComponentType) {
// Match entities with both Sprite and Transform components
// 匹配同时具有Sprite和Transform组件的实体
super(Matcher.empty().all(SpriteComponent, transformType));
this.bridge = bridge;
this.batcher = new RenderBatcher();
this.transformType = transformType;
}
/**
* Called when system is initialized.
* 系统初始化时调用。
*/
public override initialize(): void {
super.initialize();
this.logger.info('EngineRenderSystem initialized | 引擎渲染系统初始化完成');
}
/**
* Called before processing entities.
* 处理实体之前调用。
*/
protected begin(): void {
// Clear the batch | 清空批处理
this.batcher.clear();
// Clear screen | 清屏
this.bridge.clear(0, 0, 0, 1);
// Update input | 更新输入
this.bridge.updateInput();
}
/**
* Process all matched entities.
* 处理所有匹配的实体。
*
* @param entities - Entities to process | 要处理的实体
*/
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const sprite = entity.getComponent(SpriteComponent);
const transform = entity.getComponent(this.transformType) as unknown as ITransformComponent | null;
if (!sprite || !transform || !sprite.visible) {
continue;
}
// Calculate UV with flip | 计算带翻转的UV
let uv = sprite.uv;
if (sprite.flipX || sprite.flipY) {
uv = [...sprite.uv] as [number, number, number, number];
if (sprite.flipX) {
[uv[0], uv[2]] = [uv[2], uv[0]];
}
if (sprite.flipY) {
[uv[1], uv[3]] = [uv[3], uv[1]];
}
}
const renderData: SpriteRenderData = {
x: transform.position.x,
y: transform.position.y,
rotation: transform.rotation,
scaleX: transform.scale.x,
scaleY: transform.scale.y,
originX: sprite.originX,
originY: sprite.originY,
textureId: sprite.textureId,
uv,
color: sprite.color
};
this.batcher.addSprite(renderData);
}
}
/**
* Called after processing entities.
* 处理实体之后调用。
*/
protected end(): void {
// Submit batch and render | 提交批处理并渲染
if (!this.batcher.isEmpty) {
this.bridge.submitSprites(this.batcher.getSprites());
}
this.bridge.render();
}
/**
* Get the number of sprites rendered.
* 获取渲染的精灵数量。
*/
get spriteCount(): number {
return this.batcher.count;
}
/**
* Get engine statistics.
* 获取引擎统计信息。
*/
getStats() {
return this.bridge.getStats();
}
/**
* Load a texture.
* 加载纹理。
*/
loadTexture(id: number, url: string): void {
this.bridge.loadTexture(id, url);
}
}

View File

@@ -0,0 +1,72 @@
/**
* Type definitions for engine bridge.
* 引擎桥接层的类型定义。
*/
/**
* Sprite render data for batch submission.
* 用于批量提交的精灵渲染数据。
*/
export interface SpriteRenderData {
/** Position X. | X位置。 */
x: number;
/** Position Y. | Y位置。 */
y: number;
/** Rotation in radians. | 旋转角度(弧度)。 */
rotation: number;
/** Scale X. | X缩放。 */
scaleX: number;
/** Scale Y. | Y缩放。 */
scaleY: number;
/** Origin X (0-1). | 原点X0-1。 */
originX: number;
/** Origin Y (0-1). | 原点Y0-1。 */
originY: number;
/** Texture ID. | 纹理ID。 */
textureId: number;
/** UV coordinates [u0, v0, u1, v1]. | UV坐标。 */
uv: [number, number, number, number];
/** Packed RGBA color. | 打包的RGBA颜色。 */
color: number;
}
/**
* Texture load request.
* 纹理加载请求。
*/
export interface TextureLoadRequest {
/** Unique texture ID. | 唯一纹理ID。 */
id: number;
/** Image URL. | 图片URL。 */
url: string;
}
/**
* Engine statistics.
* 引擎统计信息。
*/
export interface EngineStats {
/** Frames per second. | 每秒帧数。 */
fps: number;
/** Number of draw calls. | 绘制调用次数。 */
drawCalls: number;
/** Number of sprites rendered. | 渲染的精灵数量。 */
spriteCount: number;
/** Frame time in milliseconds. | 帧时间(毫秒)。 */
frameTime: number;
}
/**
* Camera configuration.
* 相机配置。
*/
export interface CameraConfig {
/** Camera X position. | 相机X位置。 */
x: number;
/** Camera Y position. | 相机Y位置。 */
y: number;
/** Zoom level. | 缩放级别。 */
zoom: number;
/** Rotation in radians. | 旋转角度(弧度)。 */
rotation: number;
}

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020", "DOM"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./bin",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "bin", "dist"]
}