| 
Войти

Вход

Регистрация

Я не помню пароль

Войти через Google
Порог горячего 13
  • Kukabara
    Kukabara

    Он ещё и из железа варит!))

    +1
  • Kukabara
    Kukabara

    Я б тогда вообще вам уже не писала, видимо::biggrin::

    +1
  • Kukabara
    Kukabara

    Для меня как для нешарящего это уже ювелирный шаг))

    +1
Правила сайта
Пользовательское соглашение
О ПД
Принципы самоуправления
Нашёл ошибку?
©2026 Varius Soft

VariusSoft
VariusSoft Серия: Пишем танчики на JavaScript Сообщество: GameDev Опубликовано 3 часа назад
  • [моё]
  • GameDev
  • Программирование
  • Длиннопост
  • Battle City

«Танчики» на HTML5

Уничтожение врагов

Что ж. Намеченный план был выполнен полностью.

Было три итерации, сначала сделал так, чтоб танки могли крошить всех (в том числе и враги уничтожали друг друга).

Потом я запретил им убивать друг друга, а на третьем этапе ещё добавил взаимоуничтожение снарядов игрока и врага при попадании.

Давайте посмотрим, что там по коду интересного и не очень.

Из нового: появился файлик effects.js, который отвечает за всякие красивости, из самого заметного: взрывы)

    const EFFECT_CONFIG = {
    [EffectType.EXPLOSION_SMALL]: {
        maxFrames: 3,
        frameTime: 3,  // 3 игровых кадра на кадр анимации (100ms при 30 FPS)
        size: BULLET_SIZE * 2,
        colors: ['#FFFF00', '#FFA500', '#FF4500']  // Жёлтый → оранжевый → красный
    },
    [EffectType.EXPLOSION_BIG]: {
        maxFrames: 5,
        frameTime: 3,
        size: TANK_SIZE * 1.5,
        colors: ['#FFFFFF', '#FFFF00', '#FFA500', '#FF4500', '#8B0000']  // Белый → жёлтый → оранжевый → красный → тёмно-красный
    },
    [EffectType.SPAWN]: {
        maxFrames: 4,
        frameTime: 4,
        size: TANK_SIZE,
        colors: ['#FFFFFF', '#888888', '#FFFFFF', '#888888']  // Мигание
    }
};

И сам класс эффекта:

    export class Effect {
    /**
     * @param {number} x - Логическая координата X (центр эффекта)
     * @param {number} y - Логическая координата Y (центр эффекта)
     * @param {string} type - Тип эффекта (EffectType)
     */
    constructor(x, y, type = EffectType.EXPLOSION_SMALL) {
        this.x = x;
        this.y = y;
        this.type = type;

        // Получаем конфигурацию для этого типа эффекта
        const config = EFFECT_CONFIG[type] || EFFECT_CONFIG[EffectType.EXPLOSION_SMALL];

        this.maxFrames = config.maxFrames;
        this.frameTime = config.frameTime;
        this.size = config.size;
        this.colors = config.colors;

        // Состояние анимации
        this.frame = 0;
        this.age = 0;
    }

    /**
     * Обновление состояния эффекта
     * @param {number} dt - Время кадра (не используется при fixed timestep)
     */
    update(dt) {
        this.age++;

        // Переход к следующему кадру анимации
        if (this.age >= this.frameTime) {
            this.frame++;
            this.age = 0;
        }
    }

    /**
     * Проверка завершения анимации
     * @returns {boolean} true если анимация закончилась
     */
    isDone() {
        return this.frame >= this.maxFrames;
    }

    /**
     * Отрисовка эффекта
     */
    render() {
        if (this.isDone()) return;

        const ctx = foregroundCtx;

        // Конвертация логических координат в физические
        const px = GAME_FIELD_X + this.x * GAME_SCALE;
        const py = GAME_FIELD_Y + this.y * GAME_SCALE;

        // Размер эффекта меняется в зависимости от кадра
        const progress = this.frame / (this.maxFrames - 1);
        const currentSize = this.size * GAME_SCALE * (0.5 + progress * 0.5);

        // Цвет из массива цветов
        const colorIndex = Math.min(this.frame, this.colors.length - 1);
        const color = this.colors[colorIndex];

        // Рисуем взрыв как круг
        ctx.beginPath();
        ctx.arc(px, py, currentSize / 2, 0, Math.PI * 2);
        ctx.fillStyle = color;
        ctx.fill();

        // Внутренний круг (ядро взрыва)
        if (this.type === EffectType.EXPLOSION_BIG && this.frame < this.maxFrames - 1) {
            ctx.beginPath();
            ctx.arc(px, py, currentSize / 4, 0, Math.PI * 2);
            ctx.fillStyle = '#FFFFFF';
            ctx.fill();
        }
    }
}

