Числа №15. Об играх
Пока бы закончить с числами; осталось две заметки.
В прошлый раз я рассказывал о числах с фиксированной запятой и о том, как они использовались в играх. В том числе упомянул первый Старкрафт (BroodWar), о котором и хочу поговорить.
Старкрафт – это стратегия реального времени фирмы Blizzard; вышла в 1998 году. Она стала прорывом во многих областях и надолго задала планку. Старкрафт отличался запредельным числом игровых механик и их комбинаций. Расы были кардинально разными: зерги делали ставку на массу, протоссы – на мощь, терраны – на технологии. Юниты были наземные и летающие, транспортные, невидимые, с обычным уроном и массовым. Были псионики – юниты, которые атаковали только магией.
Игра, хотя и была двумерной, поддерживала многоуровневый ландшафт, и это влияло на исход боя: юниты ниже мазали по тем, что выше.
Не все тонкости игры были очевидны. Скажем, юниты делились на классы (легкий, тяжелый и другие), атака тоже была разных типов. Позже игроки составили таблицы, кто по кому наносит какой урон.
Добавьте к этому отличную кампанию, полную пафоса; крутые CGI-ролики; сетевую игру в компьютерном классе; Battle.net для счастливчиков с интернетом в 98 году – и станет ясно, что игра была прорывом. До сих пор она остается одной из главных киберспортивных дисциплин, а в Южной Корее – национальным видом спорта.
Чтобы обеспечить все это добро, движок считал много чисел. В 98 году не все процессоры поддерживали плавающие числа, поэтому в Старкрафте использовались числа с фиксированной запятой. Код игры никогда не открывали, однако ее устройство ясно из публикаций разработчиков и специального SDK для ботов. Последнее особенно интересно: Старкрафт был первой игрой, под которую писали ИИ-ботов. Были даже шоу-матчи между известными игроками и ботами по аналогии с шашками Go и AlphaStar. Ежегодно проходят турниры для разрабочиков ботов.
Интересен следующий факт: числа, что мы видим в игре, на самом деле являются дробными. Скажем, у морпеха (марика) 40 единиц здоровья, атака 6, броня 0. У зилота (zealot, воин протоссов) здоровье 100 единиц, защитное поле на 60 пунктов, атака 16. Все числа выглядят как целые, удобно складывать и вычитать их.
На уровне кода каждое число – это контейнер с фиксированной запятой. Тип,
который его описывает, называется fp8, что означает fixed point 8. Восьмерка
подразумевает число битов, отведенных под дробную часть. Таким образом, fp8
хранит числа с точностью до 1/256 или 0.00390625 в десятичной системе.
Я не видел полного описания этого типа. Однако логично предположить: если он
занимал 16 бит, то на целую часть оставалось 8 бит. Таким образом целая часть
вмещала диапазон от -128 до 127. Это мало. Поэтому скорее всего тип занимал 32
бита (обычный int), и на целую часть отводилось 24 бита вместе со
знаком. Диапазон таком образом был от -(2^23) до (2^23)-1 или от
-8,388,608 до 8,388,607.
Восемь миллионов – достаточно, чтобы покрыть все случаи игры, включая особенные. Например, юнитов-боссов в кампании с колоссальным запасом здоровья.
И все-таки: зачем игре дробные числа? Мы ведь выяснили, что у зилота 160 здоровья, а у марика 6. Это значит, потребуется 27 атак, чтобы марик победил зилота. Все удобно, но нет – за этим удобством кроется фиксированная запятая.
Причина в следующем: если все числа целые, то в какой-то момент они портят игровой баланс. Такая сложная игра как Старкрафт обязана учитывать фракции, чтобы не вышла досадная вещь, которую мы рассмотрим ниже.
Про морпеха вы уже знаете; а у зергов есть юнит под названием ультралиск. Это слонопотам, который хорош против морпехов. Изначально у него 1 единица брони. Всем юнитам зергов можно сделать три апгрейда на броню +1. Наконец, ультралиску доступен его личный апгрейд +2 к броне. В сумме они дают 6 брони.
Единица брони снижает урон на единицу. А теперь смотрите: ультралиск со всеми апгрейдами имеет 6 брони, а морпех без апгрейдов на атаку наносит 6 урона. 6 - 6 = 0. Если задача решается в целых числах, то морпех наносит нулевой урон, и ультралиск полностью для него неуязвим!
Проблему можно решить тем, чтобы обязать игрока, который играет за терранов, сделать апгрейд +1 для мариков. Тогда морпех будет наносить единичку урона: 6 - 7 = -1. Но это слабое решение. Общая беда в том, что если у юнита высокая броня, он неуязвим для всех юнитов с атакой ниже брони.
Возможна абсурдная ситуация: у юнита слабая атака, но высокая броня, скажем атака 10 и броня 20. Представим теперь, что игроки выбрали одинакову расу. Оба настроили этих юнитов и пошли воевать. В результате ни одна армия не победит другую: каждая атака на 10 единиц нивелируется броней в 20 единиц. Такой себе геймдизайн.
На самом деле броня в 6 единиц – это не показатель -6 ко вражеской атаке. Это коэффициент ее снижения. В Старкрафте и других играх Blizzard броня устроена логарифмически. Вспомним как устроен логарифмический рост: на малых значениях он почти линейный. Значения брони 1 или 2 дают снижение урона на 1 и 2 соответственно. Однако далее рост замедляется: показатель 3 снижает урон не на 3, а на 2.9, 4 – на 3.5 и так далее.
Числа я беру из головы, потому что не готов запускать Старкрафт и все это проверять. Однако такая же механика справедлива для Warcraft, Diablo и других игр Blizzard. Логарифмическая броня – это залог того, юнит никогда не станет неуязвим. Даже если выдать юниту 100 брони, урон по нему будет снижен до условных 0.01 здоровья. Да, это мало, и здоровье будет тикать по единичке в минуту. Но урон все-таки будет, и сотня-другая обычных юнитов смогут победить тяжеловеса.
Как следствие, даже ультралиска с броней +6 можно победить морпехами без апгрейдов. Каждый марик будет наносить не 0, а 0.25 урона (условно). Разумеется, игроку за терранов в такой ситуации ничего не светит, но дизайн игры в этом не виноват. Не получится создать юнита, который полностью неуязвим, и это правильно.
Должно быть, вы замечали в других играх: долбишь врага, а здоровье убывает на
единичку каждый сотый удар. На самом деле его здоровье — дробное число и урон
наносится тоже дробный. В интерфейсе все показатели выглядят целыми, чтобы не
смущать игрока длинными хвостами. Кому понравится 2.513523734353450003 урона?
Поскольку логарифмы ведут к дробным числам, вот откуда нужда в числах с фиксированной запятой.
По той же причине в раннем Старкрафте был баг: “мертвые” юниты с нулевым здоровьем. Такой юнит мог воевать и погибал только от следующего удара. Как вы поняли, он не был мертвым: целая часть здоровья была нулевой, но оставались единичные биты в дробной части. Это случалось из-за показателей брони, сплеш-урона с затуханием и многих других факторов. Старкрафт не округлял дробные числа: чтобы показать целую часть, он просто сдвигал fp8 на восемь битов вправо. Результат был 0, но юнит считался живым. Позже это исправили.
Да, многое я бы мог рассказать про Старкрафт – сколько времени в него прощелкал! В качестве бонуса предлагаю цикл статей разработчика, который пишет ботов и понимает устройство игры как бог.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter