다른 챗봇을 만들지 마세요: Rive와 함께 'Duolingo-Style' AI Companion 설계
Source: Dev.to
우리는 “AI 래퍼”에 빠져 허우적거리고 있습니다. AI 언어 튜터, 롤플레이 앱, 혹은 정신 건강 동반자를 만들고 있다면, 한 가지 문제가 있습니다: 텍스트 인터페이스는 지루하다는 것.
현재 레이스에서 승리하고 있는 앱들(예: Duolingo의 Lily 혹은 character.ai)은 단순히 토큰을 출력하는 것이 아니라, 퍼포먼스를 렌더링하고 있습니다.
AI 인터랙션을 전문으로 하는 Rive 애니메이터로서, 나는 이 프로젝트들의 백엔드를 많이 보았습니다. “장난감” 앱과 “제품” 사이의 차이는 보통 한 가지에 달합니다: 립싱크 아키텍처.
이 글에서는 단순한 볼륨 바운싱을 넘어 음소‑정확한 스피치를 구현하기 위해 Rive를 사용해 반응형 립싱크 AI 캐릭터를 만드는 기술적 설정을 풀어보겠습니다.
아키텍처: 퍼펫 vs. 퍼페티어
생동감 있는 캐릭터를 만들려면 다음과 같이 관심사를 분리하세요:
- 퍼펫 (Rive) – 숫자 입력에 따라 형태를 변형시키는 상태 머신.
- 퍼페티어 (당신의 코드) – 오디오를 파싱하고 퍼펫에 신호를 보내는 React/Flutter/Swift 로직.
레벨 1: “뮤펫” 방식 (진폭)
빠른 방법. 내일 MVP가 필요하다면 여기서 시작하세요. 오디오 진폭의 RMS(Root Mean Square)를 분석합니다.
Rive 설정: 1‑D 블렌드 상태. 입력 0 = 입 닫힘, 입력 100 = 입 크게 열림.
// Example (pseudo‑code)
riveInput.value = normalizedVolume;
문제: 마치 뮤펫처럼 보입니다. 캐릭터가 “OO”와 “EE” 소리를 동일하게 입을 크게 열어, 뉘앙스가 부족합니다.
레벨 2: “비세메” 방식 (음소 매핑)
Duolingo 방식. 볼륨 사용을 중단하고 비세메(음소의 시각적 대응)를 사용합니다. 많은 TTS 제공업체(Azure Speech SDK, AWS Polly)는 비세메 이벤트—특정 타임스탬프에서 입 모양을 설명하는 정수—를 반환합니다.
Rive 상태 머신
단일 “입 열림” 블렌드 대신, ~12‑15개의 개별 입 모양을 가진 상태 머신을 구축합니다. 예시:
| 비세메 | 설명 |
|---|---|
| Sil | 침묵 / 대기 |
| PP | 입술을 눌러서 – P, B, M |
| FF | 이가 입술에 닿음 – F, V |
| TH | 혀가 튀어나옴 – TH |
| DD | 혀가 이 뒤에 위치 – T, D, S |
| kk | 뒤쪽이 열림 – K, G |
| aa | 넓게 열림 – A |
| O | 둥글게 – O |
| … | (계속) |
이들을 viseme_id라는 Number Input에 매핑합니다.
코드 로직
프론트엔드(React Native, Flutter 등)에서 비세메 이벤트를 수신하고 Rive에 전달합니다:
ttsService.on('visemeReceived', (visemeID) => {
// 1. Get the Rive input
const mouthInput = riveArtboard.findInput('viseme_id');
// 2. Map the TTS provider's ID to your Rive ID
// (Azure has 21 shapes, Rive might only need 12)
const mappedID = mapAzureToRive(visemeID);
// 3. Update the state
mouthInput.value = mappedID;
});
비밀: 레이어드 마이크로‑행동
립싱크는 환상의 약 50 %에 불과합니다. 캐릭터가 말하는 동안 눈을 깜빡이지 않으면 불쾌감을 줍니다.
해결책: Rive에서 레이어드 상태 머신을 사용해 여러 타임라인이 충돌 없이 동시에 재생되도록 합니다.
- 레이어 1 – 입 (코드가 제어).
- 레이어 2 – 눈 (자체 루프). Rive 내부의 “Randomize” 리스너가 2–5 초마다 자동으로 눈 깜빡임이나 눈동자 움직임을 트리거합니다.
- 레이어 3 – 감정 (
isBored,isHappy,isThinking같은 Boolean 입력).
“멈춤” 처리 (지연)
AI 음성 채팅에서 가장 큰 UX 적은는 LLM이 답변을 생성하는 동안 발생하는 2–3 초의 침묵입니다. 캐릭터가 멈추면 안 됩니다.
- 사용자가 말을 멈춤 → 앱이
isThinking = true로 설정. - Rive 애니메이션 – 캐릭터가 고개를 들어 올리거나, 손가락을 두드리거나, (비꼬는 성격이라면) 눈을 굴립니다.
- 오디오 스트림 시작 →
isThinking = false로 설정; 비세메 데이터 흐름이 재개됩니다.