Pull to refresh

Comments 14

...обработчик будет добавлять эту клавишу в массив this.game.keys. Условие && this.game.keys.indexOf(e.key) === -1 запретит добавлять в массив клавишу, если та уже присутствует в нем. Обработчик события отпускания (keyup) — наоборот, сначала проверяет наличие клавиши в массиве и при ее наличии там — удаляет с помощью метода splice.

А не лучше ли использовать Set?

Неплохая идея.

Автор видео кстати не объяснил, почему мы здесь вообще массив используем. Нельзя ли просто по нажатию/отпусканию клавиши менять скорость игрока ?! Но я так понял это какая-то общепринятая практика.

Чтобы привязаться к игровому времени. На скорости это не так очевидно, а вот со стрельбой становится понятнее, ведь мы можем наспамить 100500 событий выстрела за условные 16мс.

Привет)

Игра из статьи с морским коньком не работает на мобилках - клавиши вверх и вниз к тачам не привязаны)

Можете поделиться кусочком кода. Доработаю)

Делал похожую игрушку года 2 назад, базовый вариант реализовал. Кажется когда захотел добавить каких-то фич, не смог тогда решить проблему с большим количеством игровых объектов и расчетом их коллизий. У меня было много циклов, наверное с событиями мало работал. Какой-то архитектурный момент не проработал.

Описали бы в статье. Народ подсказал бы)

Возможно будет более практичным использовать Intersection Observer для Collision Detection.

Возможно. Спасибо за идею!

необходимо чтобы игра могла "адаптироваться" к Вашей частоте смены кадров

Но у вас это не реализовано, есть только расчет интервалов, что никак к адаптации не относится. Или вы что-то упустили?

Ну как я понял из оригинального видео - эти интервалы (deltaTime) могут отличаться на разных машинах. Поэтому мы и передаем его в качестве параметра в методы update(). По идее это и есть адаптация. Я думаю в любом случае это можно проверить - адаптируется игра или нет.

Еще в данной статье не было сказано про переменную которая отвечает за игровое время, но в полной версии игры она есть (speed в классе Game). Возможно вы ее имели ввиду. Во второй части напишу про нее.

Я имею ввиду, что возьмем (полную версию) ваш класс Projectile (опускаю неважные свойства и методы):

Показать код
class Projectile {
    constructor() {
        this.speed = 10;
    }
    update() {
        this.x += this.speed;
    }
}

Как видите, Projectile движется со скорость 10 пикселей в кадр. Но сколько кадров в секунду зависит от частоты обновления экрана (display refresh rate) написано на MDN. Если у вас экран 60 Гц, то update выполняется 60 раз, значит за секунду Projectile переместится на 60*10=600 пикселей. Если у вас экран 120 Гц, то update выполняется (возможно) 120 раз, значит за секунду Projectile переместится на 120*10=1200 пикселей. Если у вас условный смартфон, где оптимизации расхода заряда батареи могут дать 30 Гц, то update выполняется 30 раз, значит за секунду Projectile переместится на 30*10=300 пикселей. Поэтому скорость и другие величины не должны зависеть от кадров в вашей игре. Правильно же привязываться ко времени (как и в жизни), т.е. скорость в пикселях в секунду, а время в секундах. Для этого deltaTime нужно представлять как коэффициент времени, в секундах. Т.е. вы его должны передавать во ВСЕ update-функции, где во ВСЕХ выражениях изменения значений свойств, приводящих к анимации, использовать как множитель.

Передаем коэффициент времени везде и исправляем константы скорости и времени:

Показать код
function animate() {
    // ...
    // rAF calls are paused when running in background tabs
    if (deltaTime > 100) deltaTime = 100; // minimum 10 fps
    // ...
    game.update(deltaTime / 1000); // dt in seconds
    // ...
}
class Game {
    constructor() {
        this.enemyInterval = 2; // in seconds
        this.ammoInterval = 0.350;
        this.timeLimit = 30;
        this.speed = 60; // pixels per second
    }
    update(deltaTime) {
        this.background.update(deltaTime);
        this.background.layer4.update(deltaTime);
        this.player.update(deltaTime);
        this.particles.forEach(particle => particle.update(deltaTime));
        this.explosions.forEach(explosion => explosion.update(deltaTime));
        this.enemies.forEach(enemy => enemy.update(deltaTime));
    }
}
class Background {
    update(deltaTime) {
        this.layers.forEach(layer => layer.update(deltaTime));
    }
}
class Layer {
    update(deltaTime) {
        this.x -= this.game.speed * this.speedModifier * deltaTime;
    }
}
class UI {
    draw() {
        const formattedTime = (this.game.gameTime).toFixed(1);
    }
}
class Player {
    constructor() {
        this.maxSpeed = 480;
        this.powerUpLimit = 10;
    }
    update(deltaTime) {
        this.y += this.speedY * deltaTime;
        this.projectiles.forEach(projectile => projectile.update(deltaTime));
    }
}
class Projectile {
    constructor() {
        this.speed = 600;
    }
    update(deltaTime) {
        this.x += this.speed * deltaTime;
    }
}
class Particle {
    constructor(game, x, y) {
        this.speedX = Math.random() * 360 - 180;
        this.speedY = Math.random() * -900;
        this.gravity = 1800;
        this.va = Math.random() * 12 - 6;
    }
    update(deltaTime) {
        this.angle += this.va * deltaTime;
        this.speedY += this.gravity * deltaTime;
        this.x -= (this.speedX + this.game.speed) * deltaTime;
        this.y += this.speedY * deltaTime;
    }
}
class Explosion {
    constructor() {
        this.interval = 1 / this.fps;
    }
    update(deltaTime) {
        this.x -= this.game.speed * deltaTime;
    }
}
class Enemy {
    constructor() {
        this.speedX = Math.random() * -90 - 270;
    }
    update(deltaTime) {
        this.x += (this.speedX - this.game.speed) * deltaTime;
    }
}
class Drone extends Enemy {
    constructor() {
        this.speedX = Math.random() * -252 - 30;
    }
}
class HiveWhale extends Enemy {
    constructor() {
        this.speedX = Math.random() * -72 - 12;
    }
}

Тоже самое касается спрайтовых анимаций, в классах Player, Explosion, Enemy. Но там сами попробуйте, как их привязать ко времени, а не кадрам. Думаю смысл в общем понятен.

Да, здесь соглашусь. Т.к. даже чисто математически размерности величин не равны

update() {
        this.x += this.speed;
}

получается мы к координате прибавляем скорость, что не есть правильно. Нужно speed домножить на время.

Спасибо за пояснения!

Видела объяснение этой или почти этой игры в ютуб и захотела сделать. Но там все только видео и не было образца кода. По видео, как-то не удобно код писать. Да и на английском всё. Очень обрадовалась, когда наткнулась на это описание. Спасибо вам за проделанную работу. Делала по вашей инструкции, но попадаются непонятные вещи для новичка. Бывает пропущено, что куда вставлять. Прямоугольник остался желтым почему-то, враги не появились. Потом решила скачать ваш готовый проект с гитхаба, а он а некоторых участках кода отличается, от того что здесь написано. Было бы здорово, усовершенствовать объяснение, сделать более подробным и код хотелось бы, чтобы был похож на тот, который работает. Если будет время, конечно, а то начинающему трудно допетрить до некоторых вещей.

Sign up to leave a comment.

Articles