Статьи

Понимание игрового цикла — Basix

Почти каждую игру можно представить как имеющую одну основную функцию, которая содержит всю игровую логику и которая запускается либо тогда, когда пользователь что-то делает, либо по истечении определенного времени. Этот цикл запуска одной и той же основной функции снова и снова называется игровым циклом , и его важно понять для любого вида разработки игр.

Вы, вероятно, играли в настольную игру Chutes and Ladders (или Snakes and Ladders, как мы ее называем в Великобритании).

(Фото предоставлено incurable_hippie на Flickr)

Каждый игрок бросает кубик (или вращает спиннер) и продвигается вперед на указанное количество клеток. Квадрат, на котором они приземляются, может заставить их скользить назад или подниматься вперед на несколько мест. Игрок выигрывает игру, когда добирается до последнего квадрата.

Итак, на изображении выше, посадка на Квадрат 6 заставляет вас подняться на четыре квадрата до Квадрата 10; посадка на Квадрат 19 заставляет вас сдвинуться назад на 15 квадратов к Квадрату 4; и приземление на Квадрат 64 означает, что вы выиграли игру.

Теперь предположим, что вы играете в одиночку, чтобы попрактиковаться. Вы делаете то же самое, что и выше, снова и снова, пока не дойдете до площади 64. Как бы вы это представили в коде?

Вы, вероятно, начали бы с создания массива для хранения значений квадратов. Большинство элементов будет содержать ноль, но некоторые будут содержать либо положительное число (указывающее лестницу), либо отрицательное число:

Примечание: это псевдокод, а не AS3

01
02
03
04
05
06
07
08
09
10
11
var specialSquares:Array = [];
specialSquares[0] = 0;
specialSquares[1] = 0;
 
 
specialSquares[6] = +4;
 
 
specialSquares[19] = -15;

…и так далее. Тогда у вас будет функция для перемещения игрока в зависимости от числа на кубике:

1
2
3
4
5
function movePlayer(squares:Number) {
    newSquare = currentSquare + squares;
    newSquare += specialSquares[newSquare];
    currentSquare = newSquare;
}

Затем вы можете поместить это в большую функцию, представляющую весь ход:

1
2
3
4
5
6
7
function takeATurn() {
    diceNumber = random(1, 6);
    movePlayer(diceNumber);
    if (currentSquare == 64) {
        //player has won!
    }
}

Эта функция takeATurn() инкапсулирует основную игровую логику для одиночных лотков и лестниц. Но как нам заставить эту функцию работать? Есть несколько вариантов:


Мы могли бы разместить кнопку на экране и вызывать takeATurn() каждом нажатии. Если вы когда-нибудь играли в Facebook Scrabble или «Слова с друзьями», вы видели эту опцию в действии.


Мы можем заставить takeATurn() запускаться каждые шестьдесят секунд.

На самом деле, этот вариант немного сложнее, чем может показаться. На практике мы никогда не видим игр без какого- либо участия игрока; без взаимодействия это не может считаться игрой. Но все же в некоторых играх присутствует элемент «календарного времени». Рассмотрим FarmVille: вы, игрок, сажаете свои посевы, и затем каждые несколько минут (или часов) они развиваются немного дальше, от семян до побегов и фруктов. И в некоторых режимах Цивилизации вам предоставляется определенное количество времени (скажем, пять минут), чтобы сделать все свои ходы за один «ход»; по истечении этих пяти минут запускается основная логическая функция игры.


Некоторые игры используют смесь этих двух вариантов: например, у Mafia Wars есть ресурс под названием «энергия», который пополняет одну единицу каждые пять минут; вы выполняете действия в игре, используя этот ресурс, поэтому основная логическая функция игры по-прежнему вызывается действием пользователя, оно просто ограничено по времени.

Этот шаблон является общим для большинства игр: один фрагмент кода, содержащий основную игровую логику, запускается повторно. Мы называем это игровой цикл .

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

Таким образом, в Цивилизации, галочка каждые пять минут. В «Словах с друзьями» твой ход вызывает галочку. Другими словами, игровой цикл запускается один раз за такт.


