Skip to main content
← 블로그

별에 얼굴을 그린 날

VauDium·

페칫 어시스턴트의 별 마스코트에 눈과 입을 그렸습니다. 별을 통통하게 만들고, 표정이 스르르 바뀌게 하고, 끼어들 때 지을 진지한 얼굴을 만들다 한 번 뚱해졌습니다. 별을 살아 있게 만든 하루의 기록입니다.

별에 얼굴을 그린 날

페칫 어시스턴트 화면 한가운데에는 별이 하나 떠 있습니다. 답을 생각할 때 좌우로 갸웃하고, 말을 마치면 한 바퀴 돌고, 가만히 있을 때는 몇 초에 한 번씩 끄덕이거나 숨을 쉽니다. 몸짓은 꽤 오래전부터 있었는데, 얼굴이 없었습니다. 잘 움직이는 도형이었지, 누군가는 아니었습니다.

오늘 눈과 입을 그렸습니다. 그리고 하루 종일 그 얼굴을 만졌습니다.

1. 눈 두 개, 작은 입 하나

시안은 여러 벌이었지만 고른 건 가장 적은 것이었습니다. 점 눈 두 개와 작은 미소 하나. 눈썹도, 볼도, 하이라이트도 없습니다. 별은 작고(56px) 자주 도는데, 그릴수록 돌 때 지저분해집니다.

제약이 하나 있었습니다. 이 별은 몸짓 중에 회전합니다. 회전하는 동안에도 실루엣이 별로 읽혀야 해서, 이목구비를 별 안쪽의 마름모 영역 안에만 두기로 했습니다. 팔까지 나가면 돌 때 얼굴이 별을 삼킵니다.

표정은 다섯 가지입니다. 별이 이미 하고 있던 몸짓에 얼굴을 하나씩 짝지었습니다.

  • 가만히 있을 때: 기본 미소
  • 답을 생각하는 중: 감은 눈
  • 할 일이 완료됐을 때: 눈웃음
  • 별을 탭했을 때: 놀란 입
  • 게이트에서 말을 건넬 때: 윙크

생각할 때 눈을 감는 것 하나로 별이 갑자기 사람처럼 보였습니다. 몸짓만 있을 때는 “돌고 있다”였는데, 눈을 감으니 “생각하고 있다”가 됐습니다.

잉크 색은 별 색의 밝기로 자동 결정됩니다. 파란 별에는 흰 눈, 노란 별에는 검은 눈. 게이트 모달은 종류마다 별 색이 달라서 이게 필요했습니다.

2. 조금 더 뚱뚱하게

얼굴을 얹고 보니 별이 말랐습니다. 안쪽 마름모가 좁아서 눈 두 개가 겨우 들어갔고, 마스코트라기엔 날카로웠습니다.

별 모양을 바꾸면 되는데, 문제는 그 별이 앱 전체 64곳에서 아이콘으로 쓰이는 것과 같은 파일이었다는 점입니다. 목록의 포인트 컬러 별, 라이브러리의 템플릿 별, 전부 같은 SVG입니다. 마스코트를 살찌우면 아이콘 64개가 같이 살찝니다.

그래서 마스코트 전용 별을 따로 만들었습니다. 바깥 꼭짓점은 그대로, 안쪽 네 꼭짓점만 중심에서 멀리 벌린 모양입니다. 원래 값이 2.5였고, 3.2로 시작해서 “조금 더”를 거쳐 3.8까지 갔다가, “조금 덜”로 3.5를 보고, 다시 3.8로 돌아왔습니다. 옆에 세워 놓고 비교하지 않으면 정할 수 없는 종류의 숫자였습니다. 통통해지니 확실히 더 귀엽습니다.

몸통이 넓어지자 얼굴이 작아 보였습니다. 눈 간격과 입 폭을 1.3배로 키웠습니다. 표정 다섯 종을 전부 같은 중심에서 같은 비율로 키워서, 서로 겹쳐 놓아도 어긋나지 않습니다.

3. 표정은 스르르 바뀌어야 한다

여기까지는 정지 그림이었습니다. 생각을 시작하면 눈이 감기는데, 한 프레임 만에 감깁니다. 몸짓은 부드러운데 얼굴만 뚝뚝 바뀌니, 얼굴이 몸에 붙어 있지 않고 위에 얹힌 스티커처럼 보였습니다.

모바일은 표정 다섯 장을 겹쳐 두고 불투명도만 고르는 구조였습니다. 그 불투명도를 0 아니면 1로 찍던 것을 160ms 동안 보간하도록 바꿨습니다. 애니메이션 스레드에서 몸짓과 같은 프레임에 돌기 때문에 JS가 바빠도 끊기지 않습니다. 이제 눈이 감깁니다. 꺼지는 게 아니라요.

데스크톱을 열어 보니 어시스턴트 별에는 얼굴이 아예 없었습니다. 아침에 얼굴을 그릴 때 게이트 모달 세 곳에만 넣고 어시스턴트 별은 빠뜨린 겁니다. 같은 표정 층을 넣고, 몸짓을 굴리는 rAF 루프가 표정도 고르게 했습니다. 전환은 CSS transition이 같은 160ms로 합니다. 두 플랫폼이 상수 하나를 공유합니다.

4. 진지한 얼굴, 그리고 한 번 뚱해진 일

게이트는 두 종류입니다. 어려운 할 일을 시작하려 할 때 “작전을 세울까요?“라고 끼어드는 것, 완료하려 할 때 “회고를 남길까요?“라고 끼어드는 것. 둘 다 파란 말풍선입니다. 그리고 리뷰를 권유하는 노란 말풍선이 하나 더 있습니다.

아침에는 셋 다 윙크였습니다. 그런데 파란 둘은 끼어드는 확인이고 노란 하나는 초대입니다. 끼어들면서 윙크하는 건 가벼웠습니다. 파란 게이트에는 조금 더 진지한 얼굴이 필요했습니다.

첫 시도는 입을 일자로 그은 것이었습니다. 눈은 그대로 두고 미소만 뺐습니다. 화면에 띄우자마자 답이 나왔습니다. 뚱했습니다. 진지한 게 아니라 부정적으로 읽혔습니다. 눈썹이 없는 얼굴에서 입 하나가 톤을 전부 짊어지는데, 일자 입은 “싫다”에 너무 가깝습니다.

기본 미소의 곡선 깊이가 1.45인데, 그걸 0.55로 줄였습니다. 폭도 조금 좁혔습니다. 웃는 건 아닌데 뚱하지도 않은, 차분히 듣는 입이 됐습니다.

그러면서 하나를 뺐습니다. 회고 게이트는 별이 등장할 때 완료 파티클을 터뜨리고 있었습니다. 완료의 순간이니까요. 그런데 진지한 얼굴 옆에서 파티클이 터지니 얼굴과 몸이 서로 다른 말을 했습니다. 파티클을 지웠습니다. 셀레브레이션은 실제로 완료된 시점에 따로 나오니 잃는 건 없습니다.

5. 게이트를 거치면 완료음이 두 번 났다

얼굴과 상관없는 것도 하나 걸렸습니다. 회고 게이트에서 바깥을 탭해 그냥 완료하면 완료음이 중간에 끊기고 처음부터 다시 났습니다.

당연히 완료 핸들러가 두 번 불리는 줄 알았습니다. 게이트 닫기와 완료가 같은 손에서 나가니까요. 그런데 소리 재생 함수에 호출 스택을 찍어 보니 한 번이었습니다.

원인은 플레이어 쪽이었습니다. 되감기(seekTo)가 비동기인데 그 결과를 기다리지 않고 바로 재생을 시작하고 있었습니다. 평소엔 되감기가 먼저 끝나 티가 안 났지만, 게이트 배경 탭은 모달 닫힘 애니메이션과 겹쳐 되감기가 늦어졌고, 이미 나고 있던 소리가 처음으로 튀었습니다. 기다린 뒤 재생하는 걸로 끝났습니다.

증상만 보면 “두 번 호출”이 확실해 보이는 종류의 버그였습니다. 로그 한 줄이 추측 하나를 잘라 냈습니다.

오늘의 교훈

하나, 얼굴은 적게 그릴수록 잘 움직입니다. 점 눈 둘과 입 하나로도 별은 생각하고, 놀라고, 듣습니다. 더 그렸다면 돌 때마다 지저분해졌을 겁니다.

둘, 마스코트는 자기 몸이 필요합니다. 아이콘과 파일을 나눠 쓰는 동안은 마스코트를 만질 수 없습니다. 64곳이 같이 움직이니까요.

셋, 표정은 바뀌는 순간까지가 표정입니다. 한 프레임에 갈아 끼우면 스티커고, 160ms 동안 스르르 바뀌면 얼굴입니다.

넷, 일자 입은 진지가 아니라 뚱한 것입니다. 눈썹 없는 얼굴에서는 입이 톤을 전부 짊어집니다. 웃음을 빼는 것과 웃음을 줄이는 것은 다른 표정입니다.

다섯, “두 번 난다”는 “두 번 불렸다”가 아닐 수 있습니다. 로그로 호출 횟수를 먼저 세고 나서 고칩니다.

별은 여전히 별입니다. 다만 이제 생각할 때 눈을 감고, 탭하면 놀라고, 끼어들 때는 웃음을 조금 거둡니다. 하루 만에 도형이 누군가가 됐습니다.