ВОКРУГ ВСЕЛЕННОЙ — ПРОИЗВОДИТЕЛЬНОСТЬ И ОПТИМИЗАЦИЯ

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

Вступление

Шон Трейси (СТ):Привет и добро пожаловать на очередной эпизод Вокруг Вселенной. Я Шон Трейси, технический директор по контенту, техническому арту, технической анимации, общим инструментам и инструментам движка.

Сэнди Гардинер (СГ):А я Сэнди, Сэнди Гардинер.

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

СГ:Что?! Конечно, мы шутим. Но этот эпизод особенный, так как впервые снимается перед живой аудиторией в студии.

СТ:Верно, мы очень рады приветствовать некоторых наших подписчиков, сегодня присоединившихся к нам в нашей студии в Лос-Анджелесе.

СГ:Они получают взгляд изнутри на нашу студию, а также на абсолютно захватывающих мир ведущих AtV.

СТ:И это не сарказм, друзья. Так и есть. Двое ассистентов уже сбежали с площадки, а лишь несколько минут назад Джаред перевернул стол с закусками.

СГ:У нас есть закуски?

СТ:Уже нет.

СГ:Конечно же нет… Закончим с шутками, нам многое нужно рассказать в отчете по проекту Star Citizen за Март. Давайте сверимся с Рикки Джатли и взглянем, что занимало разработчиков с прошлого отчета.

СТ:Принимай эстафету, Рикки!

Отчет по проекту Star Citizen

Рикки Джатли (РД):Привет и добро пожаловать на отчет по прогрессу Постоянной Вселенной за этот месяц.

Команда по пользовательскому интерфейсу работала над голограммами статуса транспортного средства для визора, которые были полностью переписаны, чтобы внедрить новую технологию рендера в текстуру, разработанную нашей командой по рендеру. До этого транспортные средства, отображающиеся на визоре, были созданы на основе отдельного объекта, дублирующего оригинальный транспорт. С введением рендера в текстуру, транспорт, отображаемый на визоре или дисплее – теперь настоящее оригинальное отрендеренное транспортное средство. Это означает, что вместо имитации цели, теперь это реальное точное отражение объекта на планете или в космосе, точно передающее все реальные части и обвесы вроде двигателей и вооружения. Это было достигнуто путем создания узлов рендера, которые выборочно рендерятся для отображения цели с наложением специального шейдера материалов, который служит для наложения цвета и отображения уровня урона частям транспортного средства, при этом избегая использования более тяжелых элементов, связанных с полноценным физическим прямым рендером. Такой рендер цели может быть вызван в любой момент, когда игрок выбирает отображение своего транспортного средства или цели. Изначально планировалось применять это в визоре в 3.0, но в 3.1 вы сможете включать такой рендер и на многофункциональных дисплеях. Визор и дисплеи имеют собственные цели для рендера, на которых отображается интерфейс, а также опциональную камеру, которая также может показывать транспорт. Да, у нас объект рендера цели рендерит другой объект. Все это тесно связано и иногда бывает сложно разобраться, что и что рендерит. Мы также используем эту же технологию рендера в текстуру для коммуникационной системы, зачастую называемой просто «связью». Вместо того, чтобы выборочно рендерить транспортное средство, мы отрисовываем всю сцену камеры, направленной на пилота, так что игрок может видеть того, с кем общается. Эти цели рендера для связи, аналогично с голограммами статуса для визора, могут отображаться на любом дисплее, будь это многофункциональный дисплей корабля, mobiGlas или визор.

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

Еще одной технологией, внедренной в 3.0 и улучшаемой в 3.1, является система дисплеев. Изначально мы начали ее с фиксированного списка экранов, доступных через панель управления, содержащую описание доступных вариантов. Например, место пилота Gladius оборудовано одним нашлемным и шестью многофункциональными дисплеями. Теперь мы добавили возможность добавлять дополнительные экраны.  Сейчас мы реализуем это через объединение визора и экрана радара. Со временем эта механика позволит игрокам настраивать под себя свой кокпит, позволяя им добавлять и убирать вспомогательные дисплеи по желанию. Когда визор соединяется с панелью управления корабля, он получает возможность отображать множество дополнительных экранов интерфейса. Что означает, что мы можем передавать картинку с разнообразных элементов управления прямо на эти опциональные экраны визора. Среди примеров можно привести голограмму цели, экран распределения энергии и экран вооружения.

Команда по работе над процедурными планетами продолжает улучшать планетарную технологию. Мы добавили новой способ поддержки многоканальности, в то же время улучшив смешение цветов и их разделение. И все это работает, потребляя то же самое количество ресурсов. Переход от земли к орбите теперь гораздо плавнее, спасибо прогрессу в смешении цветов. Мы также отдельно выделили наборы распределения процедурных экосистем из технологии канала художественного рендеринга, что значит, что эти изменения уже будут видны на Йеле в 3.1. Наконец, мы также улучшили существующие инструменты для автоматического размещения планетарных аванпостов.

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

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

Несколько кораблей и транспортных средств приближаются к релизу. Департамент по визуальным эффектам был занят добавлением эффектов к ним, включая Anvil Terrapin, MISC Razor, Tumbril Cyclone, а также Aegis Reclaimer. Также они фокусировались на переработке эффектов попаданий Gemini R97, PRAR Distortion Scattergun и Scourge Railgun.

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

