Грязные трюки в коде игр

http://www.gamasutra.com/view/feature/132500/dirty_coding_tricks.php
  • Перевод


[Когда график поджимает и проект уже пора выпускать, программисты могут прибегать к грязным трюкам, чтобы уже наконец выпихнуть игру за дверь. В этой статье собрано девять примеров таких «костылей» из реальной жизни.]

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

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

Сними меня в самом хорошем ракурсе


Примерно четыре года назад [прим. пер.: статья написана в 2009 году] я работал программистом в многоплатформенном проекте для PlayStation 2, Xbox и GameCube. Сроки разработки подходили к концу, потому неудивительно, что в код начали проникать хаки. В версии для PS2 нашлась поздно обнаруженная проблема, которую было очень трудно отследить: при длительном тесте стабильности первого уровня со стоящим неподвижно персонажем игра «вылетала». К сожалению, эта ошибка возникала только в дисковой сборке для розничной продажи, в которой не было никакой отладочной информации. Боясь отказа в публикации от отдела проверки технического соответствия (technical requirement check, TRC) Sony, мы усердно трудились на решением.

Чтобы сузить границы возникновения ошибки, мы постоянно рендерили границу вокруг края экрана с отдельными цветами для разных частей кода. Например, синим обозначалась настройка рендеринга, зелёным обозначалось обновление управления игрока. Поскольку сбои возникали не всегда, ведущий инженер и я вручную прожигали кучу дисков и запускали их на множестве консолей PS2. (Надо учесть, что компания была независимым разработчиком, у неё не было ИТ-отдела и крутого дискозаписывающего оборудования.) Тестовый цикл — изменение кода, запись дисков, запуск и ожидание для сужения приблизительных границ возникновения ошибки — занимал долгие часы. Это были последний этап разработки, и мы могли выполнить только строго ограниченное количество таких циклов.



Проводя выходные дни в офисе в ожидания сбоя, мы убивали время за World of Warcraft. Вдруг кто-то из нас заметил, что игра не «вываливается», если повернуть камеру на 90 градусов вправо. Сначала программисты справедливо отмахнулись от этого как от случайности, скрывающей более глубокую ошибку. Исправление такого поведения не устранило бы корень проблемы. Однако мы всё равно исправили его! Когда у нас кончилось время, мы создали последний диск PS2 с повёрнутой в начале уровня камерой (версии для двух других платформ уже были готовы) и отправили на проверку. Игра прошла все тесты владельцев платформ и была вовремя выпущена на рынок.

Я не назвал бы это решение «трюком в коде», но оно определённо было «грязным». Мистика этого исправления до сих пор не раскрыта, но, к счастью, я ни разу не слышал, чтобы пользователи жаловались на такую проблему.

— Марк Кук (Mark Cooke)

Кризис самоопределения


Эта ситуация знакома всем разработчикам игр: сегодня мы отправляем на «золото» нашу игру для Xbox 1. Команда тестирует игру весь день, желая убедиться, что всё в порядке. Все радостны и спокойны, для нас работа уже закончена.

После обеда мы создаём последнюю сборку с небольшими последними настройками игрового баланса и начинаем последний сеанс тестирования, после чего происходит катастрофа: игра упорно «вылетает»! Мы все бежим к своим рабочим машинам, запускаем отладчик и пытаемся выяснить, что же происходит. Это не что-то тривиальное, вроде assert, и даже не то, что относительно сложно отследить, типа деления на ноль. Похоже, что в нескольких фрагментах памяти находится «мусор», но проверка памяти сообщает, что всё в порядке. Что происходит?

Много часов спустя наши мечты о выпуске в срок разрушены. Мы пытаемся найти ошибку и обнаруживаем, что один файл данных загружается с неверными данными. Неверные данные? Как такое возможно? Наша система ресурсов управляет каждым ресурсом с помощью 64-битного идентификатора, состоящего из CRC32 полного имени файла и CRC32 всего содержимого файла. Таким же способом мы объединяли одинаковые файлы ресурсов игры в один. У нас были десятки тысяч файлов и два года разработки, при этом конфликты никогда не возникали. Никогда.

До этого самого момента.

Оказалось, что одно из невинных небольших исправлений, которые дизайнеры проверяли после обеда, приводило к тому, что текстовый файл имел абсолютно такое же имя и CRC, что и другой файл ресурсов, несмотря на то, что они были совершенно разными!

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

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

