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

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

في 25 حزيران 2008 07:41 م، عقد وسام البهنسي حاجبيه بتفكير وقال:

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


نعم, لقد كان معرباً من قبل شركة الصناعات الإلكترونية بنفس طريقة تعريب الصخر (أي إستخدام ASCII codes فوق الـ 128 للحروف العربية), كان هنالك نوعان, الأول طرح سنة 1984 وسمي الوركاء 6001 (يحتوي فقط على بيسك) والثاني طرح سنة 1989 وسمي الوركاء 6002 (الكومبيوتر الذي كان لدي كان من هذا النوع) يقدم نمط بيسك محسن ذو سرعة أعلى ودقة عرض اكبر (حتى 640x240) إضافة إلى الأنماط القديمة وعدد من التحسينات مثل voice synthesizer كذلك فهو مجهز ببرنامج للرسم وبرنامج لتنقيح النصوص العربية بإمكانات جيدة (نسخ, لصق, دمج, إلخ وادوات متنوعة اساسية مثل مهيئ للأقراص المرنة)
كانت هنالك اداة Debugger ايضاً يمكن الدخول إليها من داخل بيئة لغة بيسك:




(الصورة تبين اداة الـ Debugger -او ما كان يسمى Mon- وهي تعمل من نمط البيسك المحسن في 6001 MK2 SR)
 
لقد عثرت على محاكيات للإصدارات الأصلية من شركة NEC التي هي طبعاًً غير معربة, وإكتشفت إن ما كنا نسميه 6002 هو نفسه من ناحية الـ Hardware جهاز NEC 6001 MK2 SR الذي وضعت صورته في الرد السابق.
 
وأعتقد إني سأفتح فعلاً موضوعا جديداً في المنتديات العامة لإستذكار أمجاد الماضي.
خاصة وإن لدي مفاجأة لجميع مستخدمي الصخر...
:)

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

الوضوع صار ذكريات يا شباب☺ .. هي ذكريات جميلة .. ولكن حاضرنا أحلى☺ ..


صرت محتار حقيقة .. جزء من الاسئلة هناك وجزء منها هنا .. لكن بما ان الاخ سلوان و وسام هنا .. فلدي نقطة أخيرة أرجو التعليق عليها ..



 Sleep(FRAME_TIME);


و



while( (timer.getTime() - startTime) < (FRAME_TIME ) );


من المفترض أنها يؤديان نفس الغرض .. ولكن  عند حساب FPS .. يختلف الناتج .. وضعت مثال في الفريق العربي ,, :
http://www.arabteam2000-forum.com/index.php?act=attach&type=post&id=67675

ما سبب اختلاف FPS  .. أو السؤال بطريقة أخرى .. أيهما أدق .. والسؤال بطريقة ثالثة .. كيف تعمل Sleep☺ ..

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

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

هنالك العديد من الملاحظات لدي حول الكود, سأذكر اهمها:
 
- لقد إستخدمت double لإستلام الزمن من دالة GetTickCount, والتي تعيد int
- حساب الـ FPS عن طريق التعداد ليس دقيقاً, معادلة حساب الـ FPS الدقيق لكل إطار هي:


Given time difference:  T in seconds
FPS = 1 / T
 
 
والآن, الكود القادم يستطيع تثبيت زمن التحديث لكل إطار بإستخدام نفس الكود الموجود في Main.cpp, وارجو ان تدرسه وتدرس الإختلافات لكي تتضح الصورة لديك اكثر:


 ...
 EnableOpenGL (hWnd, &hDC, &hRC); 
 
 // starts from here
 
    const float REQUIRED_FPS = 30.0f; // Set this to the FPS you want (you can even use fractions)
    const float FRAME_TIME_FPS = 1000.0f / REQUIRED_FPS;
    unsigned int curTime = GetTickCount();
    unsigned int prevTime = curTime; // Initially, we want prevTime to equal curTime so that
                                     // the difference (frameTime) is zero for the first rendered frame
    unsigned int frameTime = 0; // The difference between current and previous time in milliseconds
                                // or the time every frame takes to render
    int update_fps = 0; // This is a counter used to make fps display update once every 30 frames
 
    while (!bQuit)
    {
        /* check for messages */
        if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        {
            /* handle or dispatch messages */
            if (msg.message == WM_QUIT)
            {
                bQuit = TRUE;
            }
            else
            {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
            }
        }
        else
        {
            char titleBar[25]; // avoid using "static" within functions at all costs
            drawSomething(10);
            curTime = GetTickCount();
            frameTime = curTime - prevTime;
            if( frameTime < FRAME_TIME_FPS )
            {
                int difference = FRAME_TIME_FPS - frameTime;
                Sleep(difference);
                frameTime += difference;
            }
            update_fps++;
            if( update_fps > 30 )
            {
                int FPS = 1000*(1 / frameTime); // it is multiplied by 1000 because frameTime is in milliseconds
                wsprintf(titleBar, "%d FPS", FPS);
                SetWindowText(hWnd, titleBar);
                update_fps = 0;
            }
            prevTime = curTime;
        }
    }
 
 // Ends here
 
 /* shutdown OpenGL */
    DisableOpenGL (hWnd, hDC, hRC);
 ...
 
 
وأتمنى كذلك ان تستفسر عن أي شئ غير واضح.
 
بإنتظار لعبة الدبابات ;)

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

في 27 يونيو 2008 10:22 ص، غمغم الشمري باستغراب قائلاً:

الوضوع صار ذكريات يا شباب☺ .. هي ذكريات جميلة .. ولكن حاضرنا أحلى☺ ..
 
أعتذر. أنا السبب في ذلك...

 

بتاريخ 27 يونيو 2008 10:22 ص، قطب الشمري حاجبيه بشدة وهو يقول:

من المفترض أنها يؤديان نفس الغرض .. ولكن  عند حساب FPS .. يختلف الناتج .. وضعت مثال في الفريق العربي ,, :
http://www.arabteam2000-forum.com/index.php?act=attach&type=post&id=67675

ما سبب اختلاف FPS  .. أو السؤال بطريقة أخرى .. أيهما أدق .. والسؤال بطريقة ثالثة .. كيف تعمل Sleep☺ ..

(يلتقط نفساً عميقاً) حسناً، المشكلة هي في افتراض خاطئ عند استعمال الإجراء Sleep.
الهدف من Sleep هو إعطاء البرنامج الفرصة لأن يقول لنظام التشغيل: أنا لا أحتاج إلى معالجة الآن.
عندها يقوم نظام التشغيل بإسقاط الـ thread النائمة من قائمة الـ threads التي يتنقل بينها المعالج، مما يعطي بقية الـ threads فرصة أكبر للعمل، وهي عادة حميدة طبعاً لو كانت لعبتك لا تحتاج إلى كل طاقة المعالج.
 
الفرضية الخاطئة هي أن Sleep ينام للمدة المحددة له بالضبط ومن ثم يعود... بينما في الحقيقة يوجد تعقيدات أكثر حول الموضوع:
 
أولاً، المؤقت المستخدم في Sleep يستمد دقته من إعدادات مؤقت timeGetTime. مما يعني أنك بحاجة إلى زيادة دقة ذلك المؤقت كي تحصل على زمن نوم أكثر دقة.
 
ثانياً، لا تعتمد أبداً على كون Sleep قادراً على العودة تماماً عند انقضاء المدة المحددة. فذلك يعتمد على عوامل خارجية تتغير باستمرار في نظام التشغيل. مثلاً المستخدم يقوم بتشغيل برنامج ثقيل أو يفتح صفحة إنترنت معقدة، عندها سيتأخر Sleep أكثر من المدة المحددة له.
 
لهذين السببين لا يجب استخدام Sleep للتوقيت الدقيق. الحل الصحيح هو أولاً زيادة دقة timeGetTime ومن ثم تعديل حلقة اللعبة لتستخدم Sleep مع تمرير القيمة 0 والدوران في حلقة مفرغة بانتظار أن تحين اللحظة المناسبة.
 
نداء Sleep مع البارامتر 0 هو حالة خاصة تقول فيها للنظام:فقط أعطِ بقية الـ threads الفرصة للتنفس وأعد لي السيطرة مباشرة. وهذه العملية ليست شيئاً خاصاً بويندوز فقط، فجميع أنظمة التشغيل المتقدمة تحتاج لمثل هذه الإشارة، وبالأخص تلك التي ليست intrusive (كما هو الحال في Nintendo Wii و DS).

 

