Фамилия Имя Отчество: Как я кодил врагов

hqpfPHr

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

В чем сила, брат

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

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

  • Монотонность. Враги постоянно движутся и в каждое мгновение смертельно понятно, куда и зачем.
  • Орбитальность. Совершая небольшие подвижки вверх-вниз нетрудно добиться того, что противники начинают кружить вокруг игрока, как Фобос с Деймосом вокруг Марса. Нередко повторялась абсурдная ситуация, когда игрок вообще стоял на месте, а противнику необходимо было совершить еще полтора-два полных оборота вокруг него прежде чем настигнуть.
  • Кучкование. Противники двигаются настолько одинаково, что вскоре «слипаются» и дальше ведут себя практически, как один противник.

Робот-нинзя-убийца

Для увеличения разнообразия и сложности я добавил расчет упреждений. Враг, зная вектор текущей скорости игрока, расстояние до игрока и скаляр своей максимальной скорости, определяет точку, в которой он встретится с игроком, если все продолжат двигаться, как двигаются, и вектор своей силы (будем называть её Силой Преследования) направляет уже не на игрока, а в эту точку. Такой расчет повторяется для каждого врага каждый кадр.

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

Играть против роботов-нинзя-убийц оказалось совершенно не интересно и я сделал так, чтобы расчет упреждений использовался только пока враг далеко от игрока, а вблизи противник бы уже как раньше по-простому пёр прямо на него. Оставалось подобрать пороговое расстояние. Но как бы я ни пытался, получались либо снова терминаторы на быстром, либо разницы особо не чувствовалось с режимом без упреждений. Это было фиаско.

Тогда я сделал, чтобы расчет упреждений снова использовался всегда, на любом расстоянии, но только одним из двух противников. И это оказалось супер-круто. Сложность пришла к очень хорошему значению. А главное, стало невозможно мгновенно определить, использует противник упреждения или нет. Приходилось определять это по характеру движения противника. И делать это надо было максимально быстро, чтобы успеть скорректировать своё движение. Ведь против умного и тупого нужно двигаться по-разному.

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

К сожалению, это не помогло ни от Монотонности, ни от Орбитальности, ни от Кучкования. Чума на оба ваших дома!

В ритм, ритм, ритм, ритм двигай жопой

Я сделал, чтобы Сила Преследования была больше, когда враг близко к игроку и уменьшалась, когда враг далеко. В результате движение стало волновым: медленная фаза вдали от игрока чередовалась с быстрой при приближении к нему. Чередоваться стали соответственно и периоды высокого напряжения для игрока с периодами не такого высокого. Любопытно, что в таком варианте напряжение ощущается острее, чем когда оно постоянно высокое! Это был серьезный удар по Монотонности.

К сожалению ни Орбитальность ни Кучкование никуда не делись. К тому же у двух разных врагов периоды менялись полностью не синхронно, из-за чего ритм размазывался или вообще переставал ощущаться.

Тогда я полностью пересмотрел движение врагов. По новой модели враг двигался в двух режимах: отдых и рывок. Отличались они величиной Силы Преследования: в режиме отдыха она была сильно уменьшена, а в режиме рывка возрастала в несколько раз. Сменяются режимы по таймеру. Если объяснить другими словами, то враг стал медленным, но у него появилась абилка «рывок», которая увеличивает его Силу Преследования на определенное время и имеет кулдаун.

Ритм стал что надо, но решительной победы над О и К это не принесло. Я пробовал полностью рассинхронизировать врагов, но это опять же привело к размазыванию ритма и я остановился на правиле, что длительность полного цикла у врагов должна быть одинаковой и постоянной и что смены режимов у них должны быть в фазе. Небольшая рассинхронизация при сохранении длительности полного цикла пошла на пользу, но вновь не решила проблем целиком. Проклятье!

Какая это синкопа!

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

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

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

Во-вторых ушла Орбитальность. Шах и мат!

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

Иногда это приводило к абсурдной на вид ситуации. Враг в рывке гонится за убегающим от него игроком, почти настигает его, как вдруг рывок заканчивается. Однако, враг уже так близко, что даже на медленной скорости отдыха сможет его догнать. Но вместо этого он вдруг почему-то останавливается и вовсе начинает двигаться от игрока. Что за дичь! Но даже такое пошло на пользу. Это совершенно неожиданное поведение, оно сбивает игрока с ритма, приводит его на момент в замешательство, которое, быть может, дает второму врагу шанс настигнуть игрока. Иногда это прям похоже на командную игру. Хотя в логике такого и в помине нет.

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

Толстый и тонкий

Разработка логики противников оказалась длинным путём, полным предположений, проб, ошибок и неожиданных находок. И всё же, это был контролируемый и в известной степени предсказуемый процесс. Я знал, чего хочу, я не знал, как этого добиться, пробовал и в конечном итоге добивался. В конце пути игровой процесс получился почти в точности таким, каким я его представлял в самом начале. И на это у меня ушло много времени и сил.

И вот, после этого всего уже сильно на второй, финальной неделе разработки, я подумал: а почему бы не добавить новые виды противников? Супер-быстро очень малой кровью чисто ради лулзов я добавил Большого. Он был значительно большего размера, не использовал рывков и упреждений: просто постоянно медленно двигался в сторону игрока. По замыслу он должен был быть не опасным сам по себе, так как он медленный и предсказуемый. А вот в сочетании с быстрым умным врагом, он должен был добавлять сложности. Это не столько враг, сколько самодвижущееся препятствие, противотанковый ёж с маленькими лапками, за которым приходится следить, пока маневрируешь против основного противника.

Так и вышло, но в первую же тестовую игру произошло вот что. Быстрый был от меня далеко, между нами был Большой. И тут Быстрый сделал рывок. Так как всё двигается по физике, получилось, что он толкнул Большого и тот полетел в мою сторону со скоростью больше собственной. Это компенсировало две главные слабости большого: предсказуемость и низкую скорость. Более того добавился фактор формы врагов: Быстрый мог ударить не в центр Большого, а как-нибудь сбоку. В результате каждый раз игроку приходится в такой ситуации решать задачу типо биллиардной, чтобы понять, в какую сторону пойдет Большой.

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

К чему всё это? К чему всё это?

Я не считаю, что в итоге у меня получился какой-то уникально блестящий геймплей. Его недостатки хорошо описаны многими игроками в комментах к конкурсной заявке. Но сам процесс показался мне любопытным.

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

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

Сама логика в итоге не сложная. А вот череда проб и решений, путь, который к ней привел, оказался непростым. Непростым, но… правильным каким-то что ли!

Это напоминало импровизационную музыку. У тебя есть направление, но нет точного маршрута. Есть ограничения, компоненты, с логикой работы которых ты должен считаться, но есть и много свобод. И главное, всю дорогу было чувство, что я делаю игру не один. Будто игра сама знает, какой ей надо быть, а я лишь внимательно прислушиваюсь к ней. А только так по-моему и должно быть.