الشبكة العربية لمطوري الألعاب

خبير  أحمد عبد الغني مشاركة 1

وجدت هذا السؤال في منتدى شقيق (arabteam2000). وأحب أن أسمع الرد هنا لأن لدي ثقة كبيرة في إجابات الأعضاء المدعمة بخلفية من برمجة ألعاب حقيقية. السؤال:
---------------------------
(الشمري)

السلام عليكم .
لا أخفيكم أن الوقت .. ودقته .. هي أكثر ما يزعجني في برمجة الألعاب ..
سأذكر عدة نقاط واطلب منكم التعليق .. كمناقشة يعني .

- اذا كانت لعبتي سريعة .. فأول ما سأقوم به هو التالي
 

while(...)
{
    int firstTime  = TimeGetTime(); 
    .
    .
    int lastTime += TimeGetTime() - firstTime;
    if(lastTime > 100 ) // كل 100 جزء من الثانية .. أريد التحديث
        updatePosition
    DrawSomethingHere();

}


هل هذا عملي ؟؟؟ .
هذه الطريقة .. أعتقد مفيدة لو كانت لعبتك سريعة وتريد ابطائها .. لكن العكس لا .,

2- لماذا بعض الألعاب .. وخاص القديمة .. تجعل FPS دائما 60 فريم لكل ثانية .

3- ما فائدة QueryPerformanceFrequency .. وغيرها من الدوال .. يوقولن انها اكثر دقة .. ولم الاحظ اي فرق .. كيف تعمل مثل هذه الدالة .


أتمنى أجد مقال أو رد يفصل ال Timer تفصيل ممل ... بانتظاركم ,,

وجدت هذا الرابط .. اريد أن أقرأه لاحقا ,, وحتى لا يضيع :
http://www.geisswerks.com/ryan/FAQS/timing.html
---------------------------

وشكراً لأي توجيهات

اللهم انصر أهلنا في فلسطين وآجرنا أن نكون عوناً لهم

خبير مدير وسام البهنسي مشاركة 2

شكراً أحمد، وشكراً أساسياً لصاحب السؤال الأصلي الشمري. بالفعل الموضوع حساس وللدهشة ستجد أنه حتى الآن لا يوجد حل أمثلي لهذه المشكلة.
 
المقالة التي ذكرها الشمري في نهاية المشاركة جيدة، لكنها أيضاً تغفل بعض الحقائق الجديدة عن مؤقت QPC.
لقد لمسنا هذا الموضوع في مقالة "ربط الأجهزة الإلكترونية والمؤقتات" في مجلة ترونيكس، والموجودة ضمن مجموعة مقالات هذه الشبكة على الرابط:
http://www.agdn-online.com/papers/mmio.htm
 
إلا أن تلك المقالة أيضاً تحوي معلومات قديمة. على كل سندع هذا النقاش للنقطة الثالثة.. أما الآن فلنناقش النقطة الأولى وهي ماذا تفعل الألعاب بالوقت الزائد لديها؟

 
بعض الألعاب تفضل "شفط" موارد الجهاز بشكل كامل والتكيف مع الإمكانيات لتحسين الرسوميات مثلاً، وبعض الألعاب المسالمة تفضل فقط أخذ ما يلزمها من المعالج وترك البقية للبرامج الأخرى... الوحدة المسؤولة عن هذا هي حلقة اللعبة (Game Loop) وطريقة توقيتها...
 

هناك أسلوبين رئيسين لتوقيت حلقة اللعبة: الأسلوب الأول هو جعل اللعبة تعمل بنبض ثابت، والأسلوب الثاني هو جعل اللعبة تعمل بأقصى سرعة ممكنة مع إمكانية التكيف مع الوقت المتغير بين كل تكرار لحلقة اللعبة (هناك أساليب هجينة أيضاً بين هذين الأسلوبين).
 
الحلقة التي طرحها الشمري تنتمي إلى أسلوب هجين يعتمد على نبض ثابت في تحديث حالة اللعبة، بالإضافة إلى الرسم بأقصى سرعة ممكنة طالما أن ذلك ممكناً. هذا الأسلوب منتشر في ألعاب البي سي، ولعبة قريش كانت من ضمن هذه الألعاب أيضاً، حيث يتم حساب الطبقة السفلى من الذكاء الصناعي بنبض ثابت كل ربع ثانية، بينما يتم الرسم بسرعة كاملة وإنتاج حركات سلسة عن طريق الاستيفاء (interpolation) بين آخر نبضتين من الذكاء الصناعي ما أمكن.
 