Еще одной большой задачей для грядущего релиза была замена персонального экрана снаряжения в Star Marine, чтобы вывести его на один уровень с кодовой базой, уже использующейся в Arena Commander, а также в экранах снаряжения в mobiGlas. Это позволит нам обновлять только один кусок кода, который используется во всех экранах кастомизации. Это очень сократит работу над кодом, так как нам нужно будет исправлять баги лишь в одном месте, а не в нескольких, что означает, что игрокам нужно будет привыкнуть лишь к одному интерфейсу в рамках единой системе, так как любые улучшения пользовательского взаимодействия в будущем будут отражаться на всех экранах снаряжения разом. Внешний вид которых все еще очень грубоват, так как сейчас мы уделяем внимание тому, чтобы корректно работал код, и что мы не теряем функциональность. По сути переход на новый интерфейс означает, что теперь игроки смогут индивидуально перемешивать части легкой, средней и тяжелой брони. Так как вес брони играет более весомую роль в сохранении стабильности, возможность кастомизации различных частей добавляет отличный уровень стратегии для игроков в Star Marine. Работа над кодом этой функциональности завершена, так что сейчас команда по интерфейсу начнет обновлять визуальные элементы, перенося все в 3.1. В то же время команда также вся в работе общими визуальными улучшениями mobiGlas в целом, а также прототипирует новую раскладку, которую мы показали несколько недель назад в AtV. Мы также ищем способы улучшить видимость интерфейса mobiGlas. Одним из важных улучшений, которого мы надеемся добиться, станет то, что mobiGlas станет занимать больше места на экране, дабы увеличить разборчивость и четко отображать многие визуальные элементы. Наконец, команда собирается улучшить качество приложений, добавляя визуальные улучшения к правилам рендера и применения шейдеров к 3D-моделям, что должно привести к очень хорошим и заметным изменениям в работе с инвентарем в 3.1.

Команда технического арта в Лос-Анджелесе продолжала работу над редактором персонажа. Были отобраны новые анимации, чтобы предоставить больше вариаций движения тела. Вместо одинаковой лицевой анимации с каменным лицом, была внедрена подборка более выразительных лицевых анимаций, случайно добавляющихся манекену, чтобы вдохнуть больше жизни в модель персонажа. Кроме того, теперь есть временные иконки, необходимые для предпросмотра выбираемых опций, вроде тона кожи или цвета волос, которые будут завершены. Финальные отполированные иконки для каждого типа выбора запланированы на ближайшее будущее. Далее, команда по персонажам прошлась по всем частям тела, исключая голову, так как решила, что головам нужен еще один полировочный заход, чтобы достигнуть уровня других частей, а также нужна доработка освещения, чтобы добавить достоверности деталям головы. Еще внимание уделили переходу между текстурами головы и тела. Да этого был заметен шов между головой и торсом, так что команда работала над смягчением перехода, убрав этот шов. Мы также значительно улучшили систему камеры. Поле обзора, позиция камеры и глубина резкозти были подправлены, чтобы создать более художественную репрезентацию в окружении при выборе персонажа. И, наконец, эта же команда разбиралась с разнообразными проблемами постоянства, особенно при сохранении и входе в PU. Некоторые баги проявились во время работы над редактором персонажей, вроде выбора элемента, его сохранения и всхода в PU. Прочие проблемы, вроде быстрого пропуска выбора или отмены, также были обнаружены и решены.

Команды в Остине и Лос-Анджелесе работали над сервисными маяками, которые знаменуют начало генерации контента самими игроками. Хотя это лишь начало, для 3.1 мы планируем позволить игрокам платить друг другу за сервис вроде персональной транспортировки или помощи в бою. Как только контракт принимается, создаются маркеры для квантового перелета на позицию создавшего контракт, чтобы игрок, предоставляющий сервис, мог быстро добраться к нему. Обе стороны смогут видеть местоположение друг друга в течение действия контракта. Любая сторона может разорвать его в любой момент, но имейте в виду, что вы сможете оценить другого игрока, в то же время конец контракта не так легко определить. Например, когда завершается помощь в бою? Мы близки к завершению второго этапа этой механики. Мы все еще пытаемся поднять бэкенд-сервисы для этой системы, и в процессе столкнулись с несколькими проблемами, когда система все еще неверно общается с сервисами диффузии, которые поддерживают безопасность наших серверов и многопоточности.

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

И это все на этот ежемесячный отчет о работе, проделанной над Постоянной Вселенной. Как всегда, спасибо нашим подписчикам за спонсирование этого и прочих наших шоу. И, конечно, спасибо всем нашим бекерам за продолжающуюся поддержку разработки Star Citizen и Squadron 42. Увидимся в следующем.

 

Студия

Сэнди Гардинер (СГ):Спасибо, Рикки! Как вы могли видеть, наши разработчики заняты работой по выпуску Альфы 3.1 начиная с улучшений интерфейса кораблей и планетарной технологии, и до доработки mobiGlas.

Шон Трейси (СТ):Ага, многие элементы и улучшения вроде этого распланированы на все четыре квартальных релиза в этом году. Одной из вещей, на которой будет основной фокус, является оптимизация производительности.

СГ:Да, эти грядущие улучшения кадровой частоты будут дорабатываться в ходе всей фазы Альфы и в дальнейшем.

СТ:Давайте услышим больше о процессе оптимизации в целом от команды, которая работает над ним, в особенности этой недели.

Оптимизация

Марк Абент (МА):Оптимизация… одна из самых значительных вещей, на которой мы стараемся сфокусироваться в этом году. Наибольшая проблема заключается в том, что у нас игра про космос. У нас много, и это реально много, объектов. Слишком много, чтобы сосчитать, но я уверен, что кто-то где-то давал цифры. Чем больше у вас объектов, тем более сложная логика требуется, а еще, возможно, нужно обновлять их таймеры, проверять их, что-то делать для того, чтобы игра двигалась дальше… И большое количество таких вещей может быть очень проблематичным, если каждому нужно все это обновлять с каждым кадром. Если этих веще 5000, то всем нужно просчитать все эти 5000, прежде чем появится следующий кадр.

Кристофер Болте (КБ):С высоты птичьего полета видеоигра – это реализация симулируемого виртуального мира, построенного на базовом цикле обновления, состоящем из нескольких простых шагов:

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

Обновление состояния мира: У виртуального мира есть состояние. Такое состояние – совокупность мириадов вещей вроде существующих объектов, их позиций, их цвета, переменных и так далее. Это состояние может изменяться со временем. Например, ИИ может пройти из точки А в точку Б в ходе шагов, изменяющихся и просчитывающихся во времени.

Наконец, отрисовка сцены: Последний шаг при создании каждого кадра – это его рендеринг. На этом этапе данные обрабатываются на предмет того, что видимо, чтобы игрок мог заглянуть в этот симулируемый виртуальный мир. Объекты, которые определены как видимые, вырисовываются на дисплее. Мы сосредоточены на обновлении симуляции и шаге отрисовки сцены, так как они влияют на производительность. Если не углубляться в детали, то можно сказать, что каждый их этих шагов полагается на определенное количество операций, математических или логических, чтобы обновить состояние мира и картинку на дисплее.

Если все эти операции могут выполнить за 1/60 секунды, другими словами за 60 миллисекунд, то мир будет обновляться 60 раз в секунду, что в результате дает 60 кадров в секунду. Но если эти операции занимают 50 миллисекунд, то игра будет обновлять сцену лишь 20 раз в секунду, выдавая 20 кадров в секунду. Настоящая суть оптимизации в поиске путей уменьшения числа операций, чтобы можно было выполнять их в рамках целевой кадровой частоты на определенном «железе». Чтобы лучше объяснить, что значит оптимизация производительности в современных играх, предыдущая абстрактная модель базового цикла обновления должна быть дополнена некоторыми деталями. Несмотря на то, что последующее разъяснение все еще будет опускать многие детали, оно все же предоставит более точное описание взаимодействия разнообразных типов компонентов вроде CPU и GPU.

Возвращаясь к первоначальному примеру, один кадр выглядит следующим образом. Здесь мы используем воображаемый исполнительный блок, который производит все расчеты для обновления мира. Вводную информацию опускаем, так как обычно она не влияет на производительность. Сегодня практически у каждого компьютера есть чип, специально разработанные для рендеринга – GPU. Таким образом, обычно работа распределена между CPU и GPU. Сразу очевидно, что такая схема не очень ресурсоемкая. GPU и CPU работают раздельно, и лишь один из двух чипов занят активными расчетами в определенный момент. Мы можем улучшить эту ситуацию, позволяя GPU работать над одним кадром, пока CPU параллельно обновляет симуляцию для следующего. Такой подход называется последовательным и применяется повсеместно при работе с компьютером. Например, когда вы смотрите видео онлайн, несколько следующих кадров загружаются, в то время как несколько уже загруженных обрабатываются и показываются параллельно. Но такой подход не лишен недостатков. Когда CPU выдает данные быстрее, чем GPU их обрабатывает, GPU начинает все больше и больше отставать. Это отставание может привести к дополнительной задержке. Помимо этого, нужно держать где-то в памяти подготовленные CPU данные, пока GPU не будет готов их обработать, а так как ни у одного устройства нет бесконечной памяти, нам нужно останавливать CPU на каком-то этапе, чтобы позволить GPU нагнать его, ибо невозможно накапливать данные вечно. В данном случае мы говорим о ситуации, когда GPU тормозит CPU.

Для Star Citizen мы обычно стремимся к меньшим задержкам, и позволяем GPU отставать лишь на один кадр. Обратная же проблема может произойти в любой сфере, когда потребитель обрабатывает данные быстрее, чем их производитель, что называют «голоданием». В общем и целом, один компонент всегда будет отставать от прочим, а потому игра всегда будет чем-то ограничена, пусть это будет CPU или GPU. Таким образом, первый этап оптимизации производительности – выяснить, какой компонент на нее влияет. Кадровая частота не улучшится, если вы оптимизируете производительность GPU, когда он уже «голодает». Такой подход также называется анализом критического канала, так как помогает определить канал, который определяет производительность.

Столь же сложная ситуация относится и к многопоточным процессорам. У каждого CPU сейчас есть несколько исполнительных блоков, которые мы будем называть физическими потоками. Чтобы задействовать их, нужно было выработать стратегию по распределению операий по разным физическим потокам. Очевидным выбором было использовать схожую последовательность, как и с GPU, но в случае CPU есть два больших недостатка.

Распределение работы: Как видно на примере работы CPU и GPU, система всегда будет ограничена самым медленным компонентом. Чтобы распределить работу CPU, вам придется найти части работы, которые логично соотносятся друг с другом и занимают одинаковое время. Эта задача очень сложна, практически невозможна, в условиях динамической игры, а также с учетом увеличивающегося количества физических потоков/ядер процессоров. Вы очень быстро исчерпаете список систем, которые можно запускать в тандеме. Нам нужен либо отдельный билд игры для каждой раздельной процессорной конфигурации, что приведет к кошмару с поддержкой, а потому выбранный нами подход заключается в объединении операций и применении имманентной параллелизации, использующейся в видеоиграх. Например, редко бывает лишь один персонаж, которого нужно обновлять, а потому мы можем отдавать различных персонажей различным ядрам процессора. То же самое и со всеми нашими объектами. Такой подход позволяет нам масштабироваться более гибким образом под ядра процессора. Сейчас идет процесс изменения игровой логики, и мы переносим все больше и больше ее частей на этот подход, используя больше процессорных ядер все чаще, тем самым улучшая производительность.

