كيف نحول الشخصيات العربية للحياة باستخدام Rive
5 دقيقة قراءةMohammad Shaker

كيف نحول الشخصيات العربية للحياة باستخدام Rive

نستخدم Rive لتحريك الشخصيات في Amal بما في ذلك تزامن الشفاه، تخصيص الصور الرمزية، وشخصيات الألعاب.

Engineering

إجابة سريعة

نستخدم Rive لتحريك الشخصيات في Amal بما في ذلك تزامن الشفاه، تخصيص الصور الرمزية، وشخصيات الألعاب.

تستخدم Amal أداة Rive (المعروفة سابقاً بـ Flare) لكل عمليات تحريك الشخصيات — بما في ذلك تزامن الشفاه، تخصيص الصور الرمزية، ردود الأفعال، وشخصيات الألعاب. اخترنا Rive بدلاً من Lottie أو أوراق الرسوم المتحركة لأنها تدعم الآلات الحالة وقت التنفيذ، التلاعب البرمجي، ونظام الرسوم المعجل بواسطة GPU بسرعة 60 إطار/ثانية، كل ذلك في ملف واحد مدمج لكل شخصية.

مكتبة أصول الرسوم المتحركة

الشخصيات الأساسية

  • lip-sync-amal-01.riv
    • الشخصية الرئيسية لAmal (إصدارات للجسم الكامل والوجه فقط)
    • لوحات فنية متعددة لكل وضعية للفم (لتخطيط الصوتيات)
    • الحالات: الخمول، التحدث، الخطأ، الاحتفال، النوم
    • حجم الملف: 1.2 ميجابايت (مقابل 50+ ميجابايت لأوراق الرسوم المتحركة)
  • avatar.riv
    • صورة رمزية قابلة للتخصيص (3 لوحات فنية)
      1. الجسم الكامل: الرأس، الجسم، الأطراف مع الملابس
      2. الرأس فقط: لواجهة المستخدم وتطبيق الوالدين
      3. رفيق الفراشة: رسوم متحركة للمكافأة
    • مبني على مكونات: شكل الرأس، الشعر، العيون، الملابس، الملحقات، الألوان
    • حجم الملف: 2.4 ميجابايت
  • coin-01.riv & coins-01.riv
    • رسوم متحركة للمكافأة (العملات تتطاير، تجمع)
    • عملة واحدة: 150 كيلوبايت
    • عملات متعددة: 300 كيلوبايت
  • cute-monster-final.riv
    • شخصية رد الفعل بمؤثرات مشاعر متعددة
    • الحالات: سعيد (إجابة صحيحة)، مرتبك (غير صحيح)، يفكر (يحلل)، يحتفل (نجاح متواصل)
    • حجم الملف: 1.8 ميجابايت

أنظمة الأمثل لمنصة أندرويد

  • إصدار NDK مخصص (Rive NDK-r28) للامتثال لمحاذاة الصفحات 16KB
  • يقلل حجم السجل الثنائي بنسبة 8% مقابل الإصدار القياسي
  • يضمن التوافق مع إدارة الذاكرة المتقدمة في أندرويد 12+

أنابيب تزامن الشفاه (تعمق تقني)

الخطوة 1: توليد الصوت TTS + استخراج علامات الكلام

# 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 voice
)
audio_config = texttospeech.AudioConfig(
    audio_encoding=texttospeech.AudioEncoding.MP3,
    effects_profile_id=['small-bluetooth-speaker-class-device']  # Child's speaker
)

# طلب علامات الكلام (توقيت الصوتيات)
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)

# يتضمن الرد:
# - المحتوى الصوتي: بايتات MP3
# - النقاط الزمنية: [{character, byte_pos, time_ms}, ...]

return {
    'audio': response.audio_content,
    'speech_marks': response.timepoints  # توقيتات على مستوى الصوتيات
}

الخطوة 2: مطابقة الصوتيات مع حالات الفم في Rive

lip_sync_avatar.json تطابق الصوتيات العربية مع وضعيات الفم:

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

الخطوة 3: ينسق LipSyncController التشغيل

// 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) { // الخطوة 1: تحميل شخصية Rive riveCharacter.loadRiveFile('lip-sync-amal-01.riv');

