Смотрите вступительный пост для контекста.
Петля
В общем, разработка игры начинается с игрового цикла. Если вы приехали из делового мира разработки программного обеспечения, это будет странно. Вы не полагаетесь на события. Фил Хаак однажды спросил меня «почему петля?», На что я ответил «э-э…». Тем не менее, гораздо лучший ответ был бы на стеке потока .
Итак, мы должны использовать мастер-цикл. Если наша среда выполнения — браузер, как настроить этот цикл? Существует относительно новый API под названием, requestAnimationFrame
и это то, что большинство людей рекомендуют. Проверьте это для деталей:
(Я помню, что читал что-то негативное об API, но я не смог найти это снова.)
Я использовал requestAnimationFrame
прокладку, упомянутую в посте ирландского Пола выше. Подкладка необходима только для старых браузеров, в которых не реализован API. Кстати, мы называем каждую итерацию цикла «кадром» из-за аналогии с традиционной анимацией.
Реализация
Теперь, когда мы убедились, что requestAnimationFrame
присутствует, мы можем перейти к нашему игровому циклу. Вот загрузочный код моей игры (ну, ранняя версия):
var canvas, // the visible canvas element surface, // the 2d context of `canvas` currentScreen; // the currently rendered screen for the game function beginLoop() { var frameId = 0; var lastFrame = Date.now(); function loop() { var thisFrame = Date.now(); var elapsed = thisFrame - lastFrame; frameId = window.requestAnimationFrame(loop); currentScreen.update(elapsed); currentScreen.draw(surface); lastFrame = thisFrame; } loop(); } canvas = document.querySelector('canvas#board'); canvas.setAttribute('width', 800); canvas.setAttribute('height', 600); surface = canvas.getContext('2d'); currentScreen = startScreen; beginLoop();
Суть этой loop
функции. Имеет следующий шаг:
- захватить текущее время
- рассчитать время, прошедшее с последнего кадра
- выполнить логику игры для фрейма (это вызовы update и draw)
- запросить следующий вызов
loop
использованияrequestAnimationFrame
- записать текущее время этого кадра для расчетов в следующем
NB Этот код еще не используется frameId
. Идея в том, что этот дескриптор может быть использован для остановки цикла.
beginLoop
Функция есть только обеспечить закрытие для некоторых переменных , используемых для отслеживания состояния контура. Он запускает цикл с его начальным вызовом loop
.
Большая загадка внутри loop
— это currentScreen
объект. Здесь я думал о будущем (что может быть опасно). Я знаю, что в моей игре будет как минимум два «экрана», возможно, больше:
- экран меню Пуск
- основной экран игры (где происходит действие)
Я хотел, чтобы логика цикла работала с обоими (а также с любыми будущими экранами). Я ожидаю, что экранные объекты будут иметь два метода:
update
занимает время, прошедшее с последнего кадра, и отвечает за обновление состояния игры.draw
берет контекст рисования (с холста) и отвечает за рендеринг текущего состояния игры.
Вы также увидите, что я беру элемент canvas и фиксирую его контекст рисования. Если вы не знакомы с API-интерфейсами Canvas, я рекомендую вам начать здесь .
Почему два разных метода игровой логики?
Хранение update
и draw
функции разделены важно. Когда кадры становятся дорогостоящими для вычисления, игра может реагировать с задержкой или недетерминированным поведением. Чтобы избежать этого, вы можете захотеть, чтобы ваша игра пропускала некоторую логику во время определенной итерации цикла. Однако очень вероятно, что вы не захотите отбрасывать звонки update
. Если вы пропускаете рендеринг пары кадров, это не нужно , но если пропустить расчет местоположения снаряда, он может таинственным образом «пройти» через свою цель. Это станет более актуальным для нас, в частности, потому что я хотел бы, чтобы все игроки контролировали скорость игры (общая черта многих игр защиты башни).
Прямо сейчас update
и draw
всегда вызывается для каждой итерации цикла, поэтому различие является академическим в этом контексте. Мы могли бы, однако, рассчитать нашу частоту кадров loop
и иногда пропускать активацию, draw
если скорость замедлилась.
Теперь у нас достаточно места, чтобы начать работу над экраном меню «Пуск».