feat: move to typescript, miniplex ecs

This commit is contained in:
2026-09-05 12:07:22 +05:00
parent 63f9885e46
commit f6596f765e
69 changed files with 3932 additions and 1838 deletions
+52
View File
@@ -0,0 +1,52 @@
import { Container, Particle, ParticleContainer, Texture } from 'pixi.js'
import type { GameEntity, GameWorld } from '../ecs/world'
import { tintOf } from './textures'
/** Размер эталонной белой текстуры Pixi (Texture.WHITE). */
const WHITE_TEXTURE_SIZE = 16
/**
* Частицы — сущности ECS, отрисовка — один ParticleContainer.
* Батч всего слоя за один draw call, без Graphics на частицу.
*/
export class ParticleLayer {
private readonly container = new ParticleContainer({
dynamicProperties: { position: true, scale: true, rotation: false, color: true },
})
private readonly views = new Map<GameEntity, Particle>()
constructor(parent: Container, world: GameWorld) {
parent.addChild(this.container)
world.onEntityAdded.subscribe((entity) => {
if (!entity.particle) return
const particle = new Particle({
texture: Texture.WHITE,
x: entity.position?.x ?? 0,
y: entity.position?.y ?? 0,
anchorX: 0.5,
anchorY: 0.5,
tint: tintOf(entity.particle.color),
alpha: entity.lifetime?.remaining ?? 1,
})
this.views.set(entity, particle)
this.container.addParticle(particle)
})
world.onEntityRemoved.subscribe((entity) => {
const particle = this.views.get(entity)
if (!particle) return
this.container.removeParticle(particle)
this.views.delete(entity)
})
}
update(world: GameWorld): void {
for (const [entity, particle] of this.views) {
particle.x = entity.position?.x ?? 0
particle.y = entity.position?.y ?? 0
particle.alpha = Math.max(0, entity.lifetime?.remaining ?? 0)
const scale = (entity.particle?.size ?? 4) / WHITE_TEXTURE_SIZE
particle.scaleX = scale
particle.scaleY = scale
}
}
}