هذا يعني بالطبع أن ما يراه اللاعب قد حدث بالفعل منذ ربع ثانية، لذلك يجب أخذ الحذر، فإن زيادة التباعد بين النبضات ستؤدي إلى تقليل التجاوب في اللعبة، فتصبح مزعجة جداً وغير مفهومة.
 
كمثال آخر نذكر لعبة Need For Speed، والتي تعتمد الأسلوب الثاني بشكل كامل ولا تفترض أي ثبات في زمن النبض، إلا أنها تضع حداً أقصى للزمن المسموح بين كل نبضتين، وذلك لأن وحدة الفيزياء ليست مجهزة لحل التصادمات عبر أزمنة متباعدة، مما قد يسمح للسيارة بأن تخترق الحائط لو كانت تنطلق بسرعة كبيرة جداً تجعلها تتجاوز الحائط في فترة تقل عن الفرق بين النبضتين.
 
التعليق الوحيد لي على تلك الحلقة التي ذكرها أخونا الشمري هو أن الهدف ليس إبطاء سرعة اللعبة، وإنما ضمان نبض ثابت لها، مع الحفاظ على سلاسة الحركات بقدر إمكانيات الجهاز.
 
 
 
بالنسبة للنقطة الثانية، فإن تثبيت سرعة التحديث لـ 60 لقطة في الثانية ليست بالأمر المقتصر على الألعاب القديمة. وإنما هي تنتشر بكثرة حتى الآن وخصوصاً في الألعاب التي تعمل على أجهزة الكونسول. حيث أن استخدام نبض ثابت يبسط الحسابات بشكل كبير، وباعتبار أننا نعمل على أجهزة معروفة ولن تتغير مواصفاتها من لاعب إلى آخر فإنه يمكننا السيطرة على الأداء بدرجة أكبر منها على البي سي.
 
أحد الألعاب التي عملت عليها كانت على جهاز الـ Wii، وقد كانت تستخدم مبدءاً مماثلاً، حيث أن الحسابات الداخلية تتم بنبض ثابت 60 لقطة في الثانية، والرسم له نبض مطابق أيضاً، إلا في الحالات التي يستغرق فيها الرسم زمناً طويلاً فإنه يسمح له بالنزول فوراً إلى 30 لقطة في الثانية فقط إلى أن يتم تدارك الموقف والخروج من المشهد المعقد.
 
 
 
سأدع الرد على النقطة الثالثة إلى وقت لاحق، وسأحاول أيضاً طرح بعض الأمثلة عن الحلقات المستخدمة في الألعاب...
 
شكراً!

وسام البهنسي
مبرمج في إنفيديا وإنفريمز

خبير مدير وسام البهنسي مشاركة 3

حسناً، سأتابع الرد الذي بدأته...
 
سأضع كمثال عن حلقة اللعبة كود حلقة اللعبة المستخدم في مشروع لعبة وادي الملوك التي نعمل عليها جميعاً في المشروع الجماعي (هذا الكود من وحدة CoreLib التي أثارت بعض التساؤلات لدى المبرمجين عند بداية العمل). اللعبة تستخدم المنتج DSK|RenderSmith والذي يقدم من ضمن خدماته عملية إدارة حلقة اللعبة ليترك لك فقط تعبئة الإجراء OnIdle والذي سيستدعى كل ما سنحت الفرصة...
 
 
الكود التالي منسوخ مباشرة من DSK|RenderSmith وهو يمثل تماماً حلقة اللعبة (العاملين في مشروع وادي الملوك يعرفون متى يتم نداء هذا الكود) :
 
 

ERR AppInstance::Run(void)
{
    // Enter typical application loop
    ERR eRetval = ERR::OK;
    bool bKeepIdling = true;
 


    while (eRetval.Succeeded())
    {
         // Pump views messages
         eRetval = ProcessViewsMessages(!bKeepIdling);
         DSK_RETURNONFAIL;
 


         if (eRetval == AppInstance::ERR_RS_BREAKEXECUTION)
             break;
 


         // Idle, call application
         eRetval = OnIdle();
         DSK_RETURNONFAIL;
 


         bKeepIdling = (eRetval == AppInstance::ERR_RS_KEEPIDLING);
         if (eRetval == AppInstance::ERR_RS_BREAKEXECUTION)
             break;
    } // While the app is running
 


    return eRetval;
}
 
 
 
