3.7: Стрільба ворога
Що ми будемо робити?
У цьому підрозділі ми додамо функціональність стрільби до класу Enemy.js
з автоматичним націлюванням на гравця.
Оновлення класу Enemy.js
Додайте до класу Enemy.js
нові методи для стрільби:
javascript
// ... існуючий код ...
export class Enemy extends Tank {
constructor(options = {}) {
// ... існуючий код конструктора ...
// Система стрільби
this.shooting = {
canShoot: true,
lastShotTime: 0,
shootCooldown: 2000, // 2 секунди між пострілами
bullets: [], // масив активних куль
accuracy: 0.8 // точність стрільби (80%)
};
// ... існуючий код ...
}
// ... існуючі методи ...
/**
* Оновлення стану ворога
* @param {number} deltaTime - Час з останнього оновлення
*/
update(deltaTime) {
if (!this.isAlive) return;
// Оновлюємо таймери
this.updateTimers(deltaTime);
// Оновлюємо AI
this.updateAI(deltaTime);
// Оновлюємо рух
this.updateMovement(deltaTime);
// Оновлюємо стрільбу
this.updateShooting(deltaTime);
// Оновлюємо кулі
this.updateBullets(deltaTime);
}
/**
* Оновлення системи стрільби
* @param {number} deltaTime - Час з останнього оновлення
*/
updateShooting(deltaTime) {
// Оновлюємо час останнього пострілу
this.shooting.lastShotTime += deltaTime;
// Перевіряємо чи можна стріляти знову
if (this.shooting.lastShotTime >= this.shooting.shootCooldown) {
this.shooting.canShoot = true;
}
// Стріляємо якщо в режимі атаки і є ціль
if (this.ai.state === 'attack' &&
this.shooting.canShoot &&
this.ai.chase.target) {
this.shoot();
}
}
/**
* Стрільба ворога
*/
shoot() {
if (!this.ai.chase.target) return;
// Отримуємо позицію для стрільби
const shootPos = this.getShootPosition();
// Розраховуємо напрямок до гравця
const targetDirection = this.calculateTargetDirection();
// Додаємо неточність до стрільби
const finalDirection = this.addShootingInaccuracy(targetDirection);
// Імпортуємо клас Bullet
import('./Bullet.js').then(module => {
const { Bullet } = module;
// Створюємо нову кулю
const bullet = new Bullet({
x: shootPos.x,
y: shootPos.y,
direction: finalDirection,
owner: 'enemy',
speed: 4 // швидкість кулі ворога (повільніше за гравця)
});
// Додаємо кулю до масиву
this.shooting.bullets.push(bullet);
// Встановлюємо затримку між пострілами
this.shooting.canShoot = false;
this.shooting.lastShotTime = 0;
// Логуємо стрільбу
logger.enemyAction('Ворог стріляє', `напрямок: ${finalDirection}`);
console.log('💥 Ворог вистрілив кулю:', bullet);
});
}
/**
* Розрахунок напрямку до цілі
* @returns {string} - Напрямок до гравця
*/
calculateTargetDirection() {
if (!this.ai.chase.target) return this.direction;
const target = this.ai.chase.target;
const dx = target.x - this.x;
const dy = target.y - this.y;
// Визначаємо основний напрямок
if (Math.abs(dx) > Math.abs(dy)) {
return dx > 0 ? 'right' : 'left';
} else {
return dy > 0 ? 'down' : 'up';
}
}
/**
* Додавання неточності до стрільби
* @param {string} direction - Початковий напрямок
* @returns {string} - Фінальний напрямок з неточністю
*/
addShootingInaccuracy(direction) {
// Якщо точність 100%, повертаємо точний напрямок
if (this.shooting.accuracy >= 1.0) {
return direction;
}
// Шанс неточної стрільби
if (Math.random() > this.shooting.accuracy) {
const directions = ['up', 'down', 'left', 'right'];
const randomDirection = directions[Math.floor(Math.random() * directions.length)];
return randomDirection;
}
return direction;
}
/**
* Оновлення куль ворога
* @param {number} deltaTime - Час з останнього оновлення
*/
updateBullets(deltaTime) {
// Оновлюємо всі кулі
for (let i = this.shooting.bullets.length - 1; i >= 0; i--) {
const bullet = this.shooting.bullets[i];
// Оновлюємо кулю
bullet.update(deltaTime);
// Перевіряємо чи куля активна
if (!bullet.isBulletActive()) {
// Видаляємо неактивну кулю
this.shooting.bullets.splice(i, 1);
}
}
}
/**
* Малювання куль ворога
* @param {CanvasRenderingContext2D} ctx - Контекст для малювання
*/
renderBullets(ctx) {
// Малюємо всі активні кулі
this.shooting.bullets.forEach(bullet => {
bullet.render(ctx);
});
}
/**
* Отримання позиції для стрільби
* @returns {Object} - Позиція кулі
*/
getShootPosition() {
const centerX = this.x + this.width / 2;
const centerY = this.y + this.height / 2;
// Розраховуємо позицію кулі залежно від напрямку
switch (this.direction) {
case 'up':
return { x: centerX - 2, y: this.y - 4 };
case 'down':
return { x: centerX - 2, y: this.y + this.height };
case 'left':
return { x: this.x - 4, y: centerY - 2 };
case 'right':
return { x: this.x + this.width, y: centerY - 2 };
default:
return { x: centerX - 2, y: this.y - 4 };
}
}
/**
* Отримання всіх куль ворога
* @returns {Array} - Масив активних куль
*/
getBullets() {
return [...this.shooting.bullets];
}
/**
* Видалення кулі
* @param {Bullet} bullet - Куля для видалення
*/
removeBullet(bullet) {
const index = this.shooting.bullets.indexOf(bullet);
if (index > -1) {
this.shooting.bullets.splice(index, 1);
bullet.destroy();
}
}
/**
* Очищення всіх куль
*/
clearBullets() {
this.shooting.bullets.forEach(bullet => bullet.destroy());
this.shooting.bullets = [];
}
/**
* Встановлення затримки між пострілами
* @param {number} cooldown - Затримка в мілісекундах
*/
setShootCooldown(cooldown) {
this.shooting.shootCooldown = cooldown;
}
/**
* Встановлення точності стрільби
* @param {number} accuracy - Точність (0.0 - 1.0)
*/
setShootingAccuracy(accuracy) {
this.shooting.accuracy = Math.max(0.0, Math.min(1.0, accuracy));
}
/**
* Отримання інформації про стрільбу
* @returns {Object} - Інформація про стрільбу
*/
getShootingInfo() {
return {
canShoot: this.shooting.canShoot,
bulletsCount: this.shooting.bullets.length,
cooldown: this.shooting.shootCooldown,
accuracy: this.shooting.accuracy,
lastShotTime: this.shooting.lastShotTime
};
}
}
Оновлення методу render
Оновіть метод render
для малювання куль:
javascript
/**
* Малювання ворога на екрані
* @param {CanvasRenderingContext2D} ctx - Контекст для малювання
*/
render(ctx) {
// якщо ворог мертвий, не малюємо
if (!this.isAlive) return;
// зберігаємо поточний стан контексту (колір, стиль тощо)
ctx.save();
// викликаємо метод render батьківського класу
super.render(ctx);
// малюємо червоний хрестик
this.drawEnemyMark(ctx);
// малюємо індикатор стану AI
this.drawAIStateIndicator(ctx);
// малюємо індикатор стрільби (якщо не може стріляти)
if (!this.shooting.canShoot) {
this.drawShootCooldownIndicator(ctx);
}
// відновлюємо стан контексту (повертаємо попередні налаштування)
ctx.restore();
// малюємо кулі окремо
this.renderBullets(ctx);
}
/**
* Малювання індикатора затримки стрільби
* @param {CanvasRenderingContext2D} ctx - Контекст для малювання
*/
drawShootCooldownIndicator(ctx) {
// темно-червоний колір для індикатора затримки
ctx.fillStyle = '#c0392b';
// розмір індикатора
const indicatorSize = 3;
// розміщуємо індикатор в лівому верхньому куті танка
const indicatorX = this.x + 2;
const indicatorY = this.y + 2;
// малюємо маленький квадрат
ctx.fillRect(indicatorX, indicatorY, indicatorSize, indicatorSize);
}
Що додано до класу Enemy?
Нові властивості:
shooting.canShoot
- чи може ворог стрілятиshooting.lastShotTime
- час останнього пострілуshooting.shootCooldown
- затримка між пострілами (2 секунди)shooting.bullets
- масив активних кульshooting.accuracy
- точність стрільби (80%)
Нові методи:
updateShooting()
- оновлення системи стрільбиshoot()
- створення нової куліcalculateTargetDirection()
- розрахунок напрямку до ціліaddShootingInaccuracy()
- додавання неточностіupdateBullets()
- оновлення всіх кульrenderBullets()
- малювання кульgetShootPosition()
- отримання позиції для стрільбиsetShootingAccuracy()
- налаштування точностіdrawShootCooldownIndicator()
- індикатор затримки
Система стрільби ворога
Умови стрільби:
- Режим атаки (
ai.state === 'attack'
) - Можна стріляти (
canShoot === true
) - Є ціль (
ai.chase.target
)
Особливості:
- Автоматичне націлювання на гравця
- Система точності (80% за замовчуванням)
- Затримка 2 секунди між пострілами
- Швидкість кулі 4 пікселі за кадр
Система точності
Розрахунок напрямку:
- Визначення основного напрямку до гравця
- Додавання неточності згідно з
accuracy
- Випадковий напрямок при неточній стрільбі
Налаштування точності:
- 1.0 - 100% точність
- 0.8 - 80% точність (за замовчуванням)
- 0.5 - 50% точність
- 0.0 - повністю випадкова стрільба
Візуальні індикатори
Індикатор затримки стрільби:
- Темно-червоний квадрат в лівому верхньому куті
- Показується коли не можна стріляти
Розташування індикаторів:
- Правий верхній - стан AI
- Лівий верхній - затримка стрільби
Використання
javascript
// Створення ворога
const enemy = new Enemy({
x: 300,
y: 200,
color: '#e74c3c',
size: 32
});
// Встановлення цілі для переслідування
enemy.setTarget(player);
// Налаштування стрільби
enemy.setShootCooldown(1500); // 1.5 секунди між пострілами
enemy.setShootingAccuracy(0.9); // 90% точність
// Оновлення в ігровому циклі
enemy.update(deltaTime);
// Отримання інформації про стрільбу
const shootingInfo = enemy.getShootingInfo();
console.log('Куль ворога:', shootingInfo.bulletsCount);
// Отримання всіх куль для перевірки колізій
const bullets = enemy.getBullets();
Результат
Після додавання цього функціоналу у вас буде:
- ✅ Автоматична стрільба ворога
- ✅ Націлювання на гравця
- ✅ Система точності стрільби
- ✅ Візуальні індикатори стану
- ✅ Готовність для системи колізій
Що далі?
У наступному підрозділі ми створимо систему колізій для перевірки зіткнень між кулями та танками.