Как сделать сгенерировать

Добавил пользователь Владимир З.
Обновлено: 05.10.2024

Последние несколько недель я экспериментировал с генеративным искусством, используя PHP. Генеративное искусство - это создание произведений искусства с помощью программирования. У генеративного искусства есть разные названия, такие как процедурное искусство или креативное кодирование.

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

Я выбрал PHP, по нескольким причинам. Во-первых, потому что знаю его довольно хорошо и могу быстро писать код на нём, а во-вторых (и это самое главное), потому что с его помощью я сразу же могу сохранять изображения на жестком диске. Ещё, ввиду того, что все мои проекты написаны на PHP, то это так же позволяет легко интегрировать подобные скрипты в уже существующие мои проекты.

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

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

Таким образом, работы, которые я создал, - это пиксель-арт, вдохновленный ретро-играми. На данный момент я создал 2 работы - скрипт генерации пиксельных аватаров и генерация целых изометрических пиксельных городов, и это всё на PHP.

article-board

Это интересная тема, потому в этой статье я решил показать, как создавать генеративные изображения с помощью PHP.

Дисклеймер

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

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

Учитывая это, я сделал несколько примеров для этой статьи и довольно подробно прокомментировал их, чтобы постараться сделать код как можно более простым для понимания.

Генерация пиксельных аватаров

Скрипт генерации аватарок работает максимально примитивно. Я просто создал различные заготовки слоёв для всех частей аватара (фоновый цвет, глаза, рот), а затем использовал PHP для наложения слоёв различных частей, выбирая случайный слой для каждой из группы.

При работе с PHP я использую библиотеку GD, которая часто идет по дефолту при установке PHP. Так же, для разработки я использовал Docker, благодаря которому вы сможете запустить этот проект у себя локально в пару команд и проверить работу этого скрипта.

Если вы ещё не знакомы с Докером, то советую прочитать отличную статью для новичков, и уже наконец-то пользоваться такой мощной штукой.

Я никак не взаимодействую с базой данных, в этом скрипте всё абсолютно случайно. Красота генеративного искусства в том, что вы можете мгновенно создавать тысячи различных изображений, и нет необходимости хранить что-то долгое время. Мне очень нравится, когда я дохожу до стадии, когда мне нравится (почти) всё, что создает алгоритм.

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

В псевдокоде процесс создания случайной аватарки выглядит так:

  • Создаём изображение (imagecreatetruecolor).
  • Получаем массив всех возможных частей для первого слоя (используя функцию glob для поиска файлов в указанной папке определённой группы).
  • Перемешиваем все найденные картинки, чтобы выбрать случайный элемент.
  • Накладываем слой на результирующее изображение (imagecreatefrompng > imagecopy).
  • Повторяем для каждого слоя.
  • Сохраняем изображение (imagepng).

Вероятно, вы уже видели подобные проекты с генерацией аватарок. Все они используют разновидность приведённого здесь кода (возможно, не на PHP), они накладывают различные картинки, чтобы создать совершенно новое изображение - с достаточным количеством деталей вы можете быстро создать множество различных, уникальных аватарок.

В моём примере набор изображений составляет: 9 изображений глаз, 5 изображений рта и 7 изображений кожи - что даёт 315 уникальных комбинаций (9 * 5 * 7). Если добавить волосы, очки (и всё остальное), то можно быстро получить десятки тысяч возможных вариантов.

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

areaas-1

При создании слоёв аватарок, важно учитывать одну особенность. Все изображения частей лица (рот, глаза) должны быть одного размера (в моём случае - 32х32). А определённая группа должна быть в соответствующем участке картинки.

Глаза - вверху, рот - внизу изображения. Так, чтобы при накладывании изображений друг на друга они становились в правильное место.

Создание заготовок для генерации изображений

Мой путь генеративного искусства начинается с предварительно нарисованных объектов, которые я затем комбинирую в различных сочетаниях. Можно создавать элементы полностью с нуля с помощью PHP, но это меня не интересует, мне нравится иметь некоторый художественный контроль. Из-за влияния видеоигр я создал всё в виде пиксель-арта.

Для создания пиксельных изображений я использую Aseprite. Aseprite - это потрясающий инструмент!

Aseprite - это приложение, созданное специально для пиксель-арта, и поэтому у него довольно уникальный и необычный интерфейс. Я не люблю, когда интерфейс приложений выглядит как-то странно или нестандартно. Мне нравится, когда всё выглядит так, как должно выглядеть, но для Aseprite я сделал исключение - интерфейс идеально подходит для использования, и мне действительно нравится им пользоваться. Aseprite - платное приложение, но я с радостью заплатил бы за него снова - его использование приносит мне радость, а таких приложений не так много.

Я включил файлы .aseprite в репозиторий, чтобы вы могли использовать их в качестве отправной точки, если захотите создать свои собственные вариации.

Генеративное искусство на основе тайлов

Если вы когда-нибудь играли в 2D видеоигры, вы наверняка видели уровни, созданные с помощью двухмерных сеток тайлов. Это очень интересная задача, учитывая мой недостаток опыта работы в этой области. К тому же, я не работал с изометрическими сетками раньше - поэтому моя следующая работа была немного сложнее.

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

buildings

Основной принцип заключается в том, что у вас есть многомерный массив (массив массивов), в котором хранится информация о различных блоках. Затем вы перебираете массивы и рендерите указанные блоки на картинке.

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

Для изометрического города код выглядел примерно так:

  • Создаём вложенные массивы города.
  • Решаем, где что находится, используя некоторые простые алгоритмы, и обновляем массивы города соответствующим образом (нужно поместить дороги, парки, реки).
  • Нужно пройтись по массивам и отрисовать соответствующие тайлы. Если на карте есть место для здания, создаём случайное здание и рисуем его поверх тайла.
  • Рендерим всё на изображение.

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

Помимо расположения тайлов, при изометрическом рисовании необходимо следить за тем, чтобы всё рисовалось задом наперёд, чтобы передние объекты закрывали задние. Из-за этого я рисую всё, что связано с каждым тайлом, за один раз. Я рисую тайл, затем рисую декорацию (деревья, людей и т.д.), затем рисую здание. Если бы я нарисовал их по отдельности, они могли бы наложиться не так, как предполагалось.

Создание зданий

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

Пример сетки

Код примера для демонстрации сетки немного длиннее, чем для демонстрации пака изображений. Большая часть дополнительного кода используется для прокладки дорог и парков.

Как и в случае изометрического города, я рисую дороги и парки, а плитки выбираю из стандартного набора изображений. Сетка квадратная, не изометрическая, но принцип точно такой же.

Для примера я взял тайлсет, который я сделал примерно за 5 минут (в отличие от того, сколько было потрачено на тайлсеты для изометрического города, что по ссылке выше).

Процесс создания тайлов города выглядит примерно так:

Создаём базовое изображение и подгружаем объекты.

  • Инициализируем массив мира.
  • Добавляем дороги.
  • Добавляем парки.
  • Рисуем подготовленные данные.
  • Сохраняем в изображение.

Изучить исходный код вы можете на Github.

Дороги и парки

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

Как только вы поймете основной принцип, добавить реки, мосты и дорожки в парках будет довольно просто. Самое сложное - подстроить значения так, чтобы последовательно получить привлекательный макет.

В моей версии я не приложил много усилий, для проверок, что все различные элементы содержатся в массиве мира. Массив мог быть больше в любом направлении, но поскольку я всегда итерировался по массиву, используя заданные размеры (GRID_WIDTH и GRID_HEIGHT), эти выходящие за рамки свойства игнорировались.

Что еще?

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

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

В этой статье были приведены примеры работы с GD на PHP и реальный код по работе с графикой на примере генеративного искусства.

Ltrees2

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

Но если генераторов так много, какой можно дать совет? Это зависит от типа генераторов, который вы хотите создать. Я дам вам список вопросов, которые вы должны задать самим себе, а затем советы, соответствующие тем или иным ответам.

Первый вопрос: Что вы хотите создать?

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

Artist-in-a-Box

Итак, спросите себя: как бы эту проблему решил человек?

dress generator

От правил к генеративным методам

К сожалению, знание того, как человек делает что-то – это не то же самое, что научить компьютер сделать что-то. Люди хороши в оценивании, построении догадок и синтезе огромного количества информации о событиях прошлого. Компьютеры знают лишь то, что мы им рассказали (а многие проблемы требуют очень много неявного знания), но они хороши в выполнении расчетов и рассмотрении большого количества возможностей. Поэтому наши методы должны сделать так, чтобы компьютер решал проблемы как человек или, по крайней мере, копировал некоторые человеческие навыки. Методы, которые особенно хороши для создания генераторов (генеративные методы), дадут компьютеру некоторые из нижеперечисленных навыков:

  • Формирование знания о возможных вариантах (навык А)
  • Создание определенной структуры (навык Б)
  • Создание условных правил для выбора вариантов (А2)
  • Создание вариативности в структуре (Б2)
  • Умение задавать себе вопросы об ограничениях (я удовлетворяю этому ограничению?) (навык В)

Распространение

К примеру, вы работаете над RPG, и вам нужно раскидать по игровым локациям различных монстров (А). Высокоуровневые монстры должны быть в высокоуровневых локациях, водные монстры – в воде и так далее (А2). Сюда можно добавить немного структуры вроде нескольких монстров-малышей, блуждающих рядом с монстром-боссом. Эти методы подойдут и для лута: высокоуровневый лут вероятней всего будет выпадать из высокоуровневых монстров (А2), но при выпадении все равно будет элемент случайности, т.к. система будет выбирать одну или несколько вещей из огромного списка доступных вариантов (А).

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

Parametric

Параметрические методы

Более совершенные типы параметрических методов используют другие формы входных данных, и могут генерировать новые артефакты, которые базируются не только на числах, но и на точках, путях и графиках. Когда вы при помощи планшетной ручки рисуете в Photoshop линию, то она становится входными данными для алгоритма, который генерирует мазок, рассчитывая параметры давления, скорости и наклона. В Spore для создания существ использовался Metaballs, алгоритм создания геометрии, умеющий делать плавные трубы вдоль линий в 3D-пространстве. В числе других алгоритмов для заполнения пространства и путей – диаграмма Вороного, шум Перлина, алгоритмы триангуляции, 3D-экструзия или вращение, а также алгоритм Diamond-Square для фракталов. Эти алгоритмы особенно подходят для интерактивных генераторов, так как пользователь может обеспечить их входными данными.

Плиточные методы

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

Если вас заинтересовал этот тип генеративных методов, то тут можно более подробно почитать о способах генерации контента для настольных игр и RPG, а здесь – об изучении комиксов.

mything

Грамматические методы

Constraint solvers

Решатели ограничений

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

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

Поскольку эти инструменты пока слишком большие и громоздкие, их трудно встроить в игровые движки. Кроме того, они пока в новинку, поэтому руководств по ним не так уж много. Самое лучшее, на мой взгляд, это вот эта статья Адама Смита (Adam Smith), посвященная программированию стабильных моделей. Также обратите внимание на Craft от Иэна Хорсвилла (Ian Horswill) – это ограниченный случайный генератор чисел, недавно портированный на javascript. Помяните мое слово, пройдет немного времени, и эти методы станут очень популярными!

Агенты и симуляции

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

А теперь внимание… Люди – не единственные, кто способен решать проблемы!

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

Модель Boids берет подход машинок Брайтенберга и применяет его к стаям птиц и рыб. Каждая птица помогает своей стае сохранять нужную форму, рассчитывая собственные показатели для сцепления (перемещение поближе к членам стаи), выравнивания (движение в направлении, в котором движутся члены стаи) и отцепления (движение в сторону от членов стаи). Небольшие вариации в движении одной птицы могут сгенерировать новое движение для всей стаи.

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

Ltrees

Генетические алгоритмы, которые можно наблюдать в моем приложении для эволюции цветов, – это, собственно, не генеративные методы, т.к. для того, чтобы заработать, им по-прежнему нужен генератор (в данном случае генератор цветов). Но они являются своеобразным ориентиром, благодаря которому генератор движется к выполнению ограничений и созданию желаемого контента. Генетическому алгоритму (который, опять же, заслуживает отдельной статьи) нужно три вещи:

В случае с цветами генотип – это массив чисел с плавающей точкой. Этот массив скармливается параметрическому генератору, создающему красивые анимированные цветы (т.е. превращающему генотип в фенотип). Пользователь может посмотреть, какой цветок ему нравится (фенотип, который можно оценить). Он выбирает наиболее понравившийся цветок, его генотип клонируется и мутируется (это элемент, который можно модифицировать, помните?), после чего рождается следующее поколение, и так, раз за разом… происходит эволюция! То есть мы имеем дело с сексуальной репродукцией, которая в генетических алгоритмах используется сплошь и рядом, но ею одной дело, разумеется, не заканчивается. Существует еще много различных типов репродукций, каждая из которых еще ждет своего исследователя!

Cellular automata

Клеточные автоматы с более сложными правилами использовались для создания Dwarf Fortress, классической Powder, а с расцветом воксельной анимации обрели новую жизнь в движке, на котором был сделан Minecraft.

Итак, вы сгенерировали контент…

Вы прошерстили список генеративных методов, составили собственный список свойств и ограничений и, наконец, сделали собственный генератор!

Генератор не работает

Что-то пошло не так. Контент выглядит уродливым. Контент выглядит одинаковым. Контент похож на гениталии. Другими словами, сгенерированный контент никуда не годится. Некоторые из этих проблем решить проще, некоторые – сложнее. Ниже я попробовала описать несколько проблем, с которыми вы можете столкнуться.

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

Эстетика: самая сложная задача

uniqueness

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

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

Итого и дополнительные материалы

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

Если мой труд таки сподвиг вас поиграться с генераторами, то вот пара инструментов, с которых можно начать свои изыскания:

Игра называется — Galaxy Pass Station. Вы смотритель первой космической станции, куда прилетают гости со всей галактики. Есть множество инопланетных рас и культур, а в галактике правит Галактическое Правительство, которое устанавливает правила межзвездной миграции.

Подробнее об игре я рассказал в этой статье:

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

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

По отдельности нарисовали:

  • Каждую форму головы + варианты причесок под формы.
  • Разные части лица — уши, глаза, рты, и т.п.
  • Разные варианты костюмов.

Все это хранится через Scriptable Objects и редактируется прямо из редактора Unity. Выглядит это так:

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

На скрине представлены варианты формы головы для землян. На практике, мы выяснили, что проще всего хранить варианты причесок через форму головы. Если быть точнее, варианты чёлок, т.к. прически мы тоже храним отдельно.

Наш генератор не предполагает выбора пола. Это исходит из особенностей нашей игры. В 90% случаев сама игра должна генерировать персонажа и определять, кто примерно получился — женщина, мужчина или что-то среднее.

Вы могли заметить выше на скрине, что у каждой части тела и лица встречается опция — Female. Это процент женственности части тела — от 0 до 1 (от 0% до 100%). Он помогает нам определить пол персонажа после генерации. Тут ничего сложного:

Берем среднеарифметическое female коэффициента от всех частей тела.

  • Если результат больше 0.6, то это скорее всего женщина.
  • Если меньше 0.4, то скорее всего мужчина.
  • Если от 0.4 до 0.6 — это может быть как мужчина, так и женщина.

Если каждой части тела задать коэффициент funny (т.е. забавность), то игра сможет определять, получился ли персонаж забавным. Удобно, можно чередовать забавных персонажей с обыкновенными, чтобы игрок не скучал.

Для генерации мы используем Random с определенным seed числом. По-русски, это зерно генерации.

Мы имеем объект Random со случайным или неслучайным числом-зерном, из которого генерируются варианты глаз, ушей, волос и т.п. Это позволяет нам сохранять сгенерированного персонажа, просто, храня его зерно.

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

Алгоритм работы Random предполагает, что передав на вход одно и то же число (т.е. зерно), вы всегда получите одни и те же случайные числа. Конечно, нужно сохранять порядок их генерации. Примерно так мы и делаем.

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

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

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

Тут есть один нью-анс. Мы часто используем Pivot точку самого спрайта, чтобы регулировать корректное смещение части тела. Например, для волос, точка pivot соответствует месту, откуда приблизительно должны расти волосы. С носом похожая ситуация.

Pivot точки спрайтов позволяют нам регулировать место появления части лица или тела без изменении логики генератора.

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

Выше я писал про наш графический стиль. Всё, будет зависеть от него, но художнику нужно объяснять, что все части, которые он нарисовал, должны между собой стыковаться. У нас не было больших проблем с этим. Периодически, мы отбрасываем неподходящие варианты, хотя, их довольно мало.

Мне пока не нравятся многие наши бороды, и мы не научились нормально стыковать прически с лысиной. Поэтому лысины еще нет в конструкторе. Если вы рисуете в векторе, вам должно быть еще проще — не нужно выверять пиксели как делаем мы.

Мы не используем sprite sheets для частей тел. Да, это не очень оптимально для Unity, но в нашем случае, это не влияет на производительность так сильно, чтобы мы начали оптимизировать этот момент. Мы избавляем себя от ручной разметки спрайтов в редакторе, на что у нас уходило много времени. Однако, всегда можно использовать функцию Sprite Atlas из новых версий Unity. Она позволяет собрать несколько спрайтов в одну большую текстуру без особых изменений в игре.

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

Есть еще вариант делать это через шейдеры, у нас есть в разработке такой вариант перекрашивания.

Буду рад любым комментариям к статье, игре и т.д. Если вам понравилась игра, не забудьте её добавить к себе в список желаемого в Стиме, чтобы не пропустить релиз в 2022 году:



Вы когда-нибудь задумывались, как работает Math.random()? Что такое случайное число и как оно получается? А представьте вопрос на собеседовании — напишите свой генератор случайных чисел в пару строк кода. И так, что же это такое, случайность и возможно ли ее предсказать.

Генератор псевдослучайных чисел и генератор случайных чисел

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

Этот источник используется для накопления энтропии с последующим получением из неё начального значения (initial value, seed), которое необходимо генераторам случайных чисел (ГСЧ) для формирования случайных чисел.

Генератор ПсевдоСлучайных Чисел использует единственное начальное значение, откуда и следует его псевдослучайность, в то время как Генератор Случайных Чисел всегда формирует случайное число, имея в начале высококачественную случайную величину, которая берется из различных источников энтропии.

Энтропия — это мера беспорядка. Информационная энтропия — мера неопределённости или непредсказуемости информации.

Выходит, что чтобы создать псевдослучайную последовательность нам нужен алгоритм, который будет генерить некоторую последовательность на основании определенной формулы. Но такую последовательность можно будет предсказать. Тем не менее, давайте пофантазируем, как бы могли написать свой генератор случайных чисел, если бы у нас не было Math.random()

ГПСЧ имеет некоторый алгоритм, который можно воспроизвести.
ГСЧ — это получение чисел полностью из какого либо шума, возможность просчитать который стремится к нулю. При этом в ГСЧ есть определенные алгоритмы для выравнивания распределения.

Придумываем алгоритм ГПСЧ

Генератор псевдослучайных чисел (ГПСЧ, англ. pseudorandom number generator, PRNG) — алгоритм, порождающий последовательность чисел, элементы которой почти независимы друг от друга и подчиняются заданному распределению (обычно равномерному).

Мы можем взять последовательность каких-то чисел и брать от них модуль числа. Самый простой пример, который приходит в голову. Нам нужно подумать, какую последовательность взять и модуль от чего. Если просто в лоб от 0 до N и модуль 2, то получится генератор 1 и 0:

Эта функция генерит нам последовательность 01010101010101… и назвать ее даже псевдослучайной никак нельзя. Чтобы генератор был случайным, он должен проходить тест на следующий бит. Но у нас не стоит такой задачи. Тем не менее даже без всяких тестов мы можем предсказать следующую последовательность, значит такой алгоритм в лоб не подходит, но мы в нужном направлении.

А что если взять какую-то известную, но нелинейную последовательность, например число PI. А в качестве значения для модуля будем брать не 2, а что-то другое. Можно даже подумать на тему меняющегося значения модуля. Последовательность цифр в числе Pi считается случайной. Генератор может работать, используя числа Пи, начиная с какой-то неизвестной точки. Пример такого алгоритма, с последовательностью на базе PI и с изменяемым модулем:

Но в JS число PI можно вывести только до 48 знака и не более. Поэтому предсказать такую последовательность все так же легко и каждый запуск такого генератора будет выдавать всегда одни и те же числа. Но наш генератор уже стал показывать числа от 0 до 9. Кстати, так выглядит распределение по выпадению чисел при 10000 итерациях:

Распределение очень неравномерное, но мы получим генератор чисел от 0 до 9.

Мы можем взять не число Pi, а время в числовом представлении и это число рассматривать как последовательность цифр, причем для того, чтобы каждый раз последовательность не повторялась, мы будем считывать ее с конца. Итого наш алгоритм нашего ГПСЧ будет выглядеть так:

Вот это уже похоже на генератор псевдослучайных чисел. И тот же Math.random() — это ГПСЧ, про него мы поговорим чуть позже. При этом у нас каждый раз первое число получается разным.

Собственно на этих простых примерах можно понять как работают более сложные генераторы случайных числе. И есть даже готовые алгоритмы. Для примера разберем один из них — это Линейный конгруэнтный ГПСЧ(LCPRNG).

Линейный конгруэнтный ГПСЧ

Линейный конгруэнтный ГПСЧ(LCPRNG) — это распространённый метод для генерации псевдослучайных чисел. Он не обладает криптографической стойкостью. Этот метод заключается в вычислении членов линейной рекуррентной последовательности по модулю некоторого натурального числа m, задаваемой следующей формулой:



где a(multiplier), c(addend), m(mask) — некоторые целочисленные коэффициенты. Получаемая последовательность зависит от выбора стартового числа — т.е. seed. При разных значениях seed получаются различные последовательности случайных чисел. Пример реализации такого алгоритма на JavaScript:

Многие языки программирования используют LСPRNG (но не именно такой алгоритм(!)).

Как говорилось выше, такую последовательность можно предсказать. Так зачем нам ГПСЧ? Если говорить про безопасность, то ГПСЧ — это проблема. Если говорить про другие задачи, то эти свойства — могут сыграть в плюс. Например для различных спец эффектов и анимаций графики может понадобиться частый вызов random. И вот тут важны распределение значений и перформанс! Секурные алгоритмы не могут похвастать скоростью работы.

Еще одно свойство — воспроизводимость. Некоторые реализации позволяют задать seed, и это очень полезно, если последовательность должна повторяться. Воспроизведение нужно в тестах, например. И еще много других вещей существует, для которых не нужен безопасный ГСЧ.

Как устроен Math.random()

Метод Math.random() возвращает псевдослучайное число с плавающей запятой из диапазона [0, 1) , то есть, от 0 (включительно) до 1 (но не включая 1), которое затем можно отмасштабировать до нужного диапазона. Реализация сама выбирает начальное зерно для алгоритма генерации случайных чисел; оно не может быть выбрано или сброшено пользователем.

Как устроен алгоритм Math.random() — интересный вопрос. До недавнего времени, а именно до 49 Chrome использовался алгоритм MWC1616:

Именно этот алгоритм генерит нам последовательность псевдослучайных чисел в промежутке между 0 и 1.

UPD

то видим, что должны быть скобки:

Предсказываем Math.random()

В нем есть задача:

Что нужно вписать вместо вопросов, чтобы функция вернула true? Кажется что это невозможно. Но, это возможно, если вы заглядывали в спеку и видели алгоритм ГПСЧ V8. Решение этой задачи в свое время мне показал Роман Дворнов:

Этот код работал в 70% случаев для Chrome



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

Выходит что мы можем отреверсить Math.random() и предсказать, какое было загадано число на основе того, что получили в данный момент времени. Для этого получаем два значения через Math.random(). Затем вычисляем внутреннее состояние по этим значениям. Имея внутреннее состояние можем предсказывать следующие значения Math.random() при этом не меняя внутреннее состояние. Меняем код так так, чтобы вместо следующего возвращалось предыдущее значение. Собственно все это и описано в коде-решении для задачи random4. Но потом алгоритм изменили (подробности читайте в спеке). Его можно будет сломать, как только у нас в JS появится нормальная работа с 64 битными числами. Но это уже будет другая история.

Новый алгоритм выглядит так:

Его все так же можно будет просчитать и предсказать. Но пока у нас нет “длинной математики” в JS. Можно попробовать через TypedArray сделать или использовать специальные библиотеки. Возможно кто-то однажды снова напишет предсказатель. Возможно это будешь ты, читатель. Кто знает ?

Сrypto Random Values

Метод Math.random() не предоставляет криптографически стойкие случайные числа. Не используйте его ни для чего, связанного с безопасностью. Вместо него используйте Web Crypto API (API криптографии в вебе) и более точный метод window.crypto.getRandomValues() .

Пример генерации случайного числа:

Но, в отличие от ГПСЧ Math.random(), этот метод очень ресурсоемкий. Дело в том, что данный генератор использует системные вызовы в ОС, чтобы получить доступ к источникам энтропии (мак адрес, цпу, температуре, etc…).

Материалы про Math.random()

Больше про random в спецификации:

Хорошая статья про работу рандомайзера

Пример реализации предсказателя с Math.random()

Кстати, следить за обновлениями и прочими материалами от меня можно в телеграм канале: @prowebit

Читайте также: