Часть 1. Баллистика: наши кривые — самые кривые в мире.
Смеркалось. По морю плыли шли злые корабли, мечтая потопить друг друга. Пилоты самолётов-разведчиков в ожидании взлёта судорожно стискивали в потных ладошках фонарики для засвета врага. Линкоры медленно доворачивали башни в сторону предполагаемого противника, меряясь друг с другом калибрами и длиной стволов. Авианосцы срочно эвакуировали в штраф-ангар неправильно припаркованные на палубе самолёты, мешающие взлёту. Крейсера перемигивались световым телеграфом, обсуждая, по какому флангу устраивать раш.
В середине карты два юрких эсминца, уже сойдясь в дуэли, разминались торпедками, поскольку играли недавно и к стрельбе главным калибром ещё не приловчились.
Израсходовав все торпеды впустую, эсминцы наконец-то нашли кнопку переключения с торпед на главный калибр, и вечер перестал быть томным. Увесистые снаряды, с оглушительным грохотом вылетая из дул, взмывали в небо, чтобы обрушиться оттуда прямо на врага или хотя бы окатить его фонтаном воды, упав поблизости.
Что характерно, летели эти снаряды не как попало. Они летели строго по своим траекториям…
Теория большого взрыва
Позволю себе немного теории, чтобы наш ход мысли стал понятен. Самое простое — это когда снаряды летят по параболе. Не верите — спросите десять случайных прохожих: какую самую простую кривую они знают? Девять из них, не задумываясь, сходу назовут параболу, и лишь один, ухмыльнувшись в усы, ответит: «гиперболический арккосеканс». Что, вам по-другому ответили? Девять ответили «чо?», а десятый, глухой, — «полдвенадцатого»? Ну, может, вы где-то не там спрашивали. Я-то как раз мимо математического факультета проходил, и навстречу шли какие-то молодые люди в очках…
Если не учитывать сопротивление воздуха, то, действительно, любое брошенное тело летит по параболе. Чем меньше расстояние, тем более пологая парабола. Чем дальше — тем более крутая. Максимальная дальность достигается, если выстрелить под углом в 45 градусов (для сравнения, вода кипит при 100 градусах). Подняли ствол орудия до этого угла, бабахнули — снаряд полетел во врага и прилетел в него под тем же углом и с той же скоростью, с которой вылетел.
Казалось бы: чего еще нужно? Просто, надёжно, легко программируется, но… очень уж далеко от исторических реалий корабельных сражений. Вот птицами по свиньям кидаться — самое то, а для морских боёв — не очень.
В реальном морском бою снаряд, выпущенный на максимальную дальность, в полёте успевал замедлиться, потерять горизонтальную скорость, и прилетал во врага практически вертикально, всем своим весом обрушиваясь прямо на его палубу. В отличие от бортов, палуба часто была не бронированной или со слабой броней. Так что прилетевший сверху снаряд имел все шансы с легкостью её пробить и взорваться внутри, нанеся большой урон кораблю и до слёз огорчив экипаж.
Траектория полёта снаряда на большой дистанции
На средних дистанциях снаряд прилетал уже не отвесно сверху, а под определённым углом, чаще попадая в бронированные борта (а если попадал в палубу, то вскользь). За время полёта скорость снаряда успевала упасть, да и при попадании под углом шансы пробивания снижались. Это приводило к интересной особенности: зачастую на определённом расстоянии от противника корабль мог чувствовать себя в относительной безопасности. Вражеские снаряды не причиняли ему особого вреда, хоть и долетали до него. На каждое «дзынь — не пробил» матросы высовывались из иллюминаторов и показывали противнику язык, чтобы деморализовать его (хотя документальных подтверждений этому я нигде не нашёл, но мне так представляется).
Траектория полёта снаряда на средней дистанции
Ну и на малой дистанции снаряд прилетал в борт почти перпендикулярно и на большой скорости — и вследствие этого имел отличные шансы пробить броню и влететь прямо в кают-компанию, безнадёжно испортив настроение членам экипажа и размазав различные части их тел по стенкам и потолку.
Траектория полёта снаряда на малой дистанции
Таким образом, корабль мог получать от противника значительный урон на малой и большой дистанции, но быть практически неуязвимым на средней. При боевом противостоянии двух кораблей эта дистанция (по-научному, зона свободного маневрирования) была у каждого своя. Тогда ключевую роль начинала играть скорость. Кто имеет преимущество хотя бы в пару узлов — тот и сможет выдерживать ровно ту дистанцию, которая безопасна для него и невыгодна для противника, безнаказанно нанося урон и не получая сдачи. Тихоходу же останется только ловить плюхи и молча готовить спасательные шлюпки, не находя цензурных выражений, чтобы охарактеризовать такую подлость со стороны врага.
Все это являлось важнейшим элементом тактики морских сражений, и было бы несправедливо не отразить этих особенностей в игре. Поэтому от параболических траекторий практически сразу было решено отказаться, так как они не позволяют правильно воспроизвести ни углы, ни скорости снарядов. Законы полёта должны быть более сложными и правдоподобными. Сложными, как учебник тригонометрии для 10 класса, и правдоподобными, как предвыборные обещания кандидата в президенты.
Приступаем к практике
Условия были заданы, и мы приступили к поиску. Можно было бы искусственно погнуть эти параболы, чтобы придать им более подходящий вид. Выдать гейм-дизайнерам плоскогубцы и напильники — и пусть гнут траектории, как захотят, настраивая, под какими углами и с какими скоростями должны прилетать снаряды в зависимости от дистанции. Но где взять все эти углы и скорости, чтобы они соответствовали правильным значениям? И сколько месяцев понадобится, чтобы аккуратно подогнать псевдопараболы для десятков и даже сотен разнообразных орудий, добиваясь для каждой пары кораблей достоверных зон свободного маневрирования и корректных вероятностей нанесения урона на различном расстоянии? И сколько ещё доработок придется внести, из-за того что текущих «винтиков» не хватило, чтобы корректно настроить траектории для всех кораблей?
Нет, этот вариант запросто мог завести в тупик. Требовалось что-то более изящное и легко настраиваемое.
Обсудив сложившуюся ситуацию, разработчики пришли к выводу о том, что наиболее приемлемое решение — запрограммировать «честную» баллистику, чтобы снаряды в игре летали в точности так, как они летали в настоящих боях. Тогда и подгонять вручную ничего не придётся: добавив к честной баллистике приближенные к реальности законы пробития брони и нанесения урона (ведь мало попасть снарядом в корабль — надо его ещё и пробить), мы автоматически получим все исторические особенности и нюансы артиллерийских боёв применительно к конкретным кораблям.
Задача получалась не из простых: ведь мало того, что придётся численно моделировать законы полёта снаряда, вылетевшего с заданной скоростью и под заданным углом. Придётся решать и обратную задачу: имея заданную дистанцию, находить требуемый угол стрельбы, при котором траектория придёт точно в точку прицеливания. А если кораблей в бою штук тридцать, у каждого от 2 до 6 башен главного калибра, не считая второстепенного, да все они одновременно начнут стрелять, — сколько траекторий придется моделировать каждую секунду? Но программисты не испугались трудностей, потому что были смелыми и отважными (не зря же наш отдел HR проводит собеседования ночью на кладбище, чтобы сразу отсеять трусливых кандидатов). Собрав волю в кулак, программисты решительно занесли руки над клавиатурами и со страшной скоростью застучали по клавишам.
Для моделирования и исследования законов баллистики была создана специальная программа. Было решено для быстрого прототипирования использовать ActionScript, а когда алгоритмы будут полностью отлажены, переписать их на С++, чтобы добиться максимальной производительности. В качестве начальных параметров для траектории задавались вес снаряда, калибр, начальная скорость и коэффициент сопротивления воздуха. Для любого существовавшего корабельного орудия первые три параметра, как правило, можно выяснить, почитав старые газеты. Что касается коэффициента сопротивления воздуха, он должен иметь какое-то разумное значение — я точно не знаю, но, наверное, где-то сэмь-восэмь (но никак не тридцать три). И быть примерно одинаковым для снарядов похожей формы — из этих соображений его и подбирали.
Чтобы проверить правдоподобность законов полёта снарядов, полученные в программе дальности стрельбы сравнивались с документальными — о них, как правило, также можно прочесть в старых газетах. Для начала наши программисты попробовали просто учесть замедление снаряда от трения о воздух. Оказалось, что этого совершенно недостаточно: результаты сильно не совпадали с требуемыми. Тогда было учтено изменение плотности воздуха с высотой. Забрезжил луч надежды. Попытались смоделировать ещё несколько неочевидных физических явлений, влияющих на полет снаряда, — всё мимо. Наконец догадались учесть изменение температуры воздуха с высотой — и вот тогда совпадение с документальными данными получилось практически идеальным. Задавая вес снаряда, калибр и начальную скорость, для большинства орудий удавалось получить совпадение дистанций стрельбы с точностью до 1% (что меньше погрешностей исторических данных, которые к тому же зависят от износа стволов и ещё тысячи факторов).
Это был блестящий результат: теперь гейм-дизайнеры могли настраивать любое орудие, задавая всего три значения: калибр снаряда, вес и его начальную скорость, — и практически сразу, без дополнительных настроек, добиваться абсолютно корректных траекторий и конечных скоростей. Осознав всё это, программисты и гейм-дизайнеры поначалу не поверили своим глазам, а потом, опомнившись, радостно вскрикнули.
Полученные алгоритмы полёта снарядов были максимально оптимизированы и переписаны на С++, что дало вполне приемлемую производительность. Для решения обратной задачи — по заданной дальности найти угол возвышения орудия — строились специальные таблицы для каждого орудия. В бою значения не рассчитывались, а брались из готовой таблицы по ближайшим просчитанным точкам и усреднялись. Таблицы пришлось строить двумерные: не только по дальности, но и по высоте конечной точки. Ведь корабль вполне может целиться не только в плывущего по воде врага, но и в какую-нибудь вершину горы. Поначалу таблицы получились огромными, но впоследствии удалось уменьшить их размер, используя тот факт, что на разных участках требуется разная плотность точек. Таблицы просчитываются и запоминаются один раз для каждого вида орудия и пересчитываются, только когда характеристики орудия были изменены. Кто сумел дочитать досюда, тот молодец и, наверное, получит от меня при встрече пирожок. А чтобы не бросалось в глаза, что заумный абзац вдруг заканчивается на слово «пирожок» (а то на всех пирожков не напасёшься), допишу (вот лично вы можете дальше не читать), что для численного интегрирования дифференциальных уравнений траекторий использовались методы Эйлера и Рунге-Кутта. При этом временные затраты на нахождение точки в таблице составляют порядка Log(N), где N — общее количество точек, релятивистские же эффекты учитывать не потребовалось.
Итак, мы получили честную и легко настраиваемую баллистику. Формы траекторий, углы, дальности, скорости снарядов, а также время полёта высчитываются предельно достоверно, что даёт повод с гордостью заявлять о максимальной реалистичности поведения снарядов в игре. После всего этого оставалось лишь придумать не менее правдоподобные законы пробивания брони и нанесения урона, чтобы получить геймплей, воспроизводящий исторические особенности морских сражений. Но об этом будет рассказано уже в следующих частях.
P. S. При подготовке данного цикла статей нагло и беззастенчиво использовались материалы докладов внутренней конференции Wargaming, авторы которых принимали самое непосредственное участие в разработке алгоритмов баллистики и нанесения урона. Вот их имена: А. Лысак, А. Чулков, В. Антонов, — и тут же моя им благодарность.
Сергей Изъюров, программист