리액트로 만든 옷입히기 - 민킈 만들기 제작기

2023. 8. 21. 04:02Random

이 글은 리액트(React)로 간단히 사이드프로젝트 "민킈 만들기"를 만들기까지의 여정을 기록한 글입니다.

민킈가 누군가요?

이름부터 범상치 않다. 민킈, 옛날 귀여니 시절의 감성이 느껴질 정도의 기이한 작명센스다. 롑흔리나 같은 컨셉질 블로거일까? 아니다. "김민킈"는 바로 스트리머 우정잉의 부캐다.

https://www.youtube.com/watch?v=6mADbR8XYJU

 

김민킈는 스트리머 우정잉이 "자캐대회"라는 컨텐츠를 진행하면서 스스로 만든 우정잉의 자캐다. 자캐대회라는 컨텐츠는 그 내용이 워낙 심연이었던 탓에 역사 저 너머로 잊혀졌지만, 민킈라는 캐릭터 자체는 그 이후로도 우정잉의 또 하나의 정체성으로 자리매김했다.

팬카페 이전하기 전까지 우정잉의 카페 프사는 민킈였다. 그녀의 페르소나였던 셈.

즉석에서 그림판으로 슥삭한 그림이지만 묘한 매력이 담겨있다. 모나리자 뺨치는 이 미묘한 표정의 아녀자에게는 왠지 모를 맑은 눈의 광기가 느껴지면서 동시에 시니컬함과 도도함, 그리고 비웃음인지 기쁨인지 모를 미묘한 입꼬리의 미소가 있다. 이런 매력의 캐릭터가 우정잉 팬카페의 꾸준한 밈이 되는 건 피할 수 없는 일이었을 지 모른다.

이게 우정잉 팬카페인지 민킈 팬카페인지

 

그러던 중 예전에 유행했던 '뉴진스 토끼 만들기'가 문득 떠올랐다. 너 나 할 것 없이 뉴진스 토끼를 변형해서 회사 이미지나 특정 직업군을 투영시키곤 했었고, 이를 위한 웹앱도 있었다. 팬카페에 등장하는 각종 변형 민킈들을 보자니 자연스레 "민킈 만들기"도 만들어보면 재미있겠다는 생각이 들었다.

"민킈 만들기" 만들기

기술 스택 결정

그래 역시 너만한 게 없다.

"민킈 만들기"는 프론트엔드로만 이루어진 웹앱이 되어야 했다. 어차피 본격적으로 수익화를 한다거나, 대규모 시청자를 받거나 서비스를 키워나갈 생각이 없었으므로 백엔드가 필요하지 않았다. 당연히 사용 기술스택은 "리액트" 라이브러리였다. 프론트엔드 기술 스택 중 가장 친숙했고, 생태계도 넓어서 레퍼런스가 많았기 때문이다.

"옷 입히기"는 어떻게 만들지?

그렇다면 "뉴진스 토끼 만들기" 같은 앱은 어떻게 만들 수 있을까? 대충 UI만 보고 추측해본다면, 아마 HTML의 canvas를 사용했거나 div 요소를 중첩시켜서 활용했을 것이다. 사전에 정의된 div들을 파츠로 활용하여 사용자의 선택 결과에 따라 표시해주고, 이것을 모아서 렌더링하면 되는 것이다. 사실 이것으로 분석은 끝이다.

이렇듯 UI가 멋있고 이름이 그럴싸해봤자 결국 "뉴진스 토끼 만들기"와 "민킈 만들기" 모두 "옷 입히기"에 불과하다. 사용자의 선택에 따라 레이어화 된 이미지 컴포넌트를 열거하는 것. 그리고 이런 단순하고 흔한 유즈 케이스가 react로 구현된 예시가 없을 리가 없었다. 구글에 "dress-up react" 를 검색하자 해외 블로거가 작성한 하나의 글이 나타났다.

https://mariaeramosmorales.medium.com/make-a-dress-up-game-or-character-generator-37bdebab9601

 

Make a Dress-up Game using React and Sass

In this tutorial we are making a dress up game (or character generator) using React and Sass.

mariaeramosmorales.medium.com

이 글에서는 react와 Sass를 이용하여 단순한 옷입히기 앱을 만드는 방법을 소개하고 있었다. 예제로 만들어진 앱인만큼 기능은 매우 단순했으나 "민킈 만들기"의 베이스가 되기엔 충분했다. 나는 이 프로젝트를 기반으로 "민킈 만들기" 만들기에 착수했다.

기능 정의

UI

버전업을 거치면서 몇 번 변경이 있었지만, 이 글을 쓰는 현재 시점 최신의 UI는 위와 같다. 투박하지만 어떤 방식인지는 바로 이해가 갈 것이다.

파츠 분류

고심 끝에 결정한 민킈 만들기에서 커스터마이즈 가능한 파츠 카테고리는 아래와 같다.

  • 헤어스타일
  • 메이크업
  • 무기
  • 악세사리A
  • 악세사리B
  • 말풍선
  • 배경화면

같은 파츠 카테고리더라도 여러 파츠를 조합시킬 수 있다.

처음에는 각 파츠 카테고리에서 고를 수 있는 파츠의 수는 1개로 제한되어 있었다. 그러나 여러 종류의 파츠가 점점 추가되면서, "조합해서 써야 하는 파츠"들이 생기게 되었고, 굳이 선택의 폭을 좁힐 필요는 없다고 판단해서 같은 파츠 카테고리 안에서도 여러 파츠를 고를 수 있게 허용했다. 즉, 머리스타일이 3개이고 무기를 10개 들고 있는 민킈를 만들 수 있다는 의미다.

실제로 어느 유저가 만든 키메라 민킈

파츠의 표시 우선순위도 고민거리였다. 쉽게 설명하자면, "무기"가 "헤어스타일"보다 낮은 순위를 가지게 된다면 머리스타일이 무기를 가려버려서 착시현상 그림이 되어버리고 만다. 그렇기 때문에 파츠 카테고리간에는 명확한 "상하관계"가 있어야 했다. 

어떤 악세사리는 민킈 앞에, 어떤 악세사리는 민킈 뒤로 가야한다.

악세사리가 굳이 A와 B로 나뉜 이유도 그 때문이다. "날개"나 "꼬리" 같은 것들은 무조건 민킈의 몸보다 뒤에 표시되어야 하고, "안경" 같은 것은 민킈의 얼굴 앞에 표시되어야 했기 때문이다.

파츠 색 변경 기능 (커스텀 컬러)

1.0에는 없다가 2.0부터 추가된 기능이다. 대부분의 게임에서 지원하는 "팔레트 스왑"과 비슷하다. 파츠의 색을 RGB 피커에서 골라 꾸밀 수 있게 하는 것. 이 기능을 어떻게 도입했는지는 아래에서 설명할 것이다.

이미지 다운로드 기능

"공유하기"까지는 몰라도, 적어도 만든 결과물을 png 파일로 저장할 수 는 있어야한다고 판단하여 Download Image 버튼을 만들었다. 이 기능도 예상 외로 조금 까다로운 지점이 있었는데, 바로 iOS였다. 안드로이드, PC에서는 잘 작동하는 기능이 유독 iOS에서만 정상적으로 작동하지 않아 몇 번의 수정을 거쳐야 했다. 마찬가지로 아래에서 설명할 것이다.

Sass? Saas가 아니고?

고백한다. 백엔드 개발자인 나는 처음에 Sass를 봤을 때 당연하게도 SaaS(Software as a Service)의 오타인 줄 알았다. 알고보니 전혀 아니었고 Syntactically Awesome Style Sheets의 줄임말이란 걸 알게 되었다. 이는 "스타일 시트 전처리기"로, 기존 CSS의 단점을 보완하기 위해 만들어진 것이라고 한다.

CSS와 다르게 인상적이었던 부분은 "반복문" 혹은 "변수 선언" 같은 프로그래밍적 요소가 들어가 있다는 점이었다. 예를 든다면 아래와 같이 사용이 가능하다.

CSS에선 단순 반복 열거를 해야하는 요소를 for 문으로 간단히 처리할 수 있다.

이런 기능은 옷입히기 앱을 만드는 데 편의성 면에서 많은 도움을 줄 것이 분명했다. 개발이 진행되면서 기능적인 면보다는 컨텐츠적인 면에서 여러가지의 파츠가 추가될 텐데, 이걸 전부 css에 일일이 반영하는 것은 상당히 귀찮기 때문이다.

민킈 만들기에서는 이런 식으로 각 파츠를 SCSS를 통해 자동화시켰다.

노가다 시작

내가 이걸 왜 했지

민킈 만들기에서 가장 중요한 부분은 바로 "파츠"의 갯수였다. 옷입히기 앱에서 고작 사용자가 고를 수 있는 옷이 "빨간 드레스" "파란 드레스" 이 두가지 뿐이라면 사용하는 이유가 없지 않은가? 나는 우정잉 팬들이 각자 자기만의 민킈를 가지게 되는 것을 원했다. 그렇기에 최대한 많은 파츠를 확보해야했다.

포토샵을 켜서 민킈 이미지를 불러온 다음, 앱에서 사용할 수 있는 상태로 만들었다. 머리를 빡빡 밀어서 대머리로 만들고, 쥐고 있는 낫창을 뺏고, 입고 있는 흰색 드레스를 벗겨서 나이키 내복으로 바꿔주었다. 이제 이 사진 위에 파츠가 하나씩 추가되면서 개인화된 민킈가 생성되는 것이다.

거의 대부분의 파츠는 내가 포토샵에서 즉석으로 연필로 그려낸 것들이다. 아이디어가 떠오를 때마다 포토샵을 켜서 그리고 다시 닫고 이렇게 반복해서 조금씩 모았다.

이미지 다운로드가 안 돼요

첫 릴리즈 이후로 iOS 이용자들로부터 다운로드가 안 된다는 이야기가 나왔다. 왜인가하고 찾아봤는데, iOS는 안드로이드/PC와는 다른 파일 시스템을 가지고 있기 때문이었다. 처음에 내가 구현한 다운로드 기능은 아래와 같았다.

  const downloadImage = async () => {
    const dataUrl = await htmlToImage.toPng(domElement.current)

    const link = document.createElement("a");
    const timestamp = new Date() / 1
    const filename = `minkee-${timestamp}.png`
    link.download = filename
    link.href = dataUrl;
    link.click();
  };

html2Image 라이브러리의 도움을 받아 div를 png base64 인코딩으로 변경하고, a 태그 안에 집어넣어 클릭시키는 방식이었는데, iOS에서는 이런 식의 다운로드를 지원하지 않는다. 따라서 아이폰 유저들은 기껏 만든 결과물을 스크린 캡쳐로 보관해야하는 일이 생겼었다.

그래서 2.0에서는 그냥 다운로드를 시키는 것이 아니라, 최종 결과물을 img 태그로 띄워주는 방식으로 바꿨다. img 태그를 아이폰의 "사진"앱에 넣는 것은 가능하기 때문이다.

다운로드 버튼을 누르면 이렇게 완성본이 따로 표시된다.

  const renderImage = async () => {
    setDisplayResultImage(true)
    let dataUrl
    for (let i = 0; i < 3; i++) {
      dataUrl = await htmlToImage.toPng(domElement.current)
    }

    setResultImage(dataUrl)
  };

중간에 같은 동작을 3번하는 반복문은 왜 들어갔느냐? "사파리"가 "사파리" 해버려서 htmlToImage의 toPng() 메소드가 절대로 한번만에 성공하지 않고 딱 3번만에 성공했기 때문이다. 원인은 아직도 모르겠으나, 해결법이라도 찾은 게 다행이라 생각한다.

커스텀 컬러 기능을 구현해보자

사실 1.0을 만들 때에는 완성 그 자체에만 초점을 뒀기 때문에 아무 생각 없이 호다닥 만들었지만, 사실 내가 원했던 것은 파츠의 색상마저 RGB 피커로 지정하는 것이었다. 그러나 파츠들은 고정된 png 파일로 만들어졌기 때문에 이를 HTML과 js만으로는 색칠할 수가 없었다. 고민 끝에 나는 파츠들을 두 가지로 분리하기로 결정했다. 바로 아웃라인과 색영역이다.

이제 파츠는 outline과 color의 조합으로 이루어진다.

아웃라인은 png 파일로 따로 분리하고, 색이 들어가야 하는 부분은 SVG로 선언한 다음, 이를 div로 묶어서 파츠를 재정의하는 것이다. svg는 브라우저에서 직접 렌더링하는 벡터 정보이므로, js를 통해서도 색을 충분히 제어할 수 있었다. 이미 만들어진 파츠들을 분리해서 SVG로 만드는 것은 고역이었으나, 어도비 일러스트레이터에 비트맵 이미지를 벡터로 변환해주는 기능이 있어서 생각보단 수월하게 끝낼 수 있었다.

그래서 이거 어디서 해볼 수 있나요

굳이 이걸 해보고 싶은 사람이 있으려나 싶지만 링크를 여기 첨부해두겠다.

https://minkee-maker.netlify.app/

 

민킈 만들기

 

minkee-maker.netlify.app

깃헙 레포 있나요?

커밋 이력이 너무 개판이라 공개하기 부끄럽지만..

https://github.com/zerobell-lee/minkee-dressup

 

GitHub - zerobell-lee/minkee-dressup: 민킈야 사랑해

민킈야 사랑해. Contribute to zerobell-lee/minkee-dressup development by creating an account on GitHub.

github.com

업데이트 계획?

생각날 때마다 가끔씩 파츠 업데이트를 하거나 편의성 개선을 하지만 DAU(Daily Active User)가 0이다. 어디까지나 나 재밌자고 만든 프로젝트라서 업데이트는 고사하고 언제 사라질 지 모른다.

만들어보니 어땠나요

재미는 있었다. 단순한 옷입히기에도 이렇게 많은 고민거리가 들어간다는 점이 놀랍기도 했고, 개인적으로는 커스텀 컬러 기능 구현이 제일 뿌듯했다. 내 경험상 이런 뻘짓들도 언젠가는 좋은 자산이 되어서 다른 일에 도움이 되더라. 이 프로젝트를 통해 얻은 sass 기술이나 svg 제어술(?) 등등이 언젠가 빛을 발하길 바란다.