Tower Defence. Часть 3
Tower Defence. Часть 3: Боевая логика и движение
Здесь игра становится «живой»: враги идут по пути, башни выбирают цель, пули летят и наносят урон.
Технологии именно этого урока (lesson-12-6)
- Вектор движения в 2D (
dx,dy,distance). - Нормализация направления (
dx / distance,dy / distance). - Движение с учетом времени кадра (
step = speed * dt). - Обновление массивов с удалением элементов в цикле.
- Приоритетный выбор цели по вычисляемому критерию.
- Работа со ссылками на объекты (
bullet.target). - Последовательный игровой update-пайплайн.
Сквозной прогресс в Practice Preview
В этой части combat-модуль продолжает тот же Tower workspace из предыдущих уроков:
- Проверка задачи обновляет соответствующую боевую функцию в проектном state.
- Во вкладке
Previewможно запускать checkpoint и видеть движение/стрельбу до финала курса. - Milestone по модулю
combatзасчитывается отдельно и не сбрасывает основной прогресс уроков.
Фундамент: как работает движение в 2D
Чтобы сдвинуть объект к цели:
- Считаем разницу координат:
const dx = target.x - entity.x;
const dy = target.y - entity.y;
- Находим расстояние до цели:
const distance = Math.hypot(dx, dy);
- Вычисляем длину шага за кадр:
const step = speed * dt;
- Если
distance <= step, ставим объект точно в цель. - Иначе двигаем по направлению:
entity.x += (dx / distance) * step;
entity.y += (dy / distance) * step;
Это и есть фундамент moveTowards.
Смотри, что важно:
- если
distance === 0, делить наdistanceнельзя (будет деление на ноль); - если
distance <= step, мы специально "телепортируем" объект ровно вtarget, чтобы не пролететь точку и получить точное совпадение координат.
Это важно, потому что дальше в логике часто есть проверки вида if (enemy.x === target.x && enemy.y === target.y) или "попадание пули" через ===.
Фундамент: зачем нужен dt
dt (delta time) — время между кадрами в секундах.
Без dt на быстрых и медленных устройствах игра двигалась бы с разной скоростью. Формула speed * dt делает поведение стабильным.
Смотри, что важно: в наших задачах dt считается в секундах. Поэтому speed тоже «в пикселях в секунду», и формула step = speed * dt согласована по единицам.
Что именно ты реализуешь в задачах
moveTowards— универсальный шаг к точке.updateEnemyStep— движение одного врага по маршруту.updateEnemies— обновление всех врагов и снятие жизней при прорыве.chooseTarget— выбор приоритетной цели в радиусе башни.updateTowers— стрельба, cooldown, создание пуль.moveBullet— движение пули к цели.applyEnemyDeath— удаление врага, награда, фраг.updateBullets— обработка попаданий/промахов/удалений.buildCombatReport— метрики боевого состояния.stepCombat— оркестрация боевого шага.
Почему удаление элементов делают с конца массива
В updateEnemies и updateBullets элементы могут удаляться через splice. Чтобы не пропускать элементы и не сбивать индексы, обычно идут циклом с конца:
for (let i = arr.length - 1; i >= 0; i -= 1) {
if (/* нужно удалить */) arr.splice(i, 1);
}
Как выбирается цель башни
В game.js цель выбирается не случайно: башня ищет врага в радиусе и берет того, кто «дальше продвинулся» по пути.
Идея критерия:
const progress = enemy.pathIndex + (1 - enemy.hp / enemy.maxHp) * 0.1;
Это помогает фокусировать огонь логичнее.
Ссылки на цели: bullet.target
Пуля хранит ссылку на объект врага (bullet.target). Если враг уже умер и удален из state.enemies, пуля должна быть удалена, иначе она будет «лететь в несуществующую цель».
Поэтому перед движением пули обычно проверяют, что цель еще в списке врагов.
Важный порядок в stepCombat
updateEnemiesupdateTowersupdateBullets
Этот порядок обеспечивает предсказуемый бой и уменьшает баги синхронизации.
Результат после Part 3
У тебя работает полноценный боевой цикл TD: движение, стрельба, урон, убийства, награда и метрики.