Непредсказуемо весело: Значение рандомизации в разработке игр
![Непредсказуемо весело: Значение рандомизации в разработке игр](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2Fb4244389c0f3139092a19657b2aadf7c5420c07b-1230x410.jpg&w=3840&q=75)
Узнайте, как привнести рандомизацию в свою игру, чтобы игроки были вовлечены и с нетерпением ждали следующей сцены. Это второй пост Кристо Ноббса в серии статей о проектировании систем, дополняющий его вклад в Руководство для дизайнеров игр Unity. Ознакомьтесь с электронной книгой, чтобы узнать, как прототипировать, создавать и тестировать геймплей в Unity.
В своей предыдущей записи в блоге Кристо рассказал о том, как дизайнеры могут создавать в своих играх системы, которые приводят к интригующему и неожиданному геймплею. В этой заметке он подробно рассказывает на примерах, как настроить рандомизацию.
![Изображение на светло-фиолетовом фоне с обложкой электронной книги GamePlay & Design](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2Fe7a3427474c05b24a7d7358c959e5881ed024953-1200x675.jpg&w=3840&q=75)
С помощью визуальных подсказок вы можете побудить игроков к дальнейшему изучению систем вашего игрового мира и придумать для них уникальный опыт. Предыдущий пост, Системы, создающие экосистемы: Эмерджентный игровой дизайн, очертил пространство песочницы, где все сделано и построено из дерева с возможностью непредсказуемого распространения огня. Давайте продолжим этот пример, предоставив игрокам возможность рубить деревья топором.
Предположим, что ландшафт, на котором стоят игроки, плоский. Они не знают, в какую сторону упадет дерево, когда они его срубят. А что, если некоторые деревья - это "коряги", то есть мертвые, но все еще стоящие? Они могут упасть в любой момент. Подобный непредсказуемый элемент в игре будет пугать игроков и усиливать их игровой процесс.
Вы можете добавить любое количество визуальных подсказок, чтобы показать игрокам, что падающие деревья опасны в этом мире, и держать их начеку. Подсказки помогут игрокам найти отличия между более опасными деревьями-корягами и здоровыми и менее опасными. Им придется выбирать, как взвесить риски: Может быть, разумнее собирать хворост с уже упавших деревьев, чтобы не пострадать от мертвых деревьев, ожидающих своего часа?
Как дизайнер, обязательно наметьте, где происходят эти системные цепные реакции, и заложите в них поддержку, когда игроки приводят их в действие, например, деревья непредсказуемо падают, загораются и тем самым распространяют восхитительный хаос.
Класс сценариев Random в Unity - это статический класс, который предоставляет вам подходы для генерации случайных данных в игре. Этот класс имеет то же название, что и класс System.Random из .NET Framework, и служит аналогичным целям, но отличается от него некоторыми ключевыми особенностями, в частности тем, что он на 20-40 % быстрее, чем System.Random.
Ниже перечислены статические свойства и методы, доступные в классе Random:
Статические свойства
- insideUnitCircle: Возвращает случайную точку внутри или на окружности с радиусом 1,0 (только для чтения)
- insideUnitSphere: Возвращает случайную точку внутри или на сфере с радиусом 1.0 (только для чтения)
- onUnitSphere: Возвращает случайную точку на поверхности сферы радиусом 1,0 (только для чтения)
- Вращение: Возвращает случайное вращение (только для чтения)
- rotationUniform: Возвращает случайное вращение с равномерным распределением (только для чтения)
- штат: Получает или устанавливает полное внутреннее состояние генератора случайных чисел
- значение: Возвращает случайное число в пределах [0.0..1.0] (диапазон включительно) (только для чтения)
Статические методы
- ColorHSV: Генерирует случайный цвет из диапазона HSV и альфа-диапазона
- InitState: Инициализирует состояние генератора случайных чисел с помощью затравки
- Диапазон: Возвращает случайное число в пределах [minInclusive..maxInclusive] (диапазон включительно).
В предыдущей статье блога мы рассмотрели роль рычагов проектирования и использование ScriptableObjects для хранения этих значений. Вы можете заменить эти значения соответствующими диапазонами, используя Unity's Random.Range, который возвращает случайный float в пределах [minInclusive..maxInclusive] (диапазон включительно). Любое плавающее значение между ними, включая minInclusive и maxInclusive, будет появляться примерно раз в 10 миллионов случайных выборок.
При таком подходе вы можете взять для результата значение из заданного диапазона. Вам придется протестировать несколько диапазонов, чтобы найти тот, который подходит для ваших игровых целей, но, опять же, убедитесь, что вы разрабатываете дизайн для определенного диапазона, который вы установили.
![Изображение белого робота, наблюдающего за возгоранием ветвей деревьев в лесу](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2Ffa1ec8da73916a9e357cad89a52637e1c95c9178-1020x574.jpg&w=3840&q=75)
Случайность способствует лучшему погружению в игру. Допустим, у каждого дерева фиксированное количество здоровья - 100, а каждый удар топором отнимает у дерева 25 очков здоровья. Вскоре эта задача станет предсказуемой, а значит, скучной. Даже если вы дадите деревьям здоровье в диапазоне от 76 до 100, любое дерево будет находиться на расстоянии четырех ударов от падения. Но если попробовать меньший диапазон, скажем, от 75 до 76, то можно получить большее разнообразие игровых результатов, поскольку дереву потребуется от трех до четырех ударов, чтобы упасть.
Еще один способ сделать этот сценарий интересным - обозначать изменения здоровья не с помощью полосок здоровья, а с помощью наглядных визуальных подсказок. Это позволит игроку в процессе игры узнать, сколько взмахов топором нужно сделать, чтобы дерево упало. Визуальные подсказки добавляют некоторую ограниченную непредсказуемость, которую можно сбалансировать и настроить под целевой геймплей. Использование класса Random вместо фиксированного значения позволяет превратить монотонную задачу в увлекательное занятие.
Чтобы расширить этот пример, вы можете выбрать случайное значение между 15 и 25 очками здоровья за каждый взмах топором. Благодаря этому игроки не могут легко предсказать, сколько взмахов потребуется, чтобы срубить дерево. Им придется больше полагаться на визуальные подсказки, чтобы определить, когда дерево упадет; на такие подсказки, как размер кусков, летящих от дерева, или трещины, образующиеся на стволе, падающие ветки, звуковые эффекты и так далее.
Они не будут точно знать, когда упадет каждое дерево, но со временем, когда игроки срубят больше деревьев, они смогут делать обоснованные предположения, что в конечном итоге повысит их шансы на выживание.
![Скриншот настроек урона от случайных попаданий в Инспекторе](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2F01417e5050cabf8f20d67b4485c80a66d5cba228-900x278.png&w=3840&q=75)
Цель Randomness - дать игрокам непредсказуемые испытания, которые заставят их просчитывать риск и управлять результатом.
Давайте рассмотрим еще несколько примеров использования класса Random.
Представьте себе карточную игру, в которой есть враг с искусственным интеллектом, разыгрывающий свою карту исключительно в зависимости от хода игрока. Это событие быстро станет предсказуемым без случайности, поскольку каждый раз будет возвращать один и тот же результат.
Даже установка вероятности в 50% - это слишком простая рандомизация, которая вскоре станет очевидной для игрока. Вместо этого попробуйте добавить слои случайности, основанные на действиях игрока. Это позволит создать сложные системы, предлагающие нечто более динамичное, чем выбор между двумя картами в пуле или выбор этой карты вместо одной из многих из имеющегося пула.
Вы можете добавить уровни сложности в карточный стол, заставив противника отдавать предпочтение одним картам перед другими; решения зависят от значения, которое он получил, или от того, насколько полно он составил свой состав перед атакой, например. Карта босса может умножить свой урон, если ее разыграть с другими картами силы, поэтому противник ждет, пока в руках не окажется определенное количество карт силы, чтобы повысить сложность для игрока. Вы можете добавить "веса" такой композиции, увеличив или уменьшив вероятность того, что карта босса будет сыграна с некоторыми другими картами силы.
![Изображение карточной игры Heartstone](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2Febd35b5c479364e40e3f6b564aa67640bbde60a8-1759x894.png&w=3840&q=75)
Вы можете использовать случайность в разных формах для своих игр. Например, шум Перлина обладает естественными свойствами и генерирует градиентный шум из семпла. Попробуйте использовать его в Cinemachine, чтобы создать более органичное ощущение камеры для следящих камер от третьего лица.
![Скриншот алгоритма шума Перлина в Cinemachine](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2F558f570390cfc8a3d8b67b3be8374d460c097e81-786x377.png&w=3840&q=75)
Чтобы попробовать шум Перлина, посмотрите пакеты Starter Assets - Third-person Character Controller или Gaia в магазине Asset Store, а также документацию по использованию Mathf.PerlinNoise.
![Изображение текстуры, сэмплированной шумом Перлина (размытые черно-белые пятна)](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2F0eef6e566cc66d5f6aaef86b0c48c3895c3b8c1a-270x270.png&w=3840&q=75)
В одном из интервью Крис Батчер, один из ведущих инженеров студии Bungie, работавший над Halo 2, рассказал об искусственном интеллекте игры: "Цель не в том, чтобы создать что-то непредсказуемое. Вам нужен последовательный искусственный интеллект, чтобы игрок мог давать ему определенные команды. Игрок может делать что-то и ожидать, что ИИ отреагирует определенным образом".
![Изображение зеленого робота, стоящего посреди низкополигональной земли за пределами кубических зданий](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2Fde0d8279d41477039d06dbc0c4bf78acef74848b-1929x1123.png&w=3840&q=75)
Учитывая это, как настроить ИИ-агентов, чтобы игра была непредсказуемой и живой?
Один из способов поэкспериментировать с этим - объединить Starter Assets с инструментами AI из магазина Asset Store, такими как A* Pathfinding Project Pro, инструмент, позволяющий перемещать агента AI в заданную точку.
Как только агент ИИ движется к игроку, игрок ожидает, что его атакуют. Но что если вместо этого начать разговор? Как насчет того, чтобы добавить больше NPC, которые перемещаются и сливаются с пространством, чтобы создать более живое ощущение? Эти NPC могут выбирать очки, данные им линейно, одно за другим, или, что еще лучше, выбирать логические очки на основе набора правил, используя класс Random.
Допустим, у вас есть агент ИИ, который стреляет в игрока из слабого лука и стрел. К сожалению для агента ИИ, он должен находиться на определенном расстоянии от персонажа, поскольку максимальная дальность стрельбы составляет 10 метров. ИИ встает на позицию перед игроком, на расстоянии 10 метров, и стреляет. Это не самый интересный и идеальный вариант, особенно если добавить второго стрелка, борющегося за ту же позицию на NavMesh.
Для более интересного сценария выберите область, где враг должен приблизиться к игроку. Для этого используйте Random.insideUnitCircle, передайте результат vector2 в vector3 для осей X и Z, а затем используйте RandomRange для обеих осей, чтобы получить область с минимальным и максимальным радиусом вокруг игрока.
![Скриншот скрипта, используемого для агента ИИ, выбирающего точку атаки рядом с главным героем](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2F5e93208f3958290fa690159134d0c873ebe517c3-945x1036.png&w=3840&q=75)
![скриншот рычагов проектирования в Инспекторе](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2Fd58068bf7a3a60e5d35d77c501484c4020019fbb-645x378.png&w=3840&q=75)
ИИ-агент должен выбрать точку в приемлемом диапазоне вокруг игрока и выстрелить, а не приближаться к нему вплотную. Чтобы добавить в игру больше острых ощущений при минимальном кодировании, примените это ко всем агентам ИИ, чтобы они атаковали игрока с разных сторон. Действия ИИ предсказуемы до определенного момента, потому что вы знаете, что агенты должны находиться на определенном расстоянии, чтобы атаковать, но вы не можете предсказать углы, с которых они будут наносить удары.
![изображение низкополигонального пространства с белым перекрестием над серо-зеленым кругом](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2F7e7ef1cd1c3e377f9dd2104f562cca635421e134-1519x1294.png&w=3840&q=75)
Вы можете развить этот пример, предоставив своим агентам ИИ возможность атаковать в ближнем бою. Используйте ту же случайную точку от игрока с более узким радиусом. Таким образом, агенты ИИ выбирают из пула атак, когда приходит время нанести удар.
Игрок знает, что его может атаковать формация ближнего боя, но с какого направления? Будет ли нанесен удар сверху вниз? Или широкий взмах слева направо? Им придется читать телеграфные надписи на анимации, чтобы определить, что их ждет.
![Редактор Unity](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ffuvbjjlp%2Fproduction%2Fd540644dcad771743bed1b7360a5d19d61bee0dc-1600x949.gif&w=3840&q=75)
Этот сценарий может стать сложным, если у вас есть несколько вражеских NPC, готовых атаковать игрока одновременно. Но если посмотреть, например, на Far Cry 2 от Ubisoft, то игрока не атакуют все враги одновременно. Разные враги будут нападать в разное время и случайным образом. Вы можете узнать больше об этом примере и других сценариях ИИ в этом видео из Game Maker's Toolkit.
Если бы вы попытались реалистично воспроизвести в игре результаты действий, совершенных в реальном мире, это потребовало бы сложных, многогранных уравнений или хорошо обученного ML-агента. В итоге все это может выглядеть неуместно, потребовать много времени и технических затрат на реализацию, а при плохой архитектуре все равно будет сложно сбалансировать и контролировать с пониманием.
Однако, используя класс Random в Unity, разработчики игр могут создавать правдоподобные сцены с жестким контролем над уровнем возбуждения за меньшее время, чем это потребовалось бы в противном случае. Случайность, конечно, не всегда уместна. Предсказуемость по-прежнему важна. Но если разместить их в правильных местах и в самые подходящие моменты, можно подарить игрокам уникальные впечатления, которые заставят их играть снова и снова.