আমাদের রাইভ এনিমেশন পদ্ধতি: আরবি চরিত্রকে জীবন্ত করা
Amal অ্যাপের সমস্ত চরিত্র এনিমেশনের জন্য Rive (আগে Flare) ব্যবহার করা হয় — এর মধ্যে লিপ-সিঙ্কড স্পিচ, অবতার কাস্টমাইজেশন, ফিডব্যাক রিয়্যাকশন এবং গেম চরিত্র অন্তর্ভুক্ত। আমরা Lottie বা স্প্রাইট শীটের পরিবর্তে Rive বেছে নিয়েছি কারণ এটি রানটাইম স্টেট মেশিন, প্রোগ্রামেটিক ম্যানিপুলেশন, এবং ৬০fps-এ GPU-অ্যাক্সেলেটেড রেন্ডারিং সমর্থন করে, এবং প্রতিটি চরিত্রের জন্য একটি কমপ্যাক্ট ফাইল থাকে।
অ্যানিমেশন অ্যাসেট লাইব্রেরি
মুখ্য চরিত্রগুলি
lip-sync-amal-01.riv- মূল Amal চরিত্র (পূর্ণদেহ ও মুখমাত্র ভ্যারিয়েন্ট)
- মুখের অবস্থানের জন্য একাধিক আর্টবোর্ড (ফোনিম ম্যাপিংয়ের জন্য)
- স্টেট: idle, speaking, error, celebration, sleeping
- ফাইল সাইজ: ১.২ এমবি (স্প্রাইট শীটের জন্য ৫০+ এমবির বিপরীতে)
avatar.riv- কাস্টমাইজযোগ্য ব্যবহারকারীর অবতার (৩টি আর্টবোর্ড)
- পূর্ণদেহ: মাথা, শরীর, লিম্বস সহ পোশাক
- শুধুমাত্র মাথা: ড্যাশবোর্ড এবং প্যারেন্ট অ্যাপের জন্য
- বাটারফ্লাই সঙ্গী: পুরস্কার এনিমেশন
- কম্পোনেন্ট ভিত্তিক: মাথার আকৃতির, চুল, চোখ, কাপড়, আনুষাঙ্গিক, রং
- ফাইল সাইজ: ২.৪ এমবি
- কাস্টমাইজযোগ্য ব্যবহারকারীর অবতার (৩টি আর্টবোর্ড)
coin-01.rivএবংcoins-01.riv- পুরস্কার এনিমেশনস (কয়েন ভাসানো, সংগ্রহ)
- একক কয়েন: ১৫০ কেবি
- একাধিক কয়েন: ৩০০ কেবি
cute-monster-final.riv- বিভিন্ন আবেগের স্টেট সহ ফিডব্যাক চরিত্র
- স্টেট: খুশি (সঠিক উত্তর), বিভ্রান্ত (ভুল), চিন্তা করছে (প্রক্রিয়াধীন), উদযাপন করছে (স্ট্রিক)
- ফাইল সাইজ: ১.৮ এমবি
অ্যান্ড্রয়েড-নির্দিষ্ট অপ্টিমাইজেশন
- কাস্টম NDK বিল্ড (Rive NDK-r28) ১৬ কেবি পেজ অ্যালাইনমেন্ট মেনে চলার জন্য
- স্ট্যান্ডার্ড বিল্ডের তুলনায় বাইনারি সাইজ ৮% কমায়
- অ্যান্ড্রয়েড ১২+ এর অ্যাগ্রেসিভ মেমোরি ম্যানেজমেন্টের সাথে সামঞ্জস্য নিশ্চিত করে
লিপ-সিঙ্ক পদ্ধতি (প্রযুক্তিগত বিশ্লেষণ)
ধাপ ১: 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'] # বাচ্চাদের স্পিকার
)
# স্পিচ মার্কস (ফোনিম টাইমিং) অনুরোধ
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)
# রেসপন্সে থাকে:
# - audio_content: MP3 বাইট
# - timepoints: [{character, byte_pos, time_ms}, ...]
return {
'audio': response.audio_content,
'speech_marks': response.timepoints # ফোনিম-লেভেল টাইমস্ট্যাম্প
}
ধাপ ২: ফোনিমকে Rive মুখের স্টেটের সঙ্গে ম্যাপিং
{
"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 } },
...
]
}
ধাপ ৩: 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) {
// ধাপ ১: Rive চরিত্র লোড
riveCharacter.loadRiveFile('lip-sync-amal-01.riv');
// ধাপ ২: TTS আউটপুট থেকে স্পিচ মার্কস লোড
mapper = LipSyncMapper.fromJson(loadJsonAsset('lip_sync_avatar.json'));
// ধাপ ৩: অডিও প্লে করা এবং মুখের এনিমেশন চালানো
audioPlayer.play(AudioSource.file(audioPath));
// ধাপ ৪: প্রতিটি অডিও ফ্রেমে মুখের অবস্থান আপডেট
audioPlayer.onPositionChanged.listen((Duration position) {
String phoneme = mapper.phonemeAtTime(position.inMilliseconds);
String riveState = mapper.riveStateForPhoneme(phoneme);
riveCharacter.setStateInput('mouth_state', riveState);
});
}
}
ধাপ ৪: RiveCharacterController জীবনচক্র নিয়ন্ত্রণ করে
// শুধুমাত্র মুখ নয়, সম্পূর্ণ চরিত্র এনিমেশন নিয়ন্ত্রণ করে
class RiveCharacterController extends GetxController {
States: idle → prepare → speaking → idle → error/celebration
void startExercise() {
// idle থেকে prepare রূপান্তর (শ্রবণ প্রস্তুত)
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 Named Elements Mapping (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',
// ... ৫০+ উপাদান ম্যাপিং
};
যখন শিশু "রাউন্ড হেড + ব্লু শার্ট" নির্বাচন করে, অ্যাপটি:
- Rive উপাদান
Head_Roundসক্রিয় করে - Rive উপাদান
Shirt_Blueসক্রিয় করে - সব অন্য মাথার আকৃতি ও শার্ট রং নিষ্ক্রিয় করে
- শিশুর ব্যক্তিগত অবতার সম্পূর্ণ অ্যাপে প্রদর্শিত হয়
কেন Rive অন্যদের থেকে এগিয়ে?
| বৈশিষ্ট্য | Rive | Lottie | স্প্রাইট শীট | ভিডিও |
|---|---|---|---|---|
| স্টেট মেশিন | ✓ | ✗ | ✗ | ✗ |
| রানটাইম নিয়ন্ত্রণ | ✓ (পূর্ণ) | আংশিক | ম্যানুয়াল | ✗ (নিষ্ক্রিয়) |
| ফাইল সাইজ | ১-২ এমবি | ২-৩ এমবি | ৫০+ এমবি | ১০০+ এমবি |
| পারফরম্যান্স | ৬০fps GPU | ৩০fps CPU | ৬০fps GPU | পরিবর্তনশীল |
| ইন্টারঅ্যাক্টিভিটি | ✓ পূর্ণ | ✓ আংশিক | ✓ পূর্ণ | ✗ নেই |
| শিখতে সহজ | মধ্যম | সহজ | সহজ | সহজ |
| রক্ষণাবেক্ষণ | একটি .riv ফাইল | একটি JSON | শতাধিক ছবি | একটি ভিডিও |
Rive জিতে যায় কারণ আমরা প্রোগ্রামেটিক নিয়ন্ত্রণ, স্টেট মেশিন এবং মোবাইল অ্যাপের জন্য কমপ্যাক্ট ফাইল দরকার।
পারফরম্যান্স অপ্টিমাইজেশন
- চরিত্র প্রিলোড: পর্ব অনুযায়ী নয়, অ্যাপ চালু হওয়ার সময় .riv ফাইল লোড করা হয়
- GPU রেন্ডারিং: Rive স্বয়ংক্রিয়ভাবে GPU ব্যবহার করে, পুরানো ডিভাইসে CPU ব্যাকআপ থাকে
- মেমরি পুলিং: স্ক্রীনের মধ্য দিয়ে Rive কন্ট্রোলার রিইউজ করা হয়, গারবেজ কালেকশন রোধ করতে
- কমপ্রেশন: Rive ফাইলই কমপ্রেস করা থাকে, অতিরিক্ত অপ্টিমাইজ প্রয়োজন হয় না
ফলাফল: Snapdragon 662+ ডিভাইসে ৬০fps এনিমেশন।
প্রশ্নোত্তর
Q: Adobe Animate থেকে Rive-তে কি সরাসরি এনিমেশন এক্সপোর্ট করা যায়?
A: সরাসরি নয়। আমরা Rive-র নিজস্ব এডিটর (rive.app) ব্যবহার করি। এনিমেটররা Animate বা After Effects নয়, Rive-তে ডিজাইন করেন। ওয়ার্কফ্লো: Rive-তে চরিত্র ডিজাইন → .riv হিসাবে এক্সপোর্ট → Flutter অ্যাপে ইন্টিগ্রেট।
Q: ভিন্ন শরীরের ধরন বা প্রতিবন্ধকতা কিভাবে হ্যান্ডেল করেন?
A: অবতার কাস্টমাইজেশনে শরীরের ধরন (স্লিম, অ্যাথলেটিক, গোলাকার) ও আনুষাঙ্গিক (চশমা, হিয়ারিং এইড, মোবিলিটি এইড) অন্তর্ভুক্ত, যাতে সবাই প্রতিনিধিত্ব পায়।
Q: যদি শিশু তাদের অবতার পছন্দ না করে?
A: তারা যেকোন সময় কাস্টমাইজ করতে পারে। অ্যাপ কোনো নির্দিষ্ট লুক চাপিয়ে দেয় না — শিশুদের সম্পূর্ণ সৃষ্টিশীল নিয়ন্ত্রণ রয়েছে।



