القائمة الرئيسية

الصفحات

حل مشكلة توقف الإطارات (Stuttering) في لعبة Unity

Fixing the Stuttering Problem in Unity Game، Unity Performance، Stuttering Fix، Unity Optimization، Garbage Collection, Object Pooling, Asset Management, Profiling Unity, Smooth Gameplay, Game Development Best Practices, Frame Rate Optimization، Unity، Stuttering، توقف الإطارات، تحسين الأداء، Unity Profiler، إدارة الذاكرة، تجميع المهملات (Garbage Collection)، تحميل الأصول، تحميل غير متزامن، كاش، تحسين الكود، تجربة اللاعب، أداء اللعبة، Game Development، Unity، Stuttering، توقف الإطارات، أداء Unity، Profiler، GC، Garbage Collection، تحسين أداء، إدارة الذاكرة، تحميل الأصول، Object Pooling، Shader، Physics، FPS، حل مشكلة Stuttering في Unity، كيفية إصلاح توقف الإطارات في ألعاب Unity، تحسين أداء Unity باستخدام Profiler، تقليل تجميع المهملات (GC) في Unity، استخدام Object Pooling لتحسين الأداء في Unity، حل مشاكل بطء الأداء في ألعاب Unity، تحميل الأصول غير المتزامن في Unity، تحسين الفيزياء لتقليل Stuttering في Unity، أفضل ممارسات تحسين أداء Unity، فهم وحل Stuttering الناتج عن تجميع المهملات في Unity، تحديد سبب توقف الإطارات في Unity، نصائح لتحسين أداء اللعبة في Unity، تحسين كود #C في Unity لتقليل تخصيص الذاكرة، حل مشكلة توقف الإطارات (Stuttering) في لعبة Unity، تحسين أداء Unity باستخدام Profiler، Object Pooling، Shader، Physics، FPS، تحسين كود #C في Unity،Fixing the Stuttering Problem in Unity Game، حل مشكلة توقف الإطارات (Stuttering) في لعبة Unity،  حل مشكلة توقف الإطارات، Stuttering، لعبة Unity، حل مشكلة توقف الإطارات (Stuttering) في ألعاب Unity: دليل شامل،




حل مشكلة توقف الإطارات (Stuttering) في لعبة Unity



مشكلة "توقف الإطارات (Stuttering)" العشوائي في لعبة Unity على جهاز 
Android معين بعد تحديث نظام التشغيل الأخير.
مشكلة "توقف الإطارات" أو الـ "Stuttering" هي كابوس أي مطور ألعاب، وتؤثر بشكل كبير على تجربة اللاعب. 
إليك مقال مفصل لحل هذه المشكلة في ألعاب Unity.
في عالم تطوير الألعاب (Game Development)، تعتبر سلاسة الأداء
 (Smooth Performance) عاملاً حاسماً في تحقيق تجربة لعب غامرة 
(Immersive Gameplay Experience). لا شيء يفسد هذه التجربة أكثر
 من ظاهرة توقف الإطارات (Stuttering)، حيث تبدو اللعبة وكأنها تتجمد لبرهة 
قصيرة أو تفقد إطارات بشكل مفاجئ، مما يؤدي إلى شعور اللاعب بالتقطيع وفقدان التحكم. 
هذه المشكلة، على عكس معدل الإطارات المنخفض (Low Frame Rate) الثابت، 
هي متقطعة وغالباً ما تكون ناجمة عن أحداث غير متوقعة أو إدارة غير فعالة للموارد. 
سيتناول هذا المقال بشكل مفصل الأسباب الشائعة لـ Stuttering في Unity،
 وكيفية تشخيص المشكلة (Diagnosing Stuttering) باستخدام Unity Profiler، 
بالإضافة إلى حلول عملية (Practical Solutions) تتضمن تحسين الكود
 (Code Optimization)، إدارة الذاكرة (Memory Management)،
 وتحسين الأصول (Asset Optimization)، بهدف تحقيق أداء مستقر
 (Stable Performance) وسلاسة اللعب (Smooth Gameplay) في مشاريعك.


مالفرق بين Stuttering مقابل Low Frame Rate


قبل الغوص في الحلول، من المهم التمييز بين Stuttering ومعدل الإطارات المنخفض:
1- معدل الإطارات المنخفض (Low Frame Rate): 
يشير إلى أن اللعبة تعمل ببطء ثابت، على سبيل المثال 20 FPS بدلاً من 60 FPS. 
هذا غالباً ما يكون بسبب حمل عمل مستمر مرتفع على المعالج (CPU) أو معالج الرسوميات (GPU).
2- توقف الإطارات (Stuttering): 
يحدث عندما ينخفض معدل الإطارات فجأة إلى قيمة منخفضة جداً 
(مثلاً من 60 FPS إلى 0 FPS أو 10 FPS) لبضعة أجزاء من الثانية، ثم يعود إلى طبيعته. 
هذا غالباً ما يكون بسبب أحداث مفاجئة تستهلك موارد كبيرة.

 تشخيص المشكلة باستخدام Unity Profiler

الخطوة الأولى والأكثر أهمية لحل مشكلة Stuttering هي تحديد سببها الجذري. 
Unity Profiler هو الأداة الأقوى لذلك.

1.1. كيفية استخدام Unity Profiler:

افتح Profiler من قائمة Unity: Window > Analysis > Profiler.
شغّل اللعبة في محرر Unity أو قم بتوصيله بجهاز يعمل عليه build للعبة (يفضل الاختبار على جهاز حقيقي).
راقب الرسوم البيانية، خاصة أقسام CPU Usage و Memory و Rendering.
عند حدوث "Stutter"، ابحث عن الارتفاعات المفاجئة (Spikes) في الرسوم البيانية.

1.2. ما الذي تبحث عنه في Profiler عند حدوث Stuttering؟
* CPU Usage (Main Thread) :
- Garbage Collection (GC.Collect): ارتفاعات حادة ومتقطعة. هذا هو السبب الأكثر شيوعاً.
- Physics.Simulate: إذا كانت هناك كائنات فيزيائية كثيرة تتصادم أو تتفاعل بشكل مفاجئ.
- Loading/Unloading Assets: ارتفاعات عند تحميل أو تفريغ أصول
 كبيرة (مثل مستوى جديد، نماذج معقدة، أنسجة).
Instantiate/Destroy: إنشاء أو تدمير عدد كبير من الكائنات دفعة واحدة.
Mesh.SetVertices/SetIndices: تحديث بيانات الشبكة (Mesh) في وقت التشغيل.
()Awake(), Start: تنفيذ كود ثقيل في دوال التهيئة هذه.
* Memory :
Managed Heap Size: إذا كان ينمو باستمرار، فقد يكون هناك تسرب في
 الذاكرة (Memory Leak) مما يؤدي إلى GC.Collect متكرر.
* Rendering :
SetPass Calls/Draw Calls: ارتفاعات مفاجئة قد تشير إلى تفعيل مواد جديدة أو كائنات إضافية غير محسّنة.



الأسباب الشائعة والحلول لتوقف الإطارات Stuttering في ألعاب Unity



بمجرد تحديد السبب باستخدام Profiler، يمكنك تطبيق الحلول المناسبة.

2.1. تجميع المهملات (Garbage Collection - GC)

* المشكلة : هو السبب الأكثر شيوعاً لـ Stuttering. 
تحدث عندما يقوم مجمع المهملات (GC) بإيقاف اللعبة مؤقتاً لتنظيف الذاكرة غير المستخدمة. 
الكود الذي يقوم بتخصيص ذاكرة جديدة بشكل مستمر في كل إطار (Frame) يزيد من تكرار عملية GC.

الأسباب الشائعة لـ GC Spikes:

- إنشاء سلاسل نصية (Strings) جديدة بشكل متكرر (مثلاً، عرض النقاط في Update() 
باستخدام ()score.ToString).
- إنشاء كائنات جديدة ( new ) في كل إطار.
- استخدام اللينكيو (LINQ) بشكل مكثف في ()Update.
- استخدام foreach على المجموعات التي ليست List<T> (مثل Array أو IEnumerable)،
 حيث قد يخصص المحول (Enumerator) ذاكرة.
- استدعاء دوال Unity API التي تعيد مصفوفات (Arrays) أو مجموعات جديدة في كل مرة
 (مثلاً GameObject.FindGameObjectsWithTag()).

* الحلول:
1- تقليل تخصيص الذاكرة (Reduce Allocations):
إعادة استخدام الكائنات (Object Pooling): بدلاً من إنشاء وتدمير الكائنات 
(مثل الرصاص، الأعداء، مؤثرات الجسيمات)،
 قم بإنشاء مجموعة كبيرة منها مسبقاً (Pool) وأعد استخدامها.
#C




// مثال بسيط لـ Object Pooling
public class ObjectPool : MonoBehaviour
{
    public GameObject prefab;
    public int initialPoolSize = 10;
    private List<GameObject> pooledObjects;

    void Awake()
    {
        pooledObjects = new List<GameObject>();
        for (int i = 0; i < initialPoolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            pooledObjects.Add(obj);
        }
    }

    public GameObject GetPooledObject()
    {
        foreach (GameObject obj in pooledObjects)
        {
            if (!obj.activeInHierarchy)
            {
                return obj;
            }
        }
        // إذا لم يكن هناك كائن متاح، قم بإنشاء واحد جديد (تجنبه قدر الإمكان)
        GameObject newObj = Instantiate(prefab);
        pooledObjects.Add(newObj);
        return newObj;
    }

    public void ReturnPooledObject(GameObject obj)
    {
        obj.SetActive(false);
    }
}



--

2- تجنب تخصيص السلاسل النصية في ()Update:
#C




// تجنب هذا:
// TextMeshProUGUI scoreText;
// void Update() { scoreText.text = "Score: " + currentScore.ToString(); }

// الأفضل (استخدم StringBuilder أو قم بتحديث النص فقط عند تغيير القيمة):
private int _currentScore;
private string _scorePrefix = "Score: ";
public TextMeshProUGUI scoreText; // افترض أنك تستخدم TextMeshPro

public int CurrentScore
{
    get { return _currentScore; }
    set
    {
        if (_currentScore != value) // حدث النص فقط عند التغيير
        {
            _currentScore = value;
            scoreText.text = _scorePrefix + _currentScore.ToString();
        }
    }
}
// أو استخدام StringBuilder للكتابة المعقدة
private StringBuilder sb = new StringBuilder();
void UpdateComplexText() {
    sb.Clear();
    sb.Append("Health: ").Append(playerHealth).Append(" Ammo: ").Append(playerAmmo);
    scoreText.text = sb.ToString();
}



--

- استخدام List<T> بدلاً من Array مع foreach: تجنب تخصيص المحول.
- تجنب إنشاء كائنات Vector3, Quaternion في كل إطار: إذا كنت تعدل قيمة موجودة، قم بذلك مباشرة.
- استخدام string.Empty بدلاً من "": لتجنب تخصيص جديد.

3* دعم تجميع المهملات المتزايد (Incremental Garbage Collection):

في Unity 2019.3 والإصدارات الأحدث، يمكنك تمكين Incremental GC 
(من Project Settings > Player > Other Settings > Optimization > Use Incremental GC). 
هذا يجعل عملية GC تتم على أجزاء صغيرة عبر عدة إطارات، مما يقلل من 
التأثير على معدل الإطارات، ولكنه قد يزيد من إجمالي وقت GC قليلاً.

2.2. تحميل وتفريغ الأصول (Asset Loading/Unloading)

* المشكلة: تحميل أو تفريغ الأصول الكبيرة (مثل نماذج ثلاثية الأبعاد معقدة،
 أنسجة عالية الدقة، مقاطع صوتية طويلة) في وقت التشغيل يمكن أن يسبب توقفاً، 
خاصة إذا تم ذلك على الخيط الرئيسي (Main Thread).

* الحلول :
-1 التحميل المسبق (Preloading):
قم بتحميل الأصول التي تحتاجها في شاشات التحميل (Loading Screens) أو
 في بداية المستوى، بدلاً من تحميلها أثناء اللعب.
2- التحميل غير المتزامن (Asynchronous Loading):
استخدم Resources.LoadAsync, AssetBundle.LoadAssetAsync, 
أو نظام Addressables (الموصى به للألعاب الكبيرة) لتحميل الأصول
 في الخلفية دون إيقاف الخيط الرئيسي.
#C

// مثال بسيط لتحميل أصل غير متزامن باستخدام Resources.LoadAsync
IEnumerator LoadAssetAsync(string path)
{
    ResourceRequest request = Resources.LoadAsync<GameObject>(path);
    while (!request.isDone)
    {
        // يمكنك عرض شريط تقدم هنا: request.progress
        yield return null;
    }
    GameObject loadedObject = request.asset as GameObject;
    // استخدم الكائن المحمل
}
// لاستدعائها: StartCoroutine(LoadAssetAsync("MyPrefab"));
--

* إدارة الأصول غير المستخدمة :

استخدم Resources.UnloadUnusedAssets() بشكل استراتيجي
 (فقط في أوقات آمنة، مثل الانتقال بين المستويات، وليس أثناء اللعب). 
هذه الدالة يمكن أن تسبب Stutter إذا تم استدعاؤها في وقت غير مناسب.




2.3. تحديثات الفيزياء (Physics Updates)

* المشكلة : إذا كان لديك عدد كبير من الأجسام الفيزيائية أو تحدث تصادمات معقدة
 في نفس الوقت، فإن حسابات الفيزياء (التي تحدث في FixedUpdate()) يمكن أن تستهلك وقتاً طويلاً.

* الحلول:
* تحسين كائنات Physics:
1- قلل من استخدام Rigidbody قدر الإمكان: استخدم Colliders فقط للكائنات الثابتة.
2- تبسيط الـ Colliders: استخدم Colliders أبسط (Box, Sphere, Capsule) 
بدلاً من Mesh Colliders حيثما أمكن.
3- تقليل الطبقات (Collision Layers): استخدم طبقات التصادم لتحديد أي الأجسام
 تتفاعل مع بعضها البعض، مما يقلل من عدد حسابات التصادم.
4- تحسين طبقات الفيزياء: في Project Settings > Physics, قلل من
Physics timestep إذا كان هناك وقت كاف بين الإطارات، ولكن كن حذراً 
حيث يمكن أن يؤثر على دقة الفيزياء.
5- استخدام Rigidbody.isKinematic: للكائنات التي تحركها أنت يدوياً ولكنها 
لا تزال تحتاج إلى كاشف تصادم، اجعلها isKinematic واستخدم 
Rigidbody.MovePosition() للحركة.

2.4. إنشاء/تدمير الكائنات (Instantiate/Destroy)

* المشكلة: إنشاء وتدمير الكائنات في وقت التشغيل يتطلب تخصيص ذاكرة 
وإجراء عمليات مكلفة، مما يؤدي إلى GC Spikes.

* الحلول:
1- Object Pooling: (كما ذكر أعلاه) هو الحل الأساسي لهذه المشكلة.
2- إلغاء التفعيل/إعادة التفعيل (Deactivate/Reactivate): بدلاً من تدمير الكائنات، قم فقط بتعطيلها (gameObject.SetActive(false)) وإعادة تفعيلها لاحقاً.

2.5. البرامج التظليلية (Shaders) والمواد (Materials)

* المشكلة : 
- تجميع البرامج التظليلية (Shader Compilation):
 في بعض الأحيان، يتم تجميع البرامج التظليلية عند الحاجة
 إليها لأول مرة في اللعبة، مما يسبب توقفاً.
- تبديل المواد (Material Switching): كثرة تبديل المواد بين كائنات الرسم يمكن
 أن تزيد من عدد Draw Calls وتؤثر على الأداء.