Задержка ввода: Истоки дискуссий про 60 кадров в секунду в контексте игр связаны не только с тем, как быстро обновляются кадры и их визуальным качеством, но и с задержкой. Задержка ввода обычно измеряется в кадрах, так как каждый кадр соответствует определенному количеству операций вне зависимости от используемого «железа». Так что, если нам нужны три вводных кадра от пользователя, чтобы проиграть эффект, то ввод будет сопровождаться задержкой трижды по 16 миллисекунд, в результате давая 48-миллисекундную задержку при воспроизведении в 60 кадрах в секунду. Если наша игра проигрывается при 30 кадрах в секунду, то длительность кадра будет равна 33 миллисекундам, а задержка получится в 99 секунд, практически удваивая время между вводом и визуальным отображением, что приводит к гораздо менее плавным впечатлениям от игры. И добавление больше последовательной обработки лишь увеличит задержку, а потому мы стараемся не полагаться на последовательную обработку для улучшения производительности на стороне CPU, даже если это приведет к лучшей кадровой частоте, ибо это будет стоить нам большей неприемлемой задержки ввода.

Клайв Джонсон (КД):Среди прочих нам часто задают вопрос о том, являются ли целевой серверной кадровой частотой 30 кадров в секунду, а клиентской – 60. Как это возможно? Почему они не взаимосвязаны? А причина в том, что мы не используем метод синхронизации под названием lockstep. Этот метод подразумевает то, что при отправлении обновления от сервера клиент будет ожидать его, прежде чем скажет: «Окей, вот где должен быть объект»., а затем ему нужно ждать следующего обновления, чтобы сказать: «Хорошо, объект передвинулся сюда». И так далее. Мы же на самом деле отправляем обновления с сервера с частотой в 30 кадров в секунду, а клиент работает при 60 кадрах.  В теории клиент должен получать обновления каждые два кадра, а потому ему приходится как бы гадать, что происходит в промежуточном кадре. Так что фактически он локально симулирует все объекты. При движении объекта по плоской поверхности с фиксированной скоростью и заданным направлением, клиент может предугадать, где должен находиться объект в каждый момент, а затем он получит следующее обновление с сервера и скажет: «Объект несколько сбился с пути, мне нужно слегка подвинуть его влево», а затем несколько позже: «Ох, объект слишком сильно повернул, мне нужно чуть подвинуть его обратно». Это позволяет клиенту и серверу работать при разной кадровой частоте, а нам не нужно беспокоиться о чем-нибудь в роде использования метода lockstep или чего-то в этом духе, что может приводить к большим проблемам, приводить к задержкам при скачках интернет соединения, которые все время происходят случайным образом. Это привело бы к довольно негативному опыту. Это приемлемо для пошаговых игр, но для чего-то в духе экшн-игр в реальном времени, вроде той, что мы создаем, это не сработает.

КБ:Уменьшение количества операций может быть достигнуто несколькими способами. Какой из них применять в основном зависит от текущей ситуации, времени на имплементацию и так далее. Чаще всего совмещается несколько подходов. Можно вычислять объекты, находящиеся слишком далеко от игрока, чтобы он их видел. В данном случае нам нужно решить, после какой дистанции мы хотим отключать объекты, что делать в смежной зоне, где объекты еда видимы, как визуально имитировать передвижение объектов, когда игрок не смотрит на них, а также оценивать прочие случаи. Давайте предположим, что нам нужно отслеживать тысячу объектов в едином списке в рамках системы. Если вы удалите такой объект, то вам придется убирать его из всех списков. Прямолинейным подходом будет проверять каждую вводную в нашем списке на наличие этого объекта. Когда мы находим его, мы его убираем. После этого мы копируем каждый последующий объект в предыдущий слот, чтобы заполнить пустоту от удаленного объекта. И это всегда будет требовать тысячи операций. Такой подход может работать хорошо для небольших списков, но если список огромен или если вы очень часто убираете объекты, то это будет быстро забирать столь ценный лимит операций, а потому нам нужно более изысканное решение. Для каждого объекта мы будем запоминать позицию в списке, а когда мы перемещаем объект, мы сразу знаем его позицию. Затем мы можем взять последний элемент списка, закрыть им пустоту, и обновить ссылку к перемещенному объекту. Таким образом мы сокращаем работу до четырех операций вместо тысячи, что довольно неплохо улучшает производительность. И к прочим изменениям поведения это тоже относится, так как они – тоже объекты в наших списках. Такая свобода может быть положительной и не зависит от того, как используются объекты в списке. Это был лишь один пример оптимизации очень небольшой части игрового мира. В общем процесс оптимизации – это постоянный поиск того, во что упирается производительность игры: CPU, GPU или какой-то процессорный поток, а также выработки наилучшего решения для сокращения задействованных операций для наиболее затратных секций.