// كود بداية تشغيل البرنامج
timeBeginPeriod(1); // زيادة دقة المؤقت إلى ميللي ثانية واحدة
// تذكر إعادة الدقة إلى قيمتها الأصلية عند إغلاق اللعبة


const float REQUIRED_FPS = 30.0f; // Set this to the FPS you want (you can even use fractions)
const float FRAME_TIME_FPS = 1000.0f / REQUIRED_FPS;
DWORD curTime = timeGetTime();
DWORD prevTime = curTime;
 
while (!bQuit)
{
   /* check for messages */
   if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
   {
      ... // معالجة الرسائل
      continue;
   }

   curTime = GetTickCount();
   if (curTime-prevTime < FRAME_TIME_FPS)
   {
      Sleep(0); // أو 1، أيهما يعطيك نتيجة أفضل
      continue;
   }

   prevTime = curTime;
 
   TickGame();
   DrawGame();

}
 
 
ونصيحة أخرى: ابتعد عن SetWindowText لإظهار عداد الـ FPS لأنه إجراء بطيء جداً وسيتسبب بتخريب حساباتك بنفسه.
بدلاً من ذلك قم بالرسم مباشرة على الشاشة باستخدام OpenGL (يمكنك فعل ذلك بـ 50 سطر كود فقط تقريباً عن طريق رسم الأرقام من صورة).
 
وأخيراً أذكر أن كود وادي الملوك يعمل تماماً كالكود الموضح أعلاه، ويمكنك تحميل المشروع والنظر إلى الكود بشكل عام لمعرفة مخطط سير التنفيذ.
 
إن كان لديك أي استفسارات أو هناك أمور غير واضحة فلا تتردد بالسؤال والجميع هنا متحمسون للمساعدة!

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

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

اها! إذا طريقتي ليست المثلى بسبب عدم دقة Sleep, لم أحاول النظر في كيف تعمل Sleep من قبل..
شكراً جزيلاً وسام لتنبيهي عن ذلك لأنني كنت سأستخدم نفس الفكرة تقريباً في إطار العمل المتعدد المنصات الذي اعمل عليه حالياً (المنصات المفترض دعمها Windows - Linux - Mac).
أعتقد إني سأجري بعض الإختبارات بهذا الخصوص...

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

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




أما في 23/جمادى الثانية/1429 08:59 م، فقد تنهد وسام البهنسي بارتياح وهو يرد:

أعتذر. أنا السبب في ذلك...
قلت ذلك مازحا طبعا☺ ...



قمت بتطبيق ما كتبتوه ... وان شاء الله انحلت المشكلة .. الان هو ثابت على 62 فريم ..هناك مشكلة بسيطة .. وهي أن fps أحيانا يظهر 62  وأحيانا 32 بشكل متكرر ...
بعد البحث والتحري .. صارت المشكلة من frameTime أنها تختلف هي الاخرى .. وبعد تحرّي وبحث آخرين .. تبيّن أن حل المشكلة الوحيد هو باستخدام : QueryPerformanceCounter ..

حتى timeGetTime  ما تنفع ..
الكود يظهر لي مشوه عند اضافته هنا .. لذلك ..هو موجود كامل مع التطبيق في المرفقات .. اذا كان لديكم أي اضافة أو تصحيح .. يسعدني ذلك,,

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

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

المفروض أنه عندما تطلب 60 لقطة في الثانية فإنك تحصل على 60 لقطة في الثانية...
 
لقد قمت بإجراء تعديلات على الحلقة الرئيسية في برنامجك، وهناك عدة نقاط مهمة يجب الانتباه لها عند قياس التوقيت:
 
* تأكد من فصل الـ VSync مع الشاشة عندما تريد التحكم بتوقيت لعبتك بشكل دقيق. الكود الأصلي كان يرتبط بسرعة التجديد الحالية للشاشة، مما يعني أنه مهما كان كود لعبتك سريعاً فإنك لن تحصل على أكثر من 60 لقطة في الثانية، وستخسر الوقت الزائد في الانتظار بلا طائل.
 
* عند استخدام timeGetTime، تأكد من إعداد دقة المؤقت كما ذكرنا سابقاً، وذلك عن طريق timeBeginPeriod و timeEndPeriod.
 