* الحلول :

1- التحميل المسبق للبرامج التظليلية:
استخدم Shader Variants وقم بتضمينها في Build Settings للتأكد من تجميعها مسبقاً.
قم بتحميل المواد في بداية اللعبة لتجنب تجميع البرامج التظليلية في وقت التشغيل.
2- تجميع الدُفعات (Batching):
3- Static Batching: للكائنات الثابتة (لا تتحرك) التي تشترك في نفس المادة،
 يمكن لـ Unity تجميعها في دفعة واحدة لتقليل Draw Calls. (تفعيل Static في Inspector).
4- Dynamic Batching: للكائنات المتحركة الصغيرة التي تشترك في نفس المادة، 
يمكن لـ Unity تجميعها تلقائياً (تفعيل في Player Settings).
5- GPU Instancing: للكائنات المتعددة التي تستخدم نفس المادة وتظهر في 
المشهد (مثل الأشجار، الصخور)، استخدم GPU Instancing لتحسين
 الأداء بشكل كبير. (فعلها في إعدادات المادة).

2.6. تحديثات الواجهة الرسومية (UI Updates)
* المشكلة : تحديث عناصر UI المعقدة أو عدد كبير من النصوص في كل إطار يمكن أن يستهلك موارد CPU.

* الحلول :

1- التحديث المشروط: حدث عناصر UI فقط عندما تتغير البيانات التي تعرضها، وليس في كل إطار.
2- التحسين في TextMeshPro: إذا كنت تستخدم TextMeshPro،
 تأكد من استخدام الإعدادات المناسبة لتقليل عدد الـ batches.
3- Canvas Render Modes: اختر وضع الـ Render Mode المناسب للـ Canvas 
 (Screen Space - Overlay، Screen Space - Camera، World Space)
 بناءً على استخدامك.

2.7. Input Handling و Coroutines

* المشكلة :
- Input.GetMouseButton/GetKey في ()Update لا يسبب
 Stuttering عادةً، لكن إذا كان يؤدي إلى كود ثقيل جداً.
- Coroutines: سوء استخدام Coroutines 
(تشغيل عدد كبير جداً، أو Coroutines طويلة جداً) يمكن أن يؤثر على الأداء.

* الحلول :

1- تجميع المدخلات: إذا كان لديك العديد من المدخلات، حاول تجميعها.
2- Coroutines بحذر: استخدم Coroutines للمهام التي تستغرق وقتاً طويلاً ولا
 يمكن تنفيذها في إطار واحد، ولكن تأكد من أنها لا تقوم بعمليات مكلفة في كل yield return null.

* الخاتمة : 
تعتبر مشكلة توقف الإطارات (Stuttering) في ألعاب Unity تحدياً شائعاً
 يتطلب فهماً عميقاً لآلية عمل المحرك ومبادئ تحسين الأداء 
(Performance Optimization). باستخدام Unity Profiler بفعالية، 
يمكنك تحديد السبب الجذري للمشكلة، سواء كان ذلك بسبب تجميع المهملات 
(Garbage Collection)، تحميل الأصول (Asset Loading)، حسابات الفيزياء
 (Physics Calculations)، أو تحديثات واجهة المستخدم (UI Updates). 
من خلال تطبيق الحلول المادية والبرمجية الموصى بها 
(Recommended Hardware & Software Solutions) مثل إعادة
 استخدام الكائنات (Object Pooling)، التحميل غير المتزامن (Asynchronous Loading)،
 وتحسين البرامج التظليلية (Shader Optimization)، يمكنك 
تحويل تجربة اللعب المتقطعة إلى تجربة سلسة وممتعة 
(Smooth & Enjoyable Experience). تذكر أن تحسين الأداء هو عملية مستمرة
 (Ongoing Process)، ويجب أن يكون جزءاً أساسياً من دورة
 تطوير اللعبة (Game Development Cycle).


 
أنت الان في اول موضوع
جدول المحتويات