وفي حالة وادي الملوك، فهاهو كود الإجراء OnIdle، والذي يظهر به كيف يتم استخدام المؤقت للتحكم بالـ FPS الخاص باللعبة.
 

ERR AppState::OnIdle(void)
{
    if (!m_timerGameTick.IsFired())
    {
        // Sleeping frees the CPU for other threads and processes in the system
        // For King's Valley, the game utlizes so little CPU power, but if we don't
        // sleep, then we will have a high frequency amount of calls to our OnIdle()
        // function because DSK|RenderSmith will just check window messages and come
        // back to us... Sleep(0) is not very helpful because it just gives a peak
        // to other threads and gets back to us. Any number greater than 0 will do.
        Sleep(1);
        return (DWORD)AppInstance::ERR_RS_KEEPIDLING;
    }
 


    // Setup wait for next tick...
    float fWaitTime = 1000.0f/(float)m_iFPS - m_timerGameTick.GetElapsed();
    if (fWaitTime <= 0.0f)
        fWaitTime = 1000.0f/(float)m_iFPS;
    m_timerGameTick.WaitFor(fWaitTime);
 


    m_pApp->OnIdle();
 


    RenderMan& renderMan = CurrentRenderMan();
    if (renderMan.ProbeValidity().Failed())
        return (DWORD)AppInstance::ERR_RS_KEEPIDLING;
 


    DSKRS::ViewNavigation *pView = (DSKRS::ViewNavigation*)m_aViews[0];

    renderMan.RenderBegin();
    renderMan.RenderTargetClear();
 


    m_pApp->OnDraw();
 


    renderMan.RenderEnd();

    ERR eRetval = pView->GetRenderChain()->Present();
 

    if (eRetval.Failed())
        return (DWORD)AppInstance::ERR_RS_BREAKEXECUTION;

    return (DWORD)AppInstance::ERR_RS_KEEPIDLING;
}
 
 
المتغير m_timerGameTick يمثل وحدة المؤقت التي يقدمها محرك DSK، وهي تدعم عدة أنواع من المؤقتات، متضمنة QPC و RDTSC. في وادي الملوك نحن نستخدم مؤقت timeGetTime نظراً لأننا لا نحتاج إلى دقة عالية ولا نريد الوقوع في أي مشاكل غير متوقعة (رغم أن المحرك يحاول التغلب على هذه المشاكل قدر الإمكان).
 
أعتقد أن هذه نقطة هامة تغيب عن بعض المبرمجين، ففي نظام ويندوز يجب أن تعطي أولوية لمعالجة الرسائل كي لا تتسبب بجعل نافذة لعبتك تتصرف ببلادة، وعندما لا يكون هناك أي رسائل فإنه يمكنك بذل الوقت في معالجة اللعبة. الإجراء ProcessViewsMessages يقوم تماماً بهذه المهمة..
 
لعبة وادي الملوك تستهلك من 0% إلى 5% من زمن المعالج على حسب تعقيد المرحلة التي يلعب بها اللاعب، مما يعني أن اللعبة تستطيع العمل على جهاز بينتيوم 100 ميجاهرتز بسلاسة (جهازي سرعته 2 جيجاهرتز).
 
 
 
أما الإجابة عن النقطة الثالثة للأخ الشمري فإن النقاش يطول... لذلك سأتكلم من النهاية إلى البداية، استخدم timeGetTime أو GetTickCount طالما أن ذلك يكفيك. يمكنك بهذه الإجراءات قياس الفروق الزمنية بدقة ميللي ثانية واحدة على الأكثر، وهذه الدقة كافية جداً لتوقيت حلقة اللعبة. بهذه الطريقة ستريح نفسك من عناء التعامل مع الصداع الذي ستسببه لك المؤقتات الأخرى (QPC و RDTSC).
 
