import { Transform } from '../components/Transform.js'; import { Velocity } from '../components/Velocity.js'; import { MeshComponent } from '../components/MeshComponent.js'; import { Collidable } from '../components/Collidable.js'; import { Health } from '../components/Health.js'; import { Invincibility } from '../components/Invincibility.js'; import { PlayerTag, CoinTag, ObstacleTag, BoundaryConstrained } from '../components/Tags.js'; import { GameConfig } from './GameConfig.js'; /** * EntityFactory - creates pre-configured game entities with appropriate components. * Centralizes entity creation logic for consistency. * * @typedef {import('../ecs/World.js').EntityId} EntityId */ export class EntityFactory { /** * @param {import('../ecs/World.js').World} world - The ECS world * @param {THREE.Scene} scene - The Three.js scene */ constructor(world, scene) { /** @type {import('../ecs/World.js').World} */ this.world = world; /** @type {THREE.Scene} */ this.scene = scene; /** @type {number} Size of the game ground/play area */ this.groundSize = 30; } /** * Create the player entity * @returns {EntityId} The player entity ID */ createPlayer() { const entity = this.world.createEntity(); // Create mesh const geometry = new window.THREE.BoxGeometry(1, 1, 1); const material = new window.THREE.MeshStandardMaterial({ color: 0x4169E1, metalness: 0.3, roughness: 0.4 }); const mesh = new window.THREE.Mesh(geometry, material); mesh.castShadow = true; mesh.receiveShadow = true; this.scene.add(mesh); // Add components this.world.addComponent(entity, new Transform(0, 0.5, 0)); this.world.addComponent(entity, new Velocity()); this.world.addComponent(entity, new MeshComponent(mesh)); this.world.addComponent(entity, new Collidable(0, 'player')); // Player center point (original behavior) this.world.addComponent(entity, new Health(100)); // Invincibility starts inactive until first damage this.world.addComponent(entity, new Invincibility(GameConfig.INVINCIBILITY_DURATION, false)); this.world.addComponent(entity, new PlayerTag()); this.world.addComponent(entity, new BoundaryConstrained(this.groundSize)); return entity; } /** * Create a collectible coin entity * @param {number} [index=0] - Unique index for animation offset * @returns {EntityId} The coin entity ID */ createCoin(index = 0) { const entity = this.world.createEntity(); // Create mesh const geometry = new window.THREE.SphereGeometry(0.3, 16, 16); const material = new window.THREE.MeshStandardMaterial({ color: 0xFFD700, metalness: 0.8, roughness: 0.2, emissive: 0xFFD700, emissiveIntensity: 0.3 }); const mesh = new window.THREE.Mesh(geometry, material); mesh.castShadow = true; mesh.receiveShadow = true; this.scene.add(mesh); // Random position const x = (Math.random() - 0.5) * (this.groundSize - 4); const z = (Math.random() - 0.5) * (this.groundSize - 4); // Add components this.world.addComponent(entity, new Transform(x, 0.5, z)); this.world.addComponent(entity, new MeshComponent(mesh)); this.world.addComponent(entity, new Collidable(0.8, 'coin')); this.world.addComponent(entity, new CoinTag(index)); return entity; } /** * Create an obstacle entity * @returns {EntityId} The obstacle entity ID */ createObstacle() { const entity = this.world.createEntity(); // Create mesh const geometry = new window.THREE.BoxGeometry(1.5, 2, 1.5); const material = new window.THREE.MeshStandardMaterial({ color: 0xFF4500, metalness: 0.3, roughness: 0.7 }); const mesh = new window.THREE.Mesh(geometry, material); mesh.castShadow = true; mesh.receiveShadow = true; this.scene.add(mesh); // Random position (away from center) let posX, posZ; do { posX = (Math.random() - 0.5) * (this.groundSize - 4); posZ = (Math.random() - 0.5) * (this.groundSize - 4); } while (Math.abs(posX) < 3 && Math.abs(posZ) < 3); // Random velocity const velocity = new Velocity( (Math.random() - 0.5) * 0.05, 0, (Math.random() - 0.5) * 0.05 ); // Add components this.world.addComponent(entity, new Transform(posX, 1, posZ)); this.world.addComponent(entity, velocity); this.world.addComponent(entity, new MeshComponent(mesh)); this.world.addComponent(entity, new Collidable(1.5, 'obstacle')); this.world.addComponent(entity, new ObstacleTag()); this.world.addComponent(entity, new BoundaryConstrained(this.groundSize)); return entity; } /** * Remove entity and its mesh from scene * @param {EntityId} entityId - The entity to destroy */ destroyEntity(entityId) { // Remove mesh from scene if it exists const meshComp = this.world.getComponent(entityId, MeshComponent); if (meshComp) { this.scene.remove(meshComp.mesh); meshComp.mesh.geometry.dispose(); meshComp.mesh.material.dispose(); } // Remove entity from world this.world.removeEntity(entityId); } }