Мэттью Интриери (МИ):Существует множество перемещающихся кусочков паззла. В целом люди из технического контента и арта будут сосредоточены на вещах, касающихся GPU, количестве обращений отрисовки. У нас есть примерная планка в около 2500 обращений на все одновременно находящиеся на экране корабли, а один корабль может запрашивать где-то между 500-800 отрисовок в наивысшем разрешении. Так что мы стараемся оптимизировать это наилучшим образом. У нас в распоряжении есть несколько трюков. Один из них – LOD’ы, то есть уровни детализации. Наши художники создают меши для уровней детализации, которые снижают количество полигонов и качество материалов по мере отдаления. Их создают вручную, а также генерирую при помощи инструмента Simplygon. Мы также уменьшаем отрисовку урона или переделываем его для некоторых старых моделей кораблей вроде Mustang. Когда вы взрываете корабль, то получите все эти мелкие кусочки обломков, которые раньше создавали вручную. Теперь же мы на самом деле просто разваливаем корпус корабля пополам и меняем шейдер, так что вы видите то, что мы называем уроном UV2 мешей. Это уменьшает количество объектов-обломков, а следовательно довольно значительно уменьшает количество обращений отрисовки. Эти запросы отрисовки можно охарактеризовать как передачу материала на меши. Так что один из способов, которым мы можем снизить количество запросов отрисовки, заключается в объединении мешей. Такой процесс называется Skinning, то есть деформация мешей в реальном времени. Когда я работаю над посадочными шасси, я могу объединить в один меш множество частей: поршни, крепления и основание. И я провожу все это через скелетон, так что вместо 10 частей с десятком обращений отрисовки, получается один меш с десятком обращений отрисовки. Так что мы получаем действительно значимое сокращение. Еще один трюк, который мы используем, связан с нашими визуальными зонами и методом разделения сцены на ячейки под названием Portal Culling. Когда игрок находится снаружи корабля, мы не хотим отрисовывать интерьер, и даже когда вы в другом отсеке корабля, мы не хотим отрисовывать соседний. Вы можете представить себе порталы в виде дверей. Когда они открываются, прорисовывается внутренней пространство помещения, а когда они закрыты, то помещение и геометрия отбрасываются. Мы используем наши порталы визуальных зон, чтобы соединять двери, которые анимируются, открываются и закрываются, а следовательно открывают и закрывают порталы. И, скажем, двери в Caterpillar объеденены мешем, так что они оптимизированы, а порталы находятся на них. Вы могли заметить, что с порталами в последнее время было несколько проблем, и мы работаем над исправлением четырех или пяти значительных проблем с порталами, но мы избавимся от них, и все будет работать быстрее и опрятнее, а вы сможете снова выйти наружу и увидеть экстерьер в космосе, находясь на вашей палубе. Другая вещь, сейчас находящаяся в работе, называется полем маркированной дистанции. Над этой технологией сейчас работает Крис Рейн. Это иной способ описания пространства путем записи дистанций. Метод хранит дистанции, и на данный момент мы сделали так, что используется объемный подход, когда мы заполняем корабли вокселями, что дает нам локальную физическую сетку, но как только игрок приближается на заданную дистанцию, поле становится гораздо быстрее, мы можем описывать формы с гораздо более высоким разрешением, и можем сразу же узнавать, находитесь ли вы внутри корабля или снаружи, сталкиваетесь ли вы с кораблем или нет. Это откроет дверь для новой технологии визуализации щитов. И вместо пузыря, примерно обволакивающего корабль, вы будете видеть очень близко расположенный щит, излучаемый прямо из обшивки корабля. А еще мы очень вдохновлены тем, что при разломе корабля пополам, маркированное поле дистанции становится первым шагом к мульти-физической сетке, так что мы сможем удерживать интерьер вокруг обломков, а затем позволять вам их подбирать и ходить по кораблям после битвы, чтобы вы могли находить новые предметы и вещи таким образом.

КД:Сетевой код был значительным «бутылочным горлышком» для серверной производительности, причиной чему являлось то, что для 24 одновременно поддерживаемых в PU клиентов нам приходилось собирать всю информацию по симуляции, приходилось записывать ее 24 раза по разным каналам. А происходит это таким образом, что если вы подумаете о разных потоках, то каждый CPU проделывает разный объем работы, а главный поток в это время как бы говорит: «Вот кадр, вот другой». И в каждый момент обработки кадра, под его конец, он будет говорить: «Сетевой код, начинай, занимайся своим делом, запиши что-нибудь». А сетевой код скажет: «Окей, хорошо!» и начнет делать свою работу. И все хорошо до тех пор, пока он заканчивает ее до того, как главный поток начнет следующий кадр, но если сетевой код работал несколько медленней, то он задерживал следующий кадр в главном потоке. Это приводило к тому, что кадр задерживался до тех пор, пока не завершал работу сетевой код. На момент выхода 3.0… Чтож, он вышел с некоторым количеством запланированных изменений сетевого кода. Мы внедрили сериализованные переменные уже довольно давно, а к выходу 3.0 практически весь код игры был конвертирован с использования старых систем аспектов и RMI к их замене в виде сериализированных переменных и удаленных методов. Для нас это означает то, что мы могли бы сосредоточить наши усилия на оптимизации одного определенного раздела кода, при этом мы просто оставили старую систему, работающую не так хорошо, как нам хотелось, но так как не так уж и много вещей ее используют, то она уже не проблема. Сериализованные переменные привнесли большие изменения в получение данных от игры: Каково состояние всего вокруг? Что изменилось и что нужно обновить? Каково текущее состояние игрока, так как его нужно разослать по всей сети? Все это было распараллелено, так что вместо того, чтобы всю работу выполнял сетевой поток в самый момент синхронизации с основным потоком, он просто скажет: «Окей, сколько у нас доступных потоков? Окей, все вы, идите и прихватите данных, и нам нужно отправить их по сети». Затем следующей частью будет: «Окей, нам нужно собрать эти данные и отправить их по разным каналам, чтобы они знали, что им нужно проделать некоторую работу». Канал является репрезентацией клиента для сетевого кода. И затем после распараллеливания финальной частью было: «окей, теперь, когда все каналы получили необходимые данные для отправки, нам нужно действительно их отправить». И это также распараллелено, так что мы перешли от однопоточной работы к ее разделению по всем доступным потокам. На клиентской же стороне у нас как бы обратная ситуация. Когда клиенты получают все эти обновления с сервера, они проходят через сокет, который обрабатывается сетевым потоком, а затем наступает момент синхронизации с главным потоком, когда система говорит: «Окей, отлично, сетевой поток готов, главный поток готов. Вот вам все ваши данные, которые были получены от сервера. Начинайте следующий этап симуляции». Эта часть всегда была довольно шустрой на клиентах, честно говоря, и у клиента уходило около миллисекунды на обработку всех этих данных, и, хотя упаковка и отправка данных на сервер занимает пропорционально больше времени, их распаковка всегда была довольно быстрой. Так что сам по себе сетевой код никогда не был проблемой производительности для клиента, так как клиент по большей части получает данные от сервера, а отправляет гораздо меньше того, что получает, так что это никогда не было проблемой.

