Rive Animations: Arabische Figuren lebendig gemacht
5 Min. LesezeitMohammad Shaker

Rive Animations: Arabische Figuren lebendig gemacht

Amal benutzt Rive für alle Figurenanimationen: Lippenbewegungen, Avatars, Spielcharaktere

Engineering

Schnelle Antwort

Amal benutzt Rive für alle Figurenanimationen: Lippenbewegungen, Avatars, Spielcharaktere

Unsere Rive-Animations-Pipeline: Arabische Figuren lebendig gemacht

Amal nutzt Rive (ehemals Flare) für alle Figurenanimationen – einschließlich lippen-synchronisiertem Sprechen, Avatar-Anpassung, Rückmeldungsreaktionen und Spielfiguren. Wir entschieden uns für Rive statt Lottie oder Sprite-Sheets, weil es Laufzeit-Zustandsmaschinen, programmatische Manipulation und GPU-beschleunigtes Rendering bei 60fps in einer einzigen kompakten Datei pro Charakter unterstützt.

Die Animationsressourcenbibliothek

Kernfiguren

lip-sync-amal-01.riv

  • Haupt-Amal-Figur (Ganzkörper- und nur-Gesicht-Varianten)
  • Mehrere Artboards pro Mundposition (für Phonem-Mapping)
  • Zustände: leerlauf, spricht, Fehler, Feier, Schlafen
  • Dateigröße: 1,2 MB (vs. 50+ MB für Sprite-Sheets)

avatar.riv

  • Anpassbarer Benutzer-Avatar (3 Artboards)
    1. Ganzkörper: Kopf, Rumpf, Gliedmaßen mit Kleidung
    2. Nur-Kopf: für Dashboard und Eltern-App
    3. Schmetterlings-Begleiter: Belohnungsanimation
  • Komponentenbasiert: Kopfform, Haare, Augen, Kleidung, Accessoires, Farben
  • Dateigröße: 2,4 MB

coin-01.riv & coins-01.riv

  • Belohnungsanimationen (Münzen, die schweben und gesammelt werden)
  • Einzelne Münze: 150 KB
  • Mehrere Münzen: 300 KB

cute-monster-final.riv

  • Rückmeldecharakter mit mehreren emotionalen Zuständen
  • Zustände: glücklich (richtige Antwort), verwirrt (falsch), nachdenklich (Verarbeitung), Feier (Streak)
  • Dateigröße: 1,8 MB

Android-spezifische Optimierung

  • Benutzerdefinierter NDK-Build (Rive NDK-r28) für 16KB-Seitenausrichtungs-Konformität
  • Reduziert Binärgröße um 8% im Vergleich zum Standard-Build
  • Sorgt für Kompatibilität mit aggressivem Speichermanagement in Android 12+

Lippen-Synchronisations-Pipeline (Technische Detailtiefe)

Schritt 1: TTS-Audioerzeugung + Sprachauszeichnungsextraktion

# 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-Stimme
    )
    audio_config = texttospeech.AudioConfig(
        audio_encoding=texttospeech.AudioEncoding.MP3,
        effects_profile_id=['small-bluetooth-speaker-class-device']  # Lautsprecher für Kinder
    )
    
    # Anfordern von Sprachauszeichnungen (Phonem-Timing)
    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)
    
    # Antwort beinhaltet:
    # - audio_content: MP3-Bytes
    # - Zeitpunkte: [{Zeichen, Byte-Pos, Zeit_ms}, ...]
    
    return {
        'audio': response.audio_content,
        'speech_marks': response.timepoints  # Phonem-Level-Zeitstempel
    }

Schritt 2: Zuordnung von Phonemen zu Rive-Mundzuständen

lip_sync_avatar.json ordnet arabische Phoneme Mundpositionen zu:

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

Schritt 3: LipSync-Controller orchestriert Wiedergabe

