Клавиатура: управление с клавиш
Сегодня CodeChick наконец перестанет просто стоять и начнёт слушаться твоих клавиш — стрелок и букв, как настоящий герой видеоигры.
Главная идея урока: у p5.js есть два способа узнать про клавиши.
keyPressed()срабатывает один раз в момент нажатия — это про разовые действия. АkeyIsDown()проверяет прямо сейчас, зажата ли клавиша — это про плавное движение, пока ты держишь стрелку.
Зачем тебе клавиатура
Вспомни любую игру, в которую ты залипал: Mario, Minecraft, какой-нибудь платформер на телефоне с раскладкой кнопок на экране. Что в них главное? Ты жмёшь стрелку — и герой идёт. Отпускаешь — он встаёт. Жмёшь пробел — он прыгает. Между твоим пальцем и персонажем будто протянута невидимая нить: ты двигаешь — он реагирует. Именно это ощущение управления и превращает картинку на экране в игру. Без него на экране — мультик, который крутится сам по себе и тебя не замечает. С ним — мир, который слушается тебя.
В прошлом уроке про клики и состояния мы уже научили CodeChick реагировать на мышь: кликнул — что-то поменялось. Это здорово, но мышь — это про «ткнуть в точку», разовый жест. А вот клавиатура — это про непрерывное управление: зажал стрелку и ведёшь героя, куда хочешь, как джойстик. Разница как между «щёлкнуть выключатель» и «крутить руль»: первое — событие, второе — процесс, который длится, пока ты держишь. Сегодня мы дадим нашему цыплёнку оба этих умения сразу.
И вот что важно понять с самого начала: клавиатура для p5.js — это не одна кнопка «реагируй на клавиши», а целый набор разных инструментов под разные задачи. Прыжок по пробелу и плавное движение по стрелке — это технически две совершенно разные вещи, и если перепутать инструменты, всё сломается некрасиво: герой будет дёргаться, залипать или вовсе игнорировать клавиши. Поэтому полурока мы потратим на то, чтобы ты раз и навсегда уложил в голове, какой инструмент когда брать. Это знание окупится в каждой игре, которую ты потом напишешь.
К концу урока у тебя будет скетч, где CodeChick бегает по холсту от твоих стрелок: жмёшь вправо — он едет вправо, жмёшь вверх — поднимается, отпускаешь — замирает. А ещё по нажатию буквы он будет менять цвет, а по пробелу — телепортироваться домой. Получится маленькая песочница, которой можно реально управлять руками — твоя первая игрушка с настоящим управлением. Поехали.
Как p5.js слышит клавиши
Метафора: дверной звонок и зажатая кнопка
Представь две разные кнопки. Первая — дверной звонок: ты нажал, он один раз дзинькнул, и всё — сколько бы ты палец ни держал, второго звонка не будет, пока не отпустишь и не нажмёшь снова. Вторая — кнопка газа в машине: пока ты её держишь, машина едет; отпустил — встала. Звонок реагирует на событие нажатия, газ реагирует на состояние «зажато прямо сейчас».
В p5.js ровно эти две модели:
keyPressed() — это дверной звонок. Особая функция, которую p5.js вызывает один раз в тот момент, когда ты нажал любую клавишу. Идеально для разовых действий: прыгнуть, выстрелить, поменять цвет, поставить паузу.
keyIsDown(код) — это педаль газа. Функция, которая отвечает
true, если клавиша с таким кодом зажата прямо в этот кадр, иfalse, если нет. Вызываешь её внутриdraw()каждый кадр — и пока стрелка зажата, герой едет.
Запомни это разделение, оно — сердце всего урока. Разовое действие → keyPressed(). Непрерывное движение → keyIsDown() в draw(). Новички постоянно их путают, и от этого герой либо движется рывками по одному пикселю на нажатие, либо «залипает». Мы этого избежим.
Почему так вышло, что существуют два разных механизма? Дело в том, что у клавиатуры есть два разных вопроса, на которые хочется отвечать. Первый: «случилось ли только что нажатие?» — это момент, точка во времени, как вспышка. Второй: «держит ли пользователь клавишу сейчас?» — это длительность, состояние, которое тянется во времени. Дверной звонок отвечает на первый вопрос, педаль газа — на второй. p5.js даёт тебе по инструменту на каждый, потому что одним инструментом обе задачи красиво не решить. Если бы у тебя был только звонок, плавно вести героя пришлось бы дробным стуком по клавише; если бы только газ — невозможно было бы поймать ровно один момент нажатия для прыжка или паузы. Имея оба, ты выбираешь нужный под задачу — и всё ложится естественно.
Как узнать, какую клавишу нажали
Когда срабатывает keyPressed(), p5.js кладёт информацию о нажатой клавише в две встроенные переменные:
key— это символ клавиши в виде строки: например'a','W',' '(пробел). Удобно для букв и цифр.keyCode— это числовой код клавиши. Нужен для клавиш, у которых нет печатного символа: стрелки, Enter, Shift. У p5.js для них есть готовые имена-константы:LEFT_ARROW,RIGHT_ARROW,UP_ARROW,DOWN_ARROW,ENTER,SHIFT.
То есть для буквы r ты проверяешь key === 'r', а для стрелки влево — keyCode === LEFT_ARROW (или keyIsDown(LEFT_ARROW)). Разберём оба на живых примерах.
Разбираем на примерах
Пример 1: keyPressed() — цыплёнок прыгает по пробелу
Начнём с дверного звонка. Хотим: нажал пробел — CodeChick подпрыгнул на месте. Это разовое действие, значит используем keyPressed().
let y = 200; // вертикальная позиция цыплёнка
let jump = false; // прыгает ли он прямо сейчас
function setup() {
createCanvas(400, 400);
noStroke();
}
function draw() {
background(135, 206, 235);
// если прыгает — поднимем повыше, иначе стоит внизу
let drawY = jump ? y - 60 : y;
fill(255, 209, 64);
circle(200, drawY, 80); // тело
fill(255, 140, 30);
triangle(230, drawY - 5, 255, drawY, 230, drawY + 5); // клюв
}
function keyPressed() {
if (key === ' ') { // нажали пробел
jump = !jump; // переключаем прыжок
}
}Результат: на голубом небе сидит жёлтый цыплёнок с оранжевым клювом. Каждый раз, когда ты жмёшь пробел, он подскакивает вверх на 60 пикселей, а при следующем нажатии — опускается обратно. Это переключатель: пробел вверх — пробел вниз. Сколько бы ты ни держал пробел зажатым, прыжок происходит ровно один раз на каждое нажатие.
Главное здесь — keyPressed() вызывается p5.js сам, тебе не нужно её нигде вызывать вручную. Ты только описываешь, что должно случиться при нажатии. А проверка key === ' ' отсеивает все клавиши, кроме пробела: нажмёшь букву — ничего не произойдёт.
Пример 2: keyIsDown() — едем стрелками
Теперь педаль газа. Хотим настоящее управление: зажал стрелку — цыплёнок едет в эту сторону, отпустил — встал. Это непрерывное движение, значит keyIsDown() внутри draw().
let x = 200;
let y = 200;
let speed = 4; // на сколько пикселей сдвигаемся за кадр
function setup() {
createCanvas(400, 400);
noStroke();
}
function draw() {
background(135, 206, 235);
// проверяем стрелки прямо сейчас, каждый кадр
if (keyIsDown(LEFT_ARROW)) { x = x - speed; }
if (keyIsDown(RIGHT_ARROW)) { x = x + speed; }
if (keyIsDown(UP_ARROW)) { y = y - speed; }
if (keyIsDown(DOWN_ARROW)) { y = y + speed; }
fill(255, 209, 64);
circle(x, y, 70);
fill(255, 140, 30);
triangle(x + 26, y - 5, x + 48, y, x + 26, y + 5);
}Результат: жёлтый цыплёнок стоит в центре. Пока ты держишь стрелку вправо — он плавно едет вправо, по 4 пикселя за кадр; зажмёшь вверх — поднимается. Можно зажать две стрелки сразу (например вправо и вверх) — и он поедет по диагонали! Отпустил все клавиши — мгновенно замер. Это уже похоже на управление персонажем в настоящей игре.
Почему именно keyIsDown(), а не keyPressed()? Потому что эта функция спрашивает: «зажата ли клавиша в этот самый кадр?» И раз draw() крутится десятки раз в секунду, движение получается плавным и непрерывным, пока палец на клавише. А ещё — несколько if без else позволяют проверять все стрелки независимо, поэтому диагональ и работает: и LEFT, и UP могут быть зажаты одновременно.
Пример 3: буквы меняют цвет цыплёнка
Соединим оба подхода. Стрелками двигаем (как в примере 2), а буквами r, g, y разово перекрашиваем CodeChick — это уже работа для keyPressed() и переменной key.
let x = 200;
let y = 200;
let speed = 4;
let r = 255, g = 209, b = 64; // текущий цвет тела (жёлтый)
function setup() {
createCanvas(400, 400);
noStroke();
}
function draw() {
background(135, 206, 235);
if (keyIsDown(LEFT_ARROW)) { x = x - speed; }
if (keyIsDown(RIGHT_ARROW)) { x = x + speed; }
if (keyIsDown(UP_ARROW)) { y = y - speed; }
if (keyIsDown(DOWN_ARROW)) { y = y + speed; }
fill(r, g, b);
circle(x, y, 70);
fill(255, 140, 30);
triangle(x + 26, y - 5, x + 48, y, x + 26, y + 5);
}
function keyPressed() {
if (key === 'r') { r = 230; g = 90; b = 90; } // красный
if (key === 'g') { r = 120; g = 200; b = 120; } // зелёный
if (key === 'y') { r = 255; g = 209; b = 64; } // обратно жёлтый
}Результат: цыплёнок по-прежнему ездит стрелками, но теперь нажатие буквы r мгновенно перекрашивает его тело в красный, g — в зелёный, а y возвращает родной жёлтый. Клюв остаётся оранжевым. Движение (стрелки, непрерывно) и смена цвета (буквы, разово) живут вместе и не мешают друг другу.
Этот пример показывает золотое правило урока в действии: движение — через keyIsDown() в draw(), а разовые команды — через keyPressed(). Если бы мы попытались красить цыплёнка внутри draw() через keyIsDown('r'), оно бы тоже работало, но keyPressed() здесь чище: цвет меняется ровно в момент нажатия, а не «пока держишь».
Пример 4: keyCode внутри keyPressed() — шаг по стрелке
Иногда стрелка нужна не для плавного хода, а именно для разового шага — как в пошаговой игре или редакторе: нажал стрелку — фигура сдвинулась на одну клетку, и стой. Тут стрелка становится «дверным звонком», и ловить её надо в keyPressed(), но уже не через key (символа-то у стрелок нет!), а через keyCode.
let col = 4; // номер клетки по горизонтали (0..7)
function setup() {
createCanvas(400, 400);
noStroke();
}
function draw() {
background(135, 206, 235);
let cell = 50; // размер клетки
let cx = col * cell + cell / 2; // центр клетки по x
fill(255, 209, 64);
circle(cx, 200, 40);
fill(255, 140, 30);
triangle(cx + 15, 197, cx + 28, 200, cx + 15, 203);
}
function keyPressed() {
if (keyCode === LEFT_ARROW) { col = col - 1; }
if (keyCode === RIGHT_ARROW) { col = col + 1; }
col = constrain(col, 0, 7); // не вылезаем за 8 клеток
}Результат: цыплёнок стоит на сетке-клеточке. Каждое отдельное нажатие стрелки влево или вправо перемещает его ровно на одну клетку (50 пикселей) в эту сторону — как шашку по доске. Держать клавишу бесполезно: пока не отпустишь и не нажмёшь снова, он стоит. У краёв constrain() не даёт ему уйти за пределы восьми клеток.
Сравни Пример 2 и Пример 4: одни и те же стрелки, но поведение противоположное. Во втором — плавный непрерывный ход (keyIsDown в draw), в четвёртом — дискретные шаги по одному нажатию (keyCode в keyPressed). Это и есть то самое разделение на «газ» и «звонок», только теперь обе роли играет одна и та же клавиша-стрелка. Какую брать — решаешь ты, исходя из того, какую игру делаешь.
Шпаргалка: что брать под какую задачу
Чтобы не держать всё в голове, вот короткая таблица-памятка:
| Что нужно | Инструмент | Что проверять |
| Плавно вести героя, пока держишь | keyIsDown() в draw() | keyIsDown(RIGHT_ARROW) |
| Прыжок, выстрел, пауза — раз на нажатие | keyPressed() | key === ' ' |
| Сменить цвет/режим по букве | keyPressed() | key === 'r' |
| Шаг по клетке за нажатие стрелки | keyPressed() | keyCode === LEFT_ARROW |
Частые ошибки и подводные камни
Двигают героя внутри keyPressed() вместо keyIsDown(). Самая частая ловушка. Если написать движение в
keyPressed(), цыплёнок сдвинется на один шаг за каждое нажатие — придётся долбить по клавише, как по кнопке лифта. Для плавного хода, пока клавиша зажата, нуженkeyIsDown()вdraw().Сравнивают букву с заглавной, а жмут строчную (или наоборот).
key === 'R'не сработает, если включён обычный регистр и Shift не зажат — там придёт'r'. Если хочешь ловить букву независимо от регистра, проверяй обе:if (key === 'r' || key === 'R').Стрелки проверяют как символ. У стрелок нет печатного символа, поэтому
key === 'LEFT_ARROW'илиkey === '←'— это мимо. Стрелки ловят только по коду:keyCode === LEFT_ARROWилиkeyIsDown(LEFT_ARROW). ИменаLEFT_ARROWи подобные — это готовые числа-константы p5.js, кавычки им не нужны.Лишний else между if-проверками стрелок. Если написать
if (...LEFT...) {} else if (...RIGHT...) {}, то одновременно зажать вправо и вверх не получится — диагональ сломается, сработает только первая клавиша. Для независимых направлений используй отдельныеifбезelse.Скетч не реагирует на клавиши вообще. Чаще всего холст просто «не в фокусе»: браузер шлёт клавиши тому окну, на которое ты последним кликнул. Кликни мышкой по самому холсту — и управление оживёт. На сайте перед игрой с клавишами всегда сначала ткни в картинку скетча.
Стрелки прокручивают страницу вместо героя. Браузер по умолчанию сам реагирует на стрелки и пробел — листает страницу. Если из-за управления героем прокручивается весь сайт, добавь в конец
keyPressed()строкуreturn false;— это говорит браузеру «я обработал клавишу сам, дальше не передавай». Маленькая, но очень спасительная деталь в играх.
Мини-практика: управляемый CodeChick
Собери свою маленькую песочницу — управляемого цыплёнка, который не убегает за край. План такой:
- Возьми за основу Пример 2 со стрелками и переменными
x,y. - Добавь границы: после всех проверок стрелок не дай цыплёнку уйти с холста. Используй
constrain():x = constrain(x, 35, 365);и так же дляy. Теперь он будет упираться в стенки, а не улетать. - Добавь ускорение по Shift: заведи переменную скорости и в
draw()сделайlet speed = keyIsDown(SHIFT) ? 9 : 4;. Зажал Shift вместе со стрелкой — цыплёнок несётся вдвое быстрее. - Добавь разовое действие на пробел через
keyPressed(): например, по пробелу телепортируй цыплёнка обратно в центр (x = 200; y = 200;) — кнопка «домой».
Когда базовый вариант заработает, попробуй усложнить. Пусть буква w увеличивает размер цыплёнка, а s уменьшает — это ещё одна пара разовых команд через keyPressed() и переменную диаметра. Так ты соединишь сразу четыре механики в одном скетче: непрерывное движение (стрелки), модификатор скорости (Shift), границы холста (constrain) и разовые команды (пробел, буквы). Это уже не учебный пример, а маленькая интерактивная игрушка, в которой осмысленно сочетаются оба способа читать клавиатуру.
Если захочется пойти ещё дальше — добавь цель. Нарисуй на холсте «зёрнышко» в случайной точке, и когда цыплёнок до него доедет (проверь расстояние через dist()), пусть зёрнышко перепрыгивает на новое случайное место, а счётчик собранных зёрен растёт. Поздравляю: это уже законченная мини-игра «собери зерно», полностью на твоём управлении с клавиш. Не бойся ломать и менять числа — скорость, размеры, цвета: именно так, экспериментируя, ты прочувствуешь, как ощущается хорошее управление, а как — кривое.
Итоги
Сегодня ты дал CodeChick клавиатуру и научил его слушаться рук:
- keyPressed() — дверной звонок: срабатывает один раз в момент нажатия. Для разовых действий — прыжок, смена цвета, телепорт.
- keyIsDown(код) — педаль газа: отвечает
true, пока клавиша зажата. Вызывай вdraw()для плавного непрерывного движения. - key хранит символ клавиши (для букв:
key === 'r'), а keyCode — числовой код (для стрелок:LEFT_ARROW,UP_ARROWи т. д.). - Отдельные
ifбезelseпозволяют зажимать несколько клавиш сразу — отсюда движение по диагонали.
Теперь твой цыплёнок реагирует и на мышь, и на клавиши — он стал по-настоящему интерактивным героем, которым можно играть. Это огромный шаг: ты больше не просто рисуешь картинку, ты строишь то, что отвечает пользователю. В следующем уроке мы пойдём дальше и научимся реагировать на движение мыши и положение курсора, чтобы CodeChick следил за тобой взглядом и тянулся за указателем. Увидимся на следующей странице — и не забудь кликнуть по холсту перед игрой!