«Отправляем!»

— Ноэль Льопис (Noel Llopis)

Езда в нетрезвом виде


Мне дали задание поработать над системой управления транспортом, которая была важной частью нашей игры. К счастью, мы могли взять бóльшую часть кода у другой студии. К несчастью, код не всегда казался идеальным, как это и бывает почти со всем кодом, который не пишешь сам. Я обнаружил этот милый фрагмент кода, который получает переменную из цикла движка, но делает это максимально тупым способом (см. листинг 1).

//**************************************************
// Function: AGameVehicle::Debug_GetFrameCount
//
//! Очень грязный способ получения текущего количества кадров; переменная защищена.
//!
//! \Возвращает текущее количество кадров.
//**************************************************
UINT AGameVehicle::Debug_GetFrameCount()
{
BYTE* pEngineLoop = (BYTE*)(&GEngineLoop);
pEngineLoop += sizeof( Array<FLOAT> ) + sizeof( DOUBLE );
INT iFrameCount = *((INT*)pEngineLoop);
return iFrameCount;

Самое смешное в этом фрагменте невероятно отвратительного кода заключается в том, что у объекта была простая функция для получения количества кадров. Но даже если её бы и не было, то написавший этот код мог легко добавить её самостоятельно! Не нужно говорить, что когда я указал на ошибку, этот код не использовался ни в моей игре, ни в той, откуда он был взят. И если это не пример того, почему стоит делать анализ кода, то я уж и не знаю, какие примеры ещё нужны.

— Остин Макги (Austin McGee)

Десятичный код


Работая в [компании X], я думал, что мы никогда не доберёмся до завершения [проекта]. На одном из уровней у нас находился объект, который должен был быть скрытым. Нам не хотелось повторно экспортировать уровень и мы не использовали имена с контрольными суммами. Поэтому прямо посреди кода движка у нас было что-то вроде такого. И игра вышла с этим кодом.

if( level == 10 && object == 56 )
{
HideObject();

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

— Аноним

Все знаки говорят «нет-нет»


Эта проблема возникла при разработке нового Wolfenstein Raven Software. Я писал настройку поддержки контроллера для Xbox 360. Оказалось, что при интеграции с Live необходимо знать, какой именно контроллер передаёт события ввода. Использованный нами код ввода Doom 3 практически полностью был скопирован из Quake 3, поэтому это была довольно простая система.

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

Поэтому к нам пришла другая идея: просто используем наш быстрый покадровый распределитель памяти без фрагментации для выделения памяти, запишем в неё идентификатор контроллера, а затем передадим указатель на него в указателе события.

Выяснилось, что система событий после обработки события самостоятельно очищает с помощью free() нулевой указатель события. Разумеется, такое поведение было совершенно несовместимо с нашим multiheap-подходом. Более того, в коде присутствовали старые фрагменты кода Doom 3, которые полагались на вызов free() в коде события, поэтому мы не могли просто удалить вызов без внесения нетривиальных изменений во всю базу кода. А если добавить третий целочисленный параметр? Тогда придётся изменять несколько сотен вызовов функций.

Дедлайн был уже на носу, и у меня не было времени на решение проблемы. Поэтому я решился на немыслимое — упаковал ID контроллера в параметр указателя. В четырёхстрочном комментарии, целиком набранном «капсом», я пометил это как ужасный хак, и загрузил в базу кода. И этот костыль работал без проблем, пока вся система ввода не была заменёна чем-то немного более хорошим.

— Дэвид Дайнермен (David Dynerman)

Липкие жулики


image

Вот история проекта из ранних дней PS2. У нас была куча проблем с коллизиями/границами, которые как правило решались в последнюю минуту переписыванием системы коллизий персонажа. Её меняли на модель «коллайдеров» — набор сфер, который гораздо лучше обрабатывал столкновения, чем наше иерархическое дерево ориентированных граничных контуров (то были времена до появления движка Havok).

Однако у нас постоянно возникал редкий «баг», который сводил с ума. Мы называли его «липучестью» — каждый раз, когда персонаж игрока находился рядом со стеной и скользил вдоль неё, сфера коллайдера внезапно решала, что он находится на другой стороне стены, и не давала оторваться от неё (то есть персонаж прилипал к поверхности).

Это был одна из тех проклятых ошибок «вроде бы всё просто». Нужно всего лишь выяснить, почему сфера считает, что находится на другой стороне, и исправить это. Проблема в том, что нужно отследить, что происходит во всех случаях распознавания коллизий перед возникновением этой проблемы.

Когда нам не удалось решить проблему удобными условными контрольными точками в коде, отслеживающими странные отклики, или изменением положения, запутывавшим коллайдер, мы просто снизили частоту кадров, чтобы «баг» просто не возникал. (Настоящее решение этой проблемы потребовало бы крови, пота, слёз и других жидкостей, без которых обычно можно обойтись, но это уже другая история.)

Мы отправляли игру на тестирование и постоянно получали ответ, что игра с такой ошибкой выпущена не будет. Даже придумали одно «решение» — увеличить границы сферы настолько, чтобы гарантировать отсутствие проблем, но это никак нельзя назвать правильным исправлением. Мы перенаправили игру с этим «багом» команде художников, чтобы они исправляли его, и выгадали себе драгоценное время на поиск истинной, глубинной проблемы.

Тестеры начали отправлять нам отзывы типа «игрок сталкивается с невидимой стеной, такого быть не должно». Эта проблема того же уровня серьёзности (мы снова сузили границу коллайдера и персонаж стал близко подходить к стене, потому что «липучесть» возникала очень редко и её было трудно воспроизвести), но после пары таких циклов, за которые мы исправили остальную часть игры, решение, требовавшее крови, пота и слёз, было наконец найдено, и игра продолжила свой путь в тираж и на полки магазинов.

Не скажу, что горжусь этим циклом решения проблем путём избегания и увёрток, но, по крайней мере, нам удавалось временно снять эту ношу с плеч. Мы всегда говорили «ой, нет, мы вчера уже отправили игру на тестирование», и продолжали решать чёртову проблему.

— Аноним

Я и мои патчи


Есть старый анекдот, примерно такой:

Больной: «Доктор, у меня болит, когда делаю вот так».

Доктор: «Тогда не делайте так».

Забавно, но разве в определённой ситуации это не мудрые слова? Представьте мою боль, когда я работал над портированием трёхмерного шутера от третьего лица с PC на первую PlayStation.

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

В PC-версии игры геометрия уровней работала хорошо, но при преобразовании в значения с фиксированной запятой из-за микроскопических отличий между плавающей и фиксированной запятой проявлялись все швы, Т-образные соединения и другие проблемы. Эта сложность давала о себе знать: основной персонаж (по имени Damp) просто проваливался сквозь эти крошечные отверстия в пустоту под уровнем.

Мы исправили все найденные дыры, настроив геометрию таким образом, чтобы Damp больше не проваливался. Но когда игру отправили на тестирование издателю, он внезапно сообщил нам о множестве ошибок «проваливания сквозь мир». Каждый день находился новый список мест, через отверстия в которых мог провалиться Damp. Мы устраняли их, исправляя геометрию, а на следующий день нам сообщали ещё о десятке других мест. Так продолжалось несколько дней. Отдел тестирования издателя нанял одного работника, чьей единственной задачей были прыжки по миру по десять часов в день для поиска мест, в которые можно провалиться.

Проблема заключалась в том, что геометрия была плоха. Она не была плотной и бесшовной. Её было достаточно для PC, но не для PS1, где математика с фиксированной запятой сильно усложняла проблемы. Идеальным решением было бы исправление геометрии, делающее её бесшовной.

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

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

Наконец, до нас дошло: истинная проблема не в дырах геометрии. Проблема в том, что Damp проваливается в эти дыры. Поняв это, я смог написать очень быстрый и простой фикс, который выглядел примерно так:

IF (Damp проваливается сквозь дыру()) THEN
Не проваливаться

На самом деле код был ненамного сложнее (см. листинг 2).

Листинг 2: я и мои патчи

damp_old = damp_loc;
move_damp();
if (NoCollision())
{
damp_loc = damp_old;

Тысяча ошибок была исправлена одним махом. Теперь вместо того, чтобы проваливаться сквозь уровень, проходя над дырами, Damp просто слегка подёргивался. Мы выяснили причину наших страданий и перестали «так делать». Издатель уволил своего тестера-«прыгуна», и игра была выпущена.

То есть выпущена через какое-то время. Вдохновлённый успехом подхода «if A==плохо then NOT A», я воспользовался этим инструментом для патчинга ещё нескольких ошибок. Почти все они были связаны с кодом распознавания столкновений. Ближе к концу разработки ошибки становились всё более и более специфичными, а исправления больше походили на «не делать в_точности_это_действие» (см. листинг 3: это настоящий код, оставшийся в игре).

Листинг 3: я и мои патчи

if (damp_aliencoll != old_aliencoll &&
strcmpi("X4DOOR",damp_aliencoll->enemy->ename)==0 &&
StartArena == 6 && damp_loc.y<13370)
{
damp_loc.y = damp_old.y; // никогда не давать
damp'у прикасаться к двери. (перемещаем x и y)
damp_loc.x = damp_old.x;
damp_aliencoll = NULL; // в таком вот аксепте

Что делает этот код? Проблема возникала, когда Damp касался определённого типа дверей на определённом уровне и в определённом месте, и вместо исправления первопричины я сделал так, чтобы при касании двери Damp отодвигался от неё и притворялся, что ничего не произошло. Проблема решена.

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

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

Этот опыт настроил меня против «заплаток». Теперь я всегда стремлюсь докопаться до корней ошибки даже при наличии простого и кажущегося безопасным патча. Я хочу, чтобы мой код был здоровым. Когда вы идёте к врачу и говорите «у меня болит, когда делаю вот так», то ждёте, что он выяснит причину боли и вылечит её. Боль, как и ошибки в коде, может быть симптомом чего-то гораздо более серьёзного. Мораль: обращайтесь со своим кодом так, как с вами должен обращаться врач.

— Мик Уэст (Mick West)

В гневе я страшен


Однажды я работал в студии THQ Relic Entertainment над The Outfit, которую вы можете помнить как одну из первых игр для Xbox 360. Мы начали с движка для PC (однопоточного) и хотели примерно за 18 месяцев превратить его в законченную игру для многоядерной консоли нового поколения. Примерно за три месяца до выпуска игры она работала на 360 со скоростью примерно 5 FPS. Было очевидно, что игре нужна серьёзная оптимизация.

Замерив производительность, я понял, что кроме медленности и «PC-стиля» кода есть также куча проблем с контентом. Некоторые модели были слишком детализированными, шейдеры — слишком затратными, а на некоторых уровнях слишком много персонажей.

Сложно убедить команду из ста человек, что программисты не могут просто «исправить» производительность движка, и что необходимо изменить шаблоны работы, к которым многие привыкли. Им нужно было осознать, что производительность игры — проблема для всех, и я придумал лучший способ показать это шуткой, в которой была доля истины.

Решение заняло около часа. Коллега-программист сделал четыре фотографии моего лица: я счастлив, спокоен, немного рассержен, рву на себе волосы. Я поместил фотографию в угол экрана и привязал её к частоте кадров. Когда в игре было больше 30fps, я был счастлив, когда ниже 20, я злился.

После этих изменений подход к проблеме FPS полностью изменился: вместо «Ой, да это проблема программистов» «Хм, если я использую эту модель, то Ник рассердится! Лучше немного её оптимизировать». Люди постоянно видели, как вносимые ими изменения влияют на частоту кадров, и в результате мы выпустили игру с 30fps.

— Ник Вандерс (Nick Waanders)

Антигерой программирования


Я только что выпустился из колледжа, был молод и наивен. Мы как раз переходили к стадии бета-версии моего первого профессионального проекта — игры для PC конца 90-х. Это было увлекательное катание на американских горках, как часто происходит с проектами. Весь контент уже был готов, и игра выглядела неплохо. Однако мы нашли одну проблему: не удавалось уместиться в объём доступной памяти.

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

Мы урезали мегабайт за мегабайтом, и через несколько дней безумного напряжения достигли точки, в которой сделать было ничего уже невозможно. Хоть мы и урезали часть важного контента, освободить больше памяти мы никак бы не смогли. Измученные, мы замерили текущий объём используемой памяти. Он был на полтора мегабайта больше предельно допустимого!

В этот момент один из самых опытных программистов нашей команды, переживший многолетний опыт разработки в «старые добрые времена», решил взять решение в свои руки. Он позвал меня в свой офис и мы начали работу, которую я сначала представлял ещё одним выматывающим марафоном для освобождения памяти.

Вместо этого он открыл файл исходника и показал мне эту строку:

static char buffer[1024*1024*2];

«Видишь?» — спросил он меня. И тут же удалил её одним нажатием. Готово!

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

Он вышел из офиса и объявил, что снизил объём памяти до необходимого, и все чествовали его как героя проекта.

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

— Ноэль Льопис (Noel Llopis)
Метки:
Поделиться публикацией
Комментарии 56
  • +9
    Последние два трюка взял на вооружение!
    • +11

      Последняя история напомнила рассказ "Мне не хватило одного байта"

      • +1
        Спасибо за ссылку! Не читал подобного ещё. Но прочитав, вспомнил дела 10-12-летней давности, когда проектировал всякие штуки на pic16f84a, и некоторые тоже с жёсткими временными рамками, почти реалтайм. Приходилось высчитывать (вручную) каждую команду, сколько она тактов займёт, сколько выполняется процедура, сколько это будет в миллисекундах, и т.п. Но там не было такого адского сочетания ограничений — места хватало, время можно было выкроить ещё, чуток оптимизировав код.
        На одном из форумов в интрасети (РЖД) как-то устраивали соревнования по определённым задачам. Например, простейшая задача — бегущая строка с заданным текстом. Требуется минимизировать размер кода. И вроде бы уже нормальный код — но тут же кто-то выкладывает ещё короче, а потом ещё короче. И после сотни доработок, когда всем уже кажется, мол, всё, вот и победитель — оказывается, можно ещё сократить! Вот уж там простор для всяческих уловок.
        В то время мне ох как надоел этот машинный код, и я просто забросил тему микроконтроллеров. Ограничился использованием готового кода, благо, уже тогда была уйма наработок.
        • +1
          А сейчас в это можно играть: Shenzhen IO
          • 0
            Только вчера отправил шефу этот рассказ. Точнее, его перевод на английский, так как шеф — австралиец.

            А по поводу конкурсов — были когда-то конкурсы: 4Кб кода (обычно всякие трёхмерные поверхности всячески жеформировались), 16Кб кода (это уже с музыкой). Где-то у меня лежат эти демки…

            Когда мало кто знал, что значит Ctrl-Alt-Del
            И не каждый ребёнок калькулятор имел…
            • 0
              были когда-то конкурсы

              давноооо это было

              • 0
                Да… интересно… многое поменялось. Ощущение, что под «4К» подразумевается разрешение монитора, а не размер кода :)
                А вот как они считают «4К», если теперь здесь даже музыка есть. Что в этот объём входит? В старом варианте был файлик.ехе, который мог разве что прерывания BIOS дёргать. А под чем сейчасошние демки работают?
                • +1
                  Есть игра, которая занимает 96 КБайт это вполне приличный 3Д экшн https://ru.wikipedia.org/wiki/.kkrieger
                  • 0
                    96 — это уже много. У ZX-Spectrum было всего 32, из них шесть и три четверти — видеопамять (с феерической организацией). 3D шутера, правда, я на нём не видел. Но там и процессор был 3.5МГц.
        • 0
          Познавательно) С удовольствием прочитал.
          • +3
            загрузил в базу кода

            заменёна чем-то немного более хорошим

            Что случилось, у вас же были всегда хорошие переводы?

            • +25
              Некоторые комментарии к оригиналу статьи просто восхитительны. Например, этот:
              Back on Wing Commander 1 we were getting an exception from our EMM386 memory manager when we exited the game. We'd clear the screen and a single line would print out, something like «EMM386 Memory manager error. Blah blah blah.» We had to ship ASAP. So I hex edited the error in the memory manager itself to read «Thank you for playing Wing Commander.»

              ссылка
              • +29
                Ну и классика))

                http://bash.im/quote/413852
                Ошибка: робот погибает при попадании в него гранаты (именно от попадания, а не от взрыва) Д — дизайнер, П — программист.
                Д: программисты всё сломали! почему так получается?!
                П: естественно так получается! потому, что у гранаты масса 100 кг! зачем вы это сделали?
                Д: да?! а чтобы граната в воде тонула!
                П: а почему она с нормальной массой не тонет?
                Д: а потому что у воды плотность большая! (прим.: больше, чем у ртути)
                П: а почему плотность такая большая?!
                Д: а чтобы ящики деревянные плавали!
                П: а почему они иначе не плавают?!
                Д: а потому что у них масса 50 кг!
                П: а зачем такая масса?!
                Д: а иначе они некрасиво разваливаются!

                • +14
                  Ну и классика))

                  В дополнение :)
                  • +5
                    Ну тогда уж и еще одно
                    • +7
                      Skeeet> Играл в Сталкера. Так и не понял что курили девелоперы, но навесные замки на дверях начинают неплохо кровоточить, если их побить ножиком… Напомнило старый боян про кенгуру и рефакторинг.

                      Повторное использование объектно-ориентированного кода (в программах) вызвало головную боль у Австралийских Вооруженных Сил. Т.к. симуляторы все активнее используются для тренировок боевых действий вертолетов, от программистов требуется постоянное повышение реализма используемых сценариев, включая детальные ландшафты местности и — в случае операции Феникс — стад кенгуру (т.к. испуганные животные могут легко выдать расположение воинских частей). Hачальник отдела симуляций наземных операций Defense Science and Technology Organization приказал разработчикам смоделировать перемещения кенгуру и их реакцию на вертолеты. Будучи грамотными программистами, те использовали готовые программные объекты, описывающие поведение пехоты в аналогичной ситуации, заменив изображения солдат на изображения животных и увеличив их скорость. Желая продемонстрировать свое мастерство перед посетителями — американскими пилотами — горячие австралийские парни «разбудили» кенгуру, пройдя над ними на малой высоте во время симуляции. Кенгуру разбежались, как и предполагалось, и американцы понимающе кивнули: А затем сильно удивились, т.к. кенгуру, перегруппировавшись, появились из-за холма и выпустили тучу стингеров по злополучным вертолетам. (Программисты забыли удалить соответствующий кусок кода из «пехотных» объектов). Hачальник симулятора отметил, что пилоты с этих пор боятся кенгуру как огня, для чего, собственно, и нужен был этот кусок кода в симуляторе.

                      Была такая игрушка Jagged Alliance. Нечто вроде квеста с вкраплениями пошаговых боёв-перестрелок.

                      Так вот, в процессе боя можно было (если хватало AP — очков времени) подбежать к противнику, и попытаться отобрать у него оружие. Успешность отбора, конечно, зависела от значений «сила» и «усталость» обоих персов, причем помочь врагу «устать» помогал удар-другой кастетом или монтировкой.

                      И все было бы правильно, если бы не чудеса наследования.

                      В игре были еще и враги-нечеловеки: тигры-людоеды. Естественно, основанные на классе «враг». Так вот, надавав тигру по черепу монтировкой, у него можно было отобрать оружие «челюсти». Такая вот челюстно-лицевая хирургия в полевых условиях.

                      Еще (в «фантастическом» режиме игры) были жуки-монстры наподобие «Чужих». Тоже, как вы догадываетесь, основанные на классе «враг». Надавав жуку по панцирю монтировкой, можно было отобрать у него оружие «плевалка». Только в отличие от челюстей тигра, которые были нормальным игровым объектом (после смерти тигра он оставался на трупе и мог быть продан некоторым неписям за бабки), плевалка была dummy-объектом наподобие свинского «Люгера». А у dummy-объектов картинка в инвентаре показывалась дефолтовая. А дефолтовой картинкой был миномёт. Вот так вот: избив жука, из него можно было достать миномёт. Не стреляющий, правда, поскольку боеприпасов для плевалки предусмотрено не было.

                      Но и это не конец. В конце игры были ещё танки. Да-да, тоже основанные на классе «враг». И, набравшись наглости, вся команда чаров подбегала к танку, стучала монтировками по броне, пока, видимо, экипаж не оглохнет, и… правильно, вырывала у танка пушку...


                      И ещё из Блицкрига: Ввиду нелётной погоды крылатая ракета вернулась на базу
                      • 0
                        Огромное спасибо! Давно так не смеялся. Особенно понравилось про кенгуру со стингерами. Прочитал гуманитарию, не имеющему представления не только об ООП, но и просто о П — ничего объяснять не пришлось. (ООП великое изобретение — даже гуманитарию понятно :)
                        • 0
                          Да в JA было много приколов. Например, попавшего в плен Тень могли принять за своего (видимо из-за навыка маскировки 100%) и он мог гасить всех в секторе, в том числе убить Дейдрану…
                      • +2
                        Что ж вы делаете, я на работе, расползаюсь по столу, коллеги косятся подозрительно!
                        ААААА две полоскиии
                        • 0

                          Омг ) в закладки!

                      • +3
                        Д: а потому что у воды плотность большая! (прим.: больше, чем у ртути)
                        П: а почему плотность такая большая?!
                        Д: а чтобы ящики деревянные плавали!
                        П: а почему они иначе не плавают?!
                        Д: а потому что у них масса 50 кг!

                        Вообще-то, деревянные корабли и в 500 тонн, нормально плавают в воде => исправьте реализацию закона Архимеда в игре!

                        • +2
                          Да, но в законе Архимеда стоит объем тела.
                          Плавает тело или нет зависит от его плотности, а плотность у корабля и ящика с завышенной плотностью разные.
                          • 0

                            И что? Плотность дерева ящика/корабля — меньше воды, плотность пустого воздуха внутри ящика/корабля тоже — меньше воды => должно плавать.

                            • +2
                              Если размер ящика не менялся, а масса увеличилась, то плотность тоже увеличилась. И он должен тонуть, если не увеличить плотность жидкости.
                              • 0
                                Можно уменьшить толщину стенок ящика, оставив прежнюю плотность материала, но уменьшив общую массу объекта. Хотя не знаю, как это повлияет на разваливание в игре.
                                • 0

                                  С вероятностью 90% у физической модели ящика нет стенок. Это сплошной куб

                      • +3
                        А вот тут история про вагон метро из КДПВ. Если вкратце, то по рельсам бегает специальный NPC, а вагон заменяет его правую кисть. По ссылке есть восхитительная анимация работы!
                        • +7
                          Эмм. По ссылке в третьем же абзаце написано, что это не НПС, а сам игрок. В момент, когда игрок сидится в поезд, ему принудительно надевается броня в виде поезда (но выгладит это так, будто игрок внутри поезда), которая до кучи! заменяет правую кисть!, и включается анимация передвижения игрока. Такой способ привязать объект к игроку используя уже существующий способ, без разработки механики транспорта.
                          The truth is even stranger still: the train is in fact an armor piece that replaces the player character’s right hand entirely and gives the impression the player is inside a train. This also means it isn’t an NPC that powers the train, it’s the player themselves. A unique camera animation then plays along the track the ‘train’ takes.
                          • –1

                            (удалено)
                            ой, не успел

                          • +12

                            Построить дедупликацию на CRC32 — это сильно!

                            • 0
                              Я в этом месте тоже чаем поперхнулся. Не задаться простым вопросом «что будет, если сумма совпадет?» (а ведь совпадет, ибо принцип Дирихле).
                              • +1
                                совпадет, ибо законы Мерфи :)
                              • –3

                                Когда вы при загрузке уровня или во время игры по сотне раз проверяете ресурс (как раз проверка по хэшу), вы по-другому воспримите данный ход. Crc потому и выбирают, что он быстр.

                                • 0

                                  Софтверный CRC32 совсем не быстр. См например тут https://github.com/rurban/smhasher

                                  • 0
                                    Уверен, что там изменённые варианты для скорости. По крайней мере, дядька из Naughty Dog (The last of us, Uncharted) писал про то, что они юзают такие простые и быстрые хеши для линкинга ресурсов.
                                    • 0

                                      Ну так CRC не самый простой в реализации алгоритм. Тот же FNV1a (и другие классические, основанные на умножении) и проще, и быстрее, и не хуже в качестве хэша.

                                • 0
                                  http://www.commitstrip.com/en/2017/02/27/the-sha-1-alternative/
                                  • 0

                                    В статье было написано, что там CRC не только от содержимого, но и от имени ресурса. Как у них совпало имя, я не совсем понял, но тем не менее они могли добавить к проверке третий параметр — размер данных ресурса, тогда вероятность совпадения была бы ниже. Ну либо считать два разных хэша, например CRC и MD5

                                    • +2
                                      Так не в имени дело, CRC32, всего 32 бита, 4млрд разных вариантов, так что при достаточно большом кол-ве ресурсов вероятность совпадения возрастает до осуществимых вероятностей (действует парадокс совпадающих дней рождений)
                                  • 0
                                    Очень интересная статья, наглядно показывает, что многие даже не понимают насколько сложен и порой непредсказуем труд разработчика, и на какие ухищрения приходится идти, чтобы все работало)
                                  • +4
                                    В этом рейтинге говнобыстрокодеров забавно выглядит человек, многословно переживающий, что заюзал неиспользуемый void* под int.
                                    • 0

                                      на самом деле — не самая лучшая идея, потому что после этого становится непонятно, надо очищать память или нет

                                    • 0
                                      Пока читал про Липкий баг — аж вспотел.
                                      Точно та же ошибка была у меня в 2002г, когда делали игру для КПК. Точно так же решал каким то кривым хаком.
                                      • +4
                                        По-моему, подобные проблемы возникали у многих, кто писал свою физику.
                                        На сколько я помню (лет 10 назад), у нас в гоночках был похожий баг. В большинстве случаев коллижен обрабатывался вполне адекватно. Но в некоторых местах на трассе стояли заборы, к которым можно было намертво прилипнуть. Причина оказалась банальной. Коллижен шейп на нём был двусторонний и бесконечно тонкий. В результате на солвер приходило две абсолютно идентичные точки столкновения с прямо противоположными нормалями, и он не мог решить — куда же объект надо выталкивать. Пришлось этот случай обрабатывать отдельно.
                                        • 0
                                          Вы только что решили мою проблему…
                                          • 0
                                            В бытность учебы в колледже была у нас проектная работа по OpenGL и прочим 3d. Понятное дело я, как и большинство, делал a la бродилку без какого-либо движка: в большом кубе куча деревянных ящиков, подгружаемых из файла. Опционально, на ящики действовала физика: они могли упасть на землю, либо висеть в воздухе.

                                            Собственно «игрок» мог перемещаться и прыгать по ящикам. Так вот, если встать под висящий ящик и зажать пробел, для прыжка, камера прилипала к ящику, пока не отпустишь пробел. Можно было с зажатым пробелом подлезть к краю ящика и запрынуть на него, будто ты руками цепляешься.

                                            В общем я решил, что это не баг, а фича.
                                            • 0

                                              Это как знаменитый глич-прыжок в Ларе Крофт. Правда там механизм несколько иной


                                              • 0
                                                Ну, у меня без телепортов было )
                                                • 0

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

                                                  • 0
                                                    Я вижу, да.
                                                    У меня тоже с коллизиями фигня была. Точнее с определением возможности прыжка. За давностью лет точно не помню подробности, но кажется там считалось, что если есть нет скорости по вертикали, то можно прыгнуть, соответственно пробел задавал моментальное ускорение вверх. Вверх нельзя и камера прилипала: ускорение прыжка больше силы тяжести. Отпустишь пробел — сила тяжести возьмёт свое, а подойдёшь к краю, ускорение прыжка поднимет тебя вверх.
                                        • +1
                                          За последний трюк я бы убил!
                                          • 0
                                            Последним способом пользуюсь постоянно.
                                            Делаю электронику на STM32. Когда начинаю новый проект, всегда подключаю к нему свои самописные библиотеки. И в них как раз и расположен тот «балласт» в виде различных буферов, которые по умолчанию довольно велики. Если вдруг не хватает памяти и некуда оптимизировать основной код, я просто убираю буферы для неиспользуемых функций, и всё сразу становится хорошо. Хотя, производители микроконтроллеров часто предлагают модели, отличающиеся только по объему памяти и полностью совпадающие по остальным параметрам, что позволяет на время разработки взять самый толстый микроконтроллер из серии, а потом, если удалось ужаться, выбрать модель поменьше и подешевле.
                                            • 0
                                              Когда делали прототип Blood&Gold: Carribean! на движке Маунт-Блейда для того что бы не решать задачи поиска пути, контроля скорости и ввода «просто» заменяли модельку игрока и лошади (sic!) моделькой кораблика — так оно и ушло в релиз. Хє-хє :)
                                              • 0

                                                Последнюю байку следует вставлять в начало документации и каждого туториала по React, Angular и Electron. Чертовы хипстеры со своими топовыми маками и стомегабитным интернетом, всё равно ведь не поймут, наверное…

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