На чем написан сталкер
Ищем аномалии в X-Ray Engine
X-Ray Engine — игровой движок, который используется в играх серии S.T.A.L.K.E.R. 16 сентября 2014 года его исходный код был выложен в открытый доступ, и с тех пор его развитием занимаются фанаты. Большой размер проекта, огромное количество багов в играх — всё это располагает к отличной демонстрации возможностей статического анализатора кода PVS-Studio.
Вступление
X-Ray был создан украинской компанией GSC GameWorld для игры S.T.A.L.K.E.R.: Тень Чернобыля. Движок включает рендер с поддержкой DirectX 8.1/9.0c/10/10.1/11, физический и звуковой движки, мультиплеер и систему искусственного интеллекта A-Life. Впоследствии компания создавала движок версии 2.0 для своей новой игры, но разработка была прекращена и исходные коды утекли в сеть.
Проект вместе со всеми его зависимостями легко собирается в Visual Studio 2015. Для проверки использовался исходный код движка версии 1.6 из репозитория на GitHub и статический анализатор кода PVS-Studio 6.04, загрузить который можно по ссылке.
Copy-paste
Для начала рассмотрим ошибки, связанные с копированием кода. Сценарий их возникновения в разных случаях обычно похож: скопировали код, поменяли часть переменных, а несколько — забыли. Такие ошибки могут быстро распространяться по кодовой базе, и без статического анализатора их очень легко пропустить.
Предупреждение PVS-Studio: V533 It is likely that a wrong variable is being incremented inside the ‘for’ operator. Consider reviewing ‘i’. mxqmetric.cpp 76
Анализатор обнаружил, что во вложенном цикле for инкрементируется переменная i, а проверяется переменная j, что приводит к бесконечному циклу. Скорее всего, при копировании её просто забыли поменять.
Предупреждение PVS-Studio: V517 The use of ‘if (A) <. >else if (A) <. >‘ pattern was detected. There is a probability of logical error presence. Check lines: 1502, 1505. gstats.c 1502
Как и в предыдущем примере, здесь используются два одинаковых условия (strncmp(buff,»\\getpidr\\»,9) == 0). Сложно сказать наверняка, является ли это ошибкой или просто недостижимым кодом, но на это точно стоит обратить внимание. Возможно, что здесь должны быть блоки с getpidr/setpidr по аналогии с getpdr/setpdr.
Предупреждение PVS-Studio: V524 It is odd that the body of ‘width’ function is fully equivalent to the body of ‘height’ function. tpixel.h 1090
Методы height() и width() имеют одинаковое тело. Учитывая, что вычисляются размеры граней куба, возможно, ошибки здесь нет. Но лучше переписать метод width() следующим образом:
Неправильное использование C++
C++ — замечательный язык, который предоставляет программисту много возможностей… отстрелить себе ногу особо жестоким образом. Неопределённое поведение, утечки памяти и, конечно же, опечатки — об ошибках такого рода пойдёт речь в текущем разделе.
Предупреждение PVS-Studio: V591 Non-void function should return a value. _matrix33.h 435
В конце метода пропущен return *this. По стандарту подобный код приведёт к неопределённому поведению. Так как возвращаемое значение является ссылкой, это, скорее всего, приведёт к падению программы при попытке обратиться к возвращаемому значению.
Предупреждение PVS-Studio: V575 The null pointer is passed into ‘fclose’ function. Inspect the first argument. ogg_enc.cpp 47
Довольно интересный пример. Анализатор обнаружил, что аргумент в вызове fclose равен nullptr, что делает вызов функции бессмысленным. Можно предположить, что должны были закрыть поток in.
Ещё один пример странного кода:
Предупреждение PVS-Studio: V603 The object was created but it is not being used. If you wish to call constructor, ‘this->VertexCache::VertexCache(. )’ should be used. vertexcache.cpp 6
Вместо вызова одного конструктора из другого для инициализации экземпляра будет создан и тут же уничтожен новый объект типа VertexCache. В результате члены создаваемого объекта останутся непроинициализированными.
Предупреждение PVS-Studio: V530 The return value of function ’empty’ is required to be utilized. actor_network.cpp 657
Анализатор предупреждает, что возвращаемое функцией значение не используется. Похоже, что программист перепутал методы empty() и clear(): empty() не очищает массив, а проверяет, является ли он пустым.
Такие ошибки нередко встречаются в различных проектах. Проблема в том, что имя empty() не очевидно: некоторые воспринимают его как действие — удаление. Для того, чтобы подобной неоднозначности не возникало лучше добавлять глаголы has, is к началу метода: действительно, isEmpty() с clear() сложно перепутать.
V530 The return value of function ‘unique’ is required to be utilized. uidragdroplistex.cpp 780
Предупреждение PVS-Studio: V575 The ‘memset’ function processes ‘0’ elements. Inspect the third argument. xrdebug.cpp 104
При вызове memset аргументы перепутали местами и в итоге буфер не обнуляется, как изначально задумывалось. Подобная ошибка может жить в проекте очень долго, так как её очень трудно обнаружить. В таких местах на выручку программисту приходит статический анализатор.
Корректное использование memset:
Следующая ошибка связана с неправильно составленным логическим выражением.
Предупреждение PVS-Studio: V547 Expression is always true. Probably the ‘&&’ operator should be used here. configs_dumper.cpp 262
Подробнее об ошибках в логических выражениях можно прочитать в статье «Логические выражения в C/C++. Как ошибаются профессионалы».
Предупреждение PVS-Studio: V674 The ‘0.5f’ literal of the ‘float’ type is compared to a value of the ‘int’ type. boneprotections.cpp 54
Анализатор обнаружил сравнение целочисленного значения с вещественной константой. Возможно, что здесь по аналогии должна была использоваться функция atof, а не atoi, в другом случае стоит переписать это сравнение, чтобы оно не выглядело подозрительно. Однако сказать наверняка, является ли этот пример ошибочным или нет, может только разработчик, писавший его.
Предупреждение PVS-Studio: V595 The ‘object’ pointer was utilized before it was verified against nullptr. Check lines: 42, 47. level_bullet_manager_firetrace.cpp 42
Проверка указателя object на равенство nullptr идёт после того, как разыменовали object->ID(). В случае, когда object равен nullptr, это приведёт к падению программы.
Предупреждение PVS-Studio: V718 The ‘CoInitializeEx’ function should not be called from ‘DllMain’ function. xrcore.cpp 205
В теле DllMain нельзя использовать часть WinAPI функций, включая CoInitializeEx. Убедиться в этом можно, прочитав документацию на MSDN. Нельзя дать какой-то однозначный совет, как стоит переписать эту функцию, но стоит понимать, что такая ситуация опасна, так как она может привести к взаимной блокировке потоков или аварийному завершению.
Ошибки в приоритетах
Предупреждение PVS-Studio: V532 Consider inspecting the statement of ‘*pointer++’ pattern. Probably meant: ‘(*pointer)++’. lwio.c 316
Ошибка связана с использованием инкремента. Для наглядности перепишем данное выражение, расставив скобки:
То есть произойдёт сдвиг не содержимого по адресу bp, а самого указателя, что в данном контексте бессмысленно. Ниже по коду есть фрагменты вида *bp += N, из-за чего я и сделал вывод, что это ошибка.
Избежать подобной ошибки помогла бы расстановка скобок, что сделало бы порядок вычислений более понятным. Также неплохой методикой является использование const для аргументов, которые не должны меняться.
Предупреждение PVS-Studio: V563 It is possible that this ‘else’ branch must apply to the previous ‘if’ statement. hit_memory_manager.cpp 368
В этом фрагменте ветвь else относится ко второму if из-за своей право-ассоциативности, что не совпадает с форматированием кода. К счастью, данный случай не отражается на работе программы, тем не менее, он может усложнить процесс отладки и тестирования.
Рекомендация проста — в более-менее сложных ветвлениях расставляйте фигурные скобки.
Предупреждение PVS-Studio: V502 Perhaps the ‘?:’ operator works in a different way than it was expected. The ‘?:’ operator has a lower priority than the ‘*’ operator. hudsound.cpp 108
У тернарного условного оператора приоритет ниже, чем у умножения, поэтому порядок операций будет следующим:
Очевидно, что правильный код должен выглядеть так:
Выражения, содержащие тернарный оператор, несколько if-else ветвей или операции И/ИЛИ, — это те случаи, когда лучше поставить лишние скобки.
Лишние сравнения
Предупреждение PVS-Studio: V571 Recurring check. The ‘if (m_pPhysicsShell)’ condition was already verified in line 32. destroyablephysicsobject.cpp 33
В данном примере дважды проверяется m_pPhysicsShell. Скорее всего, вторая проверка лишняя.
Предупреждение PVS-Studio: V571 Recurring check. The ‘m_wVersion > 89’ condition was already verified in line 987. xrserver_objects_alife_items.cpp 989
Очень странный код. То ли здесь забыли выражение после if (m_wVersion > 89), то ли целую серию else-if. Данный метод требует более подробного рассмотрения разработчиком проекта.
Ошибки в типах данных
Предупреждение PVS-Studio: V636 The ‘1 / 100’ expression was implicitly cast from ‘int’ type to ‘float’ type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. gl_rendertarget.cpp 245
Значение выражения 1/100 равно 0, так как выполняется операция целочисленного деления. Чтобы получить значение 0.01f, нужно использовать вещественный литерал, переписав выражение: 1/100.0f. Хотя возможно, что данное поведение было предусмотрено автором, и ошибки здесь нет.
Предупреждение PVS-Studio: V579 The strconcat function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the first argument. space_restriction.cpp 201
Функция strconcat, в качестве первого параметра принимает длину буфера. Буфер S объявлен, как LPSTR, то есть как указатель на строку. sizeof(S) будет равен размеру указателя в байтах, то есть sizeof(char *), а не количеству символов в строке. Для вычисления длины следует использовать strlen(S).
Предупреждение PVS-Studio: V712 Be advised that compiler may delete this cycle or make it infinity. Use volatile variable(s) or synchronization primitives to avoid this. xrcdb.cpp 100
Компилятор может убрать проверку S_INIT == status в качестве оптимизации, так как переменная status не модифицируется в цикле. Для того, чтобы избежать подобного поведения, нужно использовать volatile переменные или типы синхронизации данных между потоками.
Предупреждение PVS-Studio: V547 Expression ‘squad->get_index(this) == u32(- 1)’ is always false. The value range of unsigned char type: [0, 255]. ai_rat.cpp 480
Для того, чтобы понять, почему это выражение всегда ложно, вычислим значения отдельных операндов. u32(-1) равен 0xFFFFFFFF или 4294967295. Тип, возвращаемый методом squad->get_index(. ), — u8, следовательно его максимальное значение — 0xFF или 255, что строго меньше, чем u32(-1). Соответственно, значением такого сравнения всегда будет false. Данный код легко исправить, поменяв тип данных на u8:
Та же диагностика срабатывает и для избыточных сравнений беззнаковых переменных:
Предупреждение PVS-Studio: V547 Expression ‘m_tActionCondition.m_tLifeTime >= 0’ is always true. Unsigned type value is always >= 0. script_entity_action_inline.h 115
Переменная m_tLifeTime является беззнаковой, соответственно она всегда больше или равна нулю. Является ли это лишней проверкой или же здесь скрыта ошибка в логике, судить разработчику.
История игровых движков №7 — X-ray Engine
История появления движка
Движок полностью разработала украинская игровая студия GSC Game World, над графической частью движка в основном работали программисты Алесь Шишковцов и Александр Максимчук (они же работали в 4A Games над проектом Metro 2033). Первая демонстрация движка была проведена в 2001 году:
Как видно, изначально тематики S.T.A.L.K.E.R. не было и близко — демонстрация была больше похожа на джунгли Far Cry 1. Однако уже тогда движок был достаточно продвинутым — работал на DirectX 7 (в дальнейшем к выходу Теней Чернобыля в 2007 году добавили поддержку DirectX 8 и 9), поддерживал Detail mapping, Lightmap, продвинутый AI и текстуры высокого разрешения — в одном кадре могло находиться до 4 000 000 полигонов. Однако, чтобы всю эту красоту могли тянуть компьютеры того времени, пришлось сильно ограничить размер игровой локации — она могла быть не больше 2 кв. км и грузилась сразу и целиком, но между локациями были переходы с загрузочными экранами. X-Ray был написан на C/C++ с использованием исходников Microsoft DirectX SDK, с подключенным к нему компилятором языка LUA “lua.JIT.1.1.4”. При работе использовались библиотеки Microsoft Visual C++ 7.1, Creative EAX (технология для создания звуковых эффектов окружающей среды), ODE (открытый физический движок), OpenAL(интерфейс программирования приложений (API) для работы с аудиоданными, GameSpy Client (мультиплеерная составляющая) и Color Picker.
A-Life
Еще одна особенность движка, которой могут гордиться разработчики — это система искусственного интеллекта A-Life (artificial life — искусственная жизнь). Сами разработчики описывают ее так:
Суть [A-Life] заключается в том, что персонажи в игре живут своей жизнью и существуют всё время, а не только когда их видит игрок. [. ] Мы ввели два термина, характеризующие 2 модели поведения персонажа, отличающихся степенью детализации: офлайн и онлайн. Офлайновое поведение персонажа является очень простым с точки зрения детализации: персонаж не отыгрывает анимации, звуки, не управляет активно инвентарём, не строит детализированные сглаженные пути (хотя строит пути по глобальному навигационному графу, но об этом позже) и т.д. Онлайновое поведение напротив имеет полную степень детализации. Т.е. можно считать, что офлайновое поведение является плодом онлайнового.
В нашей системе пока игрок играет на своём уровне, другие персонажи живут на других уровнях, т.е. находятся в офлайне, т.е. используют офлайновое поведение. Более того, ввиду большой населённости, не все персонажи в пределах одного уровня имеют онлайновое поведение, а лишь те, кто находится в заданном радиусе от игрока (это может зависеть от уровней, обычно в районе 150 метров) или же по желанию геймдизайнеров.
Для реализации этого симулятор следит за передвижением игрока и объектов в офлайне и переводит их в онлайн/офлайн. При вычислении перехода объектов используется стандартный трюк с инерцией: радиус перехода в офлайн больше радиуса перехода в онлайн.
Далее стоит сказать о навигации объектов в онлайне и офлайне. У нас в игре есть уровни, для каждого из которых создаётся свой навигационный граф, который используют персонажи для передвижения в онлайне. Мы называем его детальным графом. Для каждого детального графа также создаётся его менее детализированный аналог, вершины которого можно связать с вершинами такого же графа другого уровня/ей. Т.е. после объединения всех таких графов воедино мы получаем граф, который объединяет все уровни. Он и используется персонажами для передвижения в офлайне. Также им пользуются персонажи в онлайне, когда они выполняют свои стратегические цели. Например, если персонаж в онлайне решил идти на другой уровень, то он строит путь по глобальному графу, затем строит путь по детальному графу своего уровня со своей позиции до точки глобального графа. Если эта точка уже на другом уровне, то он телепортируется туда и автоматически переходит в офлайн. Для того, чтобы это не происходило на глазах у игрока, мы точки перехода для игровых персонажей ставили дальше точки перехода игрока, где-то «за углом»
Как видно, ИИ сделан очень качественно, и на протяжении всей игры нет ощущения, что Зона мертва — постоянно происходят какие-то события: перестрелки, встреча различных сталкеров, мутантов, появление новых сообщений на КПК и т.д.
В заключительной части трилогии, Зов Припяти, вышедшей в 2009 году, движок получил поддержку DirectX 11. Однако увы — все его особенности движок 8илетней давности не потянул, поэтому добавили только тесселяцию (процесс добавления новых выпуклых многоугольников в полигональную сетку с целью повышения детализации сетки, иными словами — выпуклые объекты стали реалистичнее). Общий уровень графики остался тем же:
В «X-Ray» используется свободный физический движок Open Dynamics Engine (ODE). Его особенности:
S.T.A.L.K.E.R. Wiki
Редактор продолжает работу над этой статьёй, но временно отошёл попить чаю. Пожалуйста, не спешите с собственными правками.
X-Ray | |||
Логотип | |||
Название | X-Ray Engine | ||
Ключевые разработчики | Олесь Шишковцов Александр Максимчук Дмитрий Ясенев Андрей Коломиец | ||
Среда разработки | Microsoft Visual Studio 6.0 [прим. 1] .NET [прим. 2] .NET 2003 [прим. 3] | ||
Используемые API | Boost, DirectPlay, DirectX, EAX, FreeImage, FreeMagic, Lua, Lua.JIT, NvAPI, ODE, OGG, OpenAL, SciLexer, Theora, Vorbis, XML |
X-Ray — трёхмерный игровой движок, разработанный программистами Олесем Шишковцовым и Александром Максимчуком в компании White Lynx. Впоследствии был представлен компании GSC Game World, где началась разработка проекта Oblivion Lost на этом движке.
Содержание
История разработки
Написание движка началось в 1998-м году ведущим программистом компании White Lynx — Олесем Шишковцовым. Он занимался разработкой графического движка, искусственного интеллекта и сетевым режимом. Позже к разработке присоединился Александр Максимчук, который занимался всем остальным. Движок получил рабочее название wlynxengine, на нём началась разработка трёхмерного симулятора игры «Морской бой». Предположительно, игра в первоначальном виде была выпущена для игровых автоматов и ПК, но не получила никакой известности. После этого началась разработка проекта Alien Logic, движок получил название X-Ray. Олесь занимался написанием полноценной системы движка для игры. Разработка проекта происходила нестабильно: игра то замораживалась, то разработка возобновлялась.
GSC Game World
2000 год
В 1999-м году у руководителя компании White Lynx Юрия Сторчака начались финансовые проблемы, в связи с чем ведущие программисты Олесь и Александр начали искать новую работу, не увольняясь из White Lynx. За несколько месяцев они написали техническую демо-версию сетевого шутера на своём движке X-Ray. Весной 2000-го попытались устроится на работу в компанию GSC Game World, продемонстрировали наработки на собеседовании руководителю компании Сергею Григоровичу, ему понравился результат и он принял на работу программистов. Так началось развитие движка в компании GSC.
11 сентября последняя правка и сборка редактора уровней XRay Map Editor (Gold edition), написанный в среде Microsoft Visual Developer Studio 6.0. В конце сентября, Александр Максимчук начал переносить исходный код редактора в среду Borland C++ 5.0, который был закончен к концу ноября 2000.
Иерархия классов | |
---|---|
Класс | Приуроченный подкласс |
Basic | Camera |
Object | |
Camera | Cameras_Array |
Object | Hero |
Key | |
Bridge | |
Teleport | |
Bird | |
CXR_APP | |
CXR_Input | Устройство ввода |
CXR_Inifile | ini-файл системы |
CXR_Textrloader | Загрузка и управление текстурами |
CXR_SoundManager | Загрузка и управление звуками |
CXR_Creator [прим. 4] | |
FStaticRender | Загружает, содержит и рендерит всю статику которая раз загружается и в процессе не изменяется |
CXR_Render | Рендерит все остальное |
CXR_EffectManager | Эффекты |
CXR_Meshloader | Загрузка объектов |
CXR_Cameras_Array | Камеры уровня |
CXR_Console | Консоль вывода на экран |
CXR_Inifile | ini-файл уровня |
CXR_Space | Назначение определения столкновения между объектами. Загружает и содержит Collision Forms для всей статики. добавляет, удаляет и перемещает формы для динамических объектов. |
CXR_Stationary_Array | — |
CXR_Mobile_Array | — |
CXR_Command_List | Список событий |
В процессе перевода функционала редактора на Borland C++, было решено отказаться от компиляции локаций внутри редактора и вывести компиляцию в отдельно-исполняемое приложение. 10 октября Олесь Шишковцов начал реализовывать компилятор геометрии — xrLBuilder.
В конце октября внутренний формат текстур — .t был окончательно вытеснен растровым форматом .TGA, который теперь использовался для всех текстур. Были проведены тесты отрисовки геометрии с использованием AABB, OBB отсечения и технологии progressive meshes, которая была доработана Шишковцовым. В начале ноября была оптимизирована работа просчета коллизии объекта, в частности RayPick [прим. 7] и общих математических операций, а также скелетной анимации. Движок был переведен на новый DirectX 8.0 API.
2001 год
Tavex
В 2000-м году произошло слияние White Lynx с компанией Tavex, она же TerraGame Group [прим. 13] и на движке X-Ray началась разработка некоторых новых (Tangram 3D, Pacman 3D, Titan Racer и Kremlin Puzzle) и доработка старых проектов. В 2001-м году были доработаны и выпущены Alien Logic, а также трёхмерный симулятор «Морского боя» 3D Armada (где использовалась самая первая версия движка wlynxengine).
Характеристики и особенности движка
X-Ray написан на C/C++ с использованием исходников Microsoft DirectX SDK, с подключенным к нему компилятором языка LUA “lua.JIT.1.1.4”. Использует при работе библиотеки Microsoft Visual C++ 7.1, Creative EAX, ODE, OpenAL, GameSpy Client, ColorPicker.
Графический движок
Direct3D 7 API [прим. 14] [прим. 15]
Direct3D 8.1 API (Shader Model 1.0—1.1) [прим. 17]
Direct3D 9.0\9.0c API (Shader Model 2.0—3.0)
Динамические мягкие тени
Подсчет файлов в исходниках, и их расширения
Direct3D 10 API (Shader Model 3.0—4.0)
Direct3D 10.1 API (Shader Model 4.0—4.1)
Direct3D 11 API (Shader Model 4.1—5.0)
Физический движок
В «X-Ray» используется свободный физический движок Open Dynamics Engine (ODE). Его особенности:
Игровой ИИ
Звуковой движок
Папки, количество файлов находящихся в них, и общий размер
Звуковой движок использует технологию (HRTF) для качественной генерации звука. Поддерживаются различные эффекты (эхо, зависимость громкости от расстояния источника, эффект закрытого помещения). Возможен вывод шестиканального (5.1) звука, поддерживается технология EAX 2.0. Звуковые данные сохраняются в медиаконтейнерах WAV, WMA, OGG, ADPCM.
Сетевая игра
Движок использует сетевую модель «клиент-сервер» и технологию распределённых вычислений. Движком поддерживается до 32 игроков в одном матче. Также, есть несколько особенностей:
Статус движка
Исходные коды X-Ray закрыты, сторонним компаниям по лицензии не поставляются. Хотя, судя по некоторым интервью с разработчиками, изначально движок планировали продавать. Это также косвенно подтверждает презентация, проведённая GSC в 2004 году на одной игровой выставке, где Олег Яворский рассказывал исключительно о функциях и преимуществах движка.
После релиза Lost Alpha были выложены исходники движка для некоммерческого пользования.
Список игр на X-Ray
После создания X-Ray на нём планировалось много проектов — разработчики пытались использовать движок во многих игровых жанрах. Одни из них, наиболее близкие к Oblivion Lost и S.T.A.L.K.E.R., — 2 отменённые игры, Pilgrims (рус. «Пилигримы») и Commandos (оба названия рабочие).
Piligrims
Для отработки концепции этой игры проводили тесты производительности на X-Ray, массовости мобов, загруженности ландшафта, детализацией объектов. Но её разработку было решено остановить (в последствии отменить), хотя даже после отмены Алексей Мосейчук получил задачу подготовить общее видение игры в скетчах (без живописных и атмосферных зарисовок).
Commandos
Игра в осуществлении требовала много ресурсов и разработка была отменена.