И отсечение невидимых объектов должно решить это несоответствие между клиентом и сервером. Серверам доступна абсолютная вычислительная мощность, а также способность обрабатывать что угодно с гораздо меньшими затратами, чем большинство клиентов. Идея за отсечением невидимых объектов заключается в том, что клиент заинтересован лишь в том, что в данный момент находится вокруг. Вы не сможете увидеть все, что находится на другой планете или в паре сотен километров, а может даже в глубоком космосе, это все даже не должно быть представлено. Под этим я подразумеваю то, что клиент вовсе не должен знать об этом. Это должно избавить клиент от обработки этих объектов, а также это освободит от некоторых расчетов и сервер, так как ему не придется передавать информацию об этих объектах клиенту. Эо определенно то, что мы хотим сделать, но это сделать не просто. Вам нужен способ контроля обновлений на сервере, чтобы он обновлял не все известные объекты, а только те, о которых он знает, что они находятся вокруг игрока. Те, которые игрок видеть не может, не будут обрабатываться, что сокращает время обработки на сервере. Нам пришлось изменить допущения на стороне сетевого когда, когда раньше он говорил: «окей, у меня есть объект, он сетевой, каждому клиенту нужно знать о нем». Во всем сетевом коде были допущения, на которые он полагался как на достоверные, так что нам придется их изменить. Это пошаговый процесс, когда мы распутывали веще и добавляли новые, оптимизировали все, что позволит применить отсечение невидимых объектов, и мы почти завершили этот процесс. Оставшиеся шаги заключаются в том, что, если вы представите ситуацию с работающим отсечением, а все объекты вне вашего обзора не существуют, то что случится при перелете к другой локации? Скажем, если вы совершаете квантовый перелет к другой планете. Вам будет нужно, чтобы все объекты были на месте, так что их придется вызвать. И, скажем, сервер собирался рассказать вас об этих объектах, информация приходит по сети, а затем ваш клиент должен создать их. До этого момента вызов всех объектов был синхронизирован, или, как мы зовем это, заблокирован, что означает, что на моменте кода, когда он что-то вызывает, все остальное останавливается, он идет и загружает данные для этого объекта, создает сам объект, регистрирует его физику и что-либо еще, что нужно сделать, а затем говорит: «окей, можно закончить с этой функцией, можно продолжить заниматься остальной игрой». Так что нам нужен асинхронный вызов объектов, который также известен как стриминг контейнеров объектов. Идея в том, что клиент получит сообщение от сервера и скажет: «окей, мне нужен этот объект», а затем он начнет работу и скажет: «окей, а какие файлы нужно загрузить для этого объекта? Какие данные?» И он начнет загружать их в фоновом потоке, а затем скажет: «окей, я закончил загрузку. Теперь я могу создать объект». И это не останавливает обработку игры, это не останавливает ваш клиент в процессе. Вы можете продолжать играть. Очевидно, что это занимает несколько больше времени, так что трюк в том, чтобы обеспечить завершение вызова объектов к тому моменту, как они действительно появятся, так что нужно заранее принимать решение об их вызове. Нам нужен этот стриминг контейнеров объектов, это технология, которая позволит нам передавать объекты так, чтобы работало отсечение невидимых объектов. И я действительно хочу разработать эту технологию, завершить ее как можно скорее, чтобы мы смогли начать внутренние тесты и убедиться в ее работоспособности, обнаружив все проблемы, которые она вызовет. Возможно, примером такиз проблем может служить то, что, скажем, у вас есть миссия от Майлза Экхарта, и вам нужно найти его. Майлз сидит в своем баре на Левски, а вы на Port Olisar, так что нам нужно отрисовать небольшой маркер на вашем клиенте, показывающий, куда направляться. Но при отсечении невидимых объектов Майлз Экхар и его сиденье не будут там находиться. Так на что же нам рисовать маркер? Таковы проблемы, которые нам нужно опробовать и выявить пути их решения. Существует пара разных способов, которыми мы можем разобраться с такой ситуацией, но пока мы не можем действительно внедрить отсечение, так как оно сломает геймплей. Вероятно, мы не увидим полной исплементации отсечения невидимых объектов, как я говорил, до тех пор, пока не внедрим стриминг контейнеров объектов. Но для себя нам нужно получить технологию как можно скорее, чтобы мы могли перейти на некоторые другие запланированные вещи. А еще мы завершили другую штуку. Я упомянул отсечение невидимых объектов, стриминг контейнеров объектов… А еще есть отсечение сериализованных переменных. Так как мы еще не можем отсекать невидимые объекты, так как мы еще не можем выдать эту технологию в руки бекеров, мы можем внедрить отсечение переменных. Это работает путем некой предварительной проверки дистанции, и если объект слишком далеко от клиента, сервер просто скажет: «знаешь что? Я не отправлю тебе обновлений по этому объекту». И это будет означать, что, так как объект не будет получать обновлений на клиенте, клиент может сказать: «Окей, чтож, я отправлю этот объект в спячку. Мне не нужно, чтобы CPU его обрабатывал». Объект может просто оставаться на месте, так что вы можете получить значительную часть прироста производительности, которую мы надеемся получить от отсечения невидимых объектов.

