السلام عليكم ورحمة الله
سنقوم في هذا المقال بالتعرف على الطرق التي يمكن أن نعرض النص باللغة العربية فيها على محرك الألعاب Unity3D.
لماذا؟
قد يستغرب البعض من الحاجة لعرض اللغة العربية، فمعظم الألعاب تُعرَف وتُلعَب باللغة الإنجليزية.
إذا نظرنا إلى الألعاب الشهيرة، نرى أنها أصبحت تترجم إلى العربية، بسبب توسع سوق الألعاب في الوطن العربي مؤخرًا..
لذا إذا كانت هذه الألعاب تترجم للغة العربية، فما المشكلة من دعم العرض بالعربية عند تطوير ألعابنا، إضافة للغة الإنجليزية؟
ثم إن محركات الألعاب المتقدمة أصبحت تدعم عدة مجالات أخرى غير الألعاب، مثل الأفلام والتجارب السينيمائية والافتراضية وأحيانًا تستخدم كطريقة لتعزيز التعلم، مما يعني أنه قد تزيد الحاجة لدعمها لعدد أكبر من اللغات.
هذه هي النتيجة التي سنحصل عليها في النهاية:
مشكلة اللغة العربية في المحرك Unity
في المحرر لمحرك الألعاب Unity، قم بإضافة عنصر نص Text عن طريق الضغط على Create -> UI -> Text
بعد إضافته، اضغط على العنصر Text وانظر إلى ال inspector لملاحظة خصائص النص، تأكد أنه يستخدم خط يدعم اللغة العربية، مثل الافتراضي Aerial.
قم بالكتابة باللغة العربية، هل لاحظت المشكلة؟ الحروف تظهر بشكل متقطع، والجمل لا ترتب بالترتيب الصحيح (من اليسار إلى اليمين، بدل العكس):
السبب في هذا أن الأحرف العربية يتغير شكلها عندما تُكَوِّن كلمات، حيث تظهر متصلة ببعضها حسب موقعها بالكلمة..
لذلك يجب على عارض النصوص تغيير شكل كل حرف (من ملف تُحفظ فيه جميع الحروف بأشكالها) حسب موقعه من الكلمة، لكن محرك Unity يعرضها كاللغة الإنجليزية، كل حرف على حدة.
الإضافة الشهيرة Arabic Support
أول ما قد يخطر بذهن المصمم أو المبرمج هو الذهاب إلى المتجر Asset Store والبحث عن حل لهذه المشكلة، ولحسن الحظ، هناك إضافة مخصصة لهذا.
لذلك نشكر الأخ Konash على تقديمها بشكل مجاني ومفتوح المصدر.
الآن اذهب إلى المتجر Asset Store، وابحث عن Arabic Support ثم قم تنزيل الإضافة الآتية:
بعد تنزيلها، اضغط على الاستيراد أو Import
لاحظ بالصورة الآتية أنني أثناء إضافتها لم أسترد أي من المجلدات، فقط الملف البرمجي Arabic Support و readme :
والسبب أننا لن نحتاجهم، فهم مخصصون لمكونات قديمة في المحرك تم الاستغناء عنها، وهم مجرد أمثلة سنبنيهم بأنفسنا من جديد.
الآن لنجربها، نذهب إلى ال Text الذي قمنا بإضافته سابقًا، ونضيف ملف برمجي عن طريق Add Component،
نكتب في الأعلى اسم الملف الجديد، مثلاً SetArabicFixedText، ثم نضغط على الزر New Script
ما يجب أن نفعله هو أن نأخذ النص الذي تمت كتابته في ال Text Component، نقوم بإصلاحه، ثم نعيده لمكانه.
لذلك لنفتح الملف البرمجي الجديد الآن، سنقوم بهذه العملية في دالة (وظيفة) ال Start، المحضرة مسبقًا من Unity، أي عندما تبدأ اللعبة:
using UnityEngine; using UnityEngine.UI; using System; [RequireComponent(typeof(Text)] public class SetArabicFixedText : MonoBehaviour { [SerializeField] bool useTashkeel; // عرض التشكل (الحركات) في النص؟ [SerializeField] bool useHinduNumbers; // استخدام الأرقام العربية (أو الهندية، كما ترغب بتسميتها)؟ Text textUI ; //نعرف متغير يحمل كائن/عنصر النص void Start() { textUI = GetComponent<Text>(); if(textUI.text != string.Empty) // إذا لم يكن النص فارغًا { //نعرف متغير نصي، ونعين قيمته عن طريق استدعاء دالة من الإضافة //هذه الدالة المستدعاة تأخذ النص العربي تقوم بإعادة ضبطه //ليصبح جاهزًا للعرض، ثم ترجعه كقيمة نصية string arabicFixedText = ArabicSupport.ArabicFixer.Fix(textUI.text,useTashkeel,hinduNumbers); //نقوم بتعيين النص للكائن النصي textUI textUI.text = arabicFixedText; } } }
الآن نحفظ الملف ونذهب إلى المشهد Scene، نتأكد أن العنصر Text يحتوي على نص عربي بداخله، ونبدأ اللعبة:
رائع، أصبح النص يعرض بطريقة صحيحة، أليس كذلك؟ أجل بالتأكيد.. هناك مشكلة.
“لا تخلو إضافة من مشاكل”
أولاً، الأسطر منعكسة في الترتيب، السطر الأول هو الأخير، والثاني هو الثالث، وهكذا..
ثانيًا، عند تغيير أبعاد أو حجم ال Text أثناء اللعب، فإن النص العربي لا يتأقلم، وقد يتغير ترتيب الكلمات والسطور أثناء ذلك.
لحل المشكلة، يجب تضييق نطاق البحث، لذلك سنركز على ال TextUI فقط، دون أن نتطرق إلى ال TextMeshProUGUI، (سأتطرق إليه في هذا القسم)
حل المشاكل عند استخدام العنصر UI Text
ملاحظة: هذا الجزء يحتوي على الكثير من الخطوات والتعليمات البرمجية، يمكن الاختصار إلى أسفل المقال لرؤية الملفات البرمجية كاملة.
الطريقة الأولى: إصلاح النص يدويًًا بالبرمجة
لنفكر في الخوارزمية أولاً: بما أن مشكلة انعكاس الأسطر لا تحدث إلا عند “إصلاح” النص بالإضافة Arabic Support.
فما علينا فعله قبل إصلاح النص، هو أن نحصل على نص كل سطر على حدة، نقوم بإصلاحه، ونكرر هذه العملية لكل الأسطر. ثم نضيفهم إلى ال Text بعد تفريغه.
هكذا نحنا لن نقوم بإصلاح نص يحتوي على أكثر من سطر، وبالتالي لن نحصل على مشكلة انعكاس الأسطر.
بالنسبة للتطبيق: عند تحديد نص العنصر Text، فإنه يتم تقسيمه إلى أسطر على حسب المساحة المتوفرة، لذا يجب أن نعتمد على العنصر Text للحصول على عدد الأسطر.
أحد خصائص ال Text هي cachedTextGenerator، كائن من النوع: TextGenerator
تحتوي هذه الخاصية على بيانات عن النص مثل عدد الأسطر ومؤشر البداية لكل سطر وما إلى ذلك.
ولكن هذه القيمة لا يتم تعبئتها بالبيانات التي نريدها إلا بعد إطار واحد 1Frame من التعديل على نص ال Text.
لذلك، الحل الذي وجدته مناسبًا هو أن ننشئ نسخة من هذا النوع بأنفسنا، وإعطائها معلومات عن النص الذي نريد تعبئته في العنصر Text.
قبل أن أكمل، قمت بإنشاء ملف برمجي script جديد، سيحتوي على كل الدوال المساعدة..
وعرفته بأنه مشترك static، لأستطيع جعل الدوال التي بداخله Extension Functions، أي أنها تابعة لنوع معين.
وهذه خطوة اختيارية، أعتقد أنها تساعد في تنظيم العمل:
public static class CustomTextFunctions { //لاحظ أن الملف لا يشتق/يرث من النوع MonoBehaviour لأننا لن نضيفه على أي GameObject بداخل اللعبة، ثم أزلت الدوال Start و Update. //هنا سنضيف الدوال الخاصة بمالجة النصوص }
والآن لنضيف الدوال(الوظائف) إلى هذا الملف..
الدالة التالية تستلم عنصر Text والنص المطلوب، وتقوم بتكوين وإرجاع كائن من النوع TextGenerator يحتوي على المعلومات التي نحتاجها:
public static TextGenerator GetTextGenerator(Text textUI,string text) { textUI.text = text; //تعيين النص للعنصر النصي //تعريف كائن من النوع TextGenerator، الذي سيحتوي على المعلومات TextGenerator textGenerator = new TextGenerator(); //نعرف كائن آخر سيحتوي على أبعاد العنصر text في الواجهة //السبب أن عدد الأسطر الجديدة المطلوبة يعتمد على المساحة المتوفرة TextGenerationSettings generationSettings = textUI.GetGenerationSettings(textUI.rectTransform.rect.size); //نقدم النص والإعدادات لل textGenerator ، // ليقوم بحساب عدد الأسطر وما إلى ذلك، // عن طريق دالة Populate textGenerator.Populate(text, generationSettings); //إرجاع الكائن الذي يحتوي على المعلومات المطلوبة return textGenerator; }
الآن ما علينا فعله هو استخدام هذه الدالة للحصول على معلومات عن الأسطر، ثم استخراج النصوص من هذه الأسطر وإعادة ترتيبها.
وهذا ما تقوم به الدالة الآتية. (قمت بكتابة تعليقات لكل سطر يحتاج الشرح):
public static string FixArabicUITextLines(this Text textUIItem, bool useTashkeel, bool hinduNumbers, string text) { textUIItem.text = text; TextGenerator textGenerator = GetTextGenerator(textUIItem,text); string reversedText = ""; string tempLine; //حلقة تكرارية بعدد الأسطر الموجودة for (int i = 0; i < textGenerator.lineCount; i++) { //للأسف، لا يتم توفير نص كل سطر بشكل مباشر //يجب علينا استخراجه بأنفسنا ^_^ //سنحصل على موقع بداية ونهاية كل سطر، ثم نقصه من النص الأصلي //موقع البداية للسطر هو بكل بساطة: int startIndex = textGenerator.lines[i].startCharIdx; //أما موقع النهاية: //إذا لم نصل لآخر سطر بعد int endIndex = i < textGenerator.lines.Count - 1 ? //نحصل على موقع نهاية السطر من موقع بداية السطر اللاحق: textGenerator.lines[i + 1].startCharIdx : //ولكن في السطر الأخير، سنحصل عليه من طول النص كاملاً: textUIItem.text.Length; //نحصل على نص السطر الحالي، وهو معرف بموقعي البداية والنهاية //دالة Substring تقتص جزء من نص //استنادًا إلى موقع البداية والطول tempLine = textUIItem.text.Substring(startIndex, endIndex - startIndex); //نقوم بإصلاح هذا السطر ثم إضافته إلى النص المعكوس النهائي reversedText += ArabicSupport.ArabicFixer.Fix(tempLine, useTashkeel, hinduNumbers).Trim('\n'); reversedText += Environment.NewLine;//نفصل بين كل سطر بمسافة enter } return reversedText; }
لا تقلق إن لم تفهم هذا الملف البرمجي بالكامل، فهو يستخدم عدة تقنيات برمجية وعدة خصائص لعنصر النصوص Text.
لكن ما يجب أن نفهمه هو الخوارزمية، كيف يقوم هذا الملف بإعادة ترتيب الأسطر؟
إن مشكلة انعكاس الأسطر لم تظهر إلا بعد استخدام الإضافة Arabic Support، أي أنه عند إعطائها نص مكون من عدة أسطر، ستقوم بعكس الأسطر.
ولكننا هنا لن نصلح النص باستخدامها مباشرة، بل سنقوم بتصحيح كل سطر لوحده، بعد الحصول عليهم من النص الأصلي المرتب بطريقة صحيحة، أي أنه لن يتم انعكاسهم من البداية أصلاً.
بما أن الدالة ترجع لنا نوع نصي string، فهي لا تقوم بتعبئة النص تلقاء نفسها في عنصر ال Text على الواجهة، وهذا ما علينا فعله بأنفسنا.
نذهب إلى الملف البرمجي الذي أنشأناه في البداية، ونبدل محتوى دالة ال Start بالآتي:
.... //نعرف متغير يجفظ النص المطلوب private string neededText; .... void Start(){ if(textUI.text != string.Empty) { neededText = UIText.text; UIText.text = UIText.FixArabicUITextLines(useTashkeel, useHinduNumbers, neededText); ; } } ..
قم بتجربة اللعبة، هل لاحظت ترتيب الأسطر؟ أصبح للجملة معنى الآن :).
ولكن قبل أن نفرح كثيرًا، لنجرب تغيير أبعاد العنصر Text، سنجد أن ترتيب الكلمات والسطور يصبح خاطئًا، والسبب في ذلك أنه عند تغيير الأبعاد، فإن محرك Unity يقوم بإعادة ترتيب النص ليلائم الأبعاد (المساحة) الجديدة،
ولكنه يعتمد في ذلك على ترتيب الكلمات من اليسار إلى اليمين، لذلك قد تأتي الكلمة الثانية قبل الأولى، وربما تكون لوحدها في السطر الأول.
لحل هذه المشكلة، كل ما علينا فعله هو القيام بعملية معالجة السطور بشكل مستمر، ليس فقط في دالة Start
لنجرب إضافتها أيضًا إلى دالة ال Update، لتتم كل إطار:
.. void Update(){ UIText.text = UIText.FixArabicUITextLines(useTashkeel, useHinduNumbers, neededText); } ..
جرب تغيير أبعاد عنصر النص، أصبح يتأقلم الآن.
المشكلة أن إعادة هذه العملية كل إطار قد يبطئ معدل الإطارات، خاصة عند وجود العديد من النصوص التي تتحرك، عند تشغيل ملف Animation مثلاً.
لذلك، أقترح أن لا نقوم بالمعالجة إلا عندما يتوقف عنصر ال Text عن تغيير أبعاده، أي ليس في اللحظة التي يحدث فيها التغيير، ولكن في اللحظة التي يتوقف فيها التغيير وينتهي.
وهذا ما يؤديه التعديل الآتي على الملف:
... RectTransform rectTransform; // إضافة المتغيرين private bool hasChangedLastFrame; private float previousWorldRectWH; ... void Start(){ ... rectTransform = GetComponent<RectTransform>(); //نجمع طول وعرض عنصر النص ونحفظهم في المتغير.. //لنستطيع بعد ذلك ملاحظة التغيير في الأبعاد. previousWorldRectWH = rectTransform.rect.width + rectTransform.rect.height; } void Update() { //نحسب مجموع طول وعرض عنصر النص في كل إطار Frame float currentRectWH = rectTransform.rect.width + rectTransform.rect.height; //إذا حصل تغيير في الأبعاد في الإطار السابق، ولكن لم يحصل أي تغيير بين الإطار السابق والإطار الحالي: if(hasChangedLastFrame && previousWorldRectWH == currentRectWH( { //نقوم بإعادة ترتيب النص UIText.text = UIText.FixArabicUITextLines(useTashkeel, useHinduNumbers , neededText); } hasChangedLastFrame = currentRectArea != previousWorldRectWH; previousWorldRectWH = rectTransform.rect.width + rectTransform.rect.height; } ...
لاحظ أنني جمعت العرض والارتفاع، ولكن يمكن القيام بأي عملية أخرى عليهم، مثل الطرح أو الضرب، المهم أن نستطيع مقارنة الأبعاد السابقة بالأبعاد الحالية.
الطريقة الثانية: عكس ال linespacing
أحد خصائص العنصر Text هي line spacing، لنجرب تغيير هذا الرقم إلى 1- ، (أثناء تشغيل اللعبة)
هل لاحظت؟ انعكس ترتيب الأسطر للترتيب الصحيح، ولكن هذا الحل سيء لعدة أسباب:
- لم يعد ينقطع النص. فعندما يصبح النص أطول من المساحة المتوفرة، فإنه يخرج عن حدوده ممتدًّا رأسيًّا (إلى الأسفل).
- إذا كان آخر سطرٍ مملوء للنصف، مثلاً، فإنه عند إعادة الترتيب سيصبح أول سطر مملوء نصفه فقط، ويكمل الكلمات في السطر التالي.. مع أن النص الأصلي يفترض أن يستمر دون انقطاع.
- خاصية ال Best Fit لن تعمل بشكل صحيح عند القيام بهذا التعديل
إذا كانت هذه العيوب لا تمهك، باشر باستخدام هذه التقنية، فهي توفر الوقت.
دعم ال Text Mesh Pro UGUI
ملاحظة: سأقوم باستخدام والتطبيق على العنصر الخاص بالواجهات TextMeshPro UGUI، ولكن التعليمات نفسها من المفترض أن تعمل على العنصر الأصلي TextMeshPro، ولكنني لم أجربها
بما أن ال TextMeshPro أصبح مدمجًا في محرك Unity، دعمه يعتبر أساسيًّا، وبخاصة أنه أفضل بكثير في المشاهد الواقعية أو الحركية من العنصر الأساسي Text.
(يمكنك تفعيل مكتبة ال TextMeshPro في مشروعك عن طريق اتباع الخطوات في هذا الرابط)
وأيضًأ، لحسن الحظ، هناك إضافة مخصصة له لاستخدام اللغة العربية: RTL TextMeshPro.
ولكن قبل أن نتطرق إليها، يجب أن نحل مشكلة الخطوط العربية.
مشكلة الألف المقصورة
إذا جربت أن تكتب باللغة العربية في العنصر TextMeshPro UGUI، فقد تظهر الأحرف العربية بشكل مربعات:
السبب أن الخط الافتراضي liberation sans لا يدعم الأحرف العربية، لذا يجب علينا إضافة خطوط أخرى بأنفسنا.
ولكن إضافة الخطوط لل TextMeshPro مختلفة بعض الشيء، حيث يجب استخدام أداة تسمى FontAssetCreator لتكوين موارد الخطوط.
وقبل البدء بهذه العملية، يجب الحصول على خط يدعم اللغة العربية لاستخدامه مع الأداة
ولسبب ما، الخط Aerial لا يعمل مع هذه الأداة، لهذا قمت بإضافة خطوط مخصصة من الخارج.
وبالنسبة لمشكلة الألف المقصورة، عند كتابة كلمة تحتوي عليها متصلة بالحرف الذي يليها لا يظهر الحرف، وقد يظهر مربع مكانه، كما في الصورة :
بعد التجربة والبحث وجدت أن الأمر يعتمد على الخط، ولم تظهر الألف المقصورة بشكل صحيح إلا مع استخدام أحد الخطين Changa أو Markazi.
ولكن هذا الحل لم يعمل مع الإضافة RTL Text Mesh Pro، وقمنا بسؤال صاحب الإضافة عن هذه المشكلة على UnityForums ولكن لم نحصل على حل بعد.
حتى نحصل على إجابة، أقترح الاعتماد على #الطريقة الثانية لل TextMeshPro حاليا إذا كنت مهتمًّا في عمل الألف المقصورة بشكل صحيح.
بعد تنزيل الخطوط، نقوم بإضافتهم إلى المشروع داخل ال Assets في أي مكان نريده.
والآن، سأترككم مع خطوات تكوين مورد للخط:
- نذهب إلى Window -> Text Mesh Pro -> Font Asset Creator
- في أول خانة علينا تحديد الخط، نضغط عليها ونختر أحد الخطوط التي نزلناها.
- في الخيار السادس Character Set، نختار Unicode Range، ثم في مربع النص الذي ظهر في الأسفل، نلصق الشفرة الرائعة الآتية:
0600-06FF,0750-077F,08A0-08FF,FB50-FDFF,FE70-FEFF,10E60-10E7F,1EE00-1EEFF
ما الذي تفعله هذه الشفرة؟
خيار ال CharacterSet يسمح لنا بتخصيص الأحرف التي نريد أن نستخرجها من الخط الأصلي، عند تحديده ك unicodeRange، فهذا يعني أننا سنعطيه نطاق لنوع الأحرف، مثل أن يكون “من الألف إلى الياء”… السبب في الحاجة لتخصيص الأحرف هو أن الخيارات الأخرى (مثل Asnii) لا تدعم الأحرف العربية في هذه الأداة… وبالنسبة للشفرة نفسها، فكل قيمة تعبر عن حرف معين، مثلا
0623 ترمز للألف “أ”، و 062F ترمز للدال “د”، وهذان الرمزان يقعان بين المجال 0600 و 06FF، الذي يحتوي على معظم الحروف والإشارات العربية. لذلك كتابة 0600-06FF بالترتيب مع الفاصلة بينهما يعني أن نحصل على كل الأحرف هذا المجال… والمجالات الأخرى هي للأرقام والحركات وما إلى ذلك.
- سوف نترك الإعدادات الأخرى كما هي حاليًّا (يمكن التعرف عليها من هنا)
- نضغط Generate Font Atlas، وننتظر قليلاً.
- نضغظ SaveAs ونحفظ النص في المجلد TextMeshPro -> Resources ->Fonts & Materials (لكي نستطيع استعماله عند بناء اللعبة Build)
قم بتعيين الخط لل TextMeshPro UGUO، ولاحظ الآن أن الأحرف العربية بدأت تظهر، ولو أنها لا تكون كلمات.
الطريقة الأولى: تنزيل الإضافة RTL TextMeshPro
الإضافة مفتوحة المصدر، لذلك نشكر صاحبها sorencoder على تقدميها بشكل مجاني.
يمكن إما تنزيل ال Repository كاملة من ال GitHub هنا، أو تنزيل ملف .unitypackage من هنا
بعد تنزيلها، قم بإضاف عنصر جديد، عن طريق Create -> UI ->Text RTLTMP
(هذا المكون مخصص للواجهات، أما للنص ثلاثي الأبعاد يمكن استخدام RTL Text Mesh Pro 3D)
قم بالكتابة في أول مربع نص، رائع، النص الآن سيظهر بشكل صحيح.
الجميل في هذه الإضافة أنها تقوم بإصلاح جميع المشاكل التي قمنا بإصلاحها لل Text UI، بل وتزيد في دعمها لاستخدام النص العربي مع التحريك وبالعرض الثلاثي أبعاد.
ولكن المشكلة أنه ربما لديك لعبة كاملة، وتستخدم فيها عناصر النصوص بشكل مكثف، وقمت بتصميمهم وترتيبهم ولا تريد أن تعيد كل ذلك فقط لدعم اللغة العربية.
في هذه الحالة ربما من الأفضل الاعتماد على الطريقة التي استخدمناها لل Text UI،
وأيضًا، حسب تجربتي، الإضافة تحتوي على بعض المشاكل في النصوص، مثل الألف المقصورة المتصلة، أو وقوع الألف واللام في نهاية الكلمة، مثل كلمة هؤلاء (نعم، قم بتجربتها 🙂 )
ربما هذه الأخطاء لا تهمك، ولكن بالنسبة لي، أنا أفضل أن يكون النص متسقًّا، لذلك سأستخدم الطريقة اليدوية بدلاً من هذه الإضافة.
وهذا ما سنناقشه في القسم التالي.
الطريقة الثانية : حل المشاكل يدويًّا
إذا كانت الإضافة RTL Text Mesh Pro قد وَفَّت بالغرض بالنسبة لك، فلا تقرأ هذا القسم.
سوف نقوم بتطيق الخورزمية التي استخدمناها في البداية على ال TextMeshPro.
بدايةً، نعرّف دالة جديدة تقوم بنفس عملية الدالة السابقة التي تستخرج نص كل سطر من النص الأصلي وتقوم بتصحيحه.
ستكون هذه الدالة في الملف البرمجي CustomTextFunctions:
... public static string FixArabicTMProUGUILines(this TextMeshProUGUI textMesh, bool useTashkeel, bool hinduNumbers string text) { textMesh.text = text; Canvas.ForceUpdateCanvases(); TMP_TextInfo newTextInfo = textMesh.GetTextInfo(text); string reversedText = ""; string tempLine; for (int i = 0; i < newTextInfo.lineCount; i++) { int startIndex = newTextInfo.lineInfo[i].firstCharacterIndex; tempLine = text.Substring(startIndex, newTextInfo.lineInfo[i].characterCount); reversedText += ArabicSupport.ArabicFixer.Fix(tempLine, useTashkeel, hinduNumbers).Trim('\n'); reversedText += Environment.NewLine; } return reversedText; } ...
والآن بإمكاننا استخدام هذه الدالة بنفس الطريقة لتغيير النص عند بداية اللعبة، وأيضًا بشكل مستمر عند حدوث أي تغيير.
قمت بإنشاء ملف برمجي جديد سميته FixArabicTMProUGUI، وأرففته كاملاً هنا:
using UnityEngine; using UnityEngine.UI; using TMPro; using System; [RequireComponent(typeof(TextMeshProUGUI))] public class FixArabicTMProUGUI : MonoBehaviour { [SerializeField] bool textAlreadySet = true; [SerializeField] bool updateInRealTime = true; [Tooltip("Update the textMeshPro text only after the textMeshPro has stopped changing its properties" + "(Better for performance, but makes animation worse)")] [SerializeField] bool updateOnlyOnChange = true; TextMeshProUGUI textMeshPro; [SerializeField] bool useTashkeel = false; [SerializeField] bool useHinduNumbers = false; RectTransform rectTransform; float previousWorldRectWH; string neededText; bool previousFrameNeededEdit = false; string editedText; void Awake(){ rectTransform = GetComponent<RectTransform>(); textMeshPro = GetComponent<TextMeshProUGUI>(); neededText = textMeshPro.text; initialized = true; } void Start() { if (textAlreadySet) { rectTransform.hasChanged = false; prevCorrectText = textMeshPro.FixArabicTMProUGUILines(useTashkeel, useHinduNumbers, neededText); textMeshPro.text = prevCorrectText; }else if(editTextHere){ UpdateText(customText); } } void Update() { if (!updateInRealTime) return; float currentRectArea = rectTransform.rect.height + rectTransform.rect.width; if (previousFrameNeededEdit || (!updateOnlyOnChange)) { editedText = textMeshPro.FixArabicTMProUGUILines(useTashkeel, useHinduNumbers, neededText); textMeshPro.text = editedText; previousFrameNeededEdit = false; textMeshPro.havePropertiesChanged = false; } else if (updateOnlyOnChange && (textMeshPro.havePropertiesChanged || currentRectArea != previousWorldRectWH)) { previousFrameNeededEdit = true; } previousWorldRectWH = rectTransform.rect.width + rectTransform.rect.height; } //يجب استخدام هذه الدالة لتغيير النص عند الحاجة public void UpdateText(string text) { if (!initialized) Awake(); neededText = text; textMeshPro.text = textMeshPro.FixArabicTMProUGUILines(useTashkeel, useHinduNumbers, text); } }
لم أقم بشرح الأكواد لأنهم مشابهين إلى حد كبير مع الملفات البرمجية التي شرحتها لل TextUI.
كل ما أفعله هو إصلاح الأسطر واحدًا تلو الآخر، ثم تحديث النص بدالة ال Update بعد حصول وانتهاء أي تغيير في الأبعاد أو الخصائص.
نقاط يمكن تحسينها
مازال هناك بعض النقاط التي لم ننتبه لها في الملفات البرمجية التي كتبناها لل Text UI و Text Mesh Pro.
أحدها أنه عند تحديث النص بشكل مستمر كل إطار، لم يعد بالإمكان تحديث النص المكتوب إلا باستخدام دالة خاصة بالملف البرمجي، سميتها UpdateText، يمكنك أن تجدها في رابط الملفات البرمجية.
لحل هذه المشكلة، يمكن تحديث النص مباشرة عند تغيير الخاصية text عن طريق ملاحظة التغيير في النص، والتأكد أن هذا التغيير لم يحصل بسبب تغير الأبعاد و إعادة التصحيح.
ثم بعد التأكد من ذلك، أعد تعيين المتغير neededText إلى النص الجديد، وقم بإصلاحه بدوال ال ArabicSupport.
أو يمكن إضافة عنصر string إلى الملف البرمجي بحيث يستطيع المستخدم تعديله من الخارج ( من الواجهة inspector)
ثم تعديل المتغير neededText كلما تغير العنصر النصي، وإعادة إصلاح النص.
وهذا هو الحل الذي تقدمه التعديلات الآتية على الملف البرمجي (سأقوم بعرضها لكم على ال TextMesh، ولكنها تعمل بنفس الطريقة لل TextUI)
داخل ملف FixArabicTMProUGUI:
... //التعديلات في بداية الملف using UnityEditor; [RequireComponent(typeof(TextMeshProUGUI))] [ExecuteInEditMode] public class FixArabicTMProUGUI : MonoBehaviour { [SerializeField] // السماح لتعديل النص من المحرر هنا؟ (أي من القيمة text في الأسقل) bool editTextHere; [Multiline(3)] //جعل النص يظهر بأكثر من سطر، يمكن تعديل القيمة لتغيير عدد الأسطر [SerializeField] string customText;//متغير نص يقوم المستخدم بإدخال النص فيه. [Tooltip("عينها ك true إذا لم ترد التعديل على النص من الملف البرمجي مباشرة(من خاصية ال text في الملف) ")] [SerializeField] bool textAlreadySet = true; ... void Start() { ... initialized = true; if (textAlreadySet) { ... }else if(editTextHere){ UpdateText(customText); } } ... public void UpdateText(string text) { if (!initialized) Start(); ... } } //نفذ هذا الجزء فقط في المحرر #if UNITY_EDITOR //إخبار المحرك أن الكلاس الآتي سيغير ويعدل على شكل ال inspector للملف ////البرمجي FixArabicTMProUGUI [CustomEditor(typeof(FixArabicTMProUGUI))] //يجب أن نضيف كلمة Editor بعد الاسم الأصلي //لاحظ أنه يشتق من Editor أيضًا public class FixArabicTMProUGUIEditor : UnityEditor.Editor { private string previousText; public override void OnInspectorGUI() { //حدث معلومات المتغيرات serializedObject.Update(); //أظهر المتغيرات والعناصر كما الشكل الافتراضي في واجهة الملف DrawDefaultInspector(); FixArabicTMProUGUI myScript = (FixArabicTMProUGUI)target; //نحصل على النص الذي تم إدخاله ونتأكد أنه حصل أي تغيير في النص string currentText = serializedObject.FindProperty("customText").stringValue; if( editTextHere && currentText != previousText) { myScript.UpdateText(currentText); } previousText = currentText; //نطبق التغييرات على المحرر serializedObject.ApplyModifiedProperties(); } } #endif
وأيضًا، بالنسبة لأداء ال textMeshPro..
فإن استخدام عدة عناصر منه في الوقت ذاته مع تشغيل التحديث باستمرار (وليس فقط عند التغيير) .. سيسبب نقص كبير في الأداء،
بالنسبة لي مثلاُ، في مشهد فارغ كان متوسط زمن الإطار الواحد يقارب 8 أجزاء من الألف للثانية عند استخدام عنصر واحد.
ولكن عند إضافة 5 عناصر آخرين، أصبح متوسط الزمن 30-35 جزء من الألف للثانية، ومعدل الإطارات هبط إلى 20-35 إطار في الثانية (كان حوالي 100 إطار)
لا تعتبر هذا ك benchmark (مؤشر للأداء) لكن فقط خذه بعين الاعتبار عند استخدامك للملف البرمجي.
وربما في مقال آخر سأقوم بتحسين مستوى الأداء للخوارزمية إذا لاحظت الطلب على ذلك.
الملحقات
هنا تجد الملفات البرمجية كاملة (من دون الإضافات، فقط التي كتبتها في المقال)
من المصادر التي أفادتني:
https://github.com/AbdullahAlimam/Arabic-Support-for-Unity-UI
ماذا بعد؟
ما زال علينا أن نجعل النظام مرن بين اللغات المختلفة، أي أن يستطيع المبرمج استخدام أكثر من لغة للنصوص، بناءً على إعدادات اللاعب.
وهذا ما يسمى بجعل اللعبة محلية أو Localization، وسوف نناقشه في مقال آخر إن شاءالله.
ختامًا، أتمنى أن يكون المقال قد حاز إعجابكم وأن الشرح قد أفادكم.