/
RaskolnickOFF
/
3D
Обзор
Документация
Войти
/
RaskolnickOFF
/
3D
Код
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/source/systems/enemy/EnemyBehaviorSystem.js
200 строк
9 KB
RaskolnickOFF
updates
08 июн 2026, 03:53
08 июн 2026, 03:53
ca9a567
Код
Авторство
О чём код?
// js/source/systems/enemy/EnemyBehaviorSystem.js — движение и атака врагов по AI_STATE import { Components } from '../../core/Components.js'; import { GameEvents } from '../../core/GameEvents.js'; export class EnemyBehaviorSystem { constructor(ctx) { this.chaseSpeed = ctx.settings.enemy.speed.chase; this.investigateSpeed = ctx.settings.enemy.speed.investigate; this.patrolSpeed = ctx.settings.enemy.patrol.speedMultiplier; this.patrolRadius = ctx.settings.enemy.patrol.radius; this.patrolPauseMin = ctx.settings.enemy.patrol.pauseMin; this.patrolPauseMax = ctx.settings.enemy.patrol.pauseMax; this.searchRadius = ctx.settings.enemy.memory.searchRadius; this.eventBus = ctx.eventBus; this.attackInterval = ctx.settings.enemy.combat.attackInterval; this.attackDamage = ctx.settings.enemy.combat.damage; } update(engine, dt) { const enemies = engine.with(Components.ENEMY); for (const enemy of enemies) { const transform = enemy.get(Components.TRANSFORM); if (!transform) continue; const state = enemy.get(Components.AI_STATE) || 'idle'; const canSee = enemy.get(Components.CAN_SEE); const canSmell = enemy.get(Components.CAN_SMELL); // Найти игрока let playerTransform = null; let playerDist = Infinity; let pdx = 0, pdz = 0; for (const e of engine.with(Components.PLAYER)) { const pt = e.get(Components.TRANSFORM); if (pt) { pdx = pt.x - transform.x; pdz = pt.z - transform.z; playerDist = Math.sqrt(pdx * pdx + pdz * pdz); playerTransform = pt; break; } } const knownPos = enemy.get(Components.KNOWN_POSITION); const lastKnown = enemy.get(Components.LAST_KNOWN); const origin = enemy.get(Components.ORIGIN_POSITION); let desiredVelX = 0; let desiredVelZ = 0; let attack = false; switch (state) { case 'chase': { // Всегда идём к реальной позиции игрока if (playerTransform) { const tdist = playerDist; if (tdist > 0.3) { const spd = (canSee || canSmell) ? this.chaseSpeed : this.investigateSpeed; desiredVelX = (pdx / tdist) * spd; desiredVelZ = (pdz / tdist) * spd; } } break; } case 'combat': { if (!playerTransform) break; // Стоим на месте во время боя desiredVelX = 0; desiredVelZ = 0; // --- ТИКАЕМ КУЛДАУН СТРОГО В БОЮ --- let combatTimer = enemy.get(Components.COMBAT_TIMER) || 0; if (combatTimer > 0) { combatTimer -= dt; enemy.add(Components.COMBAT_TIMER, combatTimer); } // ---------------------------------- const logKey = `${enemy.id}_${pdx.toFixed(1)}_${pdz.toFixed(1)}_${transform.rotY.toFixed(2)}`; // Проверяем возможность атаки: если близко И кулдаун прошел (<= 0) if (playerDist < 0.8 && combatTimer <= 0) { attack = true; } break; } case 'search': { if (lastKnown) { const tdx = lastKnown.x - transform.x; const tdz = lastKnown.z - transform.z; const tdist = Math.sqrt(tdx * tdx + tdz * tdz); if (tdist < this.searchRadius) { let searchDir = enemy.get(Components.SEARCH_DIRECTION); let memoryTimer = enemy.get(Components.MEMORY_TIMER) || 0; if (!searchDir || memoryTimer <= 0) { const ang = Math.random() * Math.PI * 2; searchDir = { x: Math.cos(ang), z: Math.sin(ang) }; enemy.add(Components.SEARCH_DIRECTION, searchDir); memoryTimer = 3 + Math.random() * 2; } memoryTimer -= dt; enemy.add(Components.MEMORY_TIMER, memoryTimer); desiredVelX = searchDir.x * this.investigateSpeed * 0.5; desiredVelZ = searchDir.z * this.investigateSpeed * 0.5; if (Math.random() < 0.01) { desiredVelX = 0; desiredVelZ = 0; } } } break; } case 'return': { if (origin) { const tdx = origin.x - transform.x; const tdz = origin.z - transform.z; const tdist = Math.sqrt(tdx * tdx + tdz * tdz); if (tdist > 0.3) { desiredVelX = (tdx / tdist) * this.investigateSpeed; desiredVelZ = (tdz / tdist) * this.investigateSpeed; } } break; } case 'patrol': { let patrolDir = enemy.get(Components.PATROL_DIRECTION); if (!patrolDir) { const ang = Math.random() * Math.PI * 2; patrolDir = { x: Math.cos(ang), z: Math.sin(ang) }; enemy.add(Components.PATROL_DIRECTION, patrolDir); } let pauseTimer = enemy.get(Components.PAUSE_TIMER) || 0; if (pauseTimer > 0) { pauseTimer -= dt; desiredVelX = 0; desiredVelZ = 0; } else { const spd = this.investigateSpeed * this.patrolSpeed; desiredVelX = patrolDir.x * spd; desiredVelZ = patrolDir.z * spd; if (Math.random() < 0.005) { pauseTimer = this.patrolPauseMin + Math.random() * (this.patrolPauseMax - this.patrolPauseMin); } } // Проверка выхода за радиус if (origin) { const tox = origin.x - transform.x; const toz = origin.z - transform.z; const distToOrigin = Math.sqrt(tox * tox + toz * toz); if (distToOrigin > this.patrolRadius) { patrolDir = { x: tox / distToOrigin, z: toz / distToOrigin }; enemy.add(Components.PATROL_DIRECTION, patrolDir); } } enemy.add(Components.PAUSE_TIMER, pauseTimer); break; } case 'idle': { desiredVelX = 0; desiredVelZ = 0; break; } } // Атака: срабатывает, только если стейт 'combat' разрешил атаку (флаг attack === true) if (attack && playerTransform) { for (const e of engine.with(Components.PLAYER)) { // Наносим фиксированный урон из настроек Enemy.json БЕЗ dt this.eventBus.emit(GameEvents.DAMAGE_DEALT, { targetEntity: e, amount: this.attackDamage }); // Выставляем кулдаун на перезарядку конкретно этому атаковавшему врагу enemy.add(Components.COMBAT_TIMER, this.attackInterval); break; } } enemy.add(Components.DESIRED_VELOCITY, { x: desiredVelX, z: desiredVelZ }); } } }