Amal përdor Rive (më parë Flare) për të gjitha animacionet e karaktereve — përfshirë të folurit me sinkronizim buzësh, personalizimin e avatarëve, reagimet e feedback-ut dhe personazhet e lojës. Ne zgjodhëm Rive mbi Lottie ose sprite sheets për shkak të mbështetjes së makinave të shtetit në kohë reale, manipulimit programatik dhe shfaqjes GPU të përshpejtuar në 60fps, të gjitha në një skedar kompakt për secilin karakter.
Biblioteka e aseteve të animacionit
Karakteret kryesore
lip-sync-amal-01.riv
- Karakteri kryesor Amal (variacion trup-plote dhe vetëm fytyrë)
- Disa artboard-e për çdo pozitë goje (për hartimin e fonemave)
- Gjendjet: i qetë, duke folur, gabim, festim, duke fjetur
- Madhësia e skedarit: 1.2 MB (krahasuar me 50+ MB për sprite sheets)avatar.riv
- Avatar përdoruesi i personalizueshëm (3 artboard-e)
1. Trup i plotë: kokë, trup, pjesë të tjera me veshje
2. Vetëm kokë: për panel dhe aplikacion prindëror
3. Shok fluturash: animacion shpërblimi
- Bazuar në komponentë: forma koka, flokë, sy, rroba, aksesorë, ngjyra
- Madhësia e skedarit: 2.4 MBcoin-01.riv&coins-01.riv
- Animacione shpërblimesh (monedha që fluturojnë, mblidhen)
- Monedhë e vetme: 150 KB
- Monedha të shumta: 300 KBcute-monster-final.riv
- Karakter reagimi me shumë gjendje emocionale
- Gjendjet: i lumtur (përgjigje e saktë), i hutuar (jo i saktë), duke menduar (duke procesuar), duke festuar (seritë)
- Madhësia e skedarit: 1.8 MB
Optimizim i posaçëm për Android
- Ndërtim i personalizuar NDK (Rive NDK-r28) për përputhshmëri 16KB në faqe
- Zvogëlon madhësinë e binarit me 8% krahasuar me ndërtimin standard
- Siguron përputhshmëri me menaxhimin strikt të memories në Android 12+
Procesi i sinkronizimit të buzëve (detaje teknike)
Hapi 1: Gjenerimi i audios TTS + Nxjerrja e shenjave të të folurit
# 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' # Zëri WaveNet
)
audio_config = texttospeech.AudioConfig(
audio_encoding=texttospeech.AudioEncoding.MP3,
effects_profile_id=['small-bluetooth-speaker-class-device'] # Folësi i fëmijës
)
# Kërkohet shenjat e të folurit (kohëzgjatja e fonemave)
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)
# Përgjigja përfshin:
# - audio_content: bajtet MP3
# - timepoints: [{character, byte_pos, time_ms}, ...]
return {
'audio': response.audio_content,
'speech_marks': response.timepoints # Kohët e fonemave
}
Hapi 2: Hartimi i fonemave me gjendjet e gojës në Rive
lip_sync_avatar.json lidh fonemat arabe me pozicionet e gojës:
{
"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 } },
...
]
}
Hapi 3: LipSyncController dirigjon llogjikën e luajtjes
// 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) {
// Hapi 1: Ngarkon karakterin Rive
riveCharacter.loadRiveFile('lip-sync-amal-01.riv');
// Hapi 2: Ngarkon shenjat e të folurit nga TTS
mapper = LipSyncMapper.fromJson(loadJsonAsset('lip_sync_avatar.json'));
// Hapi 3: Luaj audio dhe drejto animacionin e gojës
audioPlayer.play(AudioSource.file(audioPath));
// Hapi 4: Përditëson pozicionin e gojës në secilën kornizë audio
audioPlayer.onPositionChanged.listen((Duration position) {
String phoneme = mapper.phonemeAtTime(position.inMilliseconds);
String riveState = mapper.riveStateForPhoneme(phoneme);
riveCharacter.setStateInput('mouth_state', riveState);
});
}
}
Hapi 4: RiveCharacterController menaxhon ciklin e jetës së karakterit
// Menaxhon gjendjet e plota të animacionit të karakterit (jo vetëm gojën)
class RiveCharacterController extends GetxController {
// Gjendjet: idle → prepare → speaking → idle → error/celebration
void startExercise() {
// Ndërron gjendjet: idle → prepare (gati për dëgjim)
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();
}
}
}
Sistemi i personalizimit të avatarit
Arkitektura bazuar në komponentë
Fëmijët personalizojnë avatarin e tyre nga pjesë të ndryshme:
{
"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"]
}
}
}
Hartimi i emrave të elementeve 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',
// ... 50+ hartime të elementeve
};
Kur fëmija zgjedh “kokë e rrumbullakët + këmishë blu,” aplikacioni:
- Aktivizon elementin Rive
Head_Round - Aktivizon elementin Rive
Shirt_Blue - Çaktivizon të gjitha format e tjera të kokës dhe ngjyrat e këmishës
- Avatar i personalizuar shfaqet në të gjithë aplikacionin
Pse Rive është më i mirë se alternativat
| Veçori | Rive | Lottie | Sprite Sheets | Video |
|---|---|---|---|---|
| Makinat e shtetit | ✓ | ✗ | ✗ | ✗ |
| Kontroll në kohë reale | ✓ (plot) | Paradoksal | Manual | ✗ (pasiv) |
| Madhësia e skedarit | 1-2 MB | 2-3 MB | 50+ MB | 100+ MB |
| Performanca | 60fps GPU | 30fps CPU | 60fps GPU | Variabël |
| Ndërveprimi | ✓ Plottë | ✓ Pjesërisht | ✓ Plottë | ✗ Asnjë |
| Kurba e të mësuarit | Mesatare | Lehtë | Lehtë | Lehtë |
| Mirëmbajtje | Një skedar .riv | Një JSON | Qindra imazhe | Një video |
Rive fiton pasi ne kemi nevojë për kontroll programatik, makina shtetesh dhe skedarë kompakt për një aplikacion mobil.
Optimizimi i performancës
- Ngarkimi paraprak i karaktereve: Ngarkoni skedarët .riv gjatë nisjes së aplikacionit, jo për çdo ushtrim
- Shfaqja me GPU: Rive përdor automatikisht GPU kur është e disponueshme, me CPU si zgjidhje alternative në pajisjet më të vjetra
- Përdorimi i memorjes: Rishfrytëzoni kontrollet Rive midis ekranëve për të shmangur vonesën e mbledhjes së plehrave
- Komprimimi: Skedarët Rive janë të kompresuar natyrshëm, nuk kërkohet optimizim shtesë
Rezultati: animacione në 60fps në Snapdragon 662+ (telefonat e klasës mesatare të vitit 2019).
Pyetje të shpeshta
Pyetje: A mund të eksportoj animacione nga Adobe Animate në Rive?
Përgjigje: Jo direkt. Ne përdorim editorin origjinal të Rive (rive.app). Animatoret krijojnë në Rive dhe jo në Animate apo After Effects. Fluksi ynë: dizajnoni karakterin në Rive → eksportoni si .riv → integroni në aplikacionin Flutter.
Pyetje: Si trajtoni lloje të ndryshme trupore apo aftësi të kufizuara?
Përgjigje: Personalizimi i avatarit përfshin opsione për llojet e trupit (të hollë, atletik, i rrumbullakët) dhe aksesorë (syze, ndihma dëgjimi, ndihma lëvizjeje). Kjo siguron përfaqësim për të gjitha fëmijët.
Pyetje: Çfarë nëse një fëmijë nuk pëlqen avatarin e vet?
Përgjigje: Ata mund të personalizojnë avatarin në çdo kohë. Aplikacioni nuk e detyron një pamje të caktuar — fëmijët kanë kontroll kreativ plotësisht.



