Функция >requestAnimationFrame
, которую мы видели в главе 13, предоставляет хороший способ анимировать игру. Но интерфейс её примитивен – его использование заставляет нас отслеживать момент времени, в который она была вызвана в прошлый раз, и вызывать >requestAnimationFrame
каждый раз после каждого кадра.
Давайте определим вспомогательную функцию, оборачивающую эти скучные операции в удобный интерфейс, и позволяющую нам просто вызвать >runAnimation
, задавая ей функцию, которая принимает разницу во времени и рисует один кадр. Когда функция >frame
возвращает >false
, анимация останавливается.
>function runAnimation(frameFunc) {
> var lastTime = null;
> function frame(time) {
> var stop = false;
> if (lastTime != null) {
> var timeStep = Math.min(time - lastTime, 100) / 1000;
> stop = frameFunc(timeStep) === false;
> }
> lastTime = time;
> if (!stop)
> requestAnimationFrame(frame);
> }
> requestAnimationFrame(frame);
>}
Я назначил максимальное время для кадра в 100 миллисекунд (1/10 секунды). Когда закладка или окно браузера спрятано, вызовы >requestAnimationFrame
прекратятся, пока закладка или окно не станут снова активны. В этом случае, разница между >lastTime
и текущим временем будет равна тому времени, в течение которого страница была спрятана. Продвигать игру на всё это время было бы глупо и затратно (вспомните разделение времени в методе >animate
).
Эта функция также преобразовывает временные отрезки в секунды, которыми проще оперировать, чем миллисекундами.
Функция >runLevel
принимает объект >Level
, конструктор для >display
, и, необязательным параметром – функцию. Она выводит уровень в >document.body
и позволяет пользователю играть на нём. Когда уровень закончен (победа или поражение), >runLevel
очищает экран, останавливает анимацию, а если задана функция >andThen
, вызывает её со статусом уровня.
>var arrows = trackKeys(arrowCodes);
>function runLevel(level, Display, andThen) {
> var display = new Display(document.body, level);
> runAnimation(function(step) {
> level.animate(step, arrows);
> display.drawFrame(step);
> if (level.isFinished()) {
> display.clear();
> if (andThen)
> andThen(level.status);
> return false;
> }
> });
>}
Игра – это последовательность уровней. Когда игрок погибает, уровень начинается заново. Когда уровень закончен, мы переходим на следующий. Это можно выразить следующей функцией, принимающей массив планов уровней (массив строк) и конструктор >display
.
>function runGame(plans, Display) {
> function startLevel(n) {