티스토리 뷰

사이드 프로젝트/🩺 바디토리 : 건강기록 및 병원추천 웹서비스

대표 캐릭터 만들고 lottie로 구현하기

2022. 12. 9. 12:12

 

1️⃣ 마스코트 디자인을 캐릭터 애니메이션으로 결정한 이유

 

이번 프로젝트를 기획할 때 웹, 앱이 익숙치 않은 이용자들에게도 친숙하게 다가갈 수 있는 방법을 고민하다

전체적으로 우리 서비스를 이용하는 과정이 대화하듯이 흘러가면 어떨까 생각하게 되었다.

 

대화형 인테페이스를 가져가면 처음 이용하는 사용자에게 직관적이고 친절한 사용자 경험을 가져갈 수 있고,

의료/건강이라는 딱딱할 수 있는 주제를 조금 더 편안하게 풀어낼 수 있을 것 같았다.

 

서비스의 컨셉을 “나만의 건강비서”로 잡고, 대화형으로 풀어가려면 챗봇처럼서비스의 마스코트가 있으면 좋을 것 같아서

이왕 마스코트를 넣는다면 상황에 따라 모션이 달라지는 애니메이션으로 만들고자 했다.

 

처음 기획한 UI는 2가지인데 한가지는 귀엽고 친근한 느낌을 강조하는 챗봇 캐릭터를 사용하는 것, 다른 한가지는

시리와 같이 전문적인 보이스 어시스턴트 느낌을 강조할 수 있는 그라이언트가 들어간 오브젝트를 사용하는 것이다.

 

서비스 마스코트 UI 디자인 및 마스코트 이름 레퍼런스

 

 

그라디언트가 들어간 오브젝트는 상황에 따른 모션의 디자인을 완성도 있게 하기도 어려울 뿐더러,

구현에도 canvas를 쓰는 것이 자연스러운 움직임이 나올 것 같은데 canvas를 사용해본 사람이 아무도 없어서

사용하기 위해 시간이 많이 소요될 것으로 예상되었다.

 

마스코트를 만드는 작업이 큰 시간을 들일 정도로 우선순위가 높지는 않기 때문에

조금 더 간단하고 완성도 있게 제작할 수 있을 것 같은 캐릭터 형태의 마스코트를 사용하기로 결정하였다.

 

 

 

 

 

2️⃣ gif, svg, canvas, … Lottie?!

 

캐릭터 형태의 마스코트를 만들기로 결정하고 구현을 어떤 식으로 해야할지에 대해 고민을 해보았다. 내가 알고있던 애니메이션을 넣을 수 있는 방법은

 

1. svg 애니메이션 활용

2. gif 활용

3. canvas 활용

 

이렇게 세 가지였는데 canvas는 위에서와 같은 이유로 제외하고, svg를 사용하거나 gif를 사용하는 방법을 고려해보았다.

 

 

svg 사용 시 장점은, 

-  벡터 기반 이미지라 어떤 크기에서든 깔끔하게 렌더링된다.

-  css로 디자인(배경색에 따른 캐릭터 색 등) 수정이 가능하다.

 

svg 사용 시  단점은,

-  다양한 캐릭터 동작을 만들기에 시간이 오래걸리고 번거롭다.

 

 

 

gif 사용 시 장점은, 

-  익숙한 포맷이다.

-  상황별 모션의 변경은 src를 교체하도록 코드를 작성하여 상대적으로 간단하게 구현할 수 있다.

 

gif 사용 시 단점은,

-  모션 별로 gif를 따로따로 만들어야 한다.

-  gif는 비트맵 방식이라 사이즈에 따라 깨짐 현상이 일어날 수 있다.

-  용량이 큰 편이다.

-  한 모션 중간에 다른 모션으로 변경해야될 시 연결이 부자연스러워 보인다.

 

 

 

svg나 gif를 사용해도 내가 생각하는 만큼의 완성도 있는 캐릭터 모션이 나올 수 있을지 확신이 들지 않았다.

우선은 캐릭터 디자인과 어떤 모션들을 어떤 상황에서 보여줄 것인지를 먼저 정리하고 구현방법은 조금 더 고민해보기로 했다.

 

완성된 캐릭터 디자인과 모션 시안

 

 

 

 