المؤقت QPC (راجع مقالة "ربط الأجهزة الإلكترونية والمؤقتات") يستخدم ما يحلو له في الجهاز ليقدم أكبر دقة ممكنة. في بعض الأجهزة قد يعتمد على تعليمات مباشرة للـ BIOS، وفي بعض الحالات قد يقرأ القيم من أماكن أخرى غريبة. لذلك فإنه ليس له وحدة ثابتة تستطيع استخدامها، فعندما تنادي الإجراء QueryPerformanceCounter ستستقبل قيمة الله أعلم ماذا تعني... قد تعني عدد دورات المعالج، وقد تعني أي شيء آخر. لذلك أنت بحاجة إلى شيء ما يساعدك على تحويل هذه القيمة إلى وحدة زمنية كالثواني... وهنا يأتي الإجراء QueryPerformenceFrequency. حيث يعيد لك هذا الإجراء عدد "الوحدات" في الثانية الواحدة.
 
مثلاً:
 
أعطانا QueryPerformanceFrequency القيمة 15000000 (15 مليون وحدة في الثانية)، وسنسميها التردد.
وقمنا بنداء QueryPerformanceCounter مرتين على فترتين متباعدتين وطرحنا الفرق لنحصل على 45000000.
الآن يمكننا أن نقسم هذه القيمة على التردد لنحصل على 3 ثواني.  وهذا هو مبدأ العمل بمؤقت QPC.
 
هذا المؤقت عادة تصل دقته إلى النانوثانية، ويستخدم في تزمين الصوت مع الصورة في مشغلات الأفلام. ويمكنك استخدامه لقياس الزمن المستغرق لتنفيذ الإجراءات السريعة في ++C.
 
إلا أن هناك بعض الأمور التي تمنع QPC من أن يكون مؤقتاً أمثلياً. هذه الأمور تتلخص في شيئين: تعدد المعالجات، والمعالجات التي تغير سرعتها بشكل مستمر.
 
في حالة تعدد المعالجات، لو اعتمد QPC على عداد التعليمات في المعالج (وهي الحالة الأكثر شيوعاً) فإنك قد تحصل على نتائج غريبة بين النداءات المختلفة للإجراء، وذلك بحسب أي المعالجات قام بتنفيذ الطلب. فمثلاً المعالج الأول أنجز مئة مليون تعليمة حتى الآن، بينما الثاني أنجز 90 مليون تعليمة فقط، ولذلك فقد تحصل أحياناً على نتائج مضحكة كأن يعود الزمن إلى الوراء (الفرق بالسالب) أو يقفز قفزات كبيرة مفاجئة للأمام.
 
في الحالة الثانية فإن بعض المعالجات تغير سرعتها بشكل مستمر على حسب الطاقة المتوفرة، وهذه المعالجات تتواجد بشكل رئيسي في الأجهزة المحمولة (laptops) حيث يخفف المعالج من سرعته عندما يدخل في نظام توفير طاقة البطارية..  وهنا فإن قيمة التردد التي حسبناها مسبقاً تصبح خاطئة لأن التردد قد تغير ويجب قراءته مرة أخرى...
 
لهذه الأسباب أنا أنصح بالابتعاد عن QPC إلا إن كنت تعرف تماماً ماذا تفعل. في محرك DSK لديك الخيار لاستخدام أي المؤقتات تريد، ونستخدم QPC بشكل افتراضي مع مجموعة جيدة من الكشوف التي تحاول تفادي القراءات الخاطئة.
 
 
أرجو أن أكون بهذا قد أجبت عن أسئلة الأخ الشمري، وأن يكون الجميع قد استفادوا من هذه المعلومات...
 
تحياتي!

وسام البهنسي
مبرمج في إنفيديا وإنفريمز

موهوب  عبدالله الشمّري مشاركة 4

بارك الله في الناقل وصاحب الرد ..

المعلومات دسمة .. قرأت و قرأت .. واحتاج الى تطبيق .. لذلك أحتاج لمزيد من الوقت للتطبيق , ولي رجعة أخرى ان شاء الله .


جزاك الله خير أخوي وسام ,

--
طالب - تخصص نظم معلومات .
--

موهوب  عبدالله الشمّري مشاركة 5

السلام عليكم ..

عدت مرة أخرى☺ ..

