Но позже я поняла, что я могла бы использовать некоторые физические расчеты, чтобы создавать крутую анимацию с canvas. Черт побери! К счастью, я довольно быстро осознала, что мне не обязательно вспоминать программу старших классов. Достаточно просто схитрить.
Банальная физика
Мне нравится называть это банальной физикой, так как мы используем очень базовое понимание векторов и пишем JavaScript, который в какой-то мере воспроизводит эти векторы, но никоим образом не представляет собой реальную физическую среду. Преимущество банальной физики заключается в том, что вам на самом деле даже не нужно понимать теорию физики за ее пределами. Вам нужно понимать только то, как ее использовать для анимации.
Векторы
Вектор – это математический объект, характеризующийся величиной и направлением. Скорость и ускорение – это векторы. Если вы скажете, что собираетесь ехать в северо-восточном направлении со скоростью 35 миль в час, вы опишете вектор.
Создаем скорость с помощью JavaScript
Наверняка, вы уже знаете, что такое скорость, но я все равно вам расскажу. Вектор скорости – это вектор, который состоит из скорости и направления. Когда мы воспроизводим скорость в JavaScript, мы делим ее на два вектора – скорость в направлении x и скорость в направлении y. Здесь мы имеем дело с пикселями и анимацией JavaScript, поэтому мы можем описать нашу скорость с точки зрения количества пикселей в кадр. Квадрату в примере присвоена скорость в направлении х в размере 2 пикселей на каждый кадр, и скорость в направлении y в размере 2 пикселей на каждый кадр. В результате мы получаем линейное диагональное движение.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
// position
var x = 0;
var y = 0;
// velocity
var vx = 2;
var vy = 2;
// animation loop
function animate() {
// clear canvas
ctx.clearRect(0, 0, width, height);
// draw square
ctx.fillRect(x,y,20,20);
// update position with velocity
x += vx;
y += vy;
requestAnimationFrame(animate);
// if square is out of view reset position to the start
if (y > height || x > width) {
x = 0;
y = 0;
}
}
animate();
Создаем ускорение с JavaScript
Ускорение – это коэффициент изменения скорости объекта. И как же мы воспроизводим это в JavaScript? У нас есть скорость vx и vy, и мы можем добавить ускорение в направлении x или y в каждом кадре. То есть, если ускорение равно 0,5 пикселей, мы добавим 0,5 пикселя к скорости x и скорости y в каждом кадре. Это заставит наш квадратик ускориться.
Невероятно простой и забавный пример – давайте сделаем фонтан из частиц!
Фонтан из частиц – это первый эффект банальной физики, который я анимировала. В частности, я сделала его с помощью Flash и ActionScript, но сейчас мы можем сделать это с JavaScript и canvas. По сути, я планирую использовать объект «частица» для создания фонтана.
Чтобы сделать фонтан, нам нужно сгенерировать некоторое количество частиц. Каждая частица должна иметь свое положение и скорость (vx и vy). Начальной позицией мы сделаем центр canvas, после чего зададим произвольную скорость частиц, чтобы они «распылялись» из нашего фонтана.
function Particle(x, y, vx, vy, size, color, opacity) {
this.update = function() {
x += vx;
y += vy;
}
this.draw = function() {
ctx.globalAlpha = opacity;
ctx.fillStyle = color;
ctx.fillRect(x, y, size, size);
}
}
function createParticle(i) {
// initial position in middle of canvas
var x = width*0.5;
var y = height*0.5;
// randomize the vx and vy a little - but we still want them flying 'up' and 'out'
var vx = -2+Math.random()*4;
var vy = Math.random()*-3;
// randomize size and opacity a little & pick a color from our color palette
var size = 5+Math.random()*5;
var color = colors[i%colors.length];
var opacity = 0.5 + Math.random()*0.5;
var p = new Particle(x, y, vx, vy, size, color, opacity);
particles.push(p);
}
В результате должно получиться что-то вроде этого (возможно, вам придется нажать «rerun», чтобы их увидеть).
Частицы просто распыляются вверх и никогда не падают вниз. Нам нужно добавить немного «силы притяжения», чтобы заставить частицы падать. Мы уже знаем, как это сделать, потому что силу притяжения, по сути, можно воспроизвести с помощью ускорения в направлении y (вниз). Давайте установим значение силы притяжения (ускорения) равное 0,04 пикселя. Также мы немного уменьшим прозрачность каждого кадра, чтобы каждая частица в результате растворялась. После этого мы сделаем нулевую прозрачность в точке, в которой мы будем «возвращать» нашу частицу в центр фонтана.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
var particles = [];
var colors = ['#029DAF', '#E5D599', '#FFC219', '#F07C19', '#E32551'];
var gravity = 0.04;
function initParticles() {
for (var i = 0; i < 200; i++) {
setTimeout(createParticle, 20*i, i);
}
}
function createParticle(i) {
// initial position in middle of canvas
var x = width*0.5;
var y = height*0.5;
// randomize the vx and vy a little - but we still want them flying 'up' and 'out'
var vx = -2+Math.random()*4;
var vy = Math.random()*-3;
// randomize size and opacity a little & pick a color from our color palette
var size = 5+Math.random()*5;
var color = colors[i%colors.length];
var opacity = 0.5 + Math.random()*0.5;
var p = new Particle(x, y, vx, vy, size, color, opacity);
particles.push(p);
}
function Particle(x, y, vx, vy, size, color, opacity) {
function reset() {
x = width*0.5;
y = height*0.5;
opacity = 0.5 + Math.random()*0.5;
vx = -2+Math.random()*4;
vy = Math.random()*-3;
}
this.update = function() {
// if a particle has faded to nothing we can reset it to the starting position
if (opacity - 0.005 > 0) opacity -= 0.005 ;
else reset();
// add gravity to vy
vy += gravity;
x += vx;
y += vy;
}
this.draw = function() {
ctx.globalAlpha = opacity;
ctx.fillStyle = color;
ctx.fillRect(x, y, size, size);
}
}
function render() {
ctx.clearRect(0, 0, width, height);
for (var i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw();
}
requestAnimationFrame(render);
}
// resize
window.addEventListener('resize', resize);
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
// init
initParticles();
render();
Таким образом, у нас получится бесконечный фонтан частиц! А во второй части, мы запустим в полет нашего первого пингвина.
> Принимайте оплату от компаний через Интернет. Без сайта, ИП и ООО.
> Приём платежей от компаний для Вашего сайта. С документооборотом и обменом оригиналами.
> Автоматизация продаж и обслуживание сделок с юр.лицами. Без посредника в расчетах.
Комментарии (6)
SerafimArts
08.07.2015 20:57+3Помнится тоже когда-то подобным страдал, даже примерчик есть небольшой: jsbin.com/quxigoruho/1/edit?output
Увы, с коллизиями фигур менее шарообразных форм, тем более общими — работать намного сложнее. Всё что у меня получилось из этого — глючит и ломается. Так что интересно было бы узнать что будет в будущих статьях (это ведь ч.1, но ничего особо интересного тут нет, простите). Если не сложно (уже есть что-то в планах), спасибо.SerafimArts
09.07.2015 00:23+1Ох, не заметил, это перевод. Увидел, что ещё есть две части, да, довольно интересное продолжение, спасибо за работу по переводу, будем ждать-с.
ertaquo
08.07.2015 21:01+6Весь пост ради рекламы в конце? Тут физикой практически даже не пахнет. Написали бы заголовок типа «Простейший генератор частиц на JS Canvas своими руками», что ли… www.gamedev.ru/code/articles/?id=4249 — вот толковая статья по физике.
KvanTTT
08.07.2015 23:25Ах, да помню ее, читал с интересом… Пытался в свое время тоже графику только на паскале реализовать. Уж 10 лет прошло…
kahi4
09.07.2015 00:48+2Есть одно мелкое и невероятно важное замечание: а время отрисовки кадра кто учитывать будет? Т.е.
vy += gravity * dt; x += vx * dt; y += vy * dt;
Ну и да, стоило бы сказать, что это метод Эйлера (с почему-то выкинутым h) в чистом виде, только размазанный на объекты. Ну и ожидаемо, что частицы будут «тормозиться» при отлете.
Ну и немного конструктивной критики:
Скрытый текстМетод draw не должен привязываться к глобальному ctx, он ему должен передаваться в виде аргумента (мало ли у вас не один canvas будет, а несколько слоев).
Почему в функции render вызывается функция update? Тогда логичнее будет назвать mainLoop.
Хорошим тоном является сразу привести список частей, из которых будет состоять повествование, а то пока это тянет на часть 1.1, даже на 1.1.1.
createParticle(i) и reset делают одно и то же. Не хорошо, не хорошо! Раз учите людей что-то делать, не учите хотя бы плохому.
ctx.globalAlpha = opacity;
Кто обратно альфаканал в 1 выставлять будет?
function createParticle(i) { var x = width*0.5; var y = height*0.5; ... }
С чего вдруг частица сама решает где ей появиться? В теории, должен быть метод createParticleSystem, который принимает координаты центра, направление (в общем случае), начальное ускорение, ну и т.д.
*много бубнения по поводу определения вектора. А нам, дуракам, половину семестра читали векторы, а тут в одно предложение влезло! Ну и вы описали радиус-вектор, покуда забыли еще точку приложения этого вектора указать, а не пометили это отдельно. Не хорошо вводить в заблуждение!
Вижу, что перевод, но перевод, на мой взгляд, неудачной статьи, пронизанной попыткой гуманитария описать то, в чем он сам слабо понимает. Если выкинуть слово «физика» — еще куда не шло. И да, помнится, вот ровно эту статью кто-то уже переводил на хабре, по крайней мере ошибки там были такие же.
Zenitchik
Конечных разностей чтоли?