Animações Rive para Personagens Árabes em Apps Educativos
5 min de leituraMohammad Shaker

Animações Rive para Personagens Árabes em Apps Educativos

Descubra como usamos Rive para animações sincronizadas de fala, personalização de avatares e reações nos apps de aprendizagem de árabe.

Engineering

Resposta rápida

Descubra como usamos Rive para animações sincronizadas de fala, personalização de avatares e reações nos apps de aprendizagem de árabe.

Nosso Pipeline de Animação Rive: Dando Vida a Personagens Árabes

Amal utiliza Rive (antigo Flare) para todas as animações dos personagens — incluindo fala sincronizada com os lábios, personalização de avatar, reações de feedback e personagens em jogos. Escolhemos Rive em vez de Lottie ou folhas de sprites porque ele suporta máquinas de estado em tempo real, manipulação programática e renderização acelerada por GPU a 60fps, tudo em um único arquivo compacto por personagem.

Biblioteca de Ativos de Animação

Personagens Principais

  • lip-sync-amal-01.riv
    • Personagem principal Amal (variantes corpo inteiro e só rosto)
    • Múltiplas pranchetas para posições da boca (mapeamento de fonemas)
    • Estados: parado, falando, erro, comemoração, dormindo
    • Tamanho do arquivo: 1,2 MB (contra 50+ MB em folhas de sprites)
  • avatar.riv
    • Avatar do usuário personalizável (3 pranchetas)
      1. Corpo inteiro: cabeça, torso, membros com roupas
      2. Apenas cabeça: para painel e app dos pais
      3. Companheiro borboleta: animação de recompensa
    • Arquitetura por componentes: formato da cabeça, cabelo, olhos, roupas, acessórios e cores
    • Tamanho do arquivo: 2,4 MB
  • coin-01.riv e coins-01.riv
    • Animações de recompensa (moedas flutuando e coletando)
    • Coin simples: 150 KB
    • Múltiplas moedas: 300 KB
  • cute-monster-final.riv
    • Personagem de feedback com múltiplos estados emocionais
    • Estados: feliz (resposta certa), confuso (resposta errada), pensando (processando), comemorando (sequência)
    • Tamanho do arquivo: 1,8 MB

Otimizações específicas para Android

  • Build NDK personalizado (Rive NDK-r28) para conformidade com alinhamento de página de 16KB
  • Reduz tamanho do binário em 8% comparado ao build padrão
  • Garante compatibilidade com gerenciamento agressivo de memória no Android 12+

Pipeline de Sincronização Labial (Detalhes Técnicos)

Passo 1: Geração de Áudio TTS + Extração de Marcas de Fala

# src/services/tts_client.py
from google.cloud import texttospeech

def generate_speech_with_marks(text: str, language: str = 'ar-SA'): client = texttospeech.TextToSpeechClient()

synthesis_input = texttospeech.SynthesisInput(text=text)
voice = texttospeech.VoiceSelectionParams(
    language_code=language,
    name='ar-SA-Neural2-A'  # Voz WaveNet
)
audio_config = texttospeech.AudioConfig(
    audio_encoding=texttospeech.AudioEncoding.MP3,
    effects_profile_id=['small-bluetooth-speaker-class-device']  # Alto-falante infantil
)

# Solicitar marcas de fala (timing dos fonemas)
request = texttospeech.SynthesizeSpeechRequest(
    input=synthesis_input,
    voice=voice,
    audio_config=audio_config,
    enable_text_to_speech_as_cloud_service=True
)

response = client.synthesize_speech(request=request)

# Resposta inclui:
# - audio_content: bytes MP3
# - timepoints: [{character, byte_pos, time_ms}, ...]

return {
    'audio': response.audio_content,
    'speech_marks': response.timepoints  # Timestamps por fonema
}

Passo 2: Mapear Fonemas para Estados da Boca no Rive

lip_sync_avatar.json mapeia fonemas árabes para posições da boca:

{
  "phoneme_map": {
    "ا": { "rive_state": "mouth_a_open", "duration_ms": 200 },
    "ب": { "rive_state": "mouth_lips_closed", "duration_ms": 150 },
    "ع": { "rive_state": "mouth_pharyngeal", "duration_ms": 250 },
    "ق": { "rive_state": "mouth_uvular", "duration_ms": 180 },
    ...
  },
  "mouth_positions": [
    { "id": "mouth_a_open", "blend_values": { "jaw_open": 0.8, "lips_rounded": 0.2 } },
    { "id": "mouth_lips_closed", "blend_values": { "jaw_open": 0.1, "lips_rounded": 0.9 } },
    ...
  ]
}

Passo 3: LipSyncController Gerencia Reprodução

