Skip to content
От: Божидар Маринов Дата: Последна промяна:

Урок 4, Анимации

Допълнителни линкове към този урок:

Основни точки от този урок

Досега правихме единствено статични/неподвижни картинки. В този урок ще разгледаме няколко начина по които можем да направим анимация.

Компютърния екран е съставен от малки пиксели, оцветени в различни цветове. Когато имаме някакъв вид анимация, самите пиксели не се местят. Те единствено променят цветовете си, но физически всичко си остава на същото място. Това че накрая изглежда, че нещо се движи е единствено илюзия.

Следователно, за да направим анимация, е достатъчно единствено да покажем новата картина на мястото на старата; да прерисуваме (нарисуваме отново) това което се показва на екрана с някаква малка промяна.

В p5.js, draw() функцията (която писахме досега) се изпълнява от начало до край всеки път, когато се опреснява екрана. Опресняването на екрана е момента, в който миналите цветове се заместват с нови, и се случва примерно 60 пъти на един стандартен 60-херцов монитор. Това което е нарисувано на екрана в даден момент е познато като "кадър", или "frame", и затова може да кажем, че draw() е описание на това, което се рисува за един кадър.

В този урок ще разгледаме четири начина за правене на анимация:

Първи метод: frameCount

frameCount е променлива идваща от p5.js, която съдържа броя (count) на кадрите (frame) нарисувани до сега.

Ако направим правоъгълник чието отместване от левия край (x) е изчислено от frameCount, то той ще изглежда, че се движи на дясно, тъй като първия кадър той се намира на самия ляв край, втория кадър е отместен малко по-далеч от левия край, третия още малко и т.н.:

rect(frameCount, 50, 50, 50)

За да видим какво точно става, може да използваме командата text(текст, x, y) за да изпишем стойността на frameCount — би трябвало да видим как започва от 1 и непрекъснато се увеличава със всеки следващ кадър:

text("Някакъв текст", 100, 20) // примерен текст

text(frameCount, 10, 20)

За да направим правоъгълника да се движи по-бързо или по-бавно е достатъчно да умножим този frameCount по желаната скорост (в пиксели в кадър):

rect(frameCount * 2, 50, 50, 50) // двойно по-бърз
rect(frameCount / 2, 50, 50, 50) // двойно по-бавен

Това работи, тъй като ако frameCount първия кадър е 1, втория е 2, 3, 4, 5 …, то frameCount * 2 първия кадър е 2, втория 4, 6, 8, 10 …. Или казано иначе, правоъгълника с frameCount * 2 изминава същото разстояние като правоъгълника с frameCount за двойно по-малко време, и Следователно е двойно по-бърз.

Втори метод: наша собствена променлива

Вместо да използваме frameCount, може да постигнем същия резултат ако използваме променлива, която променяме всеки кадър:

let x = 1

function draw() {
  background(220);
  
  rect(x, 50, 50, 50)
  
  x = x + 1
  // това увеличава x с едно
  // или, (новото x) = (досегашното x) + 1
}

Ако x = x + 1 ви изглежда объркващо, може да си припомните, че за разлика от математиката, в JavaScript стойността на една променлива може да бъде сменена и че JavaScript е императивен език, в който всяка инструкция се изпълнява подред. В случая, x + 1 се изпълнява първо, и използва досегашната стойност на x; x = ... се изпълнява второ и променя последващата стойност на x.

Така, ако първия път x е 1, то след като се изпълни x = x + 1, x вече става 2, следващия път е 3, 4, 5, ... — точно както променливата frameCount всеки път е с единица по-голяма.

За да променим скоростта, когато използваме променлива, достатъчно е да сменим количеството, с което променяме x всеки път:

  rect(x, 50, 50, 50)
  x = x + 2

Това да използваме променлива вместо да изчисляваме позицията на база на frameCount ни дава повече гъвкавост в това да преместваме правоъгълника където искаме или пък да променяме скоростта от кадър на кадър. Например, може да върнем анимацията от началото ако в конзолата напишем:

x = 1

Трети метод: deltaTime

deltaTime е променлива от p5.js, която ни дава времето в милисекунди между рисуването на миналия кадър и рисуването на текущия кадър. (Гръцката буква Δ, "delta"/"делта", се използва в математиката за да обозначи разлика, а "time" означава време). Тъй като обикновено работим с монитор, който се опреснява 60 пъти в секунда, стойността на deltaTime е обикновено около 16 (1000 милисекунди в секунда / 60 кадъра в секунда = 16.66…)

