28.11.2021, Новые истории - основной выпуск
Особенности програмирования зеркал, или страх и паника в вампирском замке
(Записано по байкам знакомого. Как человек, от программных материй далекий, не могу поручиться ни за правдивость, ни за точность, ни даже за правильность изложения. Но могу поручиться, что забавно)
Где-то в далеких 90-ых, группа энтузиастов-программистов делала игрушку — приключения героя в вампирском замке. Делали на достаточно примитивных возможностях, и в итоге так и не доделали, но работа шла довольно долго и даже с некоторой продуктивностью. И все бы было хорошо, но тут появилось оно.
Зеркало.
Огромное такое зеркало во всю стену, которое висело в холле замка. Программисты недовольно ворчали – мол, зачем оно вампирам, они ж не отражаются – но, поворчав, все же согласились…
Поскольку это были 90-ые, то создавать зеркала в видеоиграх приходилось дедовским методом: неразрушимая прозрачная стена, по ту сторону которой точно такая же комната. Когда игрок входил в холл, по ту сторону зеркала немедленно генерировалась его точная копия, повторяющая все его движения. Тяжело, занудно, объемно, но работало. А в данной ситуации даже и хорошо: поскольку вампиры в зеркале отражаться не должны, то и можно было ограничиться одной лишь копией игрока. Выходило атмосферно и даже где-то изящно.
ЗАЗЕРКАЛЬНЫЙ ВОР
Как уже упоминалось выше, зазеркальное отражение было точной копией персонажа игрока. И воспроизводило его со всеми переменными. Благо, перменных-то было не очень много: несколько видов доспеха, отличавшихся в основном цветом, и оружие в руках. Поэтому при генерации зазеркальной копии, игра просто выдавала ей такое же оружие и снаряжение, что и игроку.
И тут возникла проблема.
Некоторое оружие и снаряжение в игре были уникальными, существующими в единственном экземпляре. Игра вела учет положению такого вот снаряжения, и если игрок терял или бросал его, оно оставалось на прежнем месте.
Когда персонаж, несущий уникальное снаряжение, входил в зеркальный холл, игра, естественно, пыталась выдать такое же снаряжение его отражению. И тут возникало противоречие: уникальный объект в двух местах одновременно.
В лучшем случае, игра делала попытку вручить зазеркальной копии уникальное оружие, наталкивалась на наличие его в другом месте, не могла завершить процедуру, и крэшилась.
В худшем случае, игра вручала-таки зазеркальной копии уникальное оружие, затем проверяла его положение, и затирала старое. То есть отбирала уникальный предмет у игрока, и отдавала копии. И извлечь объект из зазеркалья не было уже никакой возможности.
После долгих мучений, проблему решили, создав копии каждого уникального предмета в игре, и всучив их зазеркальному двойнику. Теперь при генерации двойника, игра сначала проверяла наличие у игрока уникальных предметов, и вызывала их копии. Правда, периодически игра все равно путала где что находится, и у персонажа оказывалась копия (FLAMINGSWORDCOPY, например), но разработчики предусмотрительно сделали копии в точности соответствующими оригиналам.
АГРЕССИВНОЕ ЗАЗЕРКАЛЬЕ
Зазеркальный двойник игрока по сути своей был обычным монстром. Только уникальным, и с отключенной моделью поведения — ее заменили повторением действий игрока.
И тут возникла проблема. Время от времени, без всякой внятной причины, зазеркальный двойник озверевал и начинал проявлять агрессивность — пытался приблизиться к игроку, палил из оружия в зеркало, и всячески демонстрировал, как он свой оригинал не любит.
Расследование необычной агрессивности доппельгрангера выявило следующее: движения монстра в игре не соответствовали полностью движениям управляемого игроком персонажа. Близко, но не совсем. Из-за этого зазеркальная копия иногда оказывалась в ситуации, когда она просто не в состоянии полностью повторить действия персонажа. Например, если игрок персонаж вставал слишком близко к столу, то его зазеркальная копия могла оказаться в положении, когда для этого ей потребовалось бы пройти внутрь стола.
Столкнувшись с таким сбоем, программа повторения действий игрока зацикливалась и останавливалась. И поскольку свято место пусто не бывает — на ее место подключался «обычный» набор действий для монстра. Зазеркальный двойник внезапно обнаруживал, что он может двигаться, что он очень не любит игрока, и должен пытаться его убить.
Неоднократые попытки решить проблему застревания в мебели ни к чему не привели. Переделывать модели персонажей и монстров было слишком затратно. Поэтому проблему решили, переведя двойника в категорию «нейтральных» существ, которые не трогали игрока, пока сами не будут атакованы. Например, какая-нибудь корова игрока не атакует до тех пор, пока игрок сам не атакует ее, или другую корову из того же стада (той же суб-категории)
И все стало еще хуже.
С нововведением, агрессивность двойника возросла многократно. Немедленно после генерации, он бросался к зеркалу, и начинал яростно стрелять по персонажу. Все попытки выяснить причину такой предубежденности проваливались раз за разом. Код двойника перебрали буквально по букве, никакой ошибки не нашли, и уже готовились впасть в отчаяние, когда, наконец (совершенно случайно) правда не всплыла на свет.
Оказалось, что все дело было в суб-категориях, деливших нейтралы на группы. В своей субкатегории, двойник должен был быть одним-единственным. Но из-за небольшой ошибки, его «напарниками» по субкатегории оказались... разрушаемые чучела-мишени на стрельбище в начале игры. Атаки на мишени в начале игры настраивали против игрока всю суб-категорию, и к моменту появления зазеркального двойника, тот уже пылал мстительной яростью и мечтал поквитаться за невинно погубленных соломенных братьев. А поскольку ошибка была в мишенях, то, естественно, никакое исследование кода двойника ее не выявляло...
Эту ошибку удалось исправить легко, но вскоре нашлась еще одна: некоторые виды атак (проклятия и т.д.) действовали на всех в пределах видимости, и задевали и двойника. Который после этого терял свой «нейтралитет» и рьяно выступал против обидчика из-за прозрачной стены. Но так как проклятия были в игре вещью редкой, то программисты просто махнули на это рукой, и решили выдать баг за фичу: каждый раз, когда нейтральность двойника нарушалась, игра выводила сообщение «ПОХОЖЕ, ЭТО НЕ ПРОСТО ЗЕРКАЛО...»
ЗАЗЕРКАЛЬНАЯ ГЕОМЕТРИЯ
Как уже упоминалось ранее, зеркало в игру ввели не сразу, а уже на достаточно продвинутой стадии разработки. Естественно, это означало, что зазеркальный холл пришлось впихивать в уже частично готовый этаж замка. И, естественно, вышло это не больно-то удачно — попробуйте-ка в средневековом замке изыскать место под еще один зал, при этом не убирая других комнат!
В результате впихиваний и ужиманий, этаж замка разбух и растолстел, и весьма слабо соотносился с тем, как замок смотрится снаружи. Но это было еще мелочью. Настоящей проблемой оказалось то, что зазеркальный холл не везде удалось упихать правильно, и в некоторых местах он пересекался с другими комнатами. Пересечения, впрочем, были мелкие, провалиться сквозь них в зазеркалье было невозхможно, и разработчики сочли, что особых проблем они не создадут.
К сожалению, координатную систему они об этом предупредить забыли.
В результате, в помещениях вокруг зазеркального зала, начали твориться странные (даже по меркам вампирского замка) дела. Торчащие (и невидимые) углы зазеркалья приводили к тому, что одни координаты накладывались на другие. И из-за этого геометрия замка перестала быть евклидовой, а его обитатели массово спятили с ума.
В деталях, все это выглядело примерно так: монстр-вампир видел персонажа, и начинал двигаться к нему. Программа приступала к просчету оптимального — кратчайшего - пути вампира, и в какой-то точке натыкалась на двойные, накладываающиеся координаты, согласно которым вампир должен был оказаться в комнате на другом конце замка. Такой путь явно не был оптимальным, программа пыталась изыскать новый, но поскольку вампир уже довольно слабо понимал, где он находится, результаты... как правило удручали. А то и пугали.
Некоторые вампиры начинали, забыв про персонажа, бешено бегать кругами, уставившись в одну точку. Другие, видя персонажа в двух шагах, разворачивались и выбегали из комнаты, оббегали по кругу весь этаж (пытаясь обогнуть зазеркальный зал в центре) Некоторые вообще решали, что находятся в зазеркалье, и пытались двигаться, строя маршрут по зазеркальному залу — бегать по несуществующим лестницам, огибать несуществующую мебель. Стрельба персонажа по вампирам выглядела не лучше — пули и стрелы выписывали странные траектории, кружились в воздухе, вылетали из комнаты.
В отчаянной попытке решить проблему, программисты не нашли ничего лучшего, как «заткнуть» точки пересечения координат различными непроходимыми препятствиями. Комнаты и коридоры вампирского замка заполонились стоящими в произвольных местах колоннами, шкафами, вазами с цветами и фонтанами. Такое решение хоть и не устранило проблему вампирского сумасшествия, но свело ее к допустимому минимуму...