Действия игры, которые требуют нескольких кадров для завершения

20

Я никогда прежде не занимался программированием игр, довольно простой вопрос.

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

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            remove all complete rows
            move rows down so there are no gaps
            if we can spawn a new block
                spawn a new current block
            else
                game over

Пока что все в игре происходит мгновенно - вещи порождаются мгновенно, строки удаляются мгновенно и т. Д. Но что, если я не хочу, чтобы что-то происходило мгновенно (то есть, анимация)?

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            ?? animate complete rows disappearing (somehow, wait over multiple frames until the animation is done)
            ?? animate rows moving downwards (and again, wait over multiple frames)
            if we can spawn a new block
                spawn a new current block
            else
                game over

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

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


источник

Ответы:

11

Традиционным решением этого является конечный автомат, который предлагается в нескольких комментариях.

Я ненавижу конечные автоматы.

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

Если вы можете использовать язык, который поддерживает их, я рекомендую сопрограммы. Они позволяют вам писать код, который выглядит следующим образом:

function TetrisPieceExplosion()
  for brightness = 0, 1, 0.2 do
    SetExplosionBrightness(brightness)
    coroutine.yield()
  end

  AllowNewBlockToFall()

  SpawnABunchOfParticles()

  RemoveBlockPhysics()

  for transparency = 0, 1, 0.5 do
    SetBlockTransparency(transparency)
    coroutine.yield()
  end

  RemoveBlockGraphics()
end

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

Насколько мне известно, эта функциональность нелегко доступна в C, C ++, C #, Objective C или Java. Это одна из главных причин, по которой я использую Lua для всей моей игровой логики :)

ZorbaTHut
источник
Вы также можете реализовать что-то подобное в других языках ООП. Представьте себе какой-то Actionкласс и очередь действий для выполнения. Когда действие завершено, удалите его из очереди и выполните следующее действие и т. Д. Более гибкий, чем конечный автомат.
bummzack
3
Это работает, но затем вы смотрите на получение из Action для каждого уникального действия. Это также предполагает, что ваш процесс хорошо вписывается в очередь - если вы хотите ветвления или циклы с неопределенными конечными условиями, решение очереди быстро выходит из строя. Это, конечно, чище, чем подход с использованием
конечного
Правда, но для примера Tetris этого должно быть достаточно :)
bummzack
Совместные рутины - но Lua как язык - отстой во многих других отношениях, я просто не могу его рекомендовать.
DeadMG
Пока вам нужно только выходить на верхнем уровне (а не из вызова вложенной функции), вы можете сделать то же самое в C #, но да, Lua сопрограммы качаются.
Великолепно
8

Я взял это из Game Coding Complete Майка МакШаффри.

Он говорит о «диспетчере процессов», который сводится к списку задач, которые необходимо выполнить. Например, процесс будет управлять анимацией рисования меча (AnimProcess), открытия двери или, в вашем случае, исчезновения ряда.

Процесс будет добавлен в список диспетчера процессов, который будет повторяться каждый кадр, и Update () вызывается для каждого. Так что очень похожие на сущности, но для действий. Там будет флаг уничтожения, чтобы удалить из списка, когда он закончил.

Другая приятная вещь о них - то, как они могут связать, имея указатель на следующий процесс. Таким образом, процесс анимации строки может состоять из:

  • AnimationProcess для исчезновения строки
  • MovementProcess для удаления фигур
  • ScoreProcess для добавления очков к баллу

(Поскольку процессы могут быть одноразовыми, условно там или там в течение X времени)

Если вы хотите больше подробностей, спросите об этом.

Коммунистическая утка
источник
3

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

DeadMG
источник
1

Вам всегда нужно знать разницу во времени между предыдущим и текущим кадром, тогда вам нужно сделать две вещи.

Решите, когда обновить вашу модель: например. в тетрисе, когда начинается удаление строки, вы больше не хотите, чтобы материал сталкивался со строкой, поэтому вы удаляете строку из «модели» вашего приложения.

Затем вам нужно обработать объект, который находится в переходном состоянии, в отдельный класс, который разрешает анимацию / событие в течение определенного периода времени. В примере с тетрисом строка будет постепенно исчезать (немного измените непрозрачность каждого кадра). После того, как непрозрачность станет равной 0, вы переместите все блоки сверху строки на один вниз.

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

Рой Т.
источник
Кроме того, в некоторых случаях тяжелые вычисления могут превышать допустимое время для одного временного шага физики (например, обнаружение столкновений и планирование пути). В этих случаях вы можете выпрыгнуть из расчетов, когда отведенное время было использовано, и продолжить вычисление в следующем кадре.
Nailer
0

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

Вы делаете разные вещи в зависимости от состояния. Например, во время «перемещения фигуры» вы игнорируете ввод игрока и вместо этого анимируете фигуру из ее текущей строки в следующую. Что-то вроде этого:

if state == ACCEPTING_INPUT:
    if player presses any key:
        handle input
    row_timer = row_timer - time_since_last_frame
    if row_timer < 0:
        state = MOVING_PIECE_DOWN
elif state == MOVING_PIECE_DOWN:
    piece.y = piece.y + piece.speed*time_since_last_frame
    if piece.y >= target_piece_y:
        piece.y = target_piece_y
        state = ACCEPTING_INPUT
ggambett
источник