КБ:Размер наших игр обычно не слишком оптимизирован в ходе разработки. Очевидно, что более быстрая версия сложнее и обладает большей структурой, даже в случае небольшой игры. Более сложный код означает больше мест, где что-то может пойти не так, что приведет к багу. Из-за этого всегда нужно выбирать между скоростью разработки и производительностью. Если мы полностью сосредоточимся на производительности, разработка механик значительно замедлится, включая и будущие элементы игры, так как их нужно будет разрабатывать в рамках более сложной кодовой базы. Чтобы еще больше ухудшить ситуацию, мы бы могли оптимизировать весь этот код, лишь чтобы после осознать, что его определенные части на самом-то деле и не нужны, или что нам нужно внедрить что-то совершенно иное, так как результат просто не интересен. Есть известная цитата «преждевременная оптимизация – корень всех зол». Другими словами, нам нужно увидеть финальную систему, чтобы должным образом понять и проанализировать ее, чтобы проделать осмысленную оптимизацию производительности, но хватит теории. В реальности в нашу игру уже можно играть, а играть при 15 кадрах в секунду не весело.  Чтобы помочь главным программистам и продюсерам решить, на каких элементах оптимизации нам следует сфокусироваться, мы проводим анализ производительности. Иногда механикам необходима оптимизация для работы, или элементу необходимо внедрение оптимизации, что связывает разработчиков, делая групповой прогресс более сложным. Иногда изменения в коде сложнее, чем предполагалось, или представляют слишком большой риск сразу после внедрения, приводя к негативным эффектам или задержкам остальных оптимизаций или механик. Мы используем набор различных инструментов для анализа производительности. Самым базовым инструментом является классическое профилирование образцов. Такое профилирование работает по принципу остановки CPU в фиксированные временные интервалы и записи того, что делал CPU за это время. Если функции требуется множество операций для большего шанса исполнения, когда CPU тестируется, в результате чего получается большее количество образцов, то это показатель большой затратности функции. Недостатком профилирования образцов является то, что оно показывает лишь функцию и то, куда потрачено время, но опускает контекст того, почему функция была выполнена, на каком CPU, в связке с каким другим кодом или высокоуровневым состоянием игры вроде количества активных транспортных средств. Для этого мы в основном используем инструментальный профилировщик, который показывает исполненную функцию менее подробно, но показывает какое ядро процессора и когда ее выполняло. Этот профилировщик может также показывать графики определенных значений производительности, в которых мы заинтересованы. Эти инструменты крайне полезны для анализа специфичной ситуации, но Star Citizen – иной «зверь». У нас бывает до 50 игроков, а еще гораздо больше запланировано, на одном общем сервере, которому нужно выполнять симуляцию физики. Это означает, что все, что делает любой игрок, может влиять также и на других игроков, замедляя сервер, заставляя его просчитывать больше операций, что косвенно можно заметить по рывкам на клиентской стороне, высоким сетевым задержкам и так далее. Все ведет к тому, что сервер отправляет обновления недостаточно часто. А потому, нам нужно воссоздавать поведение сервера.

Роберт Джонсон (РД):Мы также просматривали некоторые фреймворки телеметрии, которые мы используем для визуализации и отслеживания действительных проблем с производительностью игры внутри кодовой базы. Некоторые из проделанных с телеметрией улучшений, например, позволяют нам захватывать данные автоматически в ходе игровых тестов. Данные будут автоматически захвачены, когда кадровая частота упадет ниже определенного уровня. Например, мы можем использовать такой автоматический захват, чтобы убедиться, что мы выбираем моменты, когда сталкиваемся с разными нехорошими багами, которые вызывают значительное падение производительности. Мы затем могли бы взять эти данные для анализа в команде, чтобы увидеть первопричину проблем. Различные проблемы потом могут быть решены кем-то из инженеров команды по производительности, либо, в некоторых случаях, мы можем на самом деле обнаружить, что эти проблемы относятся к сферам инженеров из других команд, а в таком случае нам нужно будет скооперироваться с ними для поиска исправления некоторых проблем, которые могли проявиться в ходе игровых тестов. Сейчас довольно ранняя стадия для автоматического захвата с новой системы телеметрии, но начальные тесты выглядят довольно многообещающе, так что мы надеемся, что в 3.1 мы увидим многие из проблем гораздо раньше, и сможем исправить их за достаточное время перед релизом. Среди прочих вещей, нацеленных на мониторинг производительности, будут автоматические тесты. Они будут проводиться на тестовой машине для каждого создаваемого нами билда. Эти внутренние тесты будут чем-то вроде хождения автоматизированного игрока по населенным зонам игры вроде Levski, или это может быть появление игрока в корабле рядом с тридцатью кораблями-ИИ. И это автоматические тесты будут отслеживать разнообразную статистику кадровой частоты, позволяя нам отслеживать улучшения производительности от билда к билду. Или, если мы случайно несколько ухудшим производительность, то в таком случае эти тесты помогут нам выявить проблемы по мере их появления. И даже если они не обязательно предоставят идеальные замеры производительности, которую мы вероятно увидим в Live-версии, они могут по крайней мере дать нам хорошую индикацию о том, что мы движемся в верном направлении, вместо того чтобы мы внедряли множество изменений для оптимизации кода, при этом в действительности не зная, дают ли эти изменения желаемый эффект. 3.0.1 сейчас предоставляет два действительно хороших способа сбора данных, по которым мы можем решить, что нужно оптимизировать на стороне кода. Эти два способа, которые есть у нас сейчас, включают автоматически захваченные данные из внутренних игровых тестов, которые мы проводим силами до 50 человек из QA и нескольких инженеров, а другим способом сбора данных для анализа того, что мы можем оптимизировать, является просто проведение локальных тестов. При их проведении мы можем поднять сервер, подключить клиент, потенциально мы можем по желанию подключать неуправляемые клиенты, и можем играть в игру относительно нормально, но с дополнительными отладочными командами, доступными нам, мы можем проделывать вещи вроде прыжков за Olisar, сразу помещать себя в корабль, мы можем вызывать пятьдесят ИИ, так что мы можем довольно быстро поместить себя в сценарий, который будет больше приближен к релизной нагрузке и кадровой частоте, чем простые побегушки одним игроком или игра на относительно пустом сервере. И при воссоздании таких ситуаций я также обычно делаю вещи вроде вызова 50 ИИ, а затем перемещения игрока в другую игровую локацию вдали от Olisar, так как в такой ситуации мы бы ожидали, что производительность должна быть лучше, так как куча транспортных средств все еще на сервере, но они теперь столь далеко от игрока, что нам не нужно обновлять их на клиентской стороне с таким же уровнем детализации, как раньше, когда мы находились рядом с ними на Olisar. И мы бы проводили эти разнообразные сценарии локально, смотрели на данные, и путем их анализа выявляли случаи, когда мы могли бы получить явный прирост не обновляя вещи, которые не нужно. Мы также могли бы использовать эти данные для поиска наиболее тяжелых функций в игре, какие функции наиболее часто вызываются, какие функции дольше всего выполняются, какие функции больше всего занимают и очищают памяти. Мы также могли бы смотреть за функциями, которые вызываются кодом, который даже и не обязательно нужно выполнять в различных игровых режимах, или кодом, который перешел к нам по наследству от движка. И мы бы взглянули на него в поисках улучшения старого кода, который нам не нужен был, но также частенько самый большой прирост дает отбрасывание или переписывание старых систем, которые были нам не нужны, или которые не нужно было использовать так, как мы сейчас их используем в PU. Например, одной из проблем, которая всегда была у нас, является тестирование сервера, полностью забитого клиентами. В частности, сейчас мы дошли до 50 человек на карту PU, и вы можете представить необходимое количество людей и логистический кошмар при организации 50 человек из QA или разработчиков-волонтеров, чтобы они провели игровой тест игры в течение пары часов и заполнили сервис, а затем извлекли данные о производительности. И если вы наблюдаете замедления сервера во время этих тестов и спрашиваете кого-то о том, что случилось, то вам придется спросить 50 человек и получить 50 разных ответов. Так что довольно сложно побудить людей проводить эти тесты, а еще остается вопрос к качеству получаемой информации. Так что мы работаем над неуправляемыми клиентами, которые по сути являются клиентами без игроков, глупыми роботами, но все же нажимающий на кнопки… Хотя нет там кнопок, это просто программа, которая контролирует виртуальную клавиатуру, она просто имитирует джойстик, клавиатуру, движения мыши, и она достаточно умна, что может выбраться из кровати, добраться до корабля, а затем летать вокруг и стрелять в случайные стороны, чтобы генерировать достаточно нагрузки для грубой симуляции того, чего мы ожидаем от действий игроков. До этого неуправляемые клиенты запускались только на машинах разработчиков, а чтобы набрать достаточное количество клиентов для заполнения сервера, они мигрировали на виртуальные машины, работающие в облаке. Так что их нужно запускать по необходимости и отключать. Помимо этого, я упоминал получение информации лучшего качества, так что у нас уже некоторое время ведется телеметрия игровых серверов. Еще у нас поступала телеметрия и от клиентов. Все это было реализовано при помощи несколько фрагментированного подхода, так что сейчас мы снова работаем с командами по движку и DevOps, чтобы попытаться получить более унифицированный подход к получению более богатой информации о производительности, «бутылочных горлышках» на каждом клиенте на сервере, попытаться получать больше контекстуальной информации о том, что каждый делал в игре в определенный момент времени, чтобы мы могли сказать: «Окей, вот здесь происходит падение производительности… Что все делали? Понятно, этот парень взорвал Caterpillar с кучей груза… Окей, это не правильно».

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

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

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