Часть этого кода перепишется, когда я переведу игру на спрайты, оставив только смену кадров и "возраст", а все остальные визуальности удалив.

В файле коллизий появилась ещё пара новых методов:

    export function checkBulletTankCollision(bullet, tanks) {
    const bulletBox = {
        x: bullet.x,
        y: bullet.y,
        width: BULLET_SIZE,
        height: BULLET_SIZE
    };

    // Определяем, является ли владелец пули врагом
    const ownerIsEnemy = bullet.owner && bullet.owner.type !== TankType.PLAYER;

    for (const tank of tanks) {
        // Пропускаем владельца пули и уничтоженных
        if (tank === bullet.owner || tank.destroyed) continue;

        // Вражеские пули пролетают сквозь других врагов (как в оригинале)
        if (ownerIsEnemy && tank.type !== TankType.PLAYER) continue;

        const tankBox = tank.getBounds();
        if (checkAABBCollision(bulletBox, tankBox)) {
            return tank; // Попадание в танк
        }
    }

    return null;
}

export function checkBulletBulletCollisions(bullets) {
    const collisions = [];

    for (let i = 0; i < bullets.length; i++) {
        const bullet1 = bullets[i];
        if (!bullet1.active) continue;

        const box1 = {
            x: bullet1.x,
            y: bullet1.y,
            width: BULLET_SIZE,
            height: BULLET_SIZE
        };

        // Определяем, принадлежит ли пуля игроку
        const isPlayer1 = bullet1.owner && bullet1.owner.type === TankType.PLAYER;

        for (let j = i + 1; j < bullets.length; j++) {
            const bullet2 = bullets[j];
            if (!bullet2.active) continue;

            const isPlayer2 = bullet2.owner && bullet2.owner.type === TankType.PLAYER;

            // Сталкиваются только пули разных команд (игрок vs враг)
            if (isPlayer1 === isPlayer2) continue;

            const box2 = {
                x: bullet2.x,
                y: bullet2.y,
                width: BULLET_SIZE,
                height: BULLET_SIZE
            };

            if (checkAABBCollision(box1, box2)) {
                collisions.push({ bullet1, bullet2 });
            }
        }
    }

    return collisions;
}

В целом, тут можно было бы выбрать и иные решения, коллизия и методы их проверки могли бы стать более универсальными, как это сделано, скажем, в Unity. Навесить на все объекты box collider'ы и просто проверять их столкновения без отдельных методов (проверить ТанкТанк, проверить ТанкПуля, проверить ПуляПуля). В таком случае логичнее было бы добавить возможность делать коллизии триггерными, для отработки логики уже внутри самих объектов. Но в таком случае для такой простой игры значительно усложнилась бы архитектура. Поэтому в жертву универсальности я пошёл по более прямолинейному пути.

План на следующий этап:

Ну и, наверное, если с базой быстро разберусь, добавлю автоспавн врагов, как в оригинальной игре.
Ну и, наверное, если с базой быстро разберусь, добавлю автоспавн врагов, как в оригинальной игре.

Всем хороших игр!

Читать дальше...
7
+7 / -0
0
28
ТГ ВК