Ігрові дії, які потребують декількох кадрів


20

Я ніколи насправді не робив багато ігрових програмувань раніше, досить просте запитання.

Уявіть, що я будую гру Tetris, головна петля виглядає приблизно так.

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

Традиційним рішенням цього рішення є кінцева державна машина, яку пропонують у кількох коментарях.

Я ненавиджу кінцеві державні машини.

Звичайно, вони прості, їх підтримують усіма мовами, але з ними такий дивовижний біль. Кожна маніпуляція потребує тонни коду копіювання та вставки багпрона, а перетворення ефекту невеликими способами може стати величезною зміною коду.

Якщо ви можете використовувати мову, яка їх підтримує, я рекомендую супроводи. Вони дозволяють вам написати код, який виглядає так:

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 у всій своїй логіці гри :)


Ви також можете реалізувати щось у цьому напрямку в інших мовах OOP. Уявіть собі якийсь Actionклас та чергу дій, які потрібно виконати. Коли дія завершена, вийміть її з черги та виконайте наступну дію тощо. Шлях гнучкіший, ніж стан-машина.
bummzack

3
Це працює, але тоді ви дивитесь на похідне від Action для кожної унікальної дії. Він також передбачає, що ваш процес чудово вписується в чергу - якщо ви хочете розгалуження або циклі з невизначеними кінцевими умовами, рішення черги швидко виходить з ладу. Це, звичайно, чистіше, ніж підхід до державної машини, але я думаю, що спроби все-таки
козирують

Правда, але для прикладу тетрісу цього має бути достатньо :)
bummzack

Ко-рутини рок- але Луа як мова всмоктується так багато інших способів, я просто не можу її рекомендувати.
DeadMG

Поки вам потрібно лише поступатися на найвищому рівні (а не з вкладеного виклику функції), ви можете виконати те ж саме в C #, але так, Lou coroutines rock.
чудовий

8

Я беру це з «Кодування гри», завершеного Майком Макшафрі.

Він розповідає про "менеджера процесів", який зводиться до переліку завдань, які необхідно виконати. Наприклад, процес контролює анімацію для малювання меча (AnimProcess) або відкриття дверей, або у вашому випадку змушує рядок зникати.

Процес буде доданий до списку менеджера процесів, який би повторював кожен кадр і оновлення (), викликане на кожному. Тож дуже подібні утворення, але для дій. Прапор вбивства буде видалено зі списку після його завершення.

Інша акуратна річ у них - це те, як вони можуть зв’язатися, маючи вказівник на наступний процес. Таким чином, ваш процес анімаційного рядка може фактично складатися з:

  • Анімаційний процес для рядка зникає
  • A MovementProcess для видалення шматочків
  • ScoreProcess для додавання балів до оцінки

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

Якщо ви хочете більше деталей, запитайте.


3

Можна використовувати чергу пріоритетів дій. Ви натискаєте на дію, і час. Кожен кадр, ви отримуєте час, і ви спливаєте всі дії, які мають час, визначений як до цього часу, і виконайте їх. Бонус: Підхід чудово паралелізується, і ви можете реально реалізувати майже всю логіку гри таким чином.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.