لازال النقاش جاري في الفريق العربي

 http://www.arabteam2000-forum.com/index.php?showtopic=162045&pid=824692&st=10&#entry824692

بالنسبة للنقطة التي أثرتها أخي وسام

وفي 29 مايو 2008 11:48 ص، أعرب وسام البهنسي عن رأيه بالموقف كالآتي:

أحد الألعاب التي عملت عليها كانت على جهاز الـ Wii، وقد كانت تستخدم مبدءاً مماثلاً، حيث أن الحسابات الداخلية تتم بنبض ثابت 60 لقطة في الثانية، والرسم له نبض مطابق أيضاً، إلا في الحالات التي يستغرق فيها الرسم زمناً طويلاً فإنه يسمح له بالنزول فوراً إلى 30 لقطة في الثانية فقط إلى أن يتم تدارك الموقف والخروج من المشهد المعقد.


السؤال المطروح .. هو ما طرحه الاخ SandHawk في الفريق العربي ..


في 29 مايو 2008 11:48 ص، قال SandHawk بهدوء وتؤدة:

اما المشكلة الثانية فتمثل تحدياً اكبر, ماذا لو لم يكن الكومبيوتر قادر
على رسم كل إطار بزمن أسرع من 16 ميلي ثانية؟ مثلاً يستغرق 24 ميلي ثانية
حتى يكمل الرسم كل إطار, إن لم يحسب حساب لهذه المشكلة, فسيحصل بطئ في
اللعبة, حيث إن كل إطار سيبقى معروضاً 8 ميلي ثانية اكثر من المفروض, ليس
هنالك على حد علمي حل مباشر لهذه المسألة, ولكن هنالك حلول "ملتوية" إن صح
التعبير.

ما رأيك ؟


* النقطة الاخيرة .. بالرغم من أن الاخ SandHawk .. تكلم عنها باسهاب .. الا اني احب اسمع رأيك أيضا ..

لو رجعنا عشرين سنة الى الوراء ,وطلب منك عمل هذه اللعبة على الاتاري ..



هل ستعمل اللعبة " بنبض ثابت " .. أما كيف ... ( طبعا الكلام هو عن طريقة التحكم بالتوقيت وسرعة اللعبة )

--
طالب - تخصص نظم معلومات .
--

خبير مدير وسام البهنسي مشاركة 6

في 23 يونيو 2008 02:45 ص، قال الشمري بهدوء وتؤدة:

السؤال المطروح .. هو ما طرحه الاخ SandHawk في الفريق العربي ..


اما المشكلة الثانية فتمثل تحدياً اكبر, ماذا لو لم يكن الكومبيوتر قادر
على رسم كل إطار بزمن أسرع من 16 ميلي ثانية؟ مثلاً يستغرق 24 ميلي ثانية
حتى يكمل الرسم كل إطار, إن لم يحسب حساب لهذه المشكلة, فسيحصل بطئ في
اللعبة, حيث إن كل إطار سيبقى معروضاً 8 ميلي ثانية اكثر من المفروض, ليس
هنالك على حد علمي حل مباشر لهذه المسألة, ولكن هنالك حلول "ملتوية" إن صح
التعبير. 

ما رأيك ؟

طبعاً لكل جهاز مواصفات معينة. وعليك كمبرمج معرفة الحد الأدنى المطلوب دعمه في اللعبة. فمثلاً لو كنت تستهدف الأجهزة ذات المعالجات P3 كحد أصغري فإن واجبك أن تجعل اللعبة تعمل على هذه الأجهزة بشكل صحيح، وعندها لن تحتاج إلى القلق على بقية الأجهزة، لأنها إما ستكون أسرع، أو أبطأ وبالتالي غير مدعومة لأنك تكون قد وضحت في لعبتك مواصفات التشغيل الدنيا.
 
الآن فلنفرض أنني أريد للعبتي أن تعمل على جهاز P3، عندها منطقياً يجب علي تفادي المشاهد والمؤثرات الخاصة والحسابات التي لا يمكن تحقيقها ضمن الإمكانيات المتاحة للـ P3. وهنا يجب أن تقوم بموازنة... هل تريد للعبتك أن تعمل ضمن 60 لقطة في الثانية؟ أم 30؟ أم حتى 15؟
الخيار مفتوح لك، فبسرعة 60 FPS تستطيع رسم حركات ناعمة، إلا أنك تملك وقتاً أقل لمعالجة اللقطة القادمة (16.6 ميلي ثانية فقط). أما عند الرسم بـ 30 FPS فإنك الآن تملك ضعف الوقت لمعالجة الإطار القادم، لكن نعومة الحركة تخف... وهكذا..
 
فلنفرض أننا وضعنا هدفاً لأنفسنا، وهو تحقيق 30 FPS بشكل ثابت على جهاز P3. نقوم عندها ببرمجة اللعبة والمؤثرات الخاصة، وقياس الأداء كل فترة لمعرفة الوضع الحالي وما هو المجال المتبقي لك للمزيد من الإضافات. بعد أن تنتهي من وضع جميع المزايا، تصل إلى نقطة النهاية، وهي ضمان عمل اللعبة بـ 30 FPS مع كل المزايا النهائية...
هنا نقوم بتشغيل المؤقت ومتابعة اللعبة أثناء اللعب بها بشكل طبيعي. وعندما نواجه لقطة تستخدم أكثر من الزمن المتاح، فإننا نوقف اللعبة ونحاول فهم أي الأجزاء من الكود هو الذي يستهلك أكثر كمية من الوقت (profiling). ومن ثم نقوم بتحسين ذلك الكود عن طريق تكنيكات بديلة أو أي حيلة optimization متاحة، بهدف التخلص من تلك العقبة. وبعدها تعاد الكرة ثانية... إلى أن نصل إلى إصدارة تعمل بسلاسة بشكل دائم، فنكون عندها قد حققنا هدف اللعبة، ولن تواجه إي مشاكل غير متوقعة بسبب بطء جهاز اللاعب.
 
طبعاً هذا الكلام يعني أنك قد وضعت لنفسك حاجزاً لن تتجاوزه وهو الـ P3. لكن ماذا لو أردت للعبتك أيضاً أن تستفيد من الإمكانيات الإضافية على الأجهزة الأقوى؟  سؤال جيد، في هذه الحالة يجب تصميم المزايا بحيث تكون قابلة للفصل أو التشغيل وفقاً لمواصفات جهاز اللاعب. مثلاً على أجهزة P4 يتم تشغيل رسم الظلال، بينما على P3 فإن الظلال يتم فصلها ومنعها من العمل بهدف تفادي الحسابات الإضافية... وهكذا.
 


 

في 23 يونيو 2008 02:45 ص، عقد الشمري حاجبيه بتفكير وقال:

لو رجعنا عشرين سنة الى الوراء ,وطلب منك عمل هذه اللعبة على الاتاري ..



هل ستعمل اللعبة " بنبض ثابت " .. أما كيف ... ( طبعا الكلام هو عن طريقة التحكم بالتوقيت وسرعة اللعبة )


:)
 
طبعاً مثل هذه الألعاب تعمل بنبض ثابت. لعبة وادي الملوك تنتمي إلى نفس الحقبة الزمنية لهذه اللعبة، وهي أيضاً تعمل بنبض ثابت، غير أن حساباتها أعقد بقليل من لعبة الدبابات هذه (Tank Battalion).
 
للإجابة على سؤالك بشكل حرفي، إن طريقة برمجة هذه اللعبة على الأتاري (Atari2600 أو صخر أو جهاز NES) أعقد وأكثر حساسية من برمجة الألعاب على أجهزة اليوم. الأجهزة الآنفة الذكر كانت تعمل باستخدام نظام تشغيل Realtime. مما يعني أنك تستطيع معرفة كم مضى من الزمن عند كل تعليمة في اللعبة وبشكل متناهي الدقة، وذلك بسبب كون كل تعليمة لها كلفة زمنية بسيطة وواضحة (لم تكن تلك الأجهزة تملك خطوط معالجة معقدة كما هو الحال في معالجات اليوم).
 
في مثل هذه الأنظمة تتم حسابات اللعبة بناءً على زمن ثابت ومعروف، وهو ببساطة سرعة مسح المدفع الإلكتروني للتلفزيون بشكل عامودي. وهو 60 مسحة في الثانية في تلفزيونات NTSC و 50 مسحة في الثانية في تلفزيونات PAL. مما يعني أن جميع هذه الأجهزة كانت تعمل أبطأ على تلفزيونات PAL (أي أن مالكي أنظمة PAL محظوظون لأن اللعبة ستكون أسهل قليلاً).  هذا الوضع مستمر حتى الآن في الألعاب البسيطة التي توجد على الكونسول.
 
