Мышь: mouseX и mouseY

До этого момента твой цыплёнок жил сам по себе — а сейчас он впервые посмотрит на тебя и пойдёт за твоей мышью.

Главное правило урока: mouseX и mouseY — это две встроенные переменные p5.js, в которых всегда лежат текущие координаты курсора на холсте. Они сами обновляются каждый кадр, и тебе достаточно просто их прочитать.

Зачем тебе координаты мыши

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

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

К концу урока ты соберёшь вот такой кадр: голубое небо, а по нему за твоим курсором плавно скользит жёлтый CodeChick с оранжевым клювом — куда мышь, туда и он. Подведёшь курсор к углу — цыплёнок прибежит в угол. Поведёшь по кругу — он закружится следом. И всё это уместится буквально в несколько строк, потому что вся тяжёлая работа — слежение за мышью — уже сделана за тебя самим p5.js.

Звучит как магия? На самом деле это две самые простые и приятные переменные во всём p5.js. Давай разберёмся, что в них лежит и как ими пользоваться.

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

Что такое mouseX и mouseY

Две переменные, которые всё знают

Вспомни систему координат с самых первых уроков: любая точка на холсте — это пара чисел (x, y), где (0, 0) — левый верхний угол, x растёт вправо, y растёт вниз. Так вот, p5.js постоянно следит за курсором и кладёт его координаты в две специальные переменные:

  • mouseX — горизонтальная координата курсора (сколько пикселей вправо от левого края);
  • mouseY — вертикальная координата курсора (сколько пикселей вниз от верхнего края).

Представь, что у холста есть невидимый датчик, как у двери в магазине: он всё время замечает, где сейчас курсор, и шепчет эти два числа твоему коду. Тебе не нужно ничего настраивать или включать — mouseX и mouseY доступны сразу, в любой момент внутри draw(). Ты просто читаешь их, как читаешь показания часов: посмотрел — узнал текущее значение.

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

Сравни это с прошлым уроком. Там тебе приходилось вручную заводить переменную, ставить ей стартовое значение и каждый кадр прибавлять скорость — целый ритуал, чтобы объект ожил. С мышью же половина работы исчезает: значение уже посчитано и обновлено за тебя, остаётся только им воспользоваться. Можно сказать, что mouseX и mouseY — это «бесплатные» переменные состояния, которые ведёт сам p5.js, опираясь на твою руку, а не на код. Поэтому первые интерактивные скетчи получаются такими короткими и при этом эффектными.

Самый первый пример: круг под курсором

Давай сразу нарисуем кружок ровно там, где сейчас мышь.

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(135, 206, 235); // небо

  fill(255, 209, 64);        // тёплый жёлтый
  circle(mouseX, mouseY, 60); // центр круга = позиция мыши
}

Результат: на голубом небе жёлтый круг диаметром 60, который всё время держится прямо под курсором. Ведёшь мышь — круг едет следом без задержки, повторяя каждое движение руки.

Разберём по шагам, что здесь происходит. circle(mouseX, mouseY, 60) рисует круг, и вместо обычных чисел-координат мы подставили mouseX и mouseY. На каждом кадре p5.js сначала подставляет в них свежие координаты курсора, потом выполняет circle() — и круг оказывается ровно под мышью. А background() в начале draw() стирает прошлый кадр, поэтому за кругом не тянется хвост из старых кружков (помнишь правило: фон — первая строка).

Почему круг едет за мышью: всё дело в кадрах

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

Вспомни про кадры и draw(). Функция draw() повторяется десятки раз в секунду — это как страницы флипбука, которые быстро листаются. И на каждом новом кадре значения mouseX и mouseYсвежие: p5.js обновляет их перед каждым прогоном draw(). То есть кадр за кадром круг рисуется во всё новой точке — там, где курсор оказался в этот момент. Глаз склеивает эти кадры в плавное движение, как страницы блокнота складываются в анимацию.

Сделаем это совсем наглядным — выведем координаты на экран прямо во время движения мыши.

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(20);
  fill(255);
  textSize(28);
  text("x: " + mouseX, 20, 40);
  text("y: " + mouseY, 20, 80);
}

Результат: на тёмном фоне белым текстом две строки — например «x: 213» и «y: 96». Стоит шевельнуть мышью, как числа мгновенно меняются: ведёшь вправо — растёт x, ведёшь вниз — растёт y. Уведёшь курсор в левый верхний угол — оба числа стремятся к нулю.

Поэкспериментируй: подведи курсор к каждому углу холста и посмотри, какие числа получаются. В левом верхнем будет около (0, 0), в правом нижнем — около (400, 400), ровно по размеру холста. Так ты прямо глазами увидишь, что mouseX и mouseY — это не магия, а просто два числа, которые всё время держат руку на пульсе курсора.