// 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) {
    // Schritt 1: Rive-Charakter laden
    riveCharacter.loadRiveFile('lip-sync-amal-01.riv');
    
    // Schritt 2: Sprachauszeichnungen aus TTS-Ausgabe laden
    mapper = LipSyncMapper.fromJson(loadJsonAsset('lip_sync_avatar.json'));
    
    // Schritt 3: Audio abspielen und Mundanimation steuern
    audioPlayer.play(AudioSource.file(audioPath));
    
    // Schritt 4: Bei jedem Audio-Frame Mundposition aktualisieren
    audioPlayer.onPositionChanged.listen((Duration position) {
      String phoneme = mapper.phonemeAtTime(position.inMilliseconds);
      String riveState = mapper.riveStateForPhoneme(phoneme);
      
      riveCharacter.setStateInput('mouth_state', riveState);
    });
  }
}

Schritt 4: RiveCharacterController verwaltet Lebenszyklus

// Verwalten des vollständigen Animationszustands (nicht nur Mund)
class RiveCharacterController extends GetxController {
  States: leerlauf → vorbereiten → sprechen → leerlauf → Fehler/Feier
  
  void startExercise() {
    // Charakterübergänge: leerlauf → vorbereiten (bereit zum Zuhören)
    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();
    }
  }
}

Avatar-Anpassungssystem

Komponentenbasierte Architektur

Kinder passen ihren Avatar aus Teilen an:

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

Rive-Benannte-Elemente-Zuordnung (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',
  // ... 50+ Element-Zuordnungen
};

Wenn ein Kind "runder Kopf + blaues Hemd" auswählt, tut die App:

  1. Rive-Element Head_Round aktivieren
  2. Rive-Element Shirt_Blue aktivieren
  3. Alle anderen Kopfformen und Hemdfarben deaktivieren
  4. Der personalisierte Avatar des Kindes erscheint nun in der gesamten App

Warum Rive über Alternativen

Feature Rive Lottie Sprite-Sheets Video
Zustandsmaschinen
Laufzeitkontrolle ✓ (vollständig) Teilweise Manuell ✗ (passiv)
Dateigröße 1-2 MB 2-3 MB 50+ MB 100+ MB
Leistung 60fps GPU 30fps CPU 60fps GPU Variabel
Interaktivität ✓ Vollständig ✓ Teilweise ✓ Vollständig ✗ Keine
Lernkurve Mittel Einfach Einfach Einfach
Wartung Eine .riv-Datei Eine JSON Hunderte von Bildern Ein Video

Rive gewinnt, weil wir programmatische Kontrolle, Zustandsmaschinen und Kompaktheit für eine mobile App benötigen.

Leistungsoptimierung

  • Vorladen von Charakteren: .riv-Dateien während des App-Starts laden, nicht pro Übung
  • GPU-Rendering: Rive nutzt automatisch GPU, wenn verfügbar, CPU-Fallback auf alten Geräten
  • Speicherpooling: Wiederverwendung von Rive-Controllern über Bildschirme hinweg, um Garbage Collection-Pausen zu vermeiden
  • Komprimierung: Rive-Dateien sind bereits komprimiert; keine zusätzliche Optimierung erforderlich

Ergebnis: 60fps-Animationen auf Snapdragon 662+ (2019-Mittelklasse-Handys).

FAQ

F: Kann ich Animationen von Adobe Animate zu Rive exportieren? A: Nicht direkt. Wir verwenden Rives nativen Editor (rive.app). Animatoren gestalten in Rive, nicht in Animate oder After Effects. Der Workflow ist: Charakter in Rive gestalten → als .riv exportieren → in Flutter-App integrieren.

F: Wie handhabt ihr unterschiedliche Körpertypen oder Behinderungen? A: Die Avatar-Anpassung umfasst Körperoptionen (schlank, athletisch, rund) und Accessoires (Brillen, Hörgeräte, Mobilitätshilfen). Dies sorgt dafür, dass alle Kinder Repräsentation sehen.

F: Was, wenn ein Kind seinen Avatar nicht mag? A: Es kann jederzeit angepasst werden. Die App zwingt kein bestimmtes Aussehen auf – Kinder haben volle kreative Kontrolle.

Verwandte Artikel