Публикация направлена на новичком или людей плохо знакомых с VFX Graph
.
Перед началом:
Использована версия
Unity 2022.3
, должно работать на всех версиях выше2020.2
Перед началом поставьте галочку в
Preferences > Visual Effect > Experemental Operators/Blocks
Данные кривых будут указаны как "значение" = "точка х" или "промежуток от х до у". Вы можете использовать свои кривые, я даю их в основном для справки. Это не удобно для чтения, но способа лучше не нашлось.
Если значения кривой не указаны в каком-то промежутке, значит они свободно интерполируют между указанными точками.
Создаём следующий скелет:
Эффект будет состоять из Strip
'ов, так что добавляем все необходимые блоки в System
. Создаём две первые переменные, они нам понадобятся в дальнейшем.

Добавляем Set custom attribute
в Initialize
, в инспекторе даём ему имя Progress
. Устанавливаем атрибут при помощи оператора Get Attribute: spawnIndexInStrip
(обычный spawnIndex даёт неверные значения) делённого на переменную PartAmount
. Теперь в этом атрибуте хранятся числа в диапазоне от 0 до 1 в зависимости от положения на линии.
Добавляем Get Custom Attribute
, в инспекторе указываем имя атрибута Progress
, добавляем оператор Sample Bezier
, добавляем Set Position
. Соответствующим образом подключаем их друг к другу. Теперь мы можем увидеть дугу, поместив ассет на сцену! Всё что будет дальше, это операции над линией, с целью придания ей характеристик молнии.


Чтобы было удобнее размещать молнию на сцене, создаём четыре переменных для задания позиции кривой Безье, переводим позиции из local
в world
. Добавляем на GameObject
с Visual Effect
VFX Property Binder
, соответствующим образом устанавливаем значения всех переменных как позиции других GameObject
. Теперь мы можем контролировать форму и позицию кривой Безье прямо в редакторе меняя позиции указанных GameObject
'ов, избегая хардкодинга.

Set Size
= 0.05. Теперь добавим оператор шума и необходимые для его контроля переменные. Быстро перечислю переменные и их значения: NoiseFrequency
(2), NoiseOctaves
(3), NoiseRoughness
(0.5), NoisePower
(0.1), MoveDelta
(1.5). Все их подключаем к оператору Value Noise 3D
. Noise Power
и -Noise Power
используется для создания Vector 2
, и передаётся в Range
. Чтобы молния слишком не отклонялась от первой и конечной точки, умножаем Vector2
на Sample Curve
, в качестве Time
которой использован Custom Progress
. Sample Curve
равна 0 = 0, 1 = от 0.1 до 0.9, 0 = 1. Get Position
передаём в качестве координат шуму. Результат в виде Derivatives
передаём в Add Position
в Udate
.



Однако, молния каждый раз одинаковая, поскольку шум детерминистический. К тому же, молния не двигается. В реальной жизни удар молнии состоит из двух этапов: 1) сначала молния движется сверху вниз и ветвится, 2) как только одна из ветвей достигает земли по ней проходит мощный той, происходит основная вспышка. Чтобы не перегружать эффект симуляцией ветвления и оставить туториал простым (к тому же, все кроме первой достигшей поверхности ветви должны быть тусклее основной), будем просто менять координаты шума в первую половину эффекта. После вспышки молния будет замирать и исчезать.
Добавляем новый кастомный атрибут MoveSpeed
и устанавливаем его равным Total Time(Game)
. В Update также устанавливаем значение MoveSpeed
= Delta Time(VFX) * Noise Speed * Sample Curve + MoveSpeed
. Для T
кривой используем Age Over Lifetime
. Sample Curve
равна 1 = от 0 до 0.5, 0 = от 0.6 до 1. Добавляем результат MoveSpeed
к координатам шума. Кривая будет регулировать количество движения.

Добавляем HDR цвет (Мой равен R 3.08
G 7.12
B 22.6
) (Чтобы HDR работал, добавьте в Post Procces Bloom
). Умножаем цвет на кривую с T
AgeOverLife
равную 0.1 в промежутке от 0 до 0.5, 1 = от 0.6 до 0.8, и 0.4 = на координате 1.
Устанавливаем альфу тоже как кривую с таким же T
, значения 0 = 0 до 0.1, 0.1 = 0.2, 0 = 0.3 до 0.5, 1 = 0.5 до 0.85, 0 = 1. Я также умножаю альфу на кривую использованную с NoisePower, чтобы скрыть острые края начала и конца молнии.


Чтобы вторая часть эффекта выглядела лучше и отделялась от первой, добавим вспышку. А раз есть вспышка, для баланса нужна ещё и область с тёмным цветом. Это улучшит баланс контраста.
Для вспышки создаём простой (копеечный) Initialize Particle
с Output Particle Quad
. Настраиваем его по своему вкусу, здесь главное чтобы момент вспышки совпал с началом второй части молнии. Для удобства использования ассета я также нахожу центр между Pos1
и Pos2
, и устанавливаю его в качестве координаты вспышки, а расстояние между этими точками использую для Scale
по вертикали. Момент вспышки регулирую с помощью Curve
0 = 0 до 0.5, 1 = 0.6, 0 = 0.75 до 1.


Теперь, тёмная часть. Это будет декаль на поверхности, в которую ударяет молния. Всё просто, создаём партикль, поворачиваем его на -90 градусов по х, чтобы он проецировался на поверхность снизу. Для цвета использую Color2
(R 4.28
G 1.09
B 0
) (понадобиться дальше) делённый на 4, а для альфы кривую 0 = 0 до 0.5, 0.6 = 0.6, 0 = 1.



Я дополнительно добавлю стилизованное перекрестье в нижней точке молнии + небольшие отлетающие частицы, чтобы передать импакт.
Единственный примечательный момент графа перекрестья состоит в том, что чтобы корректно повернуть Quad
к камере, для угла z используется Camera.transform.angles.z + spawnIndex * 90 + 45
. То есть, первая линия перекрестья появится под углом 45 градусов к камере (spawnIndex
считает с нуля), а вторая линия под углом 135.


Для разлетающихся частиц располагаем их на сфере в Initialize
, а в Update
добавляем Conforn To Sphere Attraction Force
относительно сферы с большим радиусом (или же отрицательный Attraction Force
для сферы (или точки) меньшего диаметра).



Итого мы имеем ассет, который имеет:
Удобные якоря
GameObject
для расположения на сцене.Форму мелких неровностей, которая условно случайная.
Баланс контраста.
Ощущение импакта.