Кстати, обрати внимание на крошечную деталь, которая часто сбивает новичков: координата y растёт вниз, а не вверх, как нас учили на уроках математики. Ведёшь мышь к нижнему краю — mouseY увеличивается. Это привычно для экранов: отсчёт идёт от верхнего левого угла, как чтение текста — слева направо и сверху вниз. Если вдруг твой цыплёнок поедет «не в ту сторону» по вертикали, первым делом вспомни про это: вниз — это плюс, вверх — это минус. А ещё подвигай мышь за пределы холста и обратно: пока курсор внутри, числа меняются; когда он за краем, p5.js обычно показывает последнее значение у границы. Эти мелочи сейчас кажутся занудством, но именно они спасут тебя от часа недоумения позже.

Привязываем цыплёнка к мыши

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

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(135, 206, 235);

  // запоминаем позицию мыши в понятные имена
  let x = mouseX;
  let y = mouseY;

  // тело
  fill(255, 209, 64);
  circle(x, y, 100);

  // клюв (правее центра тела)
  fill(255, 140, 30);
  triangle(x, y - 8, x + 45, y, x, y + 12);

  // глаз (левее-выше центра)
  fill(40);
  circle(x - 22, y - 18, 14);
}

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

Вот в чём фокус. Мы один раз записали позицию мыши в удобные переменные: let x = mouseX; и let y = mouseY;. А дальше все фигуры рисуем относительно этой точки: глаз — это x - 22 и y - 18 (чуть левее и выше центра), клюв — x + 45 (правее). Поэтому, когда мышь сдвигается, сдвигаются все части разом, ведь все они посчитаны от одного и того же x, y. Это намного аккуратнее, чем втыкать mouseX в каждую фигуру по отдельности, и куда легче читать.

Плавное преследование: цыплёнок не телепортируется, а догоняет

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

let chickX = 200;
let chickY = 200;

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(135, 206, 235);

  // двигаемся на 10% оставшегося пути к мыши
  chickX = chickX + (mouseX - chickX) * 0.1;
  chickY = chickY + (mouseY - chickY) * 0.1;

  fill(255, 209, 64);
  circle(chickX, chickY, 100);
  fill(255, 140, 30);
  triangle(chickX, chickY - 8, chickX + 45, chickY, chickX, chickY + 12);
}

Результат: цыплёнок уже не прилипает к курсору, а мягко катится за ним, будто на невидимой резинке. Резко дёрнул мышь — цыплёнок плавно разгоняется и догоняет, тормозя у самой цели. Остановил курсор — он мягко доезжает и замирает рядом.

Здесь у цыплёнка появилась своя переменная состояния: chickX и chickY хранят его собственную позицию между кадрами (как в прошлом уроке). А выражение (mouseX - chickX) — это расстояние, которое осталось проехать до мыши; умножая его на 0.1, мы каждый кадр проходим лишь десятую часть пути. Чем меньше множитель, тем ленивее и плавнее погоня. Поменяй 0.1 на 0.02 — и цыплёнок станет совсем неспешным; поставь 0.5 — почти прилипнет к курсору.

Не только координаты: реагируем на близость мыши

Раз mouseX и mouseY — это обычные числа, с ними можно делать всё то же, что и с любыми числами: складывать, сравнивать, подставлять в условия. А значит, цыплёнок может не просто ехать за мышью, а реагировать на то, где она. Например, радоваться, когда курсор близко, и грустить, когда он далеко.

Чтобы узнать, далеко ли мышь от центра цыплёнка, в p5.js есть удобная функция dist() — она считает расстояние между двумя точками. Дадим ей координаты цыплёнка и координаты мыши, а потом по этому расстоянию решим, какого размера сделать глаз: близко — глаз большой и «живой», далеко — маленький.

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(135, 206, 235);

  let chickX = 200;
  let chickY = 200;

  // расстояние от центра цыплёнка до курсора
  let d = dist(chickX, chickY, mouseX, mouseY);

  // тело
  fill(255, 209, 64);
  circle(chickX, chickY, 120);

  // глаз тем больше, чем ближе мышь
  let eyeSize = 30 - d * 0.08;
  if (eyeSize < 6) {
    eyeSize = 6; // не даём глазу исчезнуть совсем
  }
  fill(40);
  circle(chickX - 20, chickY - 15, eyeSize);
}

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