Използвайки тази променлива, можем да направим анимацията, която продължава да се движи със същата скорост (в пиксели в милисекунда) независимо от скоростта с която компютъра успява да нарисува всеки кадър:

let x = 1

function draw() {
  background(220);
  
  rect(x, 50, 50, 50)
  
  x = x + deltaTime / 1000 * 50 // 50 пиксела в секунда (= 0.05 пиксела в милисекунда)
}

По този начин, нашата анимация ще има същата скорост и на 144-херцови монитори, и на 60-херцови монитори, а дори и на компютри, които не успяват да изрисуват 60 кадъра в секунда.

Четвърти метод: физическа симулация с променливи

Ако използваме променлива, в която да съхраним скоростта, с която се движи нашия правоъгълник, можем постепенно да ускорим правоъгълника, имитирайки на действието на гравитацията.

let y = 200;
let vy = -10;

function draw() {
  background(220);
  
  rect(50, y, 50, 50)
  
  y = y + vy
  vy = vy + 0.3
}

Така, анимацията изглежда много по-реалистична, но това да работим директно със скорости и ускорения е леко неудобно, тъй като е трудно да преценим докъде ще стигне анимирания предмет.

(или, комбинирайки го с deltaTime)
let y = 200;
let vy = -160; // пиксели в секунда

function draw() {
  background(220);
  
  rect(50, y, 50, 50)
  
  y = y + vy * (deltaTime / 1000)
  vy = vy + 80 * (deltaTime / 1000) // 80 пиксели в секунда в секунда (ускорение)
}

— но това не се препоръчва, поради математически причини свързани с това че разликата в "стъпката на интегриране" все пак би довела до различен резултат на различни монитори. По-добре е да се направи с фиксирана стъпка ("delta"), но за това ще говорим по-нататък, когато е нужно.

В заключение

Различните начини за анимиране имат своите плюсове и минуси:

Не всеки проект се нуждае от еднакъв подход за анимация, поради което е добре да сте запознати със повече от един начин за това да направите вашата анимация.

Задача

  1. Нарисувайте кола или друго превозно средство. Може например да използвате rect() за каросерията и circle(x, y, диаметър) за гумите. — това би трябвало да е подобно на знамето, което правихме по-рано.

    (spoiler) Примерно "превозно средство":

    Линк към p5js редактора

    fill(255, 100, 0)
    rect(100, 100, 40, 60)
    
    fill(0, 0, 0)
    circle(120, 90, 30)
  2. Използвайте променлива за да направите по-лесно преместването на тази кола наляво/надясно. — това би трябвало да е подобно на променливите за знамето, което правихме по-рано.

    (spoiler) Примерно "превозно средство" с променлива:

    Линк към p5js редактора

    let x = 100
    
    fill(255, 100, 0)
    rect(x, 100, 40, 60)
    
    fill(0, 0, 0)
    circle(x + 20, 90, 30)
  3. Направете вашата кола да се движи от единия до другия край на екрана.

    • Първо, опитайте да го направите използвайки frameCount — ако преместите променливата от стъпка две в draw(), би трябвало да можете да използвате frameCount като стойност за тази променлива.
    • След това, опитайте да използвате ваша собствена променлива — ако променливата от стъпка две я преместите извън draw(), би трябвало да можете да я използвате директно. (Но може да го направите и с две променливи, едната от които просто замества frameCount от преди малко.)
  4. Направете вашата кола постепенно да се ускорява.

    • Използвайте допълнителна променлива за скоростта.
  5. Бръммм 🛣️ 🚗


Допълнителна задача за любителите на математиката и физиката:

Използвайки факта че ускорението от гравитацията на земята е 9.83 m/s² (метри в секунда в секунда), и факта че средностатистическия монитор е с резолюция около 28 px/cm (пиксели на сантиметър) и се опреснява с честота от 60Hz (60 кадъра на секунда), какво би било правилното ускорение, което да използваме в нашия код за физическа симулация така че да изглежда че предмета пада все едно под влиянието на претеглянето на земята? Нужно ни е ускорение в px/frame² (пиксели в кадър в кадър) или в px/s² (пиксели в секунда в секунда), в зависимост кой код използваме.