* كلاس المؤقت المستخدم لا يتعامل مع مشاكل QPC كما يجب، ولذلك فإنني كنت أرى عدم تجانس بين الفترة والأخرى على جهازي (قد يختلف الوضع بالنسبة لك حسب جهازك). أما باستخدام timeGetTime فإن التوقيت كان ثابتاً ومتجانساً جداً.
 
* في الكود المعدل ستجد عداد آخر بالإضافة لعداد الـ FPS، وهذا العداد يخبرك بعدد اللقطات التي تستطيع معالجتها في الفترة بين إظهار كل لقطة والتي تليها على الشاشة. يمكنك اعتباره عداد "انشغال" البرنامج، ويمكنك عن طريقه معرفة سرعة أداء بعض العمليات الكبيرة بشكل تقريبي.
 
* بالاعتماد على العداد السابق، فقد وجدت أن استخدام إجراء QPC يستغرق زمن تنفيذ أطول من timeGetTime !  إذ انحدر العداد من 800 تقريباً إلى 500 !
 
أنصحك بإجراء بعض التجارب والاختبارات على الحلقة المعدلة، لتستوعب العمليات التي تجري بشكل كامل. لقد وضعت تعليقات (أرجو أن تكون واضحة) بالعربية على الأسطر ذات الأهمية...
 
وآمل سماع الأخبار الطيبة عن لعبتك☺

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

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

الكود الان يعمل كما آمره☺ .. جميل جدا أن يستجيب لك أحد☺ ..
 
كل الشكر والتقدير لكما .. وسام + سلوان .

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

خبير  أحمد عزالدين مشاركة 19

السلام عليكم

جزاكم الله خيرا علي هذه المعلومات القيمة

لقد ارفقت مشروع صغير من احدي الامثلة التعليمية لكورس معهد ال Game Institute الذي ادرسه وهو يستخدم كلاس CTimer
ويستخدم الدالة Tick ليعمل Fire للـ timer
لكن بصراحة الجزء الاخير من الدالة لا افهمه - ارجو من احد الاخوة الاعضاء شرحه وجزاكم الله خيرا

يمكنكم النظر للمشروع لكي تتابعوا سير البرنامج كاملا
هذا المشروع فقط يعمل تحميل لصورة في ال Back Buffer

بالتحديد الجزئية الغير مفهومة هي:
// Filter out values wildly different from current average
if ( fabsf(fTimeElapsed - m_TimeElapsed) < 1.0f  )
{
  // Wrap FIFO frame time buffer.
  memmove( &m_FrameTime[1], m_FrameTime, (MAX_SAMPLE_COUNT - 1) * sizeof(float) );
  m_FrameTime[ 0 ] = fTimeElapsed;
  if ( m_SampleCount < MAX_SAMPLE_COUNT ) m_SampleCount++;
} // End if
 
// Calculate Frame Rate
m_FPSFrameCount++;
m_FPSTimeElapsed += m_TimeElapsed;
 
if ( m_FPSTimeElapsed > 1.0f)
{
  m_FrameRate = m_FPSFrameCount;
  m_FPSFrameCount = 0;
  m_FPSTimeElapsed	= 0.0f;
} // End If Second Elapsed
 
// Count up the new average elapsed time
m_TimeElapsed = 0.0f;
for ( ULONG i = 0; i < m_SampleCount; i++ ) m_TimeElapsed += m_FrameTime[ i ];
if ( m_SampleCount > 0 ) m_TimeElapsed /= m_SampleCount;

والسلام عليكم

أحمد عزالدين
طالب دراسات عليا
جامعة كالجري

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

من المؤسف أن تجد مؤسسة تعليمية تأخذ على عاتقها تعليم برمجة الألعاب بتخصص كامل، وترتكب أخطاء مثل هذه...
 
المثال الذي أرفقته يا أحمد "يحاول" أن يقوم بالتعامل مع مشاكل مؤقت QPC لكن بطريقة سيئة جداً، فهو يترقب استقبال قيم زمنية سالبة (العودة بالزمن إلى الوراء) أو استقبال قفزات زمنية شاسعة للأمام. لكن طريقة معالجة الموقف غير فعالة وكنتيجة لن تنتج حركات ناعمة أو توقيت ثابت بالضرورة.
 
المؤقت المرفق يدعم الألعاب التي تعمل بالنظامين: عدد ثابت من اللقطات في كل ثانية، أو عدد متغير من اللقطات (على حسب البارامتر fLockFPS).
 
