@undeadter

Как организована многопоточность в играх жанра RTS?

Поясните, пожалуйста, как организованы RTS или где можно почитать об этом. Интересует та часть, которая отвечает за действия юнитов в играх, где имеется несколько команд и у каждой из них по 50+ персонажей, не считая построек. Каждый персонаж независим может гулять по карте в любом направлении, "строиться", атаковать/защищаться и тд. Пример: Age of Empires, Казаки, total war, etc. Чисто теоретически я могу организовать несколько потоков: отрисовка графики и конвейера действий для юнитов типа FIFO, но разве этого хватит для плавной игры с таким количеством действий? Сам еще не эксперементировал, а что читать не нашел по этому поводу.
  • Вопрос задан
  • 582 просмотра
Пригласить эксперта
Ответы на вопрос 4
romy4
@romy4
Exception handler
Один из вариантов — это успевать вложиться в один шаг. Все юниты движутся между клетками сетки. Они могут быть достаточно мелкими, чтобы выглядеть для пользователя незаметно. И движение юнитов не моментальное, каждый шаг всё равно занимает 50-100мс. В это время ИИ надо уложиться в расчётах следующего шага.
Ответ написан
Комментировать
@Mercury13
Программист на «си с крестами» и не только
Без параллельности можно обойтись — и поначалу лучше обойдитесь.
Если уж хочется помногопоточить — я бы разбил на два потока, интеллект и графику.

У каждого юнита три одинаковых структуры, renderInfo, bufferedInfo и aiInfo. Там могут быть координаты, курс, HP, ссылка на следующего — всё, что угодно. Ссылка на следующего — это важно, ведь юниты могут появляться и исчезать.

Поток интеллекта работает с aiInfo; где-то здесь есть и код мультиплеера. Закончив шаг, поток захватывает мьютекс и для всех юнитов (понятие «все» определяется по aiInfo) даёт bufferedInfo = aiInfo.

Поток прорисовки захватывает тот же мьютекс и для всех юнитов («все» по bufferedInfo) даёт renderInfo = bufferedInfo. А затем, освободив мьютекс, делает с этим renderInfo что хочет.
Под мьютексом будем сидеть очень мало: ни прорисовки, ни мультиплеера там нет.

Поскольку юнит исчезает из памяти только тогда, когда на него пропадают ссылки во всех трёх структурах, надо делать или объектный пул (т.е. юнит, будучи созданным, остаётся навсегда, но впоследствии этот блок памяти можно будет задействовать на другой юнит), или какие-нибудь «мусорные» указатели наподобие std::shared_ptr.

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

Разбивать интеллект на потоки откровенно тяжело, и в игре с мультиплеером я просто не знаю, как. RTS обычно передают по сети короткие пакеты наподобие «выделить юнитов в квадрате (X,Y) — (X,Y)», «добавить к выделению юнита 1234» и «отправиться в точку (X, Y)». Эти команды, будучи выполненными на разных компьютерах, выполняются одинаково.

Если объединить эти две конструкции — повторяемые расчёты и асинхронный рендеринг — возможны неточности, и, надеюсь, они будут приемлемыми — на экране (renderInfo) юнит попадает в отмеченный квадрат, а в aiInfo уже нет, посколько сдвинулся.

Отдельный вопрос — компенсация пинга в играх по интернету. Не думал пока над этим. Наверно, придётся иметь officialAiInfo и lagCompensatedAiInfo…

ЗЫ. Подсказывают, что такт игры в такой ситуёвине длится порядка 0,1 с, т.е. может продлиться несколько кадров. Тогда во всех этих …info надо описать движение так, чтобы в любой момент получить промежуточное x(t), y(t), yaw(t)… Для танка — bodyYaw(t) и turretYaw(t), для человечка — фазу анимации…

ЗЗЫ. Если игра трёхмерная и вид хоть как-то настраивается, одна из команд игры будет выглядеть «выделить юнитов в квадрате (X,Y) — (X,Y) с матрицей преобразования A».

ЗЗЗЫ. Чтобы игра была повторяемой (важно для мультиплеера), все расчёты, влияющие на течение игры, проводить в фиксированной запятой. Все переменные, с этим связанные, чем угодно, хоть нулём, но инициализировать.
Ответ написан
Комментировать
abyrkov
@abyrkov
JavaScripter
но разве этого хватит для плавной игры с таким количеством действий

Если вылизать код, то да. Главное - избегать ненужных вычислений.
Насчет потоков - можно поэксперементировать с количеством юнитов на поток, конкретно тут не скажешь.
Ответ написан
Комментировать
Tiendil
@Tiendil
Разработчик ПО.
Два потока: графика и логика.

Логика пошаговая с шагом 0.1-0.2 секунды, в зависимости от потребностей. Пошаговость означает, что реальное время в ней не учитывается, игра идёт по ходам: 1, 2, 3, 4...

Логика шлёт в графику команды, которые управляют отображением. Например, если юнит сдвинулся на X метров, то логика шлёт команду в графику: "сдвинуть юнит на X метров за время 0.1 секунды". Команда выполняется на каждой перерисовке и постепенно сдвигает спрайт/модельку юнита в нужную позицию (при 60 FPS он будет сдвинут за 6 перерисовок, каждый раз на x/6 метров). Одновременно отображение юнита может изменяться несколькими командами, например, одна перемещает танк, а другая вращает его башню.

Поток может быть и один, но принци тот же остаётся. Так в большинстве AAA игр делается, в частности, в Order of War.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

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