Смотри, что мы сделали. dist(chickX, chickY, mouseX, mouseY) вернула число — расстояние в пикселях от цыплёнка до курсора. Когда мышь рядом, это число маленькое; когда далеко — большое (до ~280 в углу холста). Дальше мы превратили это число в размер глаза: 30 - d * 0.08. Близко (d мало) — вычитаем чуть-чуть, глаз остаётся крупным; далеко (d велико) — вычитаем много, глаз уменьшается. А if страхует нас, чтобы размер не ушёл в минус и глаз не исчез. Вот и вся «эмоция» — простая арифметика над расстоянием до мыши.

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

  • Пишешь mouseX() со скобками. mouseX и mouseY — это переменные, а не функции. Скобки тут не нужны и вызовут ошибку. Просто circle(mouseX, mouseY, 60), без круглых скобок после имени.

  • Используешь mouseX внутри setup(). В setup() скетч ещё только настраивается, мышь толком не отслеживается — там mouseX равен 0. Координаты курсора имеют смысл только в draw(), который крутится каждый кадр. Читай мышь именно там.

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

  • Подставил mouseX только в одну фигуру из нескольких. Тело поехало за мышью, а клюв и глаз остались на старом месте — цыплёнок развалился. Решение: записать let x = mouseX; один раз и рисовать все части относительно этого x, y.

  • Перепутал X и Y. Подставил mouseY туда, где ждут горизонтальную координату, и цыплёнок двигается вверх-вниз, когда ты ведёшь мышь влево-вправо. Запомни: mouseX — это всегда «вправо», первый параметр; mouseY — «вниз», второй.

Мини-практика: оживи мир CodeChick

Возьми за основу пример с цыплёнком, привязанным к мыши, и доработай его сам. Вот план:

  1. Добавь цыплёнку второй глаз справа (например, x + 6 по горизонтали) — так, чтобы оба глаза ездили вместе с телом. Проверь, что лицо не разваливается при движении мыши.
  2. Сделай так, чтобы размер цыплёнка зависел от высоты курсора: подставь mouseY в диаметр тела вместо числа 100. Поведи мышь сверху вниз и посмотри, как цыплёнок раздувается. Подумай, почему вверху он крошечный.
  3. Нарисуй рядом зёрнышко (маленький овал) в фиксированной точке холста — пусть цыплёнок «гоняется» за мышью мимо неподвижного зерна. Так ты увидишь разницу между тем, что привязано к мыши, и тем, что стоит на месте.
  4. Для смелых: добавь к погоне easing из примера выше и поиграй с множителем от 0.02 до 0.4, подбирая «характер» цыплёнка — ленивый он или шустрый.

Меняй числа и сразу смотри, что вышло. Именно так, руками, лучше всего чувствуется, как координаты мыши превращаются в живое движение.

Итоги

Сегодня твой цыплёнок впервые ожил и отреагировал на тебя. Вот что ты теперь умеешь:

  • mouseX и mouseY — встроенные переменные p5.js с текущими координатами курсора; их не надо заводить, просто читай.
  • Эти значения сами обновляются каждый кадр перед вызовом draw() — поэтому фигура, привязанная к мыши, движется без всякого кода движения.
  • Чтобы фигура ехала за курсором, достаточно подставить mouseX, mouseY в её координаты.
  • Сложного героя из нескольких фигур рисуй относительно одной точки (let x = mouseX;), чтобы он не разваливался.
  • Добавив переменную состояния и easing, можно заставить цыплёнка плавно догонять мышь, а не прилипать к ней.

Дальше будет ещё живее: в следующем уроке мы научимся ловить нажатия кнопки мыши — и цыплёнок сможет не просто следить за курсором, а реагировать на клик. Хлопнул мышью — что-то произошло. Увидимся на следующей странице!

Проверьте себя
1. Что хранится в переменных mouseX и mouseY?
AРазмер холста по ширине и высоте
BТекущие координаты курсора мыши на холсте
CКоличество нажатий кнопки мыши
DСкорость движения мыши
2. Почему круг, нарисованный как circle(mouseX, mouseY, 60), движется за мышью, хотя в коде нет строк про движение?
Ap5.js сам добавляет анимацию к каждой фигуре
Bdraw() повторяется каждый кадр, а mouseX и mouseY на каждом кадре обновляются свежими координатами
Ccircle() умеет следить за курсором
DДвижение задаётся в setup()
3. Как правильно прочитать горизонтальную координату курсора?
AmouseX() — с круглыми скобками
Bmouse.x — через точку
CmouseX — просто имя переменной, без скобок
DgetMouseX()
4. Ты рисуешь цыплёнка из тела, клюва и глаза, но mouseX подставил только в тело. Что увидишь при движении мыши?
AВесь цыплёнок поедет за мышью
BТело поедет за курсором, а клюв и глаз останутся на месте — цыплёнок развалится
CЦыплёнок исчезнет
Dp5.js выдаст ошибку
5. В каком месте скетча стоит читать mouseX и mouseY?
AВ setup(), один раз при запуске
BВ draw(), который выполняется каждый кадр
CВ createCanvas()
DГде угодно — разницы нет
6. Что делает строка chickX = chickX + (mouseX - chickX) * 0.1; в примере с плавным преследованием?
AМгновенно ставит цыплёнка под курсор
BКаждый кадр сдвигает цыплёнка на десятую часть оставшегося пути до мыши, создавая эффект плавной погони (easing)
CУмножает координату мыши на 0.1
DОстанавливает цыплёнка