Заключение

Шон Трейси (СТ):Оптимизация затрагивает всех, от разработчиков до игроков. Мы учитываем осложнения с производительностью в ходе разработки Star Citizen, и у нас даже есть специальная команда по оптимизации, нагоняющая нашу собственную разработку.

Сэнди Гардинер (СГ):Верно, и с 3.1 на горизонте у нас выходит несколько кораблей. Reclaimer, Cyclone, Terrapin и Razor, а концепт Aegis Vulcan остается доступен до конца месяца. Добавьте один из этих многоцелевых кораблей поддержки к своему флоту и станьте лучом надежды для пилотов в беде.

СТ:Также у нас доступны пакет кораблей, включающий Vulcan наряду с некоторыми другими кораблями Aegis, и также набор скинов.

СГ:Верно, так что даже если вы хотите приобрести Vulcan в игре, вы можете прихватить эти яркие расцветки уже сейчас.

СТ:Не забудьте посмотреть эпизоды «Обзванивая разработчиков» и «Путеводителя по Галактике» за эту неделю на нашем сайте. А в Reverse the Verse на этой неделе, которое выйдет в эфир завтра в 8 утра по PST, Клайв Джонсон и Роб Джонсон – две стороны монеты оптимизации производительности – заглянут, чтобы ответить на ваши вопросы по итогам сегодняшнего шоу.

СГ:Большое спасибо всем нашим подписчикам за спонсирование всех наших шоу, и отдельное спасибо подписчикам, которые здесь с нами в студии.

СТ:Спасибо за то, что стали частью этого специального эпизода, а остальным спасибо за просмотр.

СГ:Также, конечно, спасибо всем нашим подписчикам, вы делаете разработку Star Citizen и Squadron 42 возможной.

СТ:И на этом пока все, до следующей недели, увидимся…

Во Вселенной!

ПОХОЖИЕ СТАТЬИ

ЕЖЕНЕДЕЛЬНАЯ НОВОСТНАЯ РАССЫЛКА RSI (14.12.18)

ЕЖЕНЕДЕЛЬНАЯ НОВОСТНАЯ РАССЫЛКА RSI (14.12.18)

Проверьте свою мощь Дорогой Гражданин, На этой неделе Альфа 3.4 добралась до широкой аудитории на PTU, а это значит, что войска тестеров собираются очистить игру от любых устоявшихся ошибок и подготовить ее к "живому" релизу. Мы изучили историю системы Kiel в новом выпуске "Галактического...

Обновление полезных ссылок в ресурсах

Приветствую всех граждан! Обновлена база данных по полезным ресурсам Star Citizen Удалена неактуальная информация Добавлен актуальный калькулятор DPS Добавлена таблица с ценами на товары в магазинах, таблица учитывает профит закупки.

Цены на минералы

Цены на минералы

Оставить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.