Создание системы частиц в 200 строчек JavaScript

Создание системы частиц в 200 строчек JavaScript

Меньше чем в 200 строчек чистого JavaScript мы создадим гибкую систему частиц с несколькими излучателями и полями гравитации, которые будут притягивать или отталкивать тысячи частиц.

Всё это началось личным проектом и превратилось в статьи получше. Если вы всё же решили более глубоко разобраться в вопросе и ищете, что прочесть, то вам есть из чего выбирать.

Ага, это всё, что нам понадобится, это мир, в котором мы будем творить. Три вложенных тега — это всё, что нужно, чтобы начать.

Чтобы сделать нашу стартовую площадку ещё более аккуратной и чистой, давайте добавим немного стилей, чтобы избавиться от полей и сделать фон чёрным.

Canvas-объект

Чтобы получить доступ к объекту canvas , нам достаточно получить элемент любым удобным для нас способом.

Canvas может иметь несколько «контекстов», и сейчас нам необходим простой 2d-контекст, который позволит нам манипулировать холстом как растром с помощью набора стандартных методов.

Чтобы максимально увеличить рабочую область для опытов с canvas, мы можем задать размеры canvas равными размерам окна браузера.

Цикл анимации

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

Очистка холста в нашем случае — простая однострочная функция, но она станет гораздо сложнее, если вам потребуется работать с несколькими буферами или состояниями.

Вызов следующей итерации цикла мог бы быть обычным setTimeout() , но этот способ повлечёт выполнение анимации, даже когда она не нужна пользователю (например, он в другой вкладке), поэтому к нам на помощь приходит requestAnimationFrame API, который позволит браузеру сообщать нам, есть ли необходимость в анимировании следующего кадра или пользователь уже лайкает котиков на YouTube. Этот метод поддерживается большинством браузеров, но если вам нужна поддержка старых браузеров, то воспользуйтесь решением Пола Айриша (Paul Irish).

Методы update() и draw() составляют большую часть логики, но мы можем пока пропустить их реализацию, чтобы удостовериться, что площадка для дальнейших экспериментов готова.

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

Основа для частицы

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

Вектор

Этот объект будет использоваться часто. Если в анимации участвует 10 000 частиц при 30 кадрах в секунду и объект вектора используется для трёх свойств каждой частицы, то мы создаём около миллиона объектов каждую секунду. Учитывая это, объект вектора стоит оптимизировать. Но в нашем случае я буду отдавать предпочтение читабельности кода, а не его оптимизации. Пока особо на этом не зацикливайтесь.

Двумерный вектор в своей основе — просто представление координат X и Y.

Объект вектора может содержать множество методов, но прямо сейчас нам понадобятся только следующие:

Частица

Теперь, когда мы получили представление вектора, можем создать объект частицы. Можно задать её свойства или за счёт значений по умолчанию получить частицу, расположенную в нуле координат.

В каждом кадре необходимо смещать частицу, и метод, осуществляющий это, прост и незатейлив. Если частица ускоряется, надо изменить скорость, после этого изменить координаты на значение скорости.

Излучатель частиц

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

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

Чтобы получить частицы, мы должны заставить излучатель излучать их. Это сводится к созданию новых частиц со свойствами, наследованными из свойств излучателя, для этого мы используем метод Vector.fromAngle .

Наша первая анимация!

Кажется, у нас есть всё, чтобы начать эмулировать состояния системы частиц, поэтому теперь мы можем приступить к реализации методов update() и draw() .

Для управления состоянием системы нам понадобятся контейнеры для частиц и излучателей. Остановимся на простых массивах.

Как вы думаете, на что должен быть похож метод update() ? Необходимо генерировать новые частицы и смещать их. Также было бы удобно привязать их к определённым участкам холста, чтобы не пришлось перерисовывать весь холст.

Функция addNewParticles() прямолинейна — каждый излучатель генерирует набор частиц, и все эти частицы добавляются в соответствующий массив.

Функция PlotParticles() такая же простая, но с небольшой уловкой. Нам не нужно отображать частицы, улетевшие за пределы холста, поэтому необходимо отслеживать эту ситуацию, удалять их и освобождать место для новых частиц. Для этого мы и используем ограничения; мы будем перерисовывать только ту небольшую часть экрана, на которой есть частицы. Нашим ограничением будет наш холст.

Состояние системы обновляется каждый кадр, и теперь мы можем нарисовать что-нибудь. В данном примере я рисую обычные квадраты, но вы можете нарисовать искры, дым, капли воды или падающие листья. Частицы! Их больше 9 000 и они везде!

Это всё! Вы можете посмотреть, как работает этот пример на Codepen: демонстрация излучателя

Добавление гравитационных полей

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

Теперь мы можем создать первое в нашей системе гравитационное поле сходным с излучателями способом.

Нам необходимо, чтобы каждая частица обновляла свою скорость и ускорение в зависимости от гравитационных полей рядом, поэтому стоит добавить соответствующий метод в прототип частицы. Внутренняя логика подсказывает, что этот метод стоило бы добавить в прототип вектора, но в данном случае мы заботимся о производительности метода, который вызывается очень часто.

Теперь уже существующую функцию plotParticles надо немного обновить, добавив вызов только что реализованного метода submitToFields прямо перед смещением частиц.

Также было бы замечательно визуализировать поля и излучатели, поэтому давайте добавим для этого утилитарный метод и будем вызывать его внутри функции draw

Демонстрации

Мои поздравления, вы, наконец, можете посмотреть законченный пример на Сopepen.io: JavaScript Particle System Demo

Поэкспериментируйте с разными комбинациями излучателей и гравитационных полей. Превратите мышку в поле или излучатель с помощью синхронизации позиции курсора и позиции излучателя или поля, на ваше усмотрение. Форкните codepen и попробуйте что-нибудь новое!

📎📎📎📎📎📎📎📎📎📎