cryengine на каком языке пишутся скрипты
Долгожданная проверка CryEngine V
Введение
CryEngine — игровой движок, созданный немецкой компанией Crytek в 2002 году и первоначально используемый в шутере от первого лица Far Cry. На CryEngine разных версий сделано много отличных игр от разных игровых студий, которые лицензировали движок: Far Cry, Crysis, Entropia Universe, Blue Mars, Warface, Homefront: The Revolution, Sniper: Ghost Warrior, Armored Warfare, Evolve и многие другие. В марте 2016 года компания Crytek анонсировала выход своего нового движка CryEngine V и вскоре опубликовала исходный код на Github.
Для проверки открытого исходного кода использовался статический анализатор PVS-Studio версии 6.05. Это инструмент для выявления ошибок в исходном коде программ, написанных на языках С, C++ и C#. Единственный верный способ использования методологии статического анализа — регулярная проверка кода на компьютерах разработчиков и build-сервере. Но для демонстрации возможностей анализатора PVS-Studio мы выполняем разовые проверки open-source проектов и пишем обзорные статьи с описанием выявленных ошибок. Впрочем, если проект показался нам интересным, по пришествию одного-двух лет мы можем вновь проверить его. По сути такие повторные проверки ничем не отличаются от разовых, так как в коде накапливается много изменений.
Особое внимание хочу уделить проверке игрового движка Unreal Engine 4. На примере этого проекта мы смогли в подробностях рассказать о правильном применении методологии статического анализа на реальном проекте: от внедрения анализатора в проект до сведения количества предупреждений до нуля с последующим контролем за появлением ошибок на новом коде. Наша работа с проектом Unreal Engine 4 вылилась в сотрудничество с компанией Epic Games, в рамках которого команда PVS-Studio исправила все предупреждения анализатора в исходном коде движка, и была написана совместная статья о проделанной работе, которая опубликована в Unreal Engine Blog (ссылка на русский перевод). Также компания Epic Games приобрела лицензию PVS-Studio для самостоятельного контроля качества кода. К аналогичному сотрудничеству мы готовы и с компанией Crytek.
Структура отчёта анализатора
В этой статье я хочу ответить на несколько часто задаваемых вопросов, связанных с количеством предупреждений и ложных срабатываний. Например, — «Сколько процентов составляют ложные срабатывания?» — или — «Почему в таком большом проекте так мало ошибок?».
Для начала, все предупреждения анализатора PVS-Studio делятся на три уровня важности: High, Medium и Low. Так High уровень содержит высоко критичные предупреждения, с наибольшей вероятностью являющиеся реальными ошибками, а Low уровень — предупреждения с низкой критичностью, либо предупреждения с очень высокой вероятностью ложно-позитивного срабатывания. Стоит помнить, что конкретный код ошибки не обязательно привязывает её к определённому уровню важности, а распределение сообщений по группам сильно зависит от контекста, в котором они были сгенерированы.
Рисунок 1 — Распределение предупреждений по уровням важности в процентном отношении
В статью невозможно уместить описание всех предупреждений и показать соответствующие фрагменты кода. Обычно статья содержит 10-40 примеров ошибок с описанием. Некоторые подозрительные места кода приводятся просто списком. Большинство предупреждений остаются нерассмотренными. В лучшем случае, после уведомления разработчиков, они спрашивают полный отчёт анализатора для детального изучения. Горькая правда заключается в том, что в большинстве проверяемых нами проектов, материала для статьи более чем достаточно после просмотра только предупреждений уровня High. И игровой движок CryEngine V — не исключение. На рисунке 2 представлена структура предупреждений уровня High.
Рисунок 2 — Описание предупреждений High уровня
Результаты проверки
Обидный copy-paste
V501 There are identical sub-expressions to the left and to the right of the ‘-‘ operator: q2.v.z — q2.v.z entitynode.cpp 93
Опечатка в одной цифре, пожалуй, одна из самых обидных опечаток, которую может допустить программист. В этой функции анализатор обнаружил подозрительное выражение (q2.v.z — q2.v.z), в котором с большой вероятностью перепутали переменные q1 и q2.
V501 There are identical sub-expressions ‘(m_eTFSrc == eTF_BC6UH)’ to the left and to the right of the ‘||’ operator. texturestreaming.cpp 919
Другой тип опечаток связан с копированием констант. В данном случае переменная m_eTFSrc два раза сравнивается с константой eTF_BC6UH, на месте которой должна быть любая другая, например, отличающая всего одной буквой — константа eTF_BC6SH.
Вот пример ленивого копирования каскада условных операторов, в одном из случаев которого забыли внести изменения в код.
V517 The use of ‘if (A) <. >else if (A) <. >‘ pattern was detected. There is a probability of logical error presence. Check lines: 970, 974. environmentalweapon.cpp 970
В предыдущем коде была хоть какая-то вероятность того, что лишнее условие — это результат слишком большого количества копий кода и одна проверка осталась просто лишней. В этом фрагменте кода из-за идентичных условных выражений переменная attackStateName никогда не примет значение «Charged Throw».
V519 The ‘BlendFactor[2]’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1265, 1266. ccrydxgldevicecontext.cpp 1266
В коде проекта обнаружилась такая вот функция, в которой из-за опечатки в индексе пропущено заполнение элемента с индексом три: BlendFactor[3]. Это место было бы просто одним из интересных мест с опечаткой, если бы анализатор не обнаружил ещё 2 фрагмента, куда скопировали код с допущенной опечаткой:
V519 The ‘m_auBlendFactor[2]’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 904, 905. ccrydxgldevicecontext.cpp 905
Вот то самое место, где продолжают пропускать заполнение элемента с индексом ‘3’. На мгновение я даже подумал, что в этом был какой-то смысл, но эта мысль меня быстро покинула, когда в конце функции я увидел обращение ко всем четырём элементам массива m_auBlendFactor. Похоже в файле ccrydxgldevicecontext.cpp просто сделали несколько копий кода с опечаткой.
V523 The ‘then’ statement is equivalent to the ‘else’ statement. d3dshadows.cpp 1410
В заключение раздела про copy-paste привожу описание ещё одной интересной ошибки. Независимо от результата условного выражения, значение m_cEF.m_TempVecs[2][Num] всегда вычисляется по одной формуле. Судя по со соседнему коду этого фрагмента, здесь нет опечатки в индексе, в этом условии хотят заполнить именно элемент с индексом ‘2’. А вот формулу расчёта, скорее всего, хотели использовать разную, но после копирования строки забыли изменить код.
Проблемы с инициализацией
V546 Member of a class is initialized by itself: ‘eConfigMax(eConfigMax)’. particleparams.h 1013
Анализатор обнаружил возможную опечатку, когда поле класса инициализируется с использованием собственного значения.
V603 The object was created but it is not being used. If you wish to call constructor, ‘this->SRenderingPassInfo::SRenderingPassInfo(. )’ should be used. i3dengine.h 2589
Здесь анализатор обнаружил неправильное использование конструктора. Программист, вероятно, подумал, что вызов конструктора таким образом без параметров внутри другого конструктора выполнит инициализацию полей класса, но это не так.
В этом коде произойдёт следующее: будет создан новый неименованный объект типа SRenderingPassInfo и тут же разрушен. В результате поля класса остаются неинициализированными. Одним из способов исправления ошибки будет написание отдельной функции инициализации и вызов её из разных конструкторов.
V688 The ‘m_cNewGeomMML’ local variable possesses the same name as one of the class members, which can result in a confusion. terrain_node.cpp 344
Анализатор обнаружил совпадения имени локальной переменной cNewGeomMML с полем класса. Часто такой код не является ошибкой, но здесь это выглядит очень подозрительно на фоне инициализации других полей класса.
V575 The ‘memset’ function processes ‘0’ elements. Inspect the third argument. crythreadutil_win32.h 294
С помощью анализатора была найдена очень интересная ошибка. При вызове функции memset() перепутали переданные аргументы, в результате чего функцию зовут для заполнения 0 байт памяти. Вот как выглядит прототип функции:
Третьим аргументом должен передаваться размер буфера, а вторым — значение, которым необходимо заполнить буфер.
V630 The ‘_alloca’ function is used to allocate memory for an array of objects which are classes containing constructors. command_buffer.cpp 62
В коде проекта встречаются места, где с помощью функции alloca() выделяют память для массива объектов. В приведённом коде, при таком способе выделения памяти у объектов класса QuatT не будет вызывать ни конструктор, ни деструктор. Подобный код может привести к работе с неинициализированными переменными и другим ошибкам.
В коде проекта встречаются случаи, когда тернарный оператор ?: возвращает одно и то же значение. Возможно, в предыдущем случае так написали для красоты кода, но зачем так сделали в этом фрагменте?
Подозрительное присваивание переменной самой себе. Разработчикам стоит проверить это место.
Приоритеты операций
V502 Perhaps the ‘?:’ operator works in a different way than it was expected. The ‘?:’ operator has a lower priority than the ‘+’ operator. gpuparticlefeaturespawn.cpp 79
Похоже, в этой функции неверно выполняется замер времени. Приоритет оператора сложения выше, чему тернарного оператора ?:, поэтому к spawn.delay сначала прибавляется значение 0 или 1, а в переменную endTime всегда записывается значение spawn.duration или fHUGE. Это довольно распространённая ошибка. Об интересных паттернах ошибок в приоритетах операций, найденных в базе ошибок PVS-Studio, я рассказал в статье: Логические выражения в C/C++. Как ошибаются профессионалы.
V634 The priority of the ‘*’ operation is higher than that of the ‘ Рисунок 3 — Плохое форматирование кода
С таким стилем программирования избежать ошибок на большом объёме практически невозможно. Так, в рассматриваемом случае планировали при определённом условии освобождать память из-под массива объектов и обнулять указатель. Но из-за неправильного форматирования указатель m_parts[i].pMatMapping обнуляется на каждой итерации цикла. Какие негативные последствия это может иметь, я предсказать не могу, но код однозначно настораживает.
Сравнение беззнаковых чисел с нулём
В проверяемых проектах часто встречаются сравнения беззнаковых чисел с нулём, результат которых всегда либо истина, либо ложь. Такой код не всегда содержит серьёзную ошибку. Часто это результат излишней осторожности, либо проверка остаётся после изменения типа переменной со знакового на беззнаковый. В любом случае такие сравнения стоит внимательно проверить.
V547 Expression ‘m_socket p3DEngine’ pointer was utilized before it was verified against nullptr. Check lines: 1477, 1480. gameserialize.cpp 1477
Разыменование и проверка указателя gEnv->p3DEngine.
Пример 3
V595 The ‘pSpline’ pointer was utilized before it was verified against nullptr. Check lines: 158, 161. facechannelkeycleanup.cpp 158
Разыменование и проверка указателя pSpline.
Разные предупреждения
V622 Consider inspecting the ‘switch’ statement. It’s possible that the first ‘case’ operator is missing. mergedmeshrendernode.cpp 999
Пожалуй, этот фрагмент кода самый странный из всех, что встречались в проекте CryEngine V. Выбор метки case не зависит от условного оператора if, даже если там if (false). В операторе switch выполняется безусловный переход к метке, если она удовлетворяет выражению switch. Без использования оператора break, с помощью такого кода можно «обходить» выполнение ненужных операторов, но опять же, сопровождать такой неочевидный код будет легко не всем разработчикам. И почему при переходе к меткам GEOM_CAPSULE и GEOM_CYLINDER выполняется один и тот же код?
V510 The ‘LogError’ function is not expected to receive class-type variable as second actual argument. behaviortreenodes_action.cpp 143
Если в описании функции невозможно указать число и типы всех допустимых параметров, то список формальных параметров завершается эллипсисом (. ), что означает: «и, возможно, еще несколько аргументов». В качестве фактического параметра для эллипсиса могут выступать только POD (Plain Old Data) типы. Если эллипсис функции в качестве параметра передается объект класса, то практически всегда свидетельствует о наличии ошибки в программе. Вместо указателя на строку в стек попадает содержимое объекта. Такой код приведет к формированию в буфере «абракадабры» или к аварийному завершению программы. В коде CryEngine V используется свой класс строки, и у него уже есть подходящий метод c_str().
Очень подозрительный фрагмент кода. После цикла for поставили точку с запятой, хотя форматирование кода подразумевает о наличии тела у цикла.
V535 The variable ‘j’ is being used for this loop and for the outer loop. Check lines: 3447, 3490. physicalworld.cpp 3490
Код проекта полон и другим опасным кодом, например, использованием одного счётчика во вложенном и внешнем циклах. Большие файлы с исходным кодом содержат запутанное форматирование и изменение одних переменных в разных местах — без статического анализа здесь не обойтись!
Анализатор посчитал, что в функцию, выполняющую действие с контейнером, передаётся итератор от другого контейнера. В этом фрагменте кода это не так и ошибки нет. Переменная rPeaks является ссылкой на m_peaks. Однако такой код может вводить в заблуждение не только анализатор кода, но и людей, которые будут сопровождать код. Не стоит так писать.
V713 The pointer pCollision was utilized in the logical expression before it was verified against nullptr in the same logical expression. actiongame.cpp 4235
Оператор if содержит довольно большое условное выражение, в котором везде выполняется обращение к указателю pCollision. Ошибка заключается в том, что на равенство нулю указатель pCollision проверяется самым последним, т.е. уже после многократного разыменовывания.
V758 The ‘commandList’ reference becomes invalid when smart pointer returned by a function is destroyed. renderitemdrawer.cpp 274
В переменную commandList сохраняется ссылка на значение, которое хранит умный указатель. При разрушении объекта умным указателем ссылка становится недействительной.
Заключение
Исправление ошибок во время написания кода почти ничего не стоит, в отличии от тех, которые находятся на этапе работы тестировщиков, а ошибки в выпущенном продукте несут уже колоссальные расходы. Независимо от используемого анализатора, сама методология статического анализа кода уже давно показала себя как очень эффективный способ контроля качества кода и программного продукта в целом.
Сотрудничество с Epic Games продемонстрировало пользу от внедрения статического анализатора в Unreal Engine 4. Мы помогли разработчикам во всех вопросах интеграции анализатора и даже исправили найденные ошибки, чтобы они могли регулярно проверять новый код проекта. К аналогичному сотрудничеству мы готовы и с компанией Crytek.
Предлагаю всем желающим попробовать PVS-Studio на своих C/C++/C# проектах.
Разработчики CryEngineV заранее были уведомлены о проверке проекта, поэтому некоторые ошибки могут быть уже исправлены.
CryEngine
Из Википедии — свободной энциклопедии
Crytek
CryEngine — игровой движок, созданный немецкой частной компанией Crytek в 2002 году и первоначально используемый в шутере от первого лица Far Cry. «CryEngine» — коммерческий движок, который предлагается для лицензирования другим компаниям. С 30 марта 2006 года все права на движок принадлежат компании Ubisoft.
Движок был лицензирован компанией NCSoft для разрабатываемой MMORPG Aion: Tower of Eternity. [2] [3]
В конце сентября 2009 года братья Ерли, основатели Crytek, дали интервью великобританскому журналу Develop, в котором заявили, что изначально CryEngine не планировался для лицензирования сторонними компаниями. CryEngine планировался стать закрытым движком для сугубо внутреннего использования. Братья объяснили [4] :
По-честному, в начале это не было приоритетным для нас. Мы просто хотели иметь проприетарный движок для своих игровых проектов. Первая компания связалась с нами в 2002 году, и они сказали, что хотят лицензировать CryEngine. Это была большая честь для нас, и после небольших раздумий мы приступили к созданию своего бизнеса по продаже лицензий и уже через небольшой период времени имели нескольких лицензиатов.
История игровых движков №1 — CryEngine
CryEngine — как много боли в этом слове для сердца геймера слилось. Движок, разработка которого началась в далеком 2000 году компанией Crytek, по праву считается одним из самых тяжелых, а игры на нем одни из самых красивых. Впервые общественность услышала о нем на выставке ECTS 2000 как о демонстрации возможностей видеокарт Nvidia — движок собрал множество положительных отзывов, после чего вдохновленная команда разработчиков принимается за работу, и в 2002 году Crytek официально заявляет, что движок полностью готов.
В 2004 году на CryEngine вышла одна из самых известных игр современности — Far Cry, являющаяся шутером с элементами хоррора. Эта была игра, надолго опередившая по графике, геймплею и ИИ свое время. Сюжет у нее прост и незамысловат — бывший боец спецназа Джек Карвер попадает на таинственный островной архипелаг, где он ищет журналистку, которая пропала без вести после атаки наёмников на его лодку. Игра включает тематические элементы, касающиеся опасностей генной инженерии, а также геноцид местных островитян, который показывается на основе мутантов, созданных безумным учёным. В отличии от более новых игр от Crytek Far Cry имел вполне себе божеские по тем временам системные требования (1 ггцевый процессор, 256 МБ ОЗУ и видеокарту со 128 МБ видеопамяти — это уровень рекомендуемых требований к другой топовой игре того времени — GTA Vice City), но уровень графики был гораздо лучше, чем у той же GTA:
Даже сейчас такая графика не доступна на смартфонах — хотя GTA Vice City был портирован под них несколько лет назад.
CryEngine 2
После продажи прав на CryEngine компания Crytek начинает разработку нового движка с незамысловатым названием CryEngine 2. В отличии от первой версии, этот движок разработан только для ПК (ибо консоли того времени — Xbox 360 и PS3 — были слишком слабы для замыслов компании), а так же полностью поддерживал х64 системы, что было редкостью для 2007 года. На этом движке вышло около 5 игр, самые известные: Crysis и дополнение к нему — Warhead.
Crysis
Игра является демонстрацией возможностей движка CryEngine 2, поэтому сюжет тут для галочки: cобытия развиваются в вымышленном будущем, в 2020 году, когда на острове около побережья Китая был обнаружен древний инопланетный космический корабль, и игроку, являющемуся, разумеется, бойцом спецназа, нужно разобраться, что же происходит. В отличии от Far Cry Crysis на момент выхода на максимальных настройках не тянул ни один компьютер. Шутка ли — большинство среднестатистических геймеров того времени сидели на Pentium 4 с 512 Мб ОЗУ и видеокартами уровня GeForce FX 5500/5600 с 64-128 Мб видеопамяти и спокойно играли на высоких настройках к GTA: San Andreas и Half-Life. Рекомендованные системные требования Crysis были такими: процессор Core 2 Duo, 2 Гб ОЗУ и видеокарта с 640 Мб видеопамяти — GeForce GTS 8800! Такое железо стоило больше 1000 долларов и то позволяло играть только на высоких настройках графики. Очень высокие настройки в FHD покорились только с выходом 200ой линейки видеокарт Nvidia через пару лет после выхода игры. Но, в отличии от современных игр, оптимизация у Crysis была на высоте, и игра демонстрировала просто шикарную картинку:
Но чтобы такую красоту сделать, Crytek пришлось постараться.
CryEngine 3
Через пару лет после выхода второй версии движка в Crytek поняли, что забрасывать консольный рынок не выгодно, и выпустили CryEngine 3, в котором оптимизировали движок под Xbox 360 и PlayStation 3. На движке вышло достаточно много игр — это фирменные Crysis 2 и 3, State of Decay, Armored Warfare и прочие. Так же были слухи, что S.T.A.L.K.E.R. 2 выйдет на этом движке, но увы — ни на этом, ни на каком другом движке продолжение сталкера так и не вышло.
Crysis 2 и 3
Игры, являющиеся продолжением и окончанием серии Crysis. От первой части отличались в основном графически, по сюжету же это все тот же научно-фантастический шутер про будущее. Эти игры, ровно как и оригинальный Crysis, заставляли плакать владельцев старых ПК — забавно, но даже сейчас, через 3 года после выхода Crysis 3, ни одна одиночная видеокарта современности не может показать 60 fps в Crysis 3 на ультра в 4К — даже GTX 1080, хотя игра вышла почти 4 года назад. Такие требования идут из-за высокого уровня графики, а не из-за плохой оптимизации (привет, Mafia 3):
Даже по современным меркам это одна из самых красивых игр, по красоте с которой может соперничать разве что Witcher 3, системные требования которого несильно ниже.
Исходный код CryEngine 5.1 опубликован на Github
Немецкая компания Crytek решила опубликовать на Github исходный код игрового движка CryEngine (последняя версия 5.1). Перенести репозиторий на Github решили недавно. Разработчики говорят, что с помощью системы контроля версий Git гораздо удобнее сравнивать ревизии и отслеживать изменения.
CryEngine написан на языке C++. Раньше новые версии движка с исходниками выпускались в zip-архивах. Чтобы определить, какие сделаны изменения, нужно было распаковать архивы предыдущей и новой версий и сравнить файлы. В системе Git всё делается проще. К тому же, публикация на Github, по мнению некоторых экспертов, мотивирует сообщество open-source разработчиков оптимизировать движок под Linux/OpenGL/Vulkan.
Кроме того, пользователям станет удобнее поддерживать CryEngine в актуальном состоянии, поскольку слияние веток кода — коронное преимущество Github, говорит ведущий инженер компании Crytek Дэвид Кэй (David Kaye).
В репозитории опубликована инструкция, как работать с системой Git и репозиторием CryEngine.
Для компиляции CryEngine требуется скачать SDK для конкретной версии движка, инcтрукцию по дальнейшим действиям см. здесь.
Настройка CryEngine для шлема Oculus Rift
На CryEngine разных версий сделано много отличных игр от разных игровых студий, которые лицензировали движок: Far Cry, Crysis, Entropia Universe, Blue Mars, Warface, Homefront: The Revolution, Sniper: Ghost Warrior, Armored Warfare, Evolve и многие другие.
Использование движка по-прежнему ограничено условиями лицензии.