Клики и состояния
Один клик — и цыплёнок засыпает; ещё клик — просыпается. Сегодня учим CodeChick реагировать на нажатие мыши и помнить, в каком он состоянии.
Состояние — это то, что скетч помнит между кадрами: открыт глаз или закрыт, день сейчас или ночь, спит цыплёнок или скачет. Клик переключает состояние, а draw() каждый кадр смотрит на него и решает, что рисовать.Зачем это вообще нужно
Представь любую игру в телефоне. Ты тапаешь по экрану — и что-то происходит: персонаж прыгает, лампочка загорается, лайк под постом меняет цвет с серого на красный. Программа не просто рисует картинку — она слушает, что ты делаешь, и помнит, что было раньше. Лайк красный? Значит, ты уже лайкнул. Тапнешь ещё раз — снова станет серым. Это и есть переключение состояния по клику.
В прошлом уроке про мышь и координаты mouseX/mouseY наш цыплёнок следил за курсором — двигался туда, куда ты ведёшь мышь. Это здорово, но он реагировал всё время, непрерывно. А сегодня мы научим его реагировать на отдельное событие — на сам момент нажатия. И не просто дёргаться, а запоминать результат: кликнул — заснул, кликнул ещё раз — проснулся.
К концу урока ты соберёшь скетч, где CodeChick спит с закрытыми глазами на тёмном фоне, а по клику просыпается: фон светлеет, глаз открывается. Каждый новый клик переключает его туда-обратно, как выключатель света в комнате. Поехали разбираться, как это устроено.
Подумай, сколько вокруг тебя вещей, которые работают по этому принципу. Кнопка паузы в плеере: жмёшь — музыка останавливается, жмёшь снова — играет дальше. Тёмная тема в приложении: один тап — и весь экран перекрашивается. Сторис в соцсети: тап вправо — следующий кадр, тап влево — предыдущий. Везде одно и то же: программа ловит твоё нажатие и меняет что-то, что она помнит. Сегодня ты впервые соберёшь такой механизм своими руками, и дальше будешь узнавать его в каждой второй программе.
Метафора: выключатель света
Самая близкая вещь из жизни — обычный выключатель на стене. У него ровно два положения: свет горит или свет погашен. Ты не держишь его пальцем всё время — ты просто щёлкаешь один раз, и он остаётся в новом положении сам. Щёлкнул снова — вернулся обратно.
В p5.js этот выключатель собирается из двух кусочков:
- Память о положении — переменная, которая хранит, горит свет сейчас или нет. У нас это будет
Переменная состояния— обычно булева (true/false). - Реакция на щелчок — функция, которая срабатывает в момент нажатия мыши. В p5.js это
mousePressed().
mousePressed() — это специальная функция p5.js: ты её просто объявляешь, а библиотека сама вызывает её один раз в тот момент, когда ты нажал кнопку мыши. Не каждый кадр, а именно на событие нажатия.Важно почувствовать разницу. draw() — это художник, который без устали перерисовывает кадр за кадром, много раз в секунду. А mousePressed() — это звоночек на двери: он молчит, молчит, а потом дзынь — кто-то нажал. Ты пишешь, что делать на этот «дзынь», а всё остальное время функция спит.
Почему важно ловить именно событие, а не проверять мышь каждый кадр? Допустим, ты решил по-другому: в draw() смотреть, нажата ли кнопка мыши (для этого есть встроенная переменная mouseIsPressed). Но draw() крутится десятки раз в секунду, а палец на кнопке ты держишь хотя бы пару десятых секунды. Значит, за один твой щелчок состояние переключится не один раз, а много — и выключатель будет бешено мигать туда-сюда, пока ты не отпустишь. С mousePressed() такой беды нет: она срабатывает ровно в момент нажатия и ровно один раз, сколько бы ты ни держал кнопку. Поэтому для переключателей всегда бери событие, а не проверку состояния кнопки.
Пример 1. Самый первый клик
Начнём с минимума: будем считать, сколько раз ты кликнул, и показывать это словом в консоли. Никакой графики — только чтобы увидеть, что событие вообще ловится.
let cliks = 0;
function setup() {
createCanvas(400, 400);
}
function draw() {
background(255, 240, 180);
}
function mousePressed() {
cliks = cliks + 1;
console.log('Цыплёнок клюнул зерно ' + cliks + ' раз');
}Результат: на экране ровный тёплый жёлтый холст, как солнечное гнездо. Сам холст не меняется, но каждый раз, когда ты кликаешь по нему мышью, в консоли появляется новая строка: «Цыплёнок клюнул зерно 1 раз», потом «...2 раз», «...3 раз». Счётчик растёт ровно на единицу за клик — значит, mousePressed() срабатывает строго по одному разу на нажатие.
Что здесь происходит по шагам
- Снаружи всех функций мы завели переменную
cliks = 0. Она живёт всё время работы скетча и помнит число между кадрами и кликами — это и есть переменная состояния. setup()один раз создаёт холст 400 на 400.draw()просто заливает фон — он крутится постоянно, но ничего интересного тут пока не делает.mousePressed()мы объявили рядом сsetup()иdraw(), на том же уровне. p5.js сам её найдёт по имени и вызовет на каждое нажатие. Внутри мы увеличиваем счётчик и печатаем строку.
Запомни главное: mousePressed() не нужно нигде «включать» или вызывать вручную. Достаточно объявить функцию ровно с таким именем — p5.js делает остальное.
Пример 2. Цыплёнок-выключатель
Теперь самое вкусное — переключение состояния. Заведём булеву переменную spit (спит): если true — цыплёнок дремлет, фон тёмный, глаз закрыт; если false — бодрствует, фон светлый, глаз открыт. Клик будет переворачивать значение.
let spit = true;
function setup() {
createCanvas(400, 400);
noStroke();
}
function draw() {
if (spit) {
background(40, 40, 70);
} else {
background(180, 220, 255);
}
// тело цыплёнка
fill(255, 215, 0);
ellipse(200, 220, 160, 150);
// голова
ellipse(200, 130, 110, 100);
// клюв
fill(255, 140, 0);
triangle(245, 125, 245, 145, 285, 135);
// глаз: открыт или закрыт
if (spit) {
stroke(0);
strokeWeight(3);
line(190, 120, 215, 120);
noStroke();
} else {
fill(0);
ellipse(205, 118, 18, 18);
}
}
function mousePressed() {
spit = !spit;
}Результат: на старте фон тёмно-синий, как ночь, и у жёлтого цыплёнка вместо глаза просто короткая чёрточка — он спит. Кликаешь мышью — и в тот же миг фон становится светло-голубым, как дневное небо, а на месте чёрточки появляется круглый чёрный глаз: цыплёнок проснулся и смотрит на тебя. Ещё клик — снова ночь и закрытый глаз. Так туда-сюда, сколько хочешь.
Сердце примера — строка spit = !spit
Знак ! читается как «не» и переворачивает булево значение наоборот: !true даёт false, а !false даёт true. Поэтому строка spit = !spit означает «возьми текущее значение и сделай противоположным». Был true — стал false, и наоборот. Это классический трюк для выключателя: одна строка, и переключение готово.
Дальше всё держится на draw(). Он каждый кадр спрашивает переменную: if (spit) — рисуй ночь и закрытый глаз, else — рисуй день и открытый. Сам mousePressed() ничего не рисует! Он только меняет переменную. А художник draw() через долю секунды увидит новое значение и перерисует картинку. Получается красивое разделение труда: клик меняет память, кадр читает память и показывает.
Пример 3. Три состояния по кругу
Выключатель с двумя положениями — это хорошо, но иногда нужно больше. Представь настроение цыплёнка: спокойное, радостное, сонное — и клик переключает их по кругу. Для этого булевой переменной мало, возьмём число-режим.
let nastroenie = 0; // 0 спокоен, 1 радостен, 2 сонный
function setup() {
createCanvas(400, 400);
noStroke();
}
function draw() {
background(255, 245, 200);
fill(255, 215, 0);
ellipse(200, 200, 180, 170);
fill(0);
if (nastroenie === 0) {
// спокоен: два кружка-глаза
ellipse(175, 185, 16, 16);
ellipse(225, 185, 16, 16);
} else if (nastroenie === 1) {
// радостен: глаза-дуги вверх
noFill();
stroke(0);
strokeWeight(4);
arc(175, 190, 24, 24, PI, TWO_PI);
arc(225, 190, 24, 24, PI, TWO_PI);
noStroke();
} else {
// сонный: чёрточки
stroke(0);
strokeWeight(4);
line(165, 185, 185, 185);
line(215, 185, 235, 185);
noStroke();
}
}
function mousePressed() {
nastroenie = (nastroenie + 1) % 3;
}Результат: жёлтый цыплёнок на кремовом фоне. Сначала у него два спокойных круглых глаза. Кликнул — глаза превратились в две весёлые дуги, будто он улыбается ими. Ещё клик — дуги стали ровными чёрточками, цыплёнок задремал. Четвёртый клик возвращает к спокойным круглым глазам, и круг повторяется.
Магия строки (nastroenie + 1) % 3
Здесь работает оператор остатка от деления %. Мы прибавляем единицу к режиму, а потом берём остаток от деления на 3. Смотри, как бежит число: 0 → 1 → 2 → снова 0. Когда дошли до 3, остаток от деления на 3 равен 0 — и счётчик красиво заворачивается обратно в начало. Этот приём называют «зацикливанием по модулю», и он пригодится тебе всюду, где нужно перебирать варианты по кругу: смена темы оформления, переключение кадров спрайта, листание слайдов.
Обрати внимание на разницу подходов. В примере 2 у нас было ровно два состояния, и булева переменная подошла идеально — она и так умеет хранить только true или false. А когда вариантов три и больше, булевой уже мало: тут на помощь приходит число-режим. Правило простое: два состояния — бери булеву и переворачивай через !; три и больше — бери число и крути его по модулю. Оба способа делают одно и то же — хранят состояние и переключают его по клику.
Пример 4. Клик по самому цыплёнку
Пока мы реагировали на клик в любом месте холста. Но в настоящих приложениях кнопки занимают не весь экран — важно понять, попал ли клик именно по объекту. Сделаем так: цыплёнок пищит (выводит сообщение и чуть подпрыгивает), только если ты кликнул прямо по нему, а не мимо.
let pisknul = false;
function setup() {
createCanvas(400, 400);
noStroke();
}
function draw() {
background(200, 235, 255);
// если пискнул — поднимем чуть выше
let y = 220;
if (pisknul) {
y = 200;
}
fill(255, 215, 0);
ellipse(200, y, 140, 130);
fill(255, 140, 0);
triangle(255, y - 5, 255, y + 15, 295, y + 5);
}
function mousePressed() {
// расстояние от клика до центра цыплёнка
let d = dist(mouseX, mouseY, 200, 220);
if (d < 70) {
pisknul = !pisknul;
console.log('Пи-пи!');
}
}Результат: на голубом фоне сидит жёлтый цыплёнок. Если ты кликаешь мимо него, в пустое небо — ничего не происходит. Но стоит кликнуть прямо по телу цыплёнка, и он подскакивает чуть вверх, а в консоли появляется «Пи-пи!». Кликнул по нему ещё раз — опускается обратно. Клики мимо по-прежнему игнорируются.
Как мы поняли, что попали по цыплёнку
Здесь работает функция dist() — она считает расстояние между двумя точками. Мы измеряем, насколько далеко место клика (mouseX, mouseY из прошлого урока) от центра цыплёнка (200, 220). Если расстояние меньше 70 — значит, клик попал внутрь его круглого тела, и тогда мы переключаем состояние. Если дальше — клик мимо, и if просто не срабатывает. Это и есть основа любой кнопки: проверить, попал ли курсор в нужную область, и только тогда что-то делать.
Частые ошибки и подводные камни
1. Кладут mousePressed() внутрь draw()
Самая популярная ошибка новичка — написать function mousePressed() внутри фигурных скобок draw() или setup(). Так нельзя: p5.js ищет эту функцию на самом верхнем уровне, рядом с setup и draw. Если спрятать её внутри другой функции, библиотека её просто не найдёт, и клики перестанут работать. Объявляй mousePressed() отдельным блоком, не вложенным.
2. Меняют картинку прямо в mousePressed() вместо переменной
Хочется внутри mousePressed() сразу нарисовать новый глаз или сменить фон. Но draw() через миг перерисует весь кадр заново и затрёт то, что ты нарисовал на клик. Правильный путь: в mousePressed() меняй переменную состояния, а рисуй всегда в draw() по этой переменной. Клик отвечает за «что запомнить», кадр — за «что показать».
3. Объявляют переменную состояния внутри функции
Если написать let spit = true; внутри draw(), она будет рождаться заново на каждом кадре и тут же сбрасываться в true — переключение не запомнится. Переменная состояния должна жить снаружи всех функций, в самом верху файла. Только тогда она переживает кадры и клики.
4. Путают = и === при проверке режима
В условии if (nastroenie === 0) нужны три знака равенства — это сравнение. Один знак = — это присваивание, и если написать if (nastroenie = 0), ты случайно обнулишь переменную прямо в проверке. Картинка застрянет на одном состоянии. Для сравнения всегда ===.
5. Забывают, что !spit ничего не меняет без присваивания
Строка !spit; сама по себе бесполезна: она вычисляет противоположное значение и тут же выбрасывает его. Чтобы переключение сохранилось, результат надо записать обратно: spit = !spit;. Без левой части spit = переменная не изменится, и цыплёнок не проснётся.
Мини-проект: режим день и ночь
Собери свой переключатель сцены. Базу бери из примера 2 и доделай сам:
- Заведи булеву переменную
den(день) со стартаtrue. - В
draw()поif (den)рисуй светлый фон и жёлтое солнце-кружок в углу; вelse— тёмный фон и белый месяц. - Цыплёнка рисуй всегда, но в режиме ночи добавь ему закрытый глаз (чёрточку), а днём — открытый круглый глаз.
- В
mousePressed()переключайden = !den;. - Усложни: добавь третье состояние «закат» через число-режим и оператор
% 3, как в примере 3, и подбери для заката оранжево-розовый фон.
Когда заработает — поэкспериментируй: пусть на закате у цыплёнка будет ещё и румянец (маленький розовый кружок на щеке). Поменяй цвета фонов, посмотри, какое сочетание тебе нравится. Это твоя сцена — играй с ней.
Итоги
Сегодня ты научил CodeChick реагировать на отдельное событие, а не дёргаться без остановки. Главное, что стоит унести с собой:
mousePressed()— функция-звоночек: p5.js сам вызывает её один раз на каждое нажатие мыши. Объявляй её на верхнем уровне.- Переменная состояния живёт снаружи функций и помнит, что было между кадрами и кликами.
- Клик меняет переменную, а рисует всегда
draw(), читая эту переменную. Не рисуй прямо в обработчике клика. - Булеву переключают через
spit = !spit, а перебор по кругу — через(режим + 1) % число.
Это фундамент любой интерактивности: лайки, кнопки, режимы, паузы в играх — всё держится на «событие меняет состояние, кадр его показывает». В следующем уроке мы дадим цыплёнку слушать клавиатуру: стрелки будут двигать его по холсту, а пробел — заставлять подпрыгивать. Тот же приём с состоянием, только источник события другой. До встречи в коде!