// الخطوة 2: تحميل علامات الكلام من مخرجات TTS
mapper = LipSyncMapper.fromJson(loadJsonAsset('lip_sync_avatar.json'));

// الخطوة 3: تشغيل الصوت مع تحريك الفم
audioPlayer.play(AudioSource.file(audioPath));

// الخطوة 4: تحديث وضع الفم في كل إطار صوتي
audioPlayer.onPositionChanged.listen((Duration position) {
  String phoneme = mapper.phonemeAtTime(position.inMilliseconds);
  String riveState = mapper.riveStateForPhoneme(phoneme);
  
  riveCharacter.setStateInput('mouth_state', riveState);
});

} }

الخطوة 4: يتحكم RiveCharacterController في دورة الحياة

// يدير الحالة الكاملة لتحريك الشخصيات (ليس فقط الفم)
class RiveCharacterController extends GetxController {
  States: idle → prepare → speaking → idle → error/celebration

void startExercise() { // انتقال الشخصية: الخمول → التحضير (الجاهزية للاستماع) 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_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 (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 mappings
};

عندما يختار الطفل "رأس دائري + قميص أزرق"، يقوم التطبيق:

  1. بتفعيل العنصر Head_Round في Rive
  2. بتفعيل العنصر Shirt_Blue في Rive
  3. بتعطيل جميع أشكال الرأس وألوان القمصان الأخرى
  4. تظهر الصورة الرمزية المخصصة للطفل الآن في كامل التطبيق

لماذا Rive بدلاً من البدائل؟

الميزة Rive Lottie أوراق الرسوم المتحركة الفيديو
الآلات الحالة
التحكم في وقت التشغيل ✓ (كامل) جزئي يدوي ✗ (سلبي)
حجم الملف 1-2 ميجابايت 2-3 ميجابايت 50+ ميجابايت 100+ ميجابايت
الأداء 60fps GPU 30fps CPU 60fps GPU متغير
التفاعل ✓ كامل ✓ جزئي ✓ كامل ✗ لا يوجد
منحنى التعلم متوسط سهل سهل سهل
الصيانة ملف .riv واحد ملف JSON واحد مئات من الصور فيديو واحد

الأفضلية لـ Rive لأننا نحتاج للتحكم البرمجي، الآلات الحالة، والدمج الملفي لتطبيق الهاتف المحمول.

تحسين الأداء

  • تحميل مسبق للشخصيات: تحميل ملفات .riv أثناء بدء التطبيق، وليس لكل تمرين
  • الرسوم المتحركة بواسطة GPU: يستخدم Rive تلقائياً GPU إذا كان متاحاً، والاعتماد على CPU للأجهزة القديمة
  • تخصيص الذاكرة: إعادة استخدام وحدات التحكم Rive عبر الشاشات لتجنب توقفات جمع القمامة
  • الضغط: ملفات Rive مضغوطة بالفعل؛ لا حاجة لتحسين إضافي

النتيجة: رسوم متحركة بسرعة 60 إطار/ثانية على Snapdragon 662+ (هواتف الفئة المتوسطة 2019).

الأسئلة الشائعة

س: هل يمكنني تصدير الرسوم المتحركة من Adobe Animate إلى Rive؟
ج: ليس بشكل مباشر. نستخدم محرر Rive الأصلي (rive.app). المصممون يصممون في Rive، وليس Animate أو After Effects. تدفق العمل هو: تصميم الشخصية في Rive → التصدير كـ .riv → الدمج في تطبيق Flutter.

س: كيف تعاملتم مع أنواع الأجسام المختلفة أو الإعاقات؟
ج: تخصيص الصور الرمزية يتضمن خيارات لأنواع الجسم (نحيفة، رياضية، دائرية) وملحقات (نظارات، مساعدات سمعية، مساعدات حركية). يضمن هذا رؤية جميع الأطفال للتمثيل.

س: ماذا إذا لم يعجب الطفل في صورتهم الرمزية؟
ج: يمكنهم التخصيص في أي وقت. التطبيق لا يفرض شكل معين — الأطفال لديهم سيطرة إبداعية كاملة.

مقالات ذات صلة