اردت كتابة هذا الموضوع فقط للتذكير و لاعلام من لا يعلم, ليس فيه الكثير من المعلومات الجديدة, و لكن قد يوفر علي جهود الاجابة على بعض الاسئلة مستقبلا ان شاء الله.
الـ game loop هي عبارة عن حلقة تكرارية (كما هو اضح من الاسم) تحصل فيها احداث اللعبة, و كل دورة من هذه الحلقة تعبر عادة عن frame واحد في اللعبة.
في كل دورة من دورات هذه الحلقة, تحصل الأشياء التالية (اقل او اكثر ..ليس بالضرورة بهذا الترتيب .. حسب النوع اللعبة .. نحن هنا نتكلم بشكل عام):
- اخذ الـ input من المستخدم (من الماوس و الكيبورد, و ايضا من الجويستك اذا كان مستخدما في اللعبة)
- معالجة الـ input و تفعيل الـ game logic من أجل تغيير حالة اللعبة
- رسم الحالة على الشاشة
الـ game logic بدوره قد يتكون من عدة اقسام:
- الذكاء الصناعي (logic بسيط او متوسط للتحكم بكائنات اللعبة)
- تحريك الكائنات و كشف التصادم
- نظام محاكاة فيزيائي physics system/engine للتحكم في كيفية حركة الأشياء
هذه قائمة مقتضبة و لا تعبر مطلقا عن كل شيء يجري خلف الكواليس في الالعاب الحقيقية,
الهدف هنا هو فقط اعطاء انطباع مبدئي.
لماذا نحتاج game loop؟
بسيطة, اي برنامج يعمل لفترة غير محددة و لا ينتهي عمله الا بحدوث امر معين لا بد له من ان يستعمل loop من نوع ما ..
ما المقصود برسم frame واحد؟
كما تعلمون كل الالعاب فيها frame rate, و هو معدل الصور اللتي يتم رسمها في الثانية, و لسبب ما, كل صورة تسمى frame او اطار, اعتقد ان السبب في هذا يعود الى المصطلحات اللتي كانت تستخدم في الطرق التقليدية في تقديم عروض السينما في الماضي, و الله أعلم.
المهم, انه في بعض الأحيان تلاحظ ان اللعبة بطيئة و الصور متقطعة .. و السبب في هذا ان الـ frame rate قليل, و السبب في ذلك هو ان المدة اللتي تستغرقها اللعبة من أجل اكمال دورة واحدة من الـ game loop طويلة نسبيا, و السبب في هذا هو ان اللعبة تقوم بالعديد من العمليات الحسابية اللتي تأخذ وقتا طويلا للتنفيذ, و قد يكون السبب في هذا هو ان اللعبة مبرمجة بطريقة سيئة, او ان بطاقة العرض video card قديمة و عملية الرسم مكلفة (تاخذ وقتا طويلا نسبيا), او غيرها من الأسباب.
اعتقد ان الـ frame rate النموذجي هو 60 صورة في الثانية ..
مثال تبسيطي جدا جدا:
#include <iostream>
#include <windows.h>
#include <conio.h>
using namespace std;
void drawPlayer( int );
int main()
{
int input = 0;
int playerPosition = 5;
//This is the Game Loop (in this example)
while( true )
{
//delay, to slow down the frame rate
Sleep( 30 );
//get input
if( _kbhit() )
input = _getch();
else
input = 0;
//handle input
if( input == 'q' )
break; //quit
else if( input == 'a' ) //move left
{
playerPosition--;
//detect collision with world boundary!!
if( playerPosition < 0 )
{
//don't let player go outside!!
playerPosition = 0;
}
}
else if( input == 'd' ) //move right
{
playerPosition++;
//detect collision with world boundary!!
if( playerPosition > 12 )
{
playerPosition = 12; //don't let player go outside!!
}
}
//draw one frame
drawPlayer( playerPosition );
}
return 0;
}
void drawPlayer( int position )
{
//put the cursor at the beginning of the screen .. in borland, this would be:
//gotoxy(0,0)
COORD zero = { 0,0 };
SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), zero );
for( int i = 0; i <= 12; i++ )
{
if( position == i )
cout << char(1); //ascii `face` character
else
cout << ' ';
}
}
الكود مكتوب على Visual c++ 2005 Express Edition, و هو قابل للترجمة و التجريب مباشرة.
الفكرة هي اخذ مدخلات من المستخدم و من ثم معالجتها للقيام بعملية ما,
في هذه الحالة, a و d تقوم بتحريك اللاعب يمنة و يسرة, بينما q تقوم بالخروج من اللعبة.
ما هي الأشياء اللتي يمكن ان تحدث في الـ game loop, و ما هي الأشياء اللتي لا يجب ان تحدث؟
عادة, نقوم في كل دورة من دورات الحلقة بتحريك الكائنات بمقدار بسيط جدا, بحيث عندما تجتمع هذه المقادير على مدى عدة frames فإنها ستظهر و كانها تتحرك بطريقة سلسة و ناعمة.
ما لا يجب ان نقوم به ابدا, هو تحريك كائن معين مساقة كبيرة في دورة واحدة, لان هذه الحركة بطبيعة الحال لن تظهر بشكل سلس, بل ستظهر بطريقة غريبة و غير مستساغة.
من الأشياء اللتي يجب تجنبها ايضا, كتابة كود ياخذ وقتا طويلا جدا في التنفيذ, مثل كتابة loop لا تخرج الا اذا قام المستخدم بادخال شيء ما!
الـ game logic داخل الـ game loop عليه دائما ان يضع في حسبانه ان ما ينفذه هو عبارة عن حدث في فترة زمنية قصيرة, يعني اجزاء من الثانية, و يجب تصميم اللعبة على هذا الأساس.
و لكن هناك سؤال ملح, ما هي المدة الزمنية اللتي تاخذها دورة واحدة من الـ game loop؟ هل علينا ان نخمن؟ ام نفترض افتراضات معينة قد لا تكون صحيحة؟
كلا ابدا, من الأفضل دائما قياس الفرق الزمني بين الفريم السابق و الفريم الحالي, و استخدامه كأساس لتحريك منطق اللعبة.
مثلا, لو اردنا تحريك كائن معين بسرعة ثابتة معينة (سنتحدث عن هذا الموضوع أكثر في مواضيع قادمة إن شاء الله), فإننا نقوم في كل فريم بحساب "ما هي المساقة اللتي يجب ان اتحركها في هذا الـ frame؟", و لو فرضنا ان الفرق الزمني بين الفريم السابق و الحالي هو timeDelta, و ان السرعة هي speed, فإن المسافة اللتي يجب ان يتحركها الكائن في هذا الفريم هي:
displacement = speed * timeDelta;
لا باس اذا لم تفهم ما أقصد, الفكرة فقط هي عدم "تخمين" الوقت او افتراض امور غير واقعية, بل يجب علينا قياس الفترة الزمنية في كل مرة.
قياس الزمن في كل فريم يمكن ببساطة ان يكون بهذا الشكل:
currentTime = getTime();
previousTime = currentTime;
while( true ) //game loop
{
currentTime = getTime(); //update current time
timeDelta = currentTime - previousTime; //calculate time delta for this frame
previousTime = currentTime; //update "previous time" to be now ..
.
.
.
.
}
اعتذر ان كان الموضوع مشتتا بعض الشيء, و ارجو ان يكون قد حقق شيئا من الفائدة.
الجميع مرحب به اذا اراد اضافة او تصحيح اي معلومات.