Ускорение, гравитация, ветер
Сегодня ты добавишь цыплёнку настоящую физику: он начнёт падать как яблоко с ветки и сноситься ветром в сторону — и всё это родится из одной короткой цепочки векторов.
Ускорение — это вектор, который каждый кадр чуть-чуть меняет скорость. Скорость, в свою очередь, каждый кадр меняет позицию. Гравитация и ветер — это просто разные ускорения, которые ты складываешь и применяешь к своему герою.
Зачем цыплёнку физика
Запусти в голове любой ролик, где что-то падает: телефон выскальзывает из руки, мяч летит с трибуны, персонаж в платформере спрыгивает с уступа. Заметил? Ничто не падает с постоянной скоростью. Сначала движение медленное, почти лениво, а потом всё быстрее и быстрее — у самой земли предмет уже свистит. Этот разгон и есть гравитация: она не задаёт скорость, она её постоянно наращивает.
В прошлом уроке про позицию и скорость ты научил цыплёнка двигаться: завёл вектор позиции pos и вектор скорости vel, и каждый кадр прибавлял скорость к позиции. Цыплёнок поехал — но поехал скучно: с одной и той же скоростью, как машинка на ровном столе. Толкнул — катится вечно, не разгоняется и не тормозит. В реальном мире так не бывает.
Чего не хватает? Не хватает того, что меняет саму скорость. Если позиция меняется скоростью, то что меняет скорость? Ответ — ускорение. Сегодня мы добавим третий вектор в цепочку, и цыплёнок оживёт по-настоящему: он будет падать с разгоном, его будет сносить ветер, а ты сможешь смешивать эти силы как ингредиенты.
К концу урока ты уронишь цыплёнка с неба под действием гравитации и увидишь, как его сносит вбок порывом ветра — и поймёшь, что вся «физика» здесь умещается в три строчки сложения векторов.
И сразу успокою: никакой страшной математики не будет. Слово «ускорение» звучит как что-то из учебника физики за девятый класс, но на деле мы просто прибавляем один вектор к другому — то самое сложение, которое ты уже делал в прошлом уроке. Вся разница в том, что теперь мы прибавляем не один раз, а в каждом кадре, и складываем не позицию со скоростью, а скорость с ускорением. Одна новая строчка — и мёртвая машинка превращается в живое падающее тело. Давай разберёмся, почему это работает.
Цепочка из трёх звеньев: ускорение → скорость → позиция
Представь, что ты едешь на велосипеде. У тебя есть три разные вещи, и их легко перепутать. Первое — где ты сейчас на дороге. Это позиция. Второе — как быстро и куда ты катишь прямо сейчас. Это скорость. Третье — давишь ли ты на педали или, наоборот, на тормоз. Это ускорение: оно не говорит, где ты и насколько быстро едешь, оно говорит, как меняется твоя скорость.
Нажал на педали — скорость растёт, ты разгоняешься. Зажал тормоз — скорость падает. Отпустил всё — скорость держится прежней, и ты катишься накатом (именно так двигался цыплёнок в прошлом уроке: скорость была, ускорения не было). Получается строгая цепочка из трёх звеньев:
| Вектор | Что хранит | Кто его меняет каждый кадр |
pos (позиция) | где цыплёнок на холсте | скорость vel |
vel (скорость) | куда и как быстро летит | ускорение acc |
acc (ускорение) | как меняется скорость | силы (гравитация, ветер) |
Каждый кадр в draw() ты делаешь два сложения, и обязательно в этом порядке: сначала ускорение прибавляешь к скорости, потом скорость прибавляешь к позиции. На векторах p5.js это выглядит так:
vel.add(acc); // скорость подрастает на величину ускорения
pos.add(vel); // позиция сдвигается на новую скоростьВот и вся физика. Звучит почти обманчиво просто, но именно из этих двух строк рождаются падение, отскок, полёт по дуге и десятки других движений. Меняется только то, что ты записываешь в acc.
Почему гравитация — это вектор
Гравитация всегда тянет вниз. На холсте «вниз» — это рост игрека (вспомни: игрек растёт вниз). Значит, гравитацию удобно записать как вектор, у которого икс равен нулю (вбок она не тянет), а игрек — маленькое положительное число:
let gravity = createVector(0, 0.2);Почему число такое крошечное — 0.2, а не, скажем, 20? Потому что ускорение прибавляется к скорости каждый кадр, а кадров за секунду — около шестидесяти. За первую секунду падения скорость накопит примерно 0.2 × 60 = 12 пикселей в кадр — этого уже достаточно, чтобы цыплёнок ощутимо разгонялся. Поставишь 20 — и он за пару кадров улетит сквозь нижний край с космической скоростью. Гравитация копится, поэтому числа берём маленькие.
Пример 1. Роняем цыплёнка
Соберём первую настоящую сцену: цыплёнок висит вверху холста и падает под собственной тяжестью, разгоняясь.
let pos;
let vel;
let gravity;
function setup() {
createCanvas(400, 400);
pos = createVector(200, 40); // старт у верхнего края
vel = createVector(0, 0); // в покое, скорости нет
gravity = createVector(0, 0.2); // тянет вниз
}
function draw() {
background(30);
// 1) ускорение (гравитация) добавляем к скорости
vel.add(gravity);
// 2) скорость добавляем к позиции
pos.add(vel);
// рисуем цыплёнка в текущей позиции
noStroke();
fill(255, 221, 51);
circle(pos.x, pos.y, 60); // тело
fill(255, 140, 0);
triangle(pos.x + 22, pos.y - 4, pos.x + 22, pos.y + 6, pos.x + 40, pos.y + 1); // клюв
fill(30);
circle(pos.x + 12, pos.y - 10, 7); // глаз
}Результат: жёлтый цыплёнок появляется у верхнего края и начинает падать. В первые кадры он еле ползёт вниз, потом всё быстрее — у нижнего края уже летит стремительно и проскакивает за пределы холста. Это и есть разгон: скорость не постоянная, она растёт с каждым кадром, потому что гравитация добавляется к ней снова и снова.
Разберём по шагам, что происходит. В setup() мы один раз задаём три вектора: позицию вверху, нулевую скорость (цыплёнок в покое) и гравитацию. В draw() каждый кадр выполняется та самая цепочка: vel.add(gravity) чуть-чуть увеличивает скорость вниз, затем pos.add(vel) сдвигает цыплёнка на эту новую скорость. На первом кадре vel станет (0, 0.2), на втором (0, 0.4), на третьем (0, 0.6) — скорость растёт линейно, а позиция поэтому едет всё дальше за кадр. Так выглядит свободное падение.
Поиграй с числами
- Сила тяжести. Поменяй
0.2на0.05— цыплёнок будет падать медленно и мягко, словно на Луне. Поставь0.6— рухнет почти мгновенно. - Толчок вверх. Задай стартовую скорость
vel = createVector(0, -8). Цыплёнок сначала подпрыгнет вверх, замедлится, на миг зависнет и полетит вниз — гравитоция переборет толчок. Это уже траектория прыжка! - Боковой разбег. Дай немного скорости вбок:
vel = createVector(3, 0). Цыплёнок полетит по красивой дуге — вправо и вниз одновременно, как мяч, брошенный с балкона.
Пример 2. Добавляем ветер
Гравитация — не единственная сила в мире. Добавим ветер: он дует вбок, то есть это вектор, у которого ненулевой икс. И вот здесь раскрывается главная красота векторов — силы складываются. Чтобы получить общее ускорение, мы просто сложим гравитацию и ветер в один вектор acc.
let pos;
let vel;
let gravity;
let wind;
function setup() {
createCanvas(400, 400);
pos = createVector(80, 40);
vel = createVector(0, 0);
gravity = createVector(0, 0.2); // вниз
wind = createVector(0.1, 0); // слабый ветер вправо
}
function draw() {
background(30);
// собираем общее ускорение из всех сил
let acc = createVector(0, 0);
acc.add(gravity);
acc.add(wind);
// та же цепочка: acc -> vel -> pos
vel.add(acc);
pos.add(vel);
noStroke();
fill(255, 221, 51);
circle(pos.x, pos.y, 60);
fill(255, 140, 0);
triangle(pos.x + 22, pos.y - 4, pos.x + 22, pos.y + 6, pos.x + 40, pos.y + 1);
fill(30);
circle(pos.x + 12, pos.y - 10, 7);
}Результат: цыплёнок стартует у левого верхнего угла и падает, но теперь его не просто роняет вниз, а ещё и сносит вправо. Получается плавная диагональная дуга: чем дольше летит, тем сильнее ускоряется и вниз (гравитация), и вправо (ветер). Траектория похожа на след падающей звезды, уходящий за нижний правый угол.
Главное, что нужно увидеть в этом примере: мы не придумывали никакой особой логики для «гравитации с ветром». Мы просто сложили два вектора-силы в один acc и пропустили его через ту же самую цепочку vel.add(acc); pos.add(vel). Захочешь добавить третью силу — например, тягу к центру холста — просто прибавишь ещё один acc.add(...). Векторы складываются как угодно, и каждый кадр их сумма решает, куда полетит цыплёнок.
Подумай, как это масштабируется. В реальной игре на персонажа разом действует куча сил: гравитация тянет вниз, ветер сносит вбок, вода выталкивает вверх, взрыв отбрасывает в сторону. Если бы для каждой пары сил пришлось писать отдельную формулу, код превратился бы в кошмар. А с векторами всё спокойно: какие бы силы ни действовали, ты складываешь их все в один acc — и физика остаётся теми же двумя строчками. Именно поэтому векторное сложение — это не школьная скукотища, а рабочий инструмент, на котором держатся целые игровые движки. Ты только что прикоснулся к тому же приёму, что крутится под капотом твоих любимых платформеров.
Почему acc обнуляется каждый кадр
Обрати внимание: acc мы создаём заново внутри draw(), на каждом кадре с нуля. Это важно. Ускорение — это не запас, который копится, а команда «вот так толкаем прямо сейчас». Толчок длится один кадр, потом всё начинается заново. Копится у нас скорость (vel живёт в setup() и накапливает изменения), а ускорение — мгновенное, поэтому его мы каждый кадр собираем свежим из текущих сил. Если этого не делать, силы будут наслаиваться сами на себя и цыплёнок взбесится — об этой ловушке поговорим ниже.
Частые ошибки и подводные камни
Физика на векторах ломается у новичков почти всегда в одних и тех же местах. Пробежимся по ним, чтобы ты узнал баг в лицо ещё до того, как он случится.
1. Перепутан порядок сложения
Самая частая путаница — сначала прибавить скорость к позиции, а уже потом ускорение к скорости. Порядок имеет значение: сперва обновляем скорость ускорением, и только потом сдвигаем позицию обновлённой скоростью. Перепутаешь — движение станет дёрганым и слегка неправильным. Запомни цепочку как лесенку сверху вниз: acc меняет vel, vel меняет pos.
2. Слишком большая гравитация
Поставил createVector(0, 10) вместо 0.2 — и цыплёнок исчезает за один кадр: скорость за пару кадров вырастает до сотен пикселей, и он телепортируется сквозь холст. Ускорение прибавляется десятки раз в секунду, поэтому числа должны быть маленькими, дробными. Если движение «слишком быстрое и резкое» — первым делом уменьшай ускорение.
3. acc забыли обнулить и он копится
Если завести acc один раз в setup() и каждый кадр делать acc.add(gravity), ускорение начнёт само себя накапливать: 0.2, потом 0.4, 0.6, 0.8… Цыплёнок будет разгоняться неестественно быстро, по нарастающей. Лекарство простое: либо создавай acc заново каждый кадр (как в примере 2), либо в конце кадра обнуляй его acc.mult(0). Ускорение — мгновенный толчок, а не копилка.
4. Гравитацию записали в скорость
Иногда вместо того чтобы прибавлять гравитацию к скорости, новичок просто задаёт vel = createVector(0, 0.2) и думает, что это падение. Но так цыплёнок будет ехать вниз с постоянной скоростью 0.2 — медленно и без разгона, как лифт. Весь смысл ускорения в том, что оно прибавляется к скорости каждый кадр и заставляет её расти. Падение без разгона — это не падение, а равномерное сползание.
5. Изменили вектор там, где не ждали
Методы вроде vel.add(acc) меняют сам вектор vel на месте, а не создают новый. Это удобно, но если ты случайно вызовешь gravity.add(wind), ты испортишь сам вектор гравитации навсегда — на следующем кадре в нём окажется уже сумма. Поэтому силы складывают в отдельный acc, а исходные векторы gravity и wind держат нетронутыми.
Мини-проект: цыплёнок в порыве ветра
Пора собрать всё своими руками. Возьми пример с ветром за основу и доработай его по чек-листу:
- Урони цыплёнка из верхнего левого угла под действием гравитации
createVector(0, 0.2)— добейся, чтобы он падал с разгоном. - Добавь ветер вправо
createVector(0.1, 0)и убедись, что траектория стала диагональной дугой, а не вертикальной линией. - Сделай ветер сильнее и слабее, поменяй его направление на влево (отрицательный икс). Посмотри, как меняется наклон дуги.
- Когда цыплёнок улетает за нижний край, верни его наверх: если
pos.y > height, задайpos.y = 0и обнули скоростьvel = createVector(0, 0). Получится бесконечный «листопад» из одного цыплёнка. - Бонус. Сделай ветер порывистым: каждый кадр задавай
wind = createVector(random(-0.15, 0.15), 0). Цыплёнок начнёт болтаться из стороны в сторону, пока падает — как настоящее пёрышко на ветру.
Когда «листопад» закрутится, поэкспериментируй с балансом сил: уменьшишь гравитацию и усилишь ветер — цыплёнок будет почти парить вбок; усилишь гравитацию — камнем рухнет вниз почти по прямой. Ты управляешь миром, просто меняя два маленьких вектора.
Итоги
Сегодня ты дал цыплёнку настоящую физику и понял, что под капотом она устроена на удивление просто. Коротко главное:
- В движении три звена: ускорение меняет скорость, скорость меняет позицию. Каждый кадр — два сложения:
vel.add(acc), потомpos.add(vel), именно в таком порядке. - Гравитация — это вектор вниз с маленьким игреком (
createVector(0, 0.2)). Числа крошечные, потому что ускорение прибавляется десятки раз в секунду и копится в скорости. - Силы складываются: чтобы добавить ветер, просто прибавь его вектор к
acc. Хоть три силы, хоть пять — сумма решает, куда полетит герой. accсобирают заново каждый кадр (или обнуляют), иначе ускорение накапливает само себя и движение взбесится. Копится скорость, а не ускорение.
Цыплёнок теперь падает и сносится ветром — но улетает за край и не возвращается. В следующем уроке мы научим его отскакивать от стен и пола: проверять, не коснулся ли он края холста, и разворачивать скорость в обратную сторону. Гравитация плюс отскок — и наш герой запрыгает по экрану, как мячик. До встречи!