السؤال التالي إذن، كيف أستطيع عندها تحريك دبابتي بسرعة أقل من بكسل واحد في كل لقطة؟ الجواب البسيط هو استخدام إحداثيات من نوع float بدلاً من int. وعند الرسم فقط تقوم بجبر هذه القيم. أما الحسابات الداخلية فتستمر باستخدام متغيرات float.
 
لاحظ أن حسابات الفاصلة العائمة هذه لم تكن موجودة على الأجهزة القديمة (قبل معالجات إنتل 486) إلا باستخدام معالج إضافي مساعد (math co-processor). لذلك كانت هذه الألعاب تستخدم حسابات الفاصلة الثابتة fixed كبديل.
 
إن أردت رؤية كل هذا الكلام السابق في موقع التنفيذ، فألقِ نظرة على كود لعبة وادي الملوك، حيث أنها تستخدم حصراً متغيرات من نوع fixed للحركة، واللعبة كلها تعمل بنبض ثابت 60 لقطة في الثانية. وهي قادرة على العمل بسرعة ثابتة على جميع الأجهزة حتى الـ Pentium 1. (طبعاً هذا أقل ما يمكننا دعمه بسبب نظام التشغيل). لاحظ أن جهاز صخر كان يملك معالج بسرعة 3.58 ميجاهرتز فقط! ومع ذلك فقد كان قادراً على تشغيل مثل هذه الألعاب... لذلك عيب علينا إن لم نستطع جعل هذه الألعاب تعمل بسرعة كاملة على الأجهزة الحديثة☺

وسام البهنسي
مبرمج في إنفيديا وإنفريمز

خبير  سلوان الهلالي مشاركة 7

هنالك أيضاً نقطة مهمة اود ان اشير إليها بخصوص العاب تلك الحقبة, هي إنها لا تقوم بعملية رسم الشاشة بأكملها كل إطار, فقط الأجزاء التي تتغير يعاد رسمها بالمزامنة مع الـ vsync.
يمكن ملاحظة إن المساحات التي تتغير بين كل إطار والإطار الذي يليه قليلة (في لعبة الدبابات تلك وكذلك في لعبة وادي الملوك), إذن إن إقتصرت عملية الرسم على تلك الأجزاء فقط فإن ذلك يخفف العبء بشكل كبير عن الـ Hardware. في الحقيقة... لم يكن لجملة Frames per second معنى آنذاك!
 
بقيت هذه الفكرة على قيد الحياة حتى اليوم, حيث إن ستراتيجيات الرسم التي تستخدمها انظمة التشغيل (مثل GDI على وندوز) تقدم دعم مباشر لذلك (Dirty Rects).

خبير مدير وسام البهنسي مشاركة 8

وفي 23 يونيو 2008 12:13 م، قال سلوان الهلالي متحمساً:

هنالك أيضاً نقطة مهمة اود ان اشير إليها بخصوص العاب تلك الحقبة, هي إنها لا تقوم بعملية رسم الشاشة بأكملها كل إطار, فقط الأجزاء التي تتغير يعاد رسمها بالمزامنة مع الـ vsync

بالفعل، إلا أن هذا التكنيك شائع أكثر في الحقيقة على أجهزة الحاسب الشخصي من الكونسول والتي كانت في تلك الحقبة الزمنية.
 
بالنسبة للأجهزة NES وصخر و Sega MasterSystemII و Sega Genesis، فقد اعتمدت على معالجات رسم تدعم الـ sprites مباشرة، لذلك فلها أسلوب مختلف في تحديث مواقع الرسم.
 

 
كمثال، عندما تبرمج جسم متحرك في صخر فأنت تقوم بتعريف sprite map بحجم 8×8 أو 16×16 حصراً، ومؤلفة من لونين (لون، وشفافية). لإظهار الـ sprite ما عليك سوى تحديد إحداثيات الرسم فقط ضمن السجلات الموافقة في المعالج. ومن ثم يتم المسح بشكل كامل في اللقطة القادمة، مع معالجة المزايا الخاصة (ككشف التصادم على مستوى البكسل بين الـ sprites في المشهد). هناك حد أقصى من الـ sprites المسموح إظهارها ضمن الخط الأفقي الواحد (4 في صخر)، بعدها يتم تجاهل الـ sprites الأخرى التي تقع على نفس الخط.
كحركة التفاف، يمكنك التبديل بين إظهار الـ sprites التي تقع على خط واحد، مما سيجعلها تبرق (تظهر وتختفي)، لكن يبقى ذلك أفضل من اختفائها تماماً (تدعى sprite multiplexing).
 
