Всем привет!
Рождество давно прошло, но после него у нас осталась занимательная история о том, как при помощи нечасто используемой возможности Core Animation можно создать пользователям праздничное настроение. Делюсь переводом статьи моего лондонского коллеги Алексиса.
Рождество всегда было для меня одним из самых любимых дней в году. Оно приносит в наши жизни много любви, смеха, счастья и волшебства.
Я родился и вырос в Испании, на Тенерифе — солнечном острове посреди Атлантического океана недалеко от побережья Африки. И, поверьте мне, Рождество на Тенерифе сильно отличается от Рождества в Лондоне, где я встречал его последние два года (с тех пор как начал работать в Badoo).
Одним из преимуществ жизни в Лондоне для меня стало созерцание снежинок. Здесь я увидел их впервые в жизни, это было просто невероятно!
Вспомнив об этом, я решил поделиться с вами одной интереснейшей историей, случившейся со мной в офисе незадолго до Рождества, перед тем как я отправился на Тенерифе, чтобы встретить праздник со своей семьёй.
Так уж случилось, что мне поручили одну необычную задачу со следующим описанием:
Хм, довольно занятно. Мы хотели добавить рождественскую анимацию со снежинками в наше iOS-приложение, и я стал тем счастливчиком, который должен был её создать. Но я не знал, с чего начать.
Как обычно, к задаче был прилинкован Sketch-файл c необходимым дизайном, который выглядел примерно так:
Как минимум я видел, что нам требуется, но не был до конца уверен, как эти снежинки должны себя вести. Чтобы прояснить все детали, я отправился общаться с дизайнерами.
Как я и подозревал, у них уже была готова великолепная анимация, нарисованная в After Effects.
Дизайнеры объяснили мне, что хотят добавить падающие сверху снежинки в уже существующую анимацию запуска приложения (а также шапку Санты на наш логотип, но это была простая подмена картинки, недостойная упоминания в этой статье).
Я знал, что анимация запуска приложения на iOS сделана с помощью Lottie, так как её добавили уже после того, как я присоединился к компании (подробности можно найти в статье Radek Cieciwa). Однако я сказал дизайнерам, что попробую найти более простые решения для показа снежинок (без необходимости использовать Lottie) — и начал исследовать различные подходы.
Вот такую анимацию мой коллега Radek сделал для Badoo. Безупречно!
А вот так я добавил падающие снежинки. Хотите знать, как у меня это получилось? Ответ вы найдёте ниже.
Системы частиц (Particle Systems)
Во время чтения различной документации про анимации я вспомнил о том, что в играх и фильмах для решения подобных задач довольно часто используют системы частиц.
«Википедия» описывает это понятие довольно точно:
«Систе́ма части́ц — используемый в компьютерной графике способ представления объектов, не имеющих чётких геометрических границ (различные облака, туманности, взрывы, струи пара, шлейфы от ракет, дым, снег, дождь и т. п.). Системы частиц могут быть реализованы как в двумерной, так и в трёхмерной графике».
Система частиц состоит из одного или нескольких графических примитивов, таких как точки, линии или изображения, именуемых частицами. Эти частицы создаются и испускаются эмиттером, являющимся частью системы частиц.
Каждая частица обладает набором параметров, которые прямо или косвенно влияют на её поведение и определяют то, как она будет отрисована. Частицы могут двигаться одновременно в большом количестве и в различных направлениях, например для создания эффекта жидкости.
Анимация начинает действовать, когда частицы испускаются системой. Система испускает частицы в случайных местах в рамках заданной системой частиц области. Она может принимать различные формы: круг, прямоугольник, сфера, параллелепипед, линия, точка и т. д.
Система также определяет свойства частиц, влияющие на их геометрию, скорость и другие параметры. Разные API для работы с эмиттером имеют разные названия схожих свойств.
Когда частицы испускаются системой одновременно, они создают ошеломляющие анимации, которые могут выглядеть как дождь, огонь или снег.
От теории к практике
Я подумал, что Apple, скорее всего, имеет встроенную поддержку системы частиц в одном из своих фреймворков. И результаты моих поисков показали, что я прав.
Система частиц является частью фреймворка Core Animation и хорошо описана в классах CAEmitterLayer и CAEmitterCell.
После изучения всей необходимой информации о системе частиц и поддерживаемых API на iOS я приступил к моей любимой части — реализации нашей задумки.
К несчастью, Рождество не вечно, поэтому нам нужна была возможность отключить снежинки удалённо после 25 декабря.
Как я упомянул выше, анимация запуска приложения была реализована с помощью Lottie. То есть мне нужно было найти способ добавить снежинки таким образом, чтобы это не затрагивало существующую анимацию и её код, потому что моё решение должно было быть удалено сразу после релиза.
Я нашёл очень простой способ это сделать — добавил новый прозрачный UIView для показа снежинок перед существующей анимацией и фоном и затем контролировал его появление удалённо с помощью флага.
Изображение выше показывает UIView, которые были использованы в финальном решении:
- UIView с системой частиц, которая испускала снежинки.
- UIViews, используемые в анимации запуска приложения, управляемые Lottie.
После того как эта проблема была решена, мне оставалось создать компонент, содержащий логику испускания частиц для генерации анимированных снежинок.
Для начала мне были необходимы изображения снежинок, которые я мог бы использовал в качестве контента для эмиттера. Они должны быть довольно простыми, не так ли?
Каждая снежинка представляла собой либо обычный, либо размытый белый круг. Я создал их самостоятельно в программе Sketch.
Некоторые детали реализации
CAEmitterLayer — это специальный CALayer, который создаёт, анимирует и отрисовывает систему частиц. Он позволяет контролировать свою геометрию, позицию, режим отрисовки и многое другое.
Я начал разработку анимации с создания слоя:
snowEmitterLayer.emitterShape = CAEmitterLayerEmitterShape.line
snowEmitterLayer.beginTime = CACurrentMediaTime()
snowEmitterLayer.timeOffset = 10.0
Мне нужно было изменить только три свойства:
- emitterShape: определяет форму слоя. я использовал линию, что позволило снежинкам появляться вдоль всего экрана;
- beginTime: является частью CAMediaTiming-протокола и представляет собой время начала анимации слоя относительно анимаций родительского слоя;
- timeOffset: также является частью CAMediaTiming-протокола и, по сути, представляет собой перемотку анимации вперёд на заданное время относительно её начала. Я указал значение в 10 секунд, что привело к тому, что в момент начала анимации снежинки уже покрывали экран целиком, и это именно то, чего мы хотели (если бы я указал значение в 0 секунд, то снежинки начали бы появляться сверху и покрыли экран целиком только спустя какое-то время).
Имея готовый слой, я создал два разных эмиттера: для снежинок потяжелее и для снежинок полегче.
Для тяжёлых снежинок я настроил эмиттер следующим образом:
let flakeEmitterCell = CAEmitterCell()
flakeEmitterCell.contents = UIImage(named: "snowflake_dot")!.cgImage
flakeEmitterCell.emissionRange = .pi
flakeEmitterCell.lifetime = 20.0
flakeEmitterCell.birthRate = 30
flakeEmitterCell.scale = 0.15
flakeEmitterCell.scaleRange = 0.6
flakeEmitterCell.velocity = 30.0
flakeEmitterCell.velocityRange = 20
flakeEmitterCell.spin = -0.5
flakeEmitterCell.spinRange = 1.0
flakeEmitterCell.yAcceleration = 30.0
flakeEmitterCell.xAcceleration = 5.0
Как видите, мне пришлось изменить значительное количество свойств, каждое из которых очень важно для достижения желаемого эффекта:
- contents: CGImage, используемый для показа одной снежинки (как вы помните, это одно из тех изображений, которые я создал самостоятельно);
- emissionRange: угол в радианах, определяющий конус, внутри которого будут появляться частицы (я выбрал угол PI, чтобы частицы были видны на всём экране);
- lifetime: определяет время жизни одной частицы;
- birthRate: определяет количество частиц, испускаемых каждую секунду;
- scale и scaleRange: влияет на размер частиц, где значение 1.0 — максимальный размер; интервал определяет отклонения в размерах между созданными частицами, что позволяет излучать частицы случайных размеров;
- velocity и velocityRange: влияет на скорость появления частиц; отклоняется случайно в рамках значения, указанного в velocityRange;
- spin и spinRange: влияют на скорость вращения, измеряемого в радианах в секунду, и случайное отклонение в рамках значения, указанного в spinRange;
- yAcceleration и xAcceleration: это два компонента вектора ускорения, применённого к эмиттеру.
Мне также понадобился второй эмиттер для создания лёгких снежинок. Все свойства остались без изменений, за исключением двух:
- contents: тут я использовал изображения с размытым кругом;
- velocity: а здесь мне пришлось уменьшить скорость падения для придания снежинкам лёгкости.
let blurryFlakeEmitterCell = CAEmitterCell()
blurryFlakeEmitterCell.contents = UIImage(named: "snowflake_blurry_dot")?.cgImage
blurryFlakeEmitterCell.velocity = 40
// Остальные свойства остались без изменений
Мне оставалось только соединить слой и эмиттеры, что оказалось очень легко:
snowEmitterLayer.emitterCells = [flakeEmitterCell, blurryFlakeEmitterCell]
self.layer.addSublayer(snowEmitterLayer)
Заключение
Я довольно быстро создал рабочий вариант с падающими снежинками, который выглядел очень неплохо. Он был довольно прост в реализации и не менял существующего кода. Я показал его дизайнерам, и он им очень понравился.
Анимации, созданные с помощью системы частиц, могут быть довольно впечатляющими и относительно простыми в реализации, если вы владеете правильными инструментами.
Больше информации о системах частиц вы найдёте в следующих источниках:
Автор: Щукин Антон