// lib/src/modules/animations/controllers/lip_sync_controller.dart
class LipSyncController extends GetxController {
  late Rive riveCharacter;
  late AudioPlayer audioPlayer;
  late LipSyncMapper mapper;

void playWithLipSync(String text, String audioPath) { // Passo 1: Carregar personagem Rive riveCharacter.loadRiveFile('lip-sync-amal-01.riv');

// Passo 2: Carregar marcas de fala do TTS
mapper = LipSyncMapper.fromJson(loadJsonAsset('lip_sync_avatar.json'));

// Passo 3: Tocar áudio enquanto anima a boca
audioPlayer.play(AudioSource.file(audioPath));

// Passo 4: Em cada quadro de áudio, atualizar posição da boca
audioPlayer.onPositionChanged.listen((Duration position) {
  String phoneme = mapper.phonemeAtTime(position.inMilliseconds);
  String riveState = mapper.riveStateForPhoneme(phoneme);
  
  riveCharacter.setStateInput('mouth_state', riveState);
});

} }

Passo 4: RiveCharacterController Gerencia Ciclo de Vida

// Gerencia o estado completo da animação do personagem (não só boca)
class RiveCharacterController extends GetxController {
  States: idle → prepare → speaking → idle → error/celebration

void startExercise() { // Transição: idle → prepare (preparar para ouvir) character.setStateInput('state_machine', 'prepare'); }

void childSpeaks(String recognizedText, double accuracy) { character.setStateInput('state_machine', 'speaking'); lipSyncController.playFeedback(recognizedText); }

void onFeedbackComplete(bool wasCorrect) { if (wasCorrect) { character.setStateInput('state_machine', 'celebrate'); playRewardAnimation(); } else { character.setStateInput('state_machine', 'error'); playEncouragingPhrase(); } } }

Sistema de Personalização de Avatar

Arquitetura por Componentes

Crianças personalizam seu avatar escolhendo partes:

{
  "avatar_customization": {
    "head_shapes": [
      { "id": "round", "rive_element": "head_round" },
      { "id": "oval", "rive_element": "head_oval" },
      { "id": "square", "rive_element": "head_square" }
    ],
    "hair_styles": [
      { "id": "ponytail", "rive_element": "hair_ponytail" },
      { "id": "braids", "rive_element": "hair_braids" },
      { "id": "straight", "rive_element": "hair_straight" }
    ],
    "colors": {
      "skin_tone": ["light", "medium", "dark"],
      "hair_color": ["black", "brown", "blonde", "red"],
      "shirt_color": ["blue", "pink", "green", "yellow", "purple"],
      "accent_color": ["red", "orange", "green", "blue"]
    }
  }
}

Mapeamento dos Elementos Nomeados Rive (avatar_customization_rive_names.dart)

const avatarRiveNames = {
  'head_round': 'Head_Round',
  'head_oval': 'Head_Oval',
  'hair_ponytail': 'Hair_Ponytail',
  'shirt_blue': 'Shirt_Blue',
  'shirt_pink': 'Shirt_Pink',
  // ... mais de 50 mapeamentos
};

Quando a criança escolhe "cabeça redonda + camisa azul", o app:

  1. Ativa elemento Rive Head_Round
  2. Ativa elemento Rive Shirt_Blue
  3. Desativa todos os outros formatos de cabeça e cores de camisa
  4. O avatar personalizado aparece em toda a experiência

Por que Usar Rive ao Invés de Outras Alternativas

RecursoRiveLottieFolhas de SpritesVídeo
Máquinas de estado
Controle em execução✓ (completo)ParcialManual✗ (passivo)
Tamanho do arquivo1-2 MB2-3 MB50+ MB100+ MB
Performance60fps GPU30fps CPU60fps GPUVariável
Interatividade✓ Completa✓ Parcial✓ Completa✗ Nenhuma
Curva de aprendizadoModeradaFácilFácilFácil
ManutençãoUm arquivo .rivUm JSONCentenas de imagensUm vídeo

Rive vence porque precisamos de controle programático, máquinas de estado e arquivos compactos para apps móveis.

Otimização de Performance

  • Pré-carregamento dos personagens: carregamos arquivos .riv no início do app, não por exercício
  • Renderização por GPU: Rive usa GPU automaticamente quando disponível e faz fallback para CPU em dispositivos antigos
  • Pool de memória: reutilizamos controladores Rive entre telas para evitar pausas de coleta de lixo
  • Compressão: arquivos Rive já são compactados, sem necessidade de otimização extra

Resultado: animações a 60fps em Snapdragon 662+ (celulares intermediários de 2019).

Perguntas Frequentes

P: Posso exportar animações do Adobe Animate para Rive?
R: Não diretamente. Usamos o editor nativo do Rive (rive.app). Animadores criam personagens no Rive, não no Animate ou After Effects. O fluxo é: criar no Rive → exportar .riv → integrar no app Flutter.

P: Como lidam com diferentes tipos de corpo ou deficiências?
R: A personalização de avatar inclui opções de tipos físicos (magro, atlético, arredondado) e acessórios (óculos, aparelhos auditivos, ajudas motoras). Isso garante representação para todas as crianças.

P: E se a criança não gostar do avatar?
R: Pode personalizar a qualquer momento. O app não força um visual fixo — as crianças têm controle criativo total.

Artigos Relacionados