[Swift] 카카오톡 송금 봉투 애니메이션 따라하기

CAEmitterLayer와 CAEmitterCell으로 비슷하게 구현해봅시다

naljin
10 min readJul 31, 2021

여러분 알져? 카카오톡 돈 봉투에 담아보낼 때 애니메이션 팡팡 터지는거!

전 항상 이걸 어떻게 했을까 궁금했었는데, CAEmitterLayerCAEmitterCell 로 비슷하게 구현 가능하다는 것을 동료분을 통해 힌트를 얻었답니다 ✦‿✦ 👍🏻

여기에 해당하는 프로퍼티는 너무 많기도 하고 저도 잘 알지 못하므로, 해당 기능을 구현하기 위해 사용했던 최소한의 프로퍼티만 사용해서 설명하도록 하겠습니다.

CAEmitterLayer

우선 CAEmitterLayer의 정의를 봅시다.

A layer that emits, animates, and renders a particle system.

ㅇㅋ particle system을 방출하고, 움직이게하고, 렌더링하는 레이어군! 그럼 여기서 particle은 뭔데?!

The particles, defined by instances of CAEmitterCell, are drawn above the layer’s background color and border.

흠.. CAEmitterCell 의 객체라고..? 우선 레이어 위에 animate 할 수 있는 cell 이 뿌려진다! 정도로 생각하면 되겠군!

여기까지 의식의 흐름 잘 따라오셨나요? 코드로는 요렇게 레이어와 셀을 지정해준답니다.

그럼 이제 emojiEmitterCell 은 어떻게 만들어진건지 보러 갑시다

CAEmitterCell

일단 주석에 다 써있긴 하지만, 하나씩 살펴봅시다.

contents

말 그대로 layer에 제공 될 contents 입니다. 해당 property를 CGImage 로 설정하여 이미지를 컨텐츠로 표시할 수 있습니다. 여기서 “EmojiTwo” 는 제가 Assets 에 넣어둔 이미지 이름이랍니다.

lifetime

cell 의 수명으로, 초 단위로 표시됩니다. 기본값은 0.0 입니다. 셀의 수명은 lifetimeRange 값으로 지정된 범위에 따라 달라집니다.

birthRate

1초당 cell 을 몇개 방출할 건지 결정합니다. 기본값은 0.0입니다. 만약 n으로 설정을 하면 한번에 n개가 방출 된 후, 1초 후에 다시 n개가 방출될 줄 알았지만, 1초에 걸쳐서 n개가 방출되는 거였습다.

scale

셀에 적용되는 scale factor를 지정합니다. 이 속성의 기본값은 1.0 입니다. 따라서 0.1을 설정하면 셀의 크기가 원래의 1/10이 됩니다. scaleRange 설정을 통해 해당 값을 지정된 범위 내에서 랜덤으로 달라지게 할 수 있습니다. 또한 scaleSpeed 속성에 따라 변경 속도가 결정됩니다.

scaleRange

위에서 언급했듯, 변경 가능한 scale 의 범위를 지정해서 셀마다 크기를 다르게 할 수 있습니다. 기본 값은 0.0입니다.

spin

셀이 얼마나 빠른 속도로 회전할 것인가를 결정하는 값입니다. 기본 값은 0.0으로, 회전 효과 없음을 뜻합니다.

spinRange

셀의 spin 은 spinRange로 지정된 범위에 따라 랜덤 값만큼 달라집니다.

emissionRange

cell 이 방출되는 각도를 뜻합니다. 기본 값은 0.0 이기 때문에, 한 방향으로만 방출될 것입니다. 만약 2 * CGFloat.pi 로 설정한다면, 이는 360 도를 뜻하기 때문에 cell이 모든 방향으로 방출 됩니다.

velocity

cell 의 속도입니다. 셀의 속도는 velocityRange 값으로 지정된 범위에 따라 달라집니다. 수치가 높을 수록 더 빠르게, 더 멀리 방출되는 효과를 가집니다( 얘 값이 높을수록 뭔가 다른 효과들이 더 잘 적용되었습니다). yAcceleration 에 의해서도 영향을 받습니다.

velocityRange

cell 의 velocity가 달라질 수 있는 범위를 뜻합니다. 만약 기본 velocity가 700이고, velocityRange 가 50 이면 각 particle은 650–750 사이의 velocity 값을 갖게 됩니다. 기본 값은 0.0입니다.

yAcceleration

cell에 적용되는 가속도 벡터의 y 값입니다. 그냥 쉽게 말하면 중력 효과라고 생각하면 될 것 같습니다. 양수면 중력이 적용되는 것처럼 보이고, 음수면 중력이 없어져서 날아가는 것 처럼 보입니다. 기본 값은 0.0입니다. velocityyAcceleration의 조합이 cell의 distance 를 결정합니다.

아래와 같이 360 도로 균등하게 cell이 뿜어져 나갈 코드가 있다고 해봅시다.

cell.emissionRange = CGFloat.pi * 2
cell.velocity = 100

만약 여기에 cell.yAcceleration = 200 를 추가한다면 cell 이 위로 올라가려다가 중력의 영향을 받아 다시 내려오게 됩니다

HandleTap

이제 탭에 따라 cell이 방출되는 포인트를 지정해줘야겠죠? 전체 뷰에 UITapGestureRecognizer 를 적용해주고 해당 좌표값을 받아서 이전에 만들어둔 emitterLayeremitterPosition 에 세팅해줍니다. 그리고 마지막으로 해당 레이어를 addSublayer를 통해 뷰의 레이어에 얹어주면 됩니다.

여기서 한가지 문제점이 있는데, 이렇게만 하면 셀의 방출이 멈추지 않고 끊임 없이 뿜어져 나온다는 것입니다. 하지만 저희가 원하는건 한번만 나오고 끝나는 거잖아요? 이를 위해 클릭할 때마다 layer의 birthRate 를 지정합니다.

여기서 좀 이상했던건 DispatchQueue.main.asyncAfter 작업을하면, 클릭시 cell의 방출 개수나 emitterPosition가 조금씩 달라진다는 것이었습니다. 하지만 우선 더 괜찮은 방법은 일단 찾지 못했고, 결과적으로는 비슷해보이는 것 같아 우선 이대로 진행을 했습니다. (더 알게 되면 추가할게여 아니면 댓글로 알려주시면 압도적 감사)

결과

뭔가 한곳에서 팡! 하고 터지는 느낌이 덜 하긴 하지만…. 족굼은 비슷해보이나여?

전체 코드

보너스 — 눈내리기

눈오면 카톡 배경으로 눈도 내리잖아요? 그건 어떻게 구현될지 한번 궁예를 해봅시다.

아까와 다른 점은 cell 에 spin이나 velocity 등의 프로퍼티가 빠지고 alphaSpeed 가 생겼다는 것입니다.

cell.alphaSpeed = -0.05

예를 들어 alphaSpeed 를 -0.1로 지정하면 초당 0.1 씩 알파 값이 줄어듭니다.

또한 layer에는 emitterShapeemitterSize 가 추가되었는데요,

emitterLayer.emitterShape = .line
emitterLayer.emitterSize = CGSize(width: view.frame.width, height: view.frame.height)

우선 emitterShape 는 particle이 어디서 발생하는지에 대한 위치 값을 정의합니다. defualt 값은 point 인데, 이는 particle 이 단일 지점에서 방출됨을 의미합니다.

이번에 설정한 line 의 의미는, particle 을 방출하는 x 위치(emitterPosition.x - emitterSize.width/2) 에서 (emitterPosition.x + emitterSize.width/2) 범위에서 방출된다는 것입니다.

범위 식을 보면 알 수 있듯 emitterShapeemitterSize 에도 영향을 받기 때문에(emitter shape 의 크기를 결정) 이를 추가로 지정해주었습니다.

결과

마무리

흠.. 8500원을 봉투에 담아 보내준.. 왜웅이에게 캡쳐해서 써도 되냐고 물어볼랬는데.. 자니..? 옵치하니..?? 맘대로 썼으니까 애옹님이 만들어준 지우개 도장이나 자랑하면서 끝내도록 하져 ㅎ_ㅎ 곧 가지러가겠다 송왜웅! 🥷🏻

참고

--

--