first commit
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* SpawnManager.js
|
||||
* Manages enemy tank spawning: timing, spawn points, composition, and limits.
|
||||
*/
|
||||
|
||||
const EnemyTank = require('../entities/EnemyTank');
|
||||
const {
|
||||
TANK_TYPE,
|
||||
MAX_ENEMIES_ON_SCREEN,
|
||||
ENEMY_SPAWN_INTERVAL,
|
||||
TILE_SIZE,
|
||||
MAP_OFFSET_X,
|
||||
MAP_OFFSET_Y,
|
||||
GRID_COLS,
|
||||
} = require('../base/GameGlobal');
|
||||
|
||||
class SpawnManager {
|
||||
constructor() {
|
||||
/** @type {Array<{col: number, row: number}>} */
|
||||
this._spawnPoints = [];
|
||||
this._currentSpawnIndex = 0;
|
||||
|
||||
// Spawn queue
|
||||
this._spawnQueue = []; // array of TANK_TYPE values
|
||||
this._spawnTimer = 0;
|
||||
this._spawnInterval = ENEMY_SPAWN_INTERVAL;
|
||||
this._totalSpawned = 0;
|
||||
this._totalEnemies = 0;
|
||||
|
||||
// Level info
|
||||
this._levelNum = 1;
|
||||
|
||||
// Power-up enemy indices (which enemies drop power-ups)
|
||||
this._powerUpIndices = new Set();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize for a new level.
|
||||
* @param {object} levelData - Level configuration from LevelData.
|
||||
*/
|
||||
init(levelData) {
|
||||
this._spawnPoints = levelData.spawnPoints || [
|
||||
{ col: 0, row: 0 },
|
||||
{ col: Math.floor(GRID_COLS / 2), row: 0 },
|
||||
{ col: GRID_COLS - 1, row: 0 },
|
||||
];
|
||||
this._currentSpawnIndex = 0;
|
||||
this._spawnTimer = 0;
|
||||
this._totalSpawned = 0;
|
||||
this._levelNum = levelData.id || 1;
|
||||
this._speedMultiplier = levelData.speedMultiplier || 1;
|
||||
|
||||
// Build spawn queue from composition
|
||||
this._spawnQueue = [];
|
||||
const comp = levelData.enemies.composition;
|
||||
this._totalEnemies = levelData.enemies.total;
|
||||
|
||||
// Add enemies by type
|
||||
for (let i = 0; i < (comp.boss || 0); i++) this._spawnQueue.push(TANK_TYPE.ENEMY_BOSS);
|
||||
for (let i = 0; i < (comp.armor || 0); i++) this._spawnQueue.push(TANK_TYPE.ENEMY_ARMOR);
|
||||
for (let i = 0; i < (comp.fast || 0); i++) this._spawnQueue.push(TANK_TYPE.ENEMY_FAST);
|
||||
for (let i = 0; i < (comp.normal || 0); i++) this._spawnQueue.push(TANK_TYPE.ENEMY_NORMAL);
|
||||
|
||||
// Shuffle the queue for variety
|
||||
this._shuffleArray(this._spawnQueue);
|
||||
|
||||
// Determine which enemies drop power-ups (roughly every 4-5 enemies)
|
||||
this._powerUpIndices.clear();
|
||||
const numPowerUps = Math.max(1, Math.floor(this._totalEnemies / 5));
|
||||
const indices = new Set();
|
||||
while (indices.size < numPowerUps) {
|
||||
indices.add(Math.floor(Math.random() * this._totalEnemies));
|
||||
}
|
||||
this._powerUpIndices = indices;
|
||||
|
||||
// Spawn first batch immediately
|
||||
this._spawnTimer = this._spawnInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update spawn timer and spawn enemies as needed.
|
||||
* @param {number} dt - Delta time in seconds.
|
||||
* @param {Array<EnemyTank>} activeEnemies - Currently alive enemies.
|
||||
* @returns {EnemyTank|null} Newly spawned enemy, or null.
|
||||
*/
|
||||
update(dt, activeEnemies) {
|
||||
if (this._spawnQueue.length === 0) return null;
|
||||
|
||||
const aliveCount = activeEnemies.filter((e) => e.alive).length;
|
||||
if (aliveCount >= MAX_ENEMIES_ON_SCREEN) return null;
|
||||
|
||||
this._spawnTimer += dt * 1000;
|
||||
if (this._spawnTimer < this._spawnInterval) return null;
|
||||
|
||||
this._spawnTimer = 0;
|
||||
return this._spawnNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn the next enemy from the queue.
|
||||
* @private
|
||||
* @returns {EnemyTank|null}
|
||||
*/
|
||||
_spawnNext() {
|
||||
if (this._spawnQueue.length === 0) return null;
|
||||
|
||||
const type = this._spawnQueue.shift();
|
||||
const spawnPoint = this._spawnPoints[this._currentSpawnIndex % this._spawnPoints.length];
|
||||
this._currentSpawnIndex++;
|
||||
|
||||
const hasPowerUp = this._powerUpIndices.has(this._totalSpawned);
|
||||
this._totalSpawned++;
|
||||
|
||||
const enemy = new EnemyTank({
|
||||
type,
|
||||
col: spawnPoint.col,
|
||||
row: spawnPoint.row,
|
||||
levelNum: this._levelNum,
|
||||
hasPowerUp,
|
||||
speedMultiplier: this._speedMultiplier,
|
||||
});
|
||||
|
||||
return enemy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fisher-Yates shuffle.
|
||||
* @private
|
||||
*/
|
||||
_shuffleArray(arr) {
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
}
|
||||
|
||||
/** Number of enemies remaining to spawn. */
|
||||
get remainingToSpawn() {
|
||||
return this._spawnQueue.length;
|
||||
}
|
||||
|
||||
/** Total enemies for this level. */
|
||||
get totalEnemies() {
|
||||
return this._totalEnemies;
|
||||
}
|
||||
|
||||
/** Total spawned so far. */
|
||||
get totalSpawned() {
|
||||
return this._totalSpawned;
|
||||
}
|
||||
|
||||
/** Whether all enemies have been spawned. */
|
||||
get allSpawned() {
|
||||
return this._spawnQueue.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpawnManager;
|
||||
Reference in New Issue
Block a user