Концептуально, как работает воспроизведение в игре?

Два оператора println обрабатываются двумя разными потоками. Результат снова зависит от того, в какой среде вы запускаете код. Например, я выполнил следующий код в IntelliJ и в командной строке по 5 раз.

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.print("OUT ");
            System.err.print("ERR ");
        }
    }
}

Это приводит к следующему выводу: Commandline

OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR

IntelliJ:

ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT 
OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR
ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT 
ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT
OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR 

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

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.print("OUT ");
            System.err.print("ERR ");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Выход в этом случае оказался

OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR
OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR
ERR OUT ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR ERR OUT ERR OUT 
ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR
OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR 

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

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

143
задан Steven Evers 29 November 2012 в 21:42
поделиться

11 ответов

Думаю, ваша первоначальная мысль была правильной. Чтобы создать воспроизведение, вы сохраняете весь ввод, полученный от пользователя (вместе с номером кадра, в котором он был получен), вместе с начальными начальными числами любых генераторов случайных чисел. Чтобы воспроизвести игру, вы сбрасываете свои ГПСЧ, используя сохраненные начальные числа, и передаете игровому движку ту же последовательность ввода (синхронизированную с номерами кадров). Поскольку многие игры обновляют состояние игры в зависимости от количества времени, которое проходит между кадрами, вам также может потребоваться сохранить длину каждого кадра.

61
ответ дан 23 November 2019 в 22:50
поделиться

Есть два основных метода:

  1. Сохранение событий (таких как действия игрока / ИИ) - как вы говорите.
  2. Сохранение состояния (полное состояние игры, например, расположение объектов в последовательные моменты).

Это зависит от того, чем вы хотите заниматься. Иногда лучше сохранять события, потому что для этого обычно требуется гораздо меньше памяти. С другой стороны, если вы хотите предоставить повторы, которые можно воспроизводить с разной скоростью и с разных начальных точек, лучше сохранять состояния. При сохранении состояний вы также можете решить, сохранять ли их после каждого события или, например, после каждого события. только 12 или 25 раз в секунду - это может уменьшить размер вашего повтора и упростить его перемотку назад / вперед.

Обратите внимание, что «состояние» не означает графическое состояние. Скорее что-то вроде позиций юнитов, состояния ресурсов и так далее. Такие вещи, как графика, системы частиц и так далее, обычно детерминированы и могут быть сохранены как «анимация X, время Y: Z».

Иногда повторы используются как схема антихема. Тогда лучше всего здесь хранить события.

18
ответ дан 23 November 2019 в 22:50
поделиться

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

Предполагая, что бомба взорвется в точке X, частицы, произведенные этим взрывом, всегда должны приводить к одному и тому же визуальному результату. Если вам нужна случайность, создайте набор случайных чисел, выберите начальное значение во время игры и сохраните это начальное значение в воспроизведении.

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

Сделайте все детерминированным, и все будет в порядке.

10
ответ дан 23 November 2019 в 22:50
поделиться

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

Если рассуждать чисто концептуально, то для воспроизведения временной шкалы событий вам нужны только действия пользователя. Программа будет реагировать точно так же, за исключением случая случайных переменных. В этом сценарии вы можете либо игнорировать случайность (имеет ли РЕАЛЬНОЕ значение, если эффекты выглядят ТОЧНО одинаково, или они могут быть случайно сгенерированы заново), либо хранить начальное значение и имитировать случайность.

4
ответ дан 23 November 2019 в 22:50
поделиться

Я полагаю, что в определенные моменты времени игра будет делать снимок состояния всего (ВСЕГО). Затем, когда происходит воспроизведение, простое использование линейной интерполяции может быть использовано для заполнения "дыр". По крайней мере, так я думаю, это будет сделано.

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

-1
ответ дан 23 November 2019 в 22:50
поделиться

Учитывая начальное состояние и последовательность действий с отметками времени , просто просмотрите последовательность, поскольку записанные действия должны произойти, есть повтор.

Чтобы случайные события повторялись точно так же, используйте начальные псевдослучайные числа и сохраните начальное число в файле воспроизведения.

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

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

10
ответ дан 23 November 2019 в 22:50
поделиться

NVidia PhysX (движок физического моделирования, который часто используется в играх) способен записывать полное состояние физической сцены во времени. Это включает в себя любые управляющие воздействия от игрового движка, что означает, что вам не нужно отслеживать семена случайных чисел, как предлагали другие. Если вы возьмете этот дамп сцены, вы сможете воспроизвести его в стороннем инструменте (предоставленном NVidia), что очень удобно для отслеживания проблем с вашими физическими моделями. Однако вы также можете использовать тот же поток физики для управления графическим движком, что позволит вам иметь нормальное управление камерой, поскольку записывается только физика, управляющая графикой. Во многих играх это включает эффекты частиц (PhysX включает некоторые очень сложные системы частиц). Что касается звука, я предполагаю, что он записывается дословно (как звуковой поток), но я не уверен.

7
ответ дан 23 November 2019 в 22:50
поделиться

Вложи мои два пенса.

В зависимости от того, что вы хотите, воспроизведение может быть выполнено через

  1. Запись видеобуфера и последующее воспроизведение,
  2. Сохранение состояния объекта каждый кадр и последующее воспроизведение,

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

  • , чтобы система была детерминированным моделированием *, так что каждый вход генерирует согласованный и ожидаемый результат
  • , если требуется случайность, убедитесь, что случайные числа могут быть воспроизведены точно в более позднее время [посмотрите на заполнение с помощью генераторов псевдослучайных чисел ГПСЧ или используйте стандартные случайные наборы]
  • разделите игровые элементы на «механические» и «эстетические» элементы. механические элементы влияют на результат [например, падение колонны и преграда пути], эстетические элементы предназначены для демонстрации и не влияют на процесс принятия решений в системе [например, эффекты визуальных частиц, такие как искры].

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

о да, как можно забыть Blinx Time Sweeper ! отличный интерактивный повтор, который был включен в настоящую игровую механику!


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

3
ответ дан 23 November 2019 в 22:50
поделиться

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

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

2
ответ дан 23 November 2019 в 22:50
поделиться

В Starcraft и Starcraft: Brood War была функция воспроизведения. После завершения матча вы можете сохранить повтор для просмотра позже. Во время воспроизведения вы можете прокручивать карту и щелкать по юнитам и зданиям, но не изменять их поведение.

Я помню, как однажды смотрел повтор матча, который был сыгран в оригинальной игре, но этот повтор просматривался в Brood War. Для тех, кто не знаком, Brood War содержит все оригинальные юниты и здания, а также множество новых. В оригинальной игре игрок победил компьютер, создав юнитов, которым компьютер не мог легко противостоять. Когда я играл в повторе Brood War, у компьютера был доступ к различным юнитам, которые он создавал и использовал для победы над игроком. Таким образом, один и тот же файл воспроизведения приводил к разному победителю в зависимости от того, какая версия Starcraft воспроизводила файл.

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

О чем следует помнить, если вы пишете движок воспроизведения.

28
ответ дан 23 November 2019 в 22:50
поделиться

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

Как уже упоминалось ранее, повторы в играх RTS сохраняются путем записи всего ввода (что имеет эффект. Прокрутка не имеет никакого эффекта). Мультиплеер также передает весь ввод

Запись всего ввода, а не просто предположение - есть библиотека для чтения повторов Warcraft3, в которой это видно.

ввод включает отметки времени для этого ответа.

1
ответ дан 23 November 2019 в 22:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: