random(): управляемый хаос

Сегодня ты научишь компьютер бросать кубик: получать случайные числа функцией random() и раскидывать вокруг цыплёнка перья так, что двух одинаковых кадров не будет.
random() — функция p5.js, возвращающая случайное число в заданном диапазоне. Один вызов — одно непредсказуемое число, и именно из этой непредсказуемости рождается живая, неповторимая картинка.

Зачем тебе случайность

Открой любую игру, в которую ты залипаешь. Звёзды на ночном фоне разбросаны как попало, искры от взрыва летят в разные стороны, трава колышется не строем, а вразнобой. Если бы дизайнер расставлял каждую звезду вручную по линейке — получился бы скучный узор из обоев, а не небо. Живое ощущение даёт именно лёгкий беспорядок.

В прошлом уроке про цикл for ты научился строить ровные ряды: цыплята выстраивались по сетке, аккуратно, как клетки в тетради. Это здорово для порядка, но природа так не работает. Перья, упавшие с цыплёнка, не лягут ровной решёткой — они разлетятся куда попало. Вот за этим «куда попало» и нужна случайность.

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

Подумай вот о чём. Когда ты делаешь скриншот мема и кидаешь другу, картинка всегда одна и та же — фиксированная. А генеративная работа похожа скорее на ленту рекомендаций: правила одни, а лента каждый раз новая. Художники по всему миру продают целые коллекции картинок, где они написали один скетч с random(), а компьютер сгенерировал тысячу непохожих вариантов. Ты сегодня делаешь ровно первый шаг к этому: учишься писать не картинку, а её рецепт.

Как работает random(): кубик внутри кода

Представь обычный игральный кубик. Ты его бросаешь — и не знаешь заранее, выпадет 1 или 6. Но ты точно знаешь правило: число будет от 1 до 6, не меньше и не больше. random() — это такой же кубик, только грани у него ты задаёшь сам.

У функции три способа вызова, и их полезно держать в голове все три.

ВызовЧто вернёт
random()дробное число от 0 (включительно) до 1 (не включая)
random(10)дробное число от 0 до 10
random(5, 15)дробное число от 5 до 15

Обрати внимание на слово «дробное». Кубик в жизни даёт только целые числа, а random() щедрее: он может вернуть 7.42, или 0.0013, или 14.999. Это удобно, когда ты раскидываешь точки по холсту — там не нужны ровные целые, наоборот, дробные координаты делают картинку плавнее.

Самое важное, что нужно почувствовать: random() возвращает значение. Это не команда «нарисуй» и не магия — это просто число, которое ты можешь сохранить в переменную, передать в fill(), в circle(), куда угодно. Везде, где ты раньше писал конкретную цифру, теперь можно поставить случайную.

Первый бросок

Давай посмотрим самый простой пример — нарисуем кружок в случайном месте холста.

function setup() {
  createCanvas(400, 400);
  background(30);
  noStroke();
  fill(255, 221, 51); // жёлтый, цвет цыплёнка

  let x = random(400); // случайное число от 0 до 400
  let y = random(400);

  circle(x, y, 40);
}

Результат: на тёмно-сером холсте появляется один жёлтый кружок диаметром 40 пикселей. Где именно он окажется — заранее неизвестно: может прижаться к верхнему углу, может оказаться в центре. Перезапусти скетч — кружок прыгнет в новое место.

Разберём построчно. random(400) бросает наш кубик с диапазоном от 0 до 400 — как раз ширина холста. Полученное число мы кладём в переменную x. То же самое для y. А дальше circle(x, y, 40) рисует кружок в этой случайной точке. Никакой магии: сначала получили числа, потом нарисовали по ним.

Случайность в цвете

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

function setup() {
  createCanvas(400, 400);
  background(30);
  noStroke();

  let r = 255;
  let g = random(150, 230); // зелёная доля гуляет
  let b = random(0, 60);    // синей почти нет

  fill(r, g, b);
  circle(200, 200, 120); // тело цыплёнка в центре
}

Результат: в центре холста — большой кружок-цыплёнок. Его цвет каждый запуск чуть разный: от тёплого оранжевого (когда зелёная доля маленькая) до ясно-жёлтого (когда она ближе к 230). Красная доля закреплена на максимуме, поэтому оттенок всегда остаётся в тёплой гамме и никогда не уходит в зелень.

Видишь приём? Мы не делаем случайным весь цвет, иначе цыплёнок мог бы стать фиолетовым или синим. Мы фиксируем красный канал и даём гулять только зелёному и синему в узких рамках. Так разнообразие есть, но герой остаётся узнаваемым. Это и называется «управляемый хаос»: ты решаешь, где случайности можно разгуляться, а где нельзя. Запомни это словосочетание — оно описывает почти всю генеративную графику. Чистый хаос выглядит как помехи на старом телевизоре, а чистый порядок — как клетка в тетради. Красота живёт посередине, и регулируешь её именно ты, шириной диапазонов random().

Случайный выбор из вариантов

Иногда тебе не нужно дробное число вообще — нужно выбрать один из нескольких вариантов. Скажем, перо у цыплёнка бывает трёх цветов. У random() для этого есть отдельный фокус: если передать ей массив, она вернёт случайный элемент.

let palette = [
  color(255, 221, 51),  // жёлтый
  color(255, 160, 40),  // оранжевый
  color(255, 120, 30)   // рыжий
];

let c = random(palette); // вернёт ОДИН цвет из трёх
fill(c);

Результат: переменная c получает один из трёх заранее выбранных цветов, и какой именно — решает случай. Это удобнее, чем гонять числа в random(): ты сам собрал палитру, которая точно красива, и доверил выбор кубику. Тот же приём работает с любым списком — формами, словами, картинками.

Раскидываем перья: random() внутри цикла

Один случайный кружок — это мило, но настоящая сила открывается, когда random() встречается с циклом for из прошлого урока. Цикл повторяет рисование много раз, а random() на каждом повторе даёт новые числа — и вместо ряда одинаковых фигур получается рассыпанное облако.

function setup() {
  createCanvas(400, 400);
  background(30);
  noStroke();

  // сначала сам цыплёнок в центре
  fill(255, 221, 51);
  circle(200, 200, 100);

  // теперь 80 перьев вокруг него
  for (let i = 0; i < 80; i++) {
    let x = random(width);
    let y = random(height);
    let d = random(4, 14); // размер пера тоже случайный
    fill(255, random(160, 210), 40);
    circle(x, y, d);
  }
}

Результат: в центре сидит крупный жёлтый цыплёнок, а по всему холсту рассыпано 80 маленьких пёрышек разного размера и слегка разного оттенка. Картинка выглядит живой и неповторимой; перезапусти — и перья лягут совсем иначе. Двух одинаковых кадров ты не получишь.

Что здесь происходит на каждом витке цикла. Переменная i считает от 0 до 79 — всего 80 повторов. Внутри каждого повтора мы заново бросаем кубик для x, для y, для размера d и для зелёной доли цвета. Поэтому каждое перо получает свою четвёрку случайных чисел и встаёт на своё место. Заметь: я использовал width и height вместо чисел 400 — это встроенные переменные p5.js с размерами холста, с ними код не сломается, если ты поменяешь размер canvas.

Поиграй с числами

Лучший способ понять случайность — крутить ручки. Попробуй прямо мысленно (или в реальном скетче) поменять:

  • Количество перьев. Замени 80 на 300 — холст загустеет, цыплёнок утонет в метели из перьев. Поставь 10 — будет редкая россыпь.
  • Диапазон размера. random(4, 14) даёт мелкие пёрышки. Сделай random(10, 40) — и перья станут крупными, разнобой заметнее.
  • Гамму цвета. Расширь зелёную долю до random(100, 255) — оттенки запрыгают сильнее, от рыжего до почти белого.

Никакого «правильного» числа тут нет. Ты художник, а random() — твоя кисть, которая немного дрожит. Сколько дрожи — решаешь ты диапазоном.

Частые ошибки и подводные камни

Случайность простая, но именно на ней новички спотыкаются особенно обидно. Вот что ломается чаще всего.

1. random() зовут один раз, а ждут разнообразия

Классика. Человек хочет 50 перьев разных размеров и пишет так:

let d = random(4, 14); // вызвали ОДИН раз, до цикла
for (let i = 0; i < 50; i++) {
  circle(random(width), random(height), d); // d всё время одно и то же!
}

Результат: перья разбросаны по холсту в разных местах, но все одинакового размера — скучно и неестественно. Случайное число посчиталось один раз и легло в d; в цикле оно просто переиспользуется. Чтобы размер менялся, вызов random() должен стоять внутри цикла.

2. Случайные числа сыплются в draw() и всё мерцает

Если положить рисование перьев в draw() вместо setup(), то random() будет бросать новые числа 60 раз в секунду. Перья начнут бешено скакать и мигать — глазам больно. Случайную, но застывшую картинку рисуй в setup() (он выполняется один раз). В draw() случайность уместна, только когда ты хочешь постоянного движения — например, мерцание звёзд.

3. Путают random(5, 15) и random(15, 5)

Первый аргумент — нижняя граница, второй — верхняя. Если случайно переставить их местами, в одних версиях p5.js диапазон отработает как ожидалось, а логика чтения кода всё равно сломается у тебя в голове. Привыкай писать от меньшего к большему: random(min, max).

4. Забывают, что число дробное

Иногда тебе нужен случайный целый индекс — например, выбрать одну из 4 картинок. Если написать random(4), получишь дробное вроде 2.71, и обращение по такому индексу к массиву сломается. Оберни вызов в floor(): floor(random(4)) даст ровно 0, 1, 2 или 3. Это частая засада на следующих уроках, запомни её заранее.

5. Ждут «равномерности на глаз»

80 случайных точек на холсте почти наверняка лягут неровно: где-то будет сгусток, где-то пустое пятно. Это нормально! Случайность не обязана выглядеть равномерно — настоящая равномерность как раз кажется неестественной. Если тебе нужна именно гладкая, «причёсанная» случайность без резких скачков, для этого есть шум Перлина — о нём поговорим в следующем уроке.

6. Забывают фиксировать фон

Ещё одна тонкость, связанная с предыдущими. Если ты рисуешь случайные перья в draw() и при этом не вызываешь background() в начале каждого кадра, перья будут накапливаться поверх старых. За пару секунд весь холст забьётся точками до белизны. Иногда такой эффект «накопления» нужен специально — получается красивая текстура. Но если ты ждал аккуратное облако из 80 перьев, а холст заплывает, проверь именно это: фон должен перерисовываться каждый кадр, либо рисование вообще должно жить в setup().

Мини-проект: облако перьев цыплёнка

Теперь твоя очередь. Возьми пример с перьями за основу и доведи его до настоящего портрета цыплёнка в метели. Вот чек-лист задания:

  1. Нарисуй в центре холста цыплёнка: большой жёлтый круг-тело и круг поменьше сверху — голову. Координаты головы привяжи к телу, чтобы они не разъезжались.
  2. Добавь оранжевый клюв — маленький треугольник или кружок сбоку головы. Это уже знакомый тебе герой курса, просто теперь вокруг него будет буря.
  3. Циклом for раскидай 60–120 перьев. Размер каждого — random(3, 16), координаты — по всему холсту через random(width) и random(height).
  4. Сделай цвет перьев слегка случайным в тёплой гамме: красную долю закрепи, зелёную пусти через random().
  5. Бонус. Добавь немного прозрачности перьям — четвёртый аргумент в fill(), например fill(255, random(160, 210), 40, 150). Перья начнут просвечивать друг сквозь друга, и облако станет воздушнее.

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

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

Итоги

Сегодня ты приручил случайность. Коротко главное:

  • random() возвращает случайное число, а не рисует само по себе — его можно поставить везде, где раньше стояла цифра.
  • Диапазон ты задаёшь сам: random(max) или random(min, max). Это грани твоего кубика.
  • Случайность раскрывается в паре с циклом for: вызов random() должен быть внутри цикла, иначе все фигуры выйдут одинаковыми.
  • Застывшую случайную картинку рисуй в setup(), иначе она замигает в draw().
  • «Управляемый хаос» — это когда ты решаешь, чему случаться, а что закрепить. Так цыплёнок остаётся жёлтым, но каждый кадр всё равно уникален.

Есть одна проблема: наш random() прыгает резко, без всякой плавности. Для перьев это здорово, а вот для плавно колышущейся травы или дыма такие скачки не годятся — там нужна гладкая случайность. В следующем уроке мы познакомимся с шумом Перлина: той же случайностью, но текучей, как вода. Цыплёнок там обзаведётся развевающимся на ветру хохолком. До встречи!

Проверьте себя
1. Что вернёт вызов random(5, 15)?
AСлучайное число от 5 до 15
BРовно число 10 (середину)
CСлучайное число от 0 до 15
DСписок из чисел 5 и 15
2. Ты хочешь нарисовать циклом 50 кружков РАЗНОГО случайного размера. Где должен стоять вызов random() для размера?
AВнутри цикла for, на каждом повторе
BОдин раз до цикла, в переменной
CВ функции background()
DВ createCanvas()
3. Почему случайную, но неподвижную картинку с перьями лучше рисовать в setup(), а не в draw()?
Adraw() повторяется много раз в секунду, и перья будут мигать и скакать
BВ draw() функция random() не работает вообще
Csetup() рисует ярче, чем draw()
DВ draw() запрещены циклы for
4. Тебе нужен случайный ЦЕЛЫЙ индекс 0, 1, 2 или 3. Как это получить?
Afloor(random(4))
Brandom(0, 3)
Crandom(4)
Drandom() * 3
5. В примере с цветом цыплёнка мы закрепили красный канал на 255 и пускали через random() только зелёный и синий. Зачем?
AЧтобы оттенок гулял, но цыплёнок оставался узнаваемо тёплым и не позеленел
BЧтобы код работал быстрее
CПотому что random() умеет менять только два канала
DЧтобы цыплёнок стал полностью случайного цвета
6. 80 случайных точек на холсте легли неровно: где-то гуще, где-то пусто. Это ошибка?
AНет, настоящая случайность почти всегда выглядит неравномерно — это нормально
BДа, нужно вызвать random() ещё раз
CДа, значит диапазон задан неверно
DДа, точки обязаны ложиться ровной сеткой