Kako Amal koristi Rive za animaciju arapskih likova
Čitanje: 5 minMohammad Shaker

Kako Amal koristi Rive za animaciju arapskih likova

Amal koristi Rive za animacije likova s usklađenim govorom i prilagodbom avatara, nudeći glatko 60fps izvođenje na mobilnim uređajima.

Engineering

Brzi odgovor

Amal koristi Rive za animacije likova s usklađenim govorom i prilagodbom avatara, nudeći glatko 60fps izvođenje na mobilnim uređajima.

Amal koristi Rive (ranije Flare) za sve animacije likova — uključujući sinkronizirani govor, prilagodbu avatara, reakcije na povratne informacije i likove u igrama. Odabrali smo Rive umjesto Lottie ili sprite listova jer podržava runtime state-machine, programatsku manipulaciju i GPU ubrzano renderiranje pri 60fps, sve u jednoj kompaktnijoj datoteci po liku.

Biblioteka animacijskih resursa

Osnovni likovi

  • lip-sync-amal-01.riv
    - Glavni Amal lik (varijante cijelog tijela i samo lica)
    - Više artboarda po poziciji usta (za mapiranje fonema)
    - Stanja: mirovanje, govor, greška, slavlje, spavanje
    - Veličina datoteke: 1.2 MB (u odnosu na 50+ MB za sprite listove)
  • avatar.riv
    - Prilagodljivi korisnički avatar (3 artboarda):
    1. Cijelo tijelo: glava, trup, udovi s odjećom
    2. Samo glava: za nadzornu ploču i roditeljsku aplikaciju
    3. Pratilac leptir: animacija nagrade
    - Komponentna struktura: oblik glave, kosa, oči, odjeća, dodaci, boje
    - Veličina datoteke: 2.4 MB
  • coin-01.riv & coins-01.riv
    - Animacije nagrada (leteći i skupljajući novčići)
    - Jedan novčić: 150 KB
    - Više novčića: 300 KB
  • cute-monster-final.riv
    - Lik za povratne informacije sa više emotivnih stanja
    - Stanja: sretan (tačan odgovor), zbunjen (pogrešan), razmišlja, slavi (niz pobjeda)
    - Veličina datoteke: 1.8 MB

Optimizacija za Android

  • Prilagođena NDK verzija (Rive NDK-r28) za usklađenost s 16KB poravnanje stranice
  • Smanjuje veličinu binarne datoteke za 8% u odnosu na standardnu izgradnju
  • Osigurava kompatibilnost s naprednim upravljanjem memorijom u Android 12+

Proces sinkronizacije usana (Tehnički detalji)

Korak 1: Generisanje TTS zvuka i vađenje oznaka govora

# 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'  # WaveNet glas
    )
    audio_config = texttospeech.AudioConfig(
        audio_encoding=texttospeech.AudioEncoding.MP3,
        effects_profile_id=['small-bluetooth-speaker-class-device']  # Zvučnik za djecu
    )
    
    # Zahtjev za oznake govora (vrijeme fonema)
    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)
    
    # Odgovor sadrži:
    # - audio_content: MP3 podaci
    # - timepoints: [{character, byte_pos, time_ms}, ...]
    
    return {
        'audio': response.audio_content,
        'speech_marks': response.timepoints  # Timestamps na nivou fonema
    }

Korak 2: Mapiranje fonema na Rive stanja usta

{
  "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 } },
    ...
  ]
}

Korak 3: Kontroler sinkronizacije usana upravlja reprodukcijom

// 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) {
    // Korak 1: Učitaj Rive lik
    riveCharacter.loadRiveFile('lip-sync-amal-01.riv');
    
    // Korak 2: Učitaj oznake govora iz TTS izlaza
    mapper = LipSyncMapper.fromJson(loadJsonAsset('lip_sync_avatar.json'));
    
    // Korak 3: Pusti zvuk dok upravljaš animacijom usta
    audioPlayer.play(AudioSource.file(audioPath));
    
    // Korak 4: Na svaki audio frame ažuriraj položaj usta
    audioPlayer.onPositionChanged.listen((Duration position) {
      String phoneme = mapper.phonemeAtTime(position.inMilliseconds);
      String riveState = mapper.riveStateForPhoneme(phoneme);
      
      riveCharacter.setStateInput('mouth_state', riveState);
    });
  }
}

Korak 4: RiveCharacterController upravlja životnim ciklusom

// Upravljanje kompletnim animacijskim stanjima lika (ne samo ustima)
class RiveCharacterController extends GetxController {
  States: idle → prepare → speaking → idle → error/celebration
  
  void startExercise() {
    // Prelaz lika: idle → prepare (spreman za slušanje)
    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();
    }
  }
}

Sistem prilagođavanja avatara

Arhitektura po komponentama

Djeca mogu prilagođavati svog avatara od dijelova:

{
  "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"]
    }
  }
}

Mapa naziva Rive elemenata (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',
  // ... preko 50 mapa elemenata
};

Kada dijete izabere „okruglu glavu + plavu košulju“, aplikacija:

  • Aktivira Rive element Head_Round
  • Aktivira Rive element Shirt_Blue
  • Deaktivira ostale oblike glave i boje košulja
  • Personalizovani avatar djeteta pojavljuje se u cijeloj aplikaciji

Zašto Rive umjesto drugih opcija

KarakteristikaRiveLottieSprite listsVideo
State machine
Kontrola u runtimeu✓ (puna)DjelimičnaRučna✗ (pasivna)
Veličina datoteke1-2 MB2-3 MB50+ MB100+ MB
Performanse60fps GPU30fps CPU60fps GPUVarijabilno
Interaktivnost✓ puna✓ djelimična✓ puna✗ nema
UčenjeUmjerenoLakoLakoLako
OdržavanjeJedna .riv datotekaJedan JSONStotine slikaJedan video

Rive pobeđuje jer je potrebna programatska kontrola, state-machine i kompaktna veličina za mobilnu aplikaciju.

Optimizacija performansi

  • Predučitavanje likova: Učitavanje .riv datoteka pri pokretanju aplikacije, ne po vježbi
  • GPU renderiranje: Rive koristi GPU kada je dostupan, uz CPU fallback na starijim uređajima
  • Mrežni ponovni uporabni kontroleri: Ponovno korištenje Rive kontrolera između ekrana za smanjenje pauza prikupljanja smeća
  • Komprimirane datoteke: Rive datoteke su već komprimirane; dodatna optimizacija nije potrebna

Rezultat: 60fps animacije na Snapdragon 662+ (telefoni srednjeg ranga iz 2019).

Česta pitanja

P: Mogu li izvesti animacije iz Adobe Animate u Rive?
A: Ne direktno. Koristimo Rive-ov vlastiti editor (rive.app). Animatori rade dizajn u Rive, ne u Animate ili After Effects. Tok rada: dizajn u Rive → izvoz u .riv → integracija u Flutter aplikaciju.

P: Kako se nosite s različitim tipovima tijela ili invaliditetima?
A: Prilagodba avatara uključuje opcije oblika tijela (vitak, atletski, okrugli) i dodatke (naočale, slušni aparati, pomagala za kretanje) da sva djeca budu zastupljena.

P: Šta ako dijete ne voli svoj avatar?
A: Može ga mijenjati u bilo kojem trenutku. Aplikacija ne nameće izgled — djeca imaju potpunu kreativnu kontrolu.

Povezani članci