أول الأخطاء أنه لا يقوم بإعداد دقة المؤقت timeGetTime قبل استخدامه، مما يعني أنه قد يعطي نتائج محدودة الدقة على بعض الأجهزة.
 
ثانياً، ضمن نظام تثبيت عدد اللقطات، فإنه يدخل في حلقة while بانتظار مرور الزمن المناسب لبدء اللقطة القادمة، وهذه الحلقة تخنق اللعبة تماماً، فلا تتيح لها أداء أي مهام جانبية كمعالجة رسائل ويندوز، ولا على الأقل إعطاء الفرصة لبقية الـ threads للتنفس (الإجراء Sleep).
 
ثالثاً، أيضاً ضمن نظام تثبيت عدد اللقطات، فإن حساب التوقيت خاطئ، ولن يعطي عدد ثابت من اللقطات في الثانية. وذلك لأنه عندما يحسب الزمن للقطة القادمة، فإنه يقوم بالاعتماد على الوقت الحالي (m_LastTime = m_CurrentTime)، فلو أن الـ current time تجاوز الفترة 16.6 (وهذا متوقع دائماً) فإنك ستنتظر لمدة 16.6 ابتداءً من ذلك الوقت. مثلاً:
 

// Next Frame
// LastTime = 100
// CurrentTime = 110
 
while (CurrentTime-LastTime < 16.6)
  wasteTimeAndRecalculateCurrentTime();
 
LastTime = CurrentTime; // CurrentTime can be greater than 116.6 !!

 
// Next Frame
// LastTime = 118  // Oops! We expected it to be 116.6 !!
// CurrentTime = 120
 
while (CurrentTime-LastTime < 16.6)
  wasteTimeAndRecalculateCurrentTime();
 
LastTime = CurrentTime; // Same mistake again! Error accumulates and becomes larger
 
// Next Frame
// LastTime = 136 (if things were right, this should've been 133.2)
// CurrentTime = 140
.
.
.
 
أرجو أن تكون قد وضحت الفكرة هنا...
 
رابعاً، طريقة معالجة مشاكل QPC سيئة، فهو يقوم "بتنعيم" الفروقات بين الأزمنة، أو أخذ معدل زمن اللقطات باستمرار (هذه وظيفة الكود الذي كنت تسأل عنه). مما يعني أنه عندما يكون الفرق الزمني 5 أجزاء من الثانية مثلاً، فإن المؤقت قد يعيد لك شيء آخر (6 أو 7) على حسب القيمة السابقة... بعبارة أخرى هو مؤقت كاذب، ولا يخبرك بالفروق الزمنية الحقيقية التي يجب أن تعتمد عليها في ضبط اللقطة القادمة.
الطريقة الفعالة هي تجاهل القيم الشاذة كلياً، وليس إدراجها وتقنيعها دون أن تلاحظ (وكأن الهدف من هذا المؤقت هو أن يوهمني أن لعبتي بخير، لكنها في الحقيقة ليست بخير).
 
خامساً، من غير الواضح ما هو نمط الـ VSync المستخدم، فهو دائماً يقوم باختيار أول نمط يذكره كرت الشاشة، بغض النظر عن ما هيته. إن تشغيل الـ VSync مع محاولة توقيت اللقطات بشكل يدوي يدمر العملية تماماً وأصلاً منطقياً لا يصلح.
 
سادساً، (وهذا تعليق على الكود بشكل عام) هناك عدد كبير من المتغيرات التي يتم استخدامها وهي تحمل قيم غير مهيئة (لا سيما D3DSettings).
 
 
لقد كان لدي فكرة مسبقة عن جودة المعلومات التي تقدمها GameInstitute، وقد زاد يقيني بها الآن☺
 
كبديل، أنصح وبشدة باستخدام الحلقة المحسنة للأخ الشمري، فهي أيضاً تدعم نفس المزايا، لكنها تعمل بشكل سليم. ما ينقصها فقط هو تعديل كود المؤقت ليتعامل مع QPC كما يجب، وإن كان رأيي الشخصي هنا هو تفادي QPC واللجوء إلى timeGetTime دائماً عند توقيت اللقطات. دع QPC لمهام أخرى تتطلب بالفعل دقته العالية.

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