캐릭터는 디자인만 먼저 완성하고, 개발을 진행하며 캐릭터가 들어가야할 자리는 우선 빈 div로 자리만 잡아놓고

다른 기능들을 먼저 구현하면서 캐릭터 애니메이션을 어떻게 해야할지 계속 고민하고 서치해보았다.

 

 

애니메이션을 위해 svg나 gif를 어떻게 활용해야하나 구글링도 해보고 다양한 사이트에서 탐색해보던 중에

코드펜에서 lottie라는 포맷을 사용하여 캐릭터의 다양한 표정을 구현하는 예제를 발견하였다!

 

See the Pen Viget Case Study: AceYourRetirement.org by Greg Kohn (@gregkohn) on CodePen.

 

 

lottie라는 형식은 처음 들어봤는데 JSON 기반의 애니메이션 파일 형식이라고 한다.

 

LottieFiles: Download Free lightweight animations for website & apps.

Effortlessly bring the smallest, free, ready-to-use motion graphics for the web, app, social, and designs. Create, edit, test, collaborate, and ship Lottie animations in no time!

lottiefiles.com

 

lottie를 사용하면 파일크기가 매우 작아 로딩이 빠르고, gif보다 고품질이며, 완성된 lottie를 lottie 에디터를 통해

자유자재로 수정도 가능하고, 벡터 속성의 애니메이션이라 부드럽게 적용가능하다.

 

 

 

gif와 lottie를 비교한 내용의 글도 있었다.

 

애니메이션 리그 : Lottie vs GIF | LottieFiles

Lottie가 처음인, 애니메이션이 처음인 당신을 위해. Lottie가 무엇인지부터, Lottie와 GIF 비교까지! 이번 글에서 확인해 볼게요. 파일의 크기, 품질, 커스터마이징, 워크플로우까지 비교해보시고 사

lottiefiles.com

 

 

한가지 단점이라면, lottie를 만드는데 after effects를 사용할 줄 알아야 한다는 점이었다.

 

하지만 lottie를 사용하면 내가 원하는 만큼 완성도 있는 캐릭터 애니메이션을 만들 수 있고,

구현에도 큰 어려움이 없을 것 같아서 lottie를 사용하는 방법을 제안했다.

(마침 팀에 모션그래픽을 했던 팀원이 있어서 내가 에펙을 잘 못다뤄도 다른 팀원이 충분히 대체할 수 있기도 했다 ㅎ.ㅎ)

 

우선 유튜브에서 after effect로 lottie를 만드는 방법을 보고 캐릭터가 눈만 깜빡이는 간단한 움직임을 만들어봤는데

생각보다 쉽게 만들기가 가능했다. 만든 json파일을 웹에 적용하는 것도 간단하게 가능하였다.

 

따라서, 캐릭터 애니메이션은 lottie 형식을 사용하기로 확정하고,

내가 캐릭터 모션의 뼈대를 만들면 모션그래픽을 할 줄 아는 팀원이 좀 더 생동감있는 표현을 추가해주는 방향으로

진행하였고 그렇게 “바디토리”서비스의 “토리”라는 캐릭터가 완성되었다.

 

 

 

 

 

3️⃣ lottie로 토리 구현하기

lottie를 웹에서 재생하기 위해서는 lottie-player가 필요한데 lottie-web이라는 라이브러리를 통해 lottie를 쉽게 다룰 수 있다.

 

react에서 lottie를 사용하기 위한 lottie-react, react-lottie 등 라이브러리가 따로 있었지만

모두 lottie-web을 디펜던시 삼아 만들어진 라이브러리들이라

이번에는 가장 기본인 lottie-web 라이브러리를 사용해보기로 하였다.

 

lottie를 사용하기로 하면서 서비스 내에서 사용되는 애니메이션들은 모두 lottie를 사용해보기로 했는데

(예를 들면, 음성 녹음이 진행 중일 때 나타나는 원형의 파동, 기록이 없을 시 보여지는 차트 애니메이션 등)

 

토리(대표 캐릭터)를 제외한 lottie 애니메이션의 경우 한가지 동작을 반복하는 형태라 구현에 큰 어려움이 없었지만

토리의 경우 다양한 동작을 상황에 따라 보여줘야하기 때문에 AnimationSegment라는 개념을 활용해야 했다.

 

우선 에펙으로 생성된 토리 JSON파일은 토리의 모든 모션이 이어 붙어져 있는데 이 때 모션별 프레임 구간을 정의해준다.

const frameSegments: AnimationSegment[] = [
    [0, 149],      // 기본 상태 (둥실둥실 + 눈 깜빡임)
    [150, 215],    // 윙크 (갸웃 + 윙크)
    [216, 276],    // 놀람 (띠용 + 느낌표)
    [277, 456],    // 슬픔 (시무룩 + 눈물)
    [457, 576],    // 기쁨 (점프 + ><눈 + 하트)
    [577, 725],    // 궁금 (두리번 + 물음표)
];

 

 

이후 playSegments 메세드에 첫번째 인자로 원하는 구간을 넘겨주면 해당 구간을 재상하는 애니메이션을 재생할 수 있다.

toryPurple.playSegments(frameSegments[segmentIndex], true);

 

 

애니메이션 호출 시 autoplay를 false로 설정하고, initialSegment는 첫 모션에 대한 프레임 구간을 props로 넘겨준다.

lottie.loadAnimation({
		container: lottieRef.current,
		renderer: "svg",
		loop: true,
		autoplay: false,
		path: "/static/lottie/tory_purple.json",
		initialSegment: frameSegments[segmentIndex],
}),

 

 

모션을 시작하는 시점을 설정해주기 위해 ready라는 state를 만들어 기본값을 false로 두고 애니메이션의 재생이 필요한 경우에만 true로 변경하도록 하였다.

const [ready, setReady] = useState<boolean>(false);

 

 

모션의 변화가 필요할 경우 해당 페이지에서 setTimeout이나 이벤트리스너를 통해 

다음 모션에 해당하는 프레임구간 frameSegments의 index값을 넘겨주도록 했다.

 

 

보라색 토리 lottie 컴포넌트의 전체 코드는 아래와 같다.

import { AnimationItem, AnimationSegment, LottiePlayer } from "lottie-web";
import { useEffect, useRef, useState } from "react";
import styled from "styled-components";
import { LottieAnimProps } from "types/lottieProps";

const ToryPurpleAnim = ({ segmentIndex, delay }: LottieAnimProps) => {
  const [ready, setReady] = useState<boolean>(false);
  const [toryPurple, setToryPurple] = useState<AnimationItem>();
  const [lottie, setLottie] = useState<LottiePlayer | null>(null);

  const lottieRef = useRef<any>();

  const frameSegments: AnimationSegment[] = [
    [0, 149],
    [150, 215],
    [216, 276],
    [277, 456],
    [457, 576],
    [577, 725],
  ];

  useEffect(() => {
    import("lottie-web").then(Lottie => setLottie(Lottie.default));
  }, []);

  useEffect(() => {
    if (lottie && lottieRef.current) {
      setToryPurple(
        lottie.loadAnimation({
          container: lottieRef.current,
          renderer: "svg",
          loop: true,
          autoplay: false,
          path: "/static/lottie/tory_purple.json",
          initialSegment: frameSegments[segmentIndex],
        }),
      );
    }

    if (delay) {
      setTimeout(() => {
        setReady(true);
      }, delay);
    } else {
      setReady(true);
    }

    return () => {
      lottie && lottie.destroy();
    };
  }, [lottie]);

  useEffect(() => {
    if (toryPurple && ready) {
      toryPurple.playSegments(frameSegments[segmentIndex], true);
    }
  }, [ready, segmentIndex, toryPurple]);

  return <LottieElem ref={lottieRef} />;
};

const LottieElem = styled.div`
  width: 100%;
`;

export default ToryPurpleAnim;

 

 

구현 완료 후 아쉬운 점은 segment로 모션이 변할 때 약간씩 끊겨보이는 느낌이 있다는 점이다.

이 부분을 개선하려면 모션의 정확한 타이밍을 맞추던지 lottie가 아닌 다른 방법을 사용해야될 것 같다.

 

그렇지만 이번에 lottie를 사용해보니 애니메이션을 퀄리티있고 편하게 구현할 수 있는 포맷이라

앞으로도 자주 사용해보면서 더 잘 구현할 수 있는 방법을 깨우쳐야겠다!

 

반응형
프로필사진
개발자 삐롱히

프론트엔드 개발자 삐롱히의 개발 & 공부 기록 블로그