أما جهاز Atari2600 فله قصة أخرى عجيبة. حيث أن تعليمات الرسم في ألعابه تكون مضمنة ضمن تعليمات منطق اللعبة. فما يحدث هو أن المبرمج قادر على تحديد موقع الشعاع الإلكتروني للشاشة عند كل تعليمة من برنامجه (تخيل!)، وهو يقوم بعدّ التعليمات إلى أن يصل إلى تعليمة يوافق زمن تنفيذها زمن وصول الشعاع عند البكسل المطلوب رسمه، فيقوم بتغيير حالة سجل تتسبب بتخريج إشارة لونية يلتقطها الـ video scanner وتظهر فوراً على التلفزيون.
 

 
تخيل رؤية كود مثلاً يقوم بزيادة الـ score الخاص باللاعب، وفجأة تجد سطراً يقول: حان الوقت لتخريج البكسل الذي ينتمي إلى الخط الأيمن الأصفر! مع تعليمة خاصة، وبعدها يستمر كود معالجة الـ score...
 
هذا الكلام جزء من ذكرياتي في برمجة صخر، والتي وصلت في قمتها إلى رسم مقاتل نينجا أسود (باستخدام مبدأ الـ sprites) في إحدى الليالي الحارة عند منزل صديقي العزيز ياسر. بعد أن قمنا سوية برسمه على ورقة مقسمة إلى مربعات صغيرة☺

وسام البهنسي
مبرمج في إنفيديا وإنفريمز

خبير  سلوان الهلالي مشاركة 9

:)
 
أفتقد تلك الأيام, انا قديم ايضاً ولكني لم اكن من جماعة الصخر ;) , بل كنت أستخدم جهاز عراقي الصنع إسمه الوركاء, مكوناته الأساسية من شركة NEC وشبيه جداً بمكونات الصخر (يعتمد معالج Z80 بتردد 3.58 MHz و ذاكرة 64 KB) ولكن مشكلته إن البيسك المضمن فيه لم يكن يمتلك إيعازات مشابهه للـ sprite في الصخر (كنت احسدكم على ذلك P: ) عدا عن الـ GET و PUT العقيمة, أمضيت سنين احاول تحريك مجموعة من البكسلات بشكل صحيح ولم استطع بإستخدام البيسك لوحده, حتى عثرت بعد زمن طويل (بعد سنة 2000) على طقم تعليمات الـ Z80 الذي بحثت عنه سنين حتى يأست, وادركت حينها إنني سأتمكن وأخيراً من القيام بما حلمت به دوماً!
 
PC-6001MK2SR in all of it's glory

http://www.old-computers.com/museum/computer.asp?st=1&c=394
 
طبعاً لم انتظر حينها, بحثت بين الصناديق والأغراض القديمة لأجد جميع مكونات الوركاء (الشاشة, الكومبيوتر, ووحدة الأقراص المرنة), لأنظفه من غبار السنين المتراكم, ثم شغلته وكتبت كود اسيمبلي لنقل البكسلات من إحداثي إلى آخر وجربته... كان يجب ان اقوم بذلك مرة واحدة على الأقل قبل ان اموت D:
 
لا زلت احتفظ بالمحاكي ;)


خبير مدير وسام البهنسي مشاركة 10

جمييل! هذه أول مرة أسمع بها عن جهاز الوركاء. هل كان نظام التشغيل معرباً؟ حيث أن نظام جهاز الصخر MSX كان من تعريب شركة العالمية.
(أفضل أن نفتتح موضوعاً جديداً لهذه الذكريات الرائعة ونتبادل فيه الخبرات)

وسام البهنسي
مبرمج في إنفيديا وإنفريمز