Super Mario Bros не вписывается ни в одну из этих категорий. Марио реагирует на вход игрока … и все же происходит все что угодно, без необходимости что-либо делать (Гумбас ходит, таймер начинает отсчет, объекты падают). Есть ли две игровые петли?

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

В Civilization у вас есть пять минут для ввода всего, что вы хотите сделать в текущем ходу, до того, как игра «заработает» и снова запустит игровой цикл, основываясь на всех ваших данных. Так что, если вы в 23-м ходу скажете, что хотите, чтобы ваши воины напали на оленя, то в 24-м ходу все получают оленину на ужин.

То же самое и с Марио. Если вы нажмете кнопку «Прыжок» во время одного тика, то на следующей итерации игрового цикла Марио начнет прыгать.

Обратите внимание, что вам не нужно рассчитывать время нажатия клавиши «Jump», как только сработает основная логическая функция игры — все ваши действия в течение периода тиков записываются и используются во время следующей итерации игрового цикла.


Вся игровая логика обрабатывается в игровом цикле. Но в игре есть нечто большее, чем логика, основным примером является графика.

Рисование графики на экране — тяжелая работа для компьютера. Предположим, у вас есть экшн-игра с отметкой 1/60 секунды; это должно заставить игру чувствовать, что она реагирует плавно на элементы управления игрока. Но что произойдет, если компьютер игрока будет слишком медленным, чтобы запустить весь код игровой логики (реагировать на ввод, моделировать гравитацию, запускать процедуры ИИ) и рисовать все на экране в течение 1/60 секунды?

В этом сценарии мы можем использовать два отдельных цикла: игровой цикл и цикл рисования . Затем мы могли бы запустить цикл отрисовки на гораздо более низкой частоте, чем цикл игры; скажем, мы обновляем экран вдвое реже, то есть каждую 1/30 секунды.

Количество вычислительной мощности, необходимой для игры, может варьироваться от уровня к уровню. Рассмотрим перестрелку: на первых нескольких уровнях на экране будет очень мало кораблей, чтобы облегчить игру игроку, в то время как на последних уровнях могут быть десятки вражеских кораблей и сотни пуль, летящих вокруг одной сцены в один раз. Игровой цикл должен выяснить, как должны перемещаться все эти объекты, а цикл прорисовки должен отображать каждый из них, поэтому, возможно, можно было запускать как игровой цикл, так и цикл прорисовки каждую 1/60 секунды на начало игры, к концу что-то должно дать.

Обычно проще замедлить цикл прорисовки, чем игровой цикл, если вам придется выбирать. Регулировка длины такта игрового цикла означает корректировку всего в вашей игре, основанного на времени; если Марио работает со скоростью 20 пикселей в секунду, и вы разрабатываете игру с длиной такта 1/60 секунды, то вы должны перемещать его на 1/3 пикселя за такт. Если вы настраиваете игровой цикл так, чтобы длина такта составляла 1/30 секунды, то вам нужно настроить его на 2/3 пикселя на такт — и даже это простое изменение по сравнению с ведением физических расчетов и процедур ИИ. последовательны.

По этой причине игры часто стремятся поддерживать постоянство такта игрового цикла и замедляют цикл дро, если требуется больше энергии. Если вы когда-либо включали счетчик FPS (сокращение от «Кадров в секунду» — количество циклов прорисовки в секунду) в шутере от первого лица, вы увидите, что он меняется в зависимости от того, сколько на экране ; Частота обновления цикла прорисовки настраивается автоматически. Игра может выглядеть странно — как потоковое видео в реальном времени при медленном интернет-соединении — но если она не запущена на компьютере с гораздо меньшей мощностью, чем думали разработчики игр, все объекты в игровом мире будут продолжать работать двигаться и взаимодействовать на правильных скоростях.

Для отличной статьи, объясняющей, как Flash справляется с этим, ознакомьтесь с постом Шона Кристманна на «Elastic Racetrack» .