الفصل الثاني‬

‫التحريك و التحكم في الوقت‬
‫تحدثنا في الفصل السابق عن بعض الأوامر البسيطة التي نستخدمها في الرسم. و لكننا قمنا بعمل رسومات تبقى كما هي منذ بداية البرنامج إلى نهايته أي لا تتحرك. و في هذا الفصل بإذن الله تعالى سنتناول كيفية عمل رسوم متحركة. إن الألعاب عبارة عن كائنات تستجيب لمدخلات اللعبة و تتفاعل معها. و يتم رسم كل التحديثات كل جزء محدد من الثانية. إن عمل التحريكات له العديد من الطرق المتنوعة و المختلفة و التي يربطها أصل واحد فقط ألا و هو الوقت. و لكي نتعامل مع عامل الوقت ينبغي أن نذكر أولًا بعض الأساسيات.



عمل المؤقتات في مكتبة جلوت:

‫يجب أن يكون هناك مؤقت Timer ـيقوم بإستدعاء دالة الرسم كل فترة محددة من الزمن. فمثلًا نريد أن نحدث الرسمة الموجودة كل 30 ميلي ثانية (الميلي ثانية هو جزء من ألف جزء من الثانية). تخيل معي أن هناك دالة يتم إستدعاؤها كل 30 ميلي ثانية و تقوم برسم كل التحديثات. ربما يكون قد تغير مكان مؤشر الفأرة أو قد تغير مكان وحش من الوحوش في اللعبة و المفترض أن تقوم بمسح الرسمة القديمة و رسم آخرى جديدة. و تذكر معي ماذا كنا نفعل حتى تقوم مكتبة جلوت‬ ‫بإستدعاء دالة الرسم. قمنا بإنشاء دالة منفصلة و أسميناها بأي إسم ثم كتبنا فيها أوامر الرسم التي نريدها. ثم عندما كنا في الدالة الرئيسية main قمنا بإخبار مكتبة جلوت بأن تستدعي هذه الدالة إذا ما لزم الأمر. سوف نقوم بإنشاء دالة عادية جداً و نقوم بوضع فيها ما نريد ثم نقوم بإخبار مكتبة جلوت بأن تستدعي تلك الدالة بعد 30 ميلي ثانية (أو طبعاً بعد المدة التي نريدها .. ليس شرطاً أن تكون 30). إنظر لهذا السطر:‬
void TimerXX (int V‬‬)

{

Display();

}
هنا قمنا بإنشاء دالة عادية جداً ثم في الدالة الرئيسية نقوم بكتابة التي:
‫;(0,‪glutTimerFunc(30,TimerXX‬‬
‫و هكذا ستقوم مكتبة جلوت بإستعداء هذه الدالة بعد 30 ميلي ثانية. و عندما تأتي الدالة بعد 30 ميلي ثانية ستقوم بإستعداء دالة الرسم. أولًا جرب أن تقول لمكتبة جلوت أن تستدعي الدالة بعد عشر ثواني حتى ترى نتيجة محسوسة أمامك. أي بدلًا من السطر السابق إكتب:‬
‫;(0,‪glutTimerFunc(10000,TimerXX‬‬
‫إن المتغير الذي وضعناه في تعريف دالة ‪ TimerXX‬هذا يتم تمريره في دالة ‪ glutTimerFunc‬في البارامتر الثالث‬ ‫الذي أخذ قيمة صفر كما ترون. و هذا يعني أننا يمكن أن نمرر قيمةً ما للمؤقت حتى يستعملها في أي شئ في الكود. الأمر مماثل‬ ‫تماماً لعملية إستدعاء دالة عادية حيث نممر لها بارامتر. و لكن الفرق هنا أن الدالة التي إستدعيناها ستنفذ بعد المدة التي‬ ‫حددناها و ليس في الحال. و المتاح فقط لتمريره إلى هذه الدالة هو متغير واحد فقط من نوع عدد صحيح ‪ .integer‬و‬‫لنجرب الآن البرنامج الذي قمنا بكتابته في الفصل السابق الذي كان ينشئ نافذة و عليها مثلث ملون. إلا أننا سوف نضع فيه بعض‬‫ التغييرات فسوف يقوم البرنامج في البداية بدون أي رسوم. ثم ستظهر الرسوم (المثلث الملون) بعد عشر ثواني. سيكون الكود‬‫بهذا الشكل:‬
#include <GL/gl.h‬>

#include <GL/glut.h‬‬>

void Display()

{

glClear(GL_COLOR_BUFFER_BIT‬‬);

glFlush‬‬();

glutSwapBuffers‬‬();

}

void Timer1(int i‬)

{

glBegin(GL_TRIANGLES‬‬);

glColor3f(1.0,0.0,0.0);
glVertex2f(0.8, 0.8);
glColor3f(0.0,0.5,1.0);
glVertex2f(0.6, -0.5);
glColor3f(1.0,1.0,0.0);
glVertex2f(-0.5, 0.0);
glEnd();
glFlush();
glutSwapBuffers();
}
int main(int argc , char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(200,200);
glutInitWindowSize(320,320);
glutCreateWindow(“Timer Test”);
glClearColor(0,0,0,1);
glutDisplayFunc(Display);
glutTimerFunc(10000,Timer1,0);
glutMainLoop();
return 0;
}
‫قم بتشغيل الكود و شاهد التغيرات التي ستحدث. ستجد أنه في البداية كانت الشاشة سوداء تماماً. طبعاً نعلم لماذا. لأننا حددنا‬ ‫في البداية في دالة ‪ glClearColor‬أننا نريد صفر و صفر و صفر أي اللون الأسود و قد تم إستدعاء دالة ‪Display‬‬ ‫لننا عندما أنشأنا النافذة كنا بحاجة إلى رسمها. و قد قلنا لمكتبة جلوت بأن تستدعي دالة ‪ إذا ما إحتجنا لعادة‬ ‫الرسم. و قد ذكرنا في دالة ‪ Display‬ أننا نريد أن ننظف النافذة ثم أنهينا الرسم و لم نرسم أي شئ. و لكننا أخبرنا مكتبة‬ ‫جلوت أن تقوم بإستدعاء دالة Timer1 ‬بعد 10000 ميلي ثانية (10 ثواني). و قد تم بالفعل أننا وجدنا الرسمة قد‬ ‫ظهرت بعد عشر ثواني. في الحقيقة هناك ملحوظة كنت أود أن ألفت نظر القارئ إليها حتى ندخل في كلم أكثر تفصيلً عن‬ ‫التحكم بالزمن في البرنامج أو اللعبة. بعد أن ظهرت الرسمة بعد عشرة ثواني قم مثلًا بتغيير حجم النافذة أو تكبيرها حتى‬ ‫تقوم مكتبة جلوت بإستدعاء دالة ‪ Display‬ و يتم تنظيف النافذة. ستجد أن المثلث قد تم مسحه بالطبع. و لكن إنتظر عشرة‬ ‫ثواني آخرى. ترى هل تم إظهار المثلث مرة آخرى ؟؟ لن تجده و السبب هو أننا قد أخبرنا مكتب جلوت بأن تستدعي دالة‬ ‫ Timer1 ‬بعد عشرة ثواني و قد تم ذلك و لم نخبرها أننا نريد تكرار هذا الأمر. لمزيد من التوضيح نحتاج في كل مرة أن‬ ‫نقوم بإخبار مكتبة جلوت أننا نريد إستدعاء دالة Timer1 بعد 10000 ميلي ثانية. سنقوم بالتعديل على دالة‬ ‫Timer1 لتكون كالتي :‬
void Timer1(int i)
{
glBegin(GL_TRIANGLES);
glColor3f(1.0,0.0,0.0);
glVertex2f(0.8, 0.8);
glColor3f(0.0,0.5,1.0);
glVertex2f(0.6, -0.5);
glColor3f(1.0,1.0,0.0);
glVertex2f(-0.5, 0.0);
glEnd();
glFlush();
glutSwapBuffers();
glutTimerFunc(10000,Timer1,0);
}
‫و هذا أشبه بعملية ال‪ Recursion‬أي عندما تستدعي الدالة نفسها فتكون شبيهه بحلقة تكرارية يتم تنفيذ فيها أوامر الدالة‬ و لكن هنا سيتم تنفيذ أوامر الدالة ثم إخبار مكتبة جلوت بإستدعاء نفس الدالة بعد عشرة ثواني. و بهذه الطريقة نجد أن‬ ‫الرسمة ستظهر أو سيتم تجديدها كل عشرة ثواني. و إذا ما قمنا بعمل أي تغيير يستدعي إعادة الرسم ستقوم مكتبة جلوت‬ ‫بإستدعاء ‪ Display‬و يتم محو الرسمة ثم بعد إتمام العشرة ثواني سيتم إظهار المثلث من جديد. طبعاً هذه ليست الطريقة‬ ‫المثلى للتحكم بالزمن في اللعبة فهناك طرق أفضل سنذكرها لحقاً.‬
‫عادةً في الألعاب (أو على القل فيما قمت ببرجمته) لا نحتاج أن نخبر مكتبة جلوت بأننا نريد إستدعاء دالة معينة إذا‬ ‫ما حدث تغيير على النافذة مثل التكبير أو تغيير الحجم أو نحو ذلك لن الرسمه سيتم إعادة رسمها تلقائياً كل مدة معينة نقوم‬ ‫نحن بتحديدها لذلك لسنا بحاجة لمعرفة ما إذا كان هناك تغير أم ل فلسنا بحاجة لستخدام دالة ‪ glutDisplayFunc‬ و‬ ‫لسنا بحاجة لعمل دالة مثل ‪ .Display‬و ستكون كل أوامر الرسم في دالة المؤقت مثل Timer1 ‬في المثال السابق. و لكن‬ ‫بعض المترجمات تتعسف في هذا الأمر و تأبى إلا أن تضع دالة تقوم بعمل ال ‪ .Display‬في ‪ gcc‬ في اللينوكس لا يطلب مني‬ ‫ذلك فلو لم تقم بإستعمال دالة ‪ glutDisplayFunc‬سيسمح لك. لكن في مترجم ‪ CodeBlocks‬ يأبى إل أن تقوم‬ ‫بذلك فنحن نخدعه كما نخدع الطفل الصغير و نقوم بعمل دالة فارغة يقوم بإستدعائها حتى يسكت !!‬
‪void DummyFunction‬‬() {}
‫و هكذا قمنا بتعريف دالة فارغة لا قيمة لها حتى إننا أسميناها ‪ DummyFunction‬ و سنقوم بإخبار مكتبة جلوت أن‬ ‫تستدعيها حتى لا توقف عمل البرنامج بسبب عدم وجودها:‬
glutDisplayFunc(DummyFunction‬‬);
‫و لكي ترى بعينك تحريكاً على النافذة يستعمل هذه الطريقة في عمل العديد من اللقطات في دالة التوقيت. إنظر معي إلى‬ ‫البرنامج التالي الذي يرسم مثلث متحرك من أسفل الشاشة إلى أعلها. سيقوم هذا البرنامج برسم مثلث و هذا المثلث سيتغير مكانه‬ ‫و يسير إلى العلى. سنقوم في البداية بجعل الكود عادي و في مرة آخرى سنضع أوامر تبطئ المعالج حتى ندرك الفرق. قم‬ ‫بكتابة الكود التالي:‬
#include <GL/gl.h>
#include <GL/glut.h>
float YY = -1;
void Timer(int I)
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(1,1,1);
glVertex2f(0.5,YY);
glVertex2f(-0.5,YY);
glColor3f(0,1,0);
glVertex2f(0,YY+0.2);
glEnd();
glFlush();
glutSwapBuffers();
YY += 0.01;
glutTimerFunc(2,Timer,0);
}
void Dummy() {};
int main (int argc , char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(0,0);
glutInitWindowSize(300,300);
glutCreateWindow(“Motion Test 1”);
glutDisplayFunc(Dummy);
glutTimerFunc(2,Timer,0);
glutMainLoop();
return 0;
}
‫في هذا البرنامج قمنا بعمل متغير عام و أسميناه ‪ YY‬ و قمنا بإعطائه قيمة -1 في البداية. ثم في دالة التوقيت ‫قمنا برسم مثلث و قمنا بوضعه حول نقطة ‪ YY‬ ثم قمنا بإضافة 0.01 على قيمة المتغير ‪ .YY‬ ثم في النهاية قمنا بإستدعاء‬ ‫الدالة ‪ Timer‬مرة آخرى عن طريق إخبار مكتبة جلوت بأن تستدعيها بعد 2 ميلي ثانية. و في ‫كل مرة ستتغير قيمة ‪YY‬‬
‫لأننا في كل مرة نزيد عليها 0.01 و لهذا ستجد أن المثلث يتحرك للعلى كما في الشكل التالي.‬
‫ثم نعود مرة آخرى للحديث عن التحكم في الزمن. و لقد قمت بعمل رسم توضيحي أو خريطة زمنية توضح كيفية عمل هذه‬ ‫المؤقتات لنعرف كيف نتحكم بالزمن (و من ثم السرعة). أولًا يجب عليك أن تعلم أن الطريقة المثلى لتنظيم الوقت في اللعبة‬ ‫هي التي تجعل اللعبة تعمل بنفس السرعة على المعالجات البطيئة نسبياً و بنفس السرعة على المعالجات ‫السريعة و ما يأتي بعدها. فمثلً إذا عملت لعبة تستطيع أن تعمل على معالجات بنتيوم2. يجب أن تعمل بنفس السرعة على‬ المعالجات التي سوف تكون بعد مائة عام !! و هذا الرسم يوضح المقصود من هذا الأمر. إنظر للصورة:‬
‫فكما ترى فإن اللون الأسود هو الوقت الذي يأخذه المعالج المتوسط العادي لكي يقوم بكل عمليات المعالجة و الرسم في لعبة من الألعاب‬ ‫أي أن مكتبة جلوت تقوم بإستدعاء الدالة التي فيها أوامر تحديث الرسم و معالجة تغيرات اللعبة. و‬ ‫الوضع الطبيعي أن تكون هناك فترة ما بين إنتهاء الأوامر و ما بين إبتداء الأوامر مرة آخرى بعد تمام 30 ميلي ثانية الآخرى.‬ ‫و بهذا ستجد أن في المعالجات البطيئة ستعمل اللعبة هكذا:

ستجد أن الوقت الذي إستغرقه الحاسوب لإتمام عمليات اللعبة أكبر نتيجة للبطء. و لكننا لن نلحظ هذا الأمر لننا مازلنا نرى أن‬ ‫السرعة ثابتة و أنه يتم تحديث الصورة كل 30 ميلي ثانية. رغم أن العمليات أبطأ و أخذت وقت أطول. و كذلك في معالجات ‫السريعة:‬
‫هنا سنجد أن هذا الحاسوب قام بكل العمليات في سرعة كبيرة و في وقت وجيز. إلا أن سرعة اللعبة مازالت ثابتة لأننا نقوم‬ ‫بتكرار العمليات كل 30 ميلي ثانية. الآن علمنا أن الطريقة المثلى للتحكم في الزمن و السرعة في اللعبة (أو البرنامج) هي أن‬ ‫نحدد فترة زمنية معينة و نكون متأكدين أن تلك الفترة سوف تكفي لكي تجعل الحاسوب ينهي كل العمليات المطلوبة ليدخل فيها‬ ‫مرة آخرى حتى يتم تحديث اللعبة بإستمرار.
‫إلى الآن ذكرنا الطريقة المثلى التي نريد أن نقوم بها في عملية ضبط الزمن و لكننا لم نحولها إلى كود بعد. إنظر إلى‬ ‫دالة التوقيت تلك و حاول أن تعرف ما الخطأ فيها.‬
void TT(int X)
{
/* كل أوامر اللعبة هنا */
glutTimerFunc(10,TT,0);
}
‫طبعاً هذه الطريقة بها العيب الآتي:‬

ربما تلاحظ أإنها تقوم بتنفيذ العمليات جيداً. و في وقت جيد حسب تجربتك للعبة على حاسوبك. و لكنك ستفاجئ بمشكلة عندما تختبر اللعبة‬ ‫على حاسب آخر. لنتتبع الكود , إنه يقوم بتنفيذ العمليات كلها و لنفرض أنه قام بها كلها في 20 ميلي ثانية على حاسوبك. ‫ثم إنتهى و جاء دور دالة ‪ glutTimerFunc‬ حيث قالت لمكتبة جلوت أن تستدعي نفس الدالة بعد 10 ميلي ثانية. سنجد‬ ‫أنه يتم تحديث اللعبة كل 30 ميلي ثانية. و لكن ما العمل إذا ما إختبرنا اللعبة على حاسب أسرع ؟؟‬

سنجد أن اللعبة قد أسرعت بطريقة جنونية. و السبب هو أن الحاسوب قام بتنفيذ الأوامر كلها على سبيل المثال في 10 ميلي‬ ‫ثانية ثم أخبرنا مكتبة جلوت بأننا نريد أن نستدعي نفس الدالة بعد 10 ميلي ثانية. و هكذا جعلنا التحديث يتم كل 20 ميلي‬ ‫ثانية و ليس 30 و هذا ما ل نريده. و كذلك سنجد نفس الأمر في الحواسيب البطيئة إذا ما إستعملنا تلك الطريقة:‬
سنجد أن الحاسوب قد قام بتنفيذ الأوامر في 28 ميلي ثانية ثم أخبرنا مكتبة جلوت بأننا نريد إستدعاء نفس الدالة بعد 10 ‫ميلي ثانية و هكذا سيتم تحديث اللعبة كل 38 ميلي ثانية. و حتى تتأكد من هذا الأمر سنقوم في البرنامج الذي كتبناه في‬ ‫البداية الذي كان يقوم بتحريك مثلث إلى العلى بتعديل بسيط يثبت صدق هذا الكلام. و هذا التعديل هو أننا سنقوم في دالة‬
‫‪ Timer‬بوضع عملية حسابية ثقيلة تجعل الحاسب يستغرق بعض الوقت للنتهاء منها و لهذا ستتغير سرعة المثلث. و لنأخذ على سبيل المثال العديد من العمليات الحسابية من الدوال المثلثية مثل ‪ sin‬ و ‪ cos‬ الموجودان في الملف الرأسي ‪ .math.h‬قم بتضمين ملف‬ ‫‪ math.h‬ ثم غير دالة ‪ Timer‬ لتكون:‬
void Timer(int I)
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(1,1,1);
glVertex2f(0.5,YY);
glVertex2f(-0.5,YY);
glColor3f(0,1,0);
glVertex2f(0,YY+0.2);
glEnd();
glFlush();
glutSwapBuffers();
YY += 0.01;
for (int i = 0 ; i < 9999999 ; i++)
cos(3.5);
glutTimerFunc(2,Timer,0);
}
‫حيث قمنا بجعل دالة ‪ cos‬تتكرر عشرة ملايين من المرات !! قم بتجربة البرنامج في هذه الحالة و لاحظ أن سرعة‬ ‫سير المثلث أصبحت أبطأ بكثير و هذا للسبب الذي ذكرناه. أن عملية إستدعاء دالة ‪ glutTimerFunc‬ في آخر الدالة ليست‬ ‫أكثر من مجرد أمر مثل ‪ Delay‬أو ‪ Sleep‬الذي ينتظر مدة محددة يحددها مستدعي الدالة. ما العمل إذاً لحل تلك المشكلة ؟؟‬ ‫و كيف نصل للطريقة التي ذكرناها في البداية التي تجعل اللعبة (أو البرنامج) يعمل بنفس السرعة في كل الأحوال ؟؟ الأمر‬ ‫بسيط جداً , ما رأيك لو أننا وضعنا إستدعاء دالة ‪ glutTimerFunc‬ في بداية الدالة و ليس في نهايتها ؟؟ عندها سيتم‬ ‫إخبار مكتبة جلوت أننا نريد مثلًا إستدعاء تلك الدالة بعد 20 ميلي ثانية, ثم مثلًا لو إفترضنا أن المعالج إستغرق 15 ميلي‬ ‫ثانية في معالجة تلك البيانات. فإنه يتبقى 5 ميلي ثانية حتى يتم إستدعاء الدالة مرة آخرى و لو مثلً إستغرق 10 ميلي‬ ‫ثانية فإنه يتبقى 10 ميلي ثانية آخرى لكي يتم إستدعاء الدالة مرة آخرى و هكذا نقوم بحل المشكلة. الآن سوف نقوم بعمل‬ ‫برنامج كتجربة لهذه المسألة. سنقوم بعمل برنامج به نفس المثلث الذي يسير من أسفل إلى أعلى. في مرة سنضع أوامر رسم‬ ‫المثلث فقط و في مرة آخرى سنضع أوامر ثقيلة جداً لتبطيء المعالج و سنرى أن سرعة سير المثلث لم تقل قط. نحن بحاجة إلى‬ ‫أن نعرف الوقت الذي أخذته دالة المؤقت التي أسميناها ‪ . Timer
‫سوف نقوم بإستعمال دالة ‪ ftime‬الموجودة في المكتبة timeb ‬لنقيس الفرق بين فترتين من فترات تحديث صورة أو لقطة من اللعبة. مثلًا عندما نستدعي ‬ دالة التوقيت بعد 50 ميلي ثانية فإن الفرق بين كل تحديث للعبة هو في حدود ال 50 ميلي ثانية. سنقوم بقياس‬ ‫هذا الزمن الآن. قم بكتابة هذا الكود:‬
#include <GL/gl.h>
#include <GL/glut.h>
#include <math.h>
#include <stdio.h>
#include <sys/timeb.h>
float YY = -1;
timeb t1,t2;
void Timer(int I)
{
glutTimerFunc(50,Timer,0);
ftime(&t2);
printf(“%d\n”,(int)(1000*(t2.time-t1.time)+(t2.millitm-t1.millitm)));
t1 = t2;
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(1,1,1);
glVertex2f(0.5,YY);
glVertex2f(-0.5,YY);
glColor3f(0,1,0);
glVertex2f(0,YY+0.2);
glEnd();
glFlush();
glutSwapBuffers();
YY += 0.01;
for (int i = 0 ; i < 999999 ; i++)
cos(3.6);
}
void Dummy() {};
int main (int argc , char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(0,0);
glutInitWindowSize(300,300);
glutCreateWindow(“Motion Test 1”);
glutDisplayFunc(Dummy);
ftime(&t1);
glutTimerFunc(50,Timer,0);
glutMainLoop();
return 0;
}
‫لاحظ أننا قد إستعملنا دالة ‪ printf‬في هذا البرنامج و التي تعمل على شاشة سطر الأوامر و ليس على النوافذ‬ ‫الرسومية التي ننشئها بمكتبة جلوت. طبعاً تذكر أننا في الفصل السابق قلنا بأننا سوف نحتاج إلى إستعمال شاشة سطر الأوامر‬ ‫‪ Console‬و لذلك إذا كنت تستخدم ‪ monodevelop‬قم بتفعيل ظهور شاشة ال‪ Console‬حتى تقوم بمشاهدة‬ ‫النتائج. أو ربما يكون للبعض سبيل آخر في هذا حيث يفضلون أن يقوموا بإخراج النتائج التي يريدون طباعتها على ملف نصي‬ ‫بدلً من سطور على ‪ Console‬و لكل منهم وجهة نظر صحيحة.‬ ‫ليس عليك أن تتعرف على تفاصيل دالة ‪ ftime‬و مكتبة ‪ timeb‬و ليس عليك أن تتقنها. فقط كل ما فعلت هو أنني‬ ‫عرفت كائنين من المركب ‪ struct timeb‬الذي يحمل بيانات الوقت بدقة الميلي ثانية. ثم قمت في بداية البرنامج بأخذ‬ ‫قيمة الوقت كما تلحظون في هذا السطر:‬
‪ftime(&t‬1);
‫ثم قمت في دالة ‪ Timer‬التي يجب أن تتكرر كل 50 ميلي ثانية بقياس الزمن و وضعه في الكائن الثاني t2 ‬و طباعة‬ ‫الفرق بين الزمنين ثم وضعت قيمة t2 ‬في الكائن t1 ‬حتى يحسب الفرق الجديد بين هذا الإستدعاء و الإستدعاء الذي يليه و هكذا. قم‬ ‫بتشغيل البرنامج و لكن تابع ما يحدث داخل نافذة سطر الأوامر التي في العادة تكون خلف النافذة التي أنشئناها. و أنظر إلى‬ ‫النتيجة. ستجد فعلً أن الفرق بين كل تحديث و تحديث حوالي 50 ميلي ثانية بالفعل. قم بعد ذلك بحذف السطرين الأخرين في‬ ‫دالة ‪ Timer‬الذان يثقلان الدالة ثم شغل البرنامج و ستجد أن الوقت بين كل تحديث و تحديث أيضاً حوالي 50 ميلي ثانية.‬ ‫و هذا ما أريد أن أصل إليه في النهاية. هو أن يعمل البرنامج بنفس السرعة سواء كان هناك ما يبطئه أم لا.‬
‫كما تلاحظ فإن المثلث في كلتا الحالتين يسير بنفس السرعة سواء في وجود بطء أو في عدم وجوده. و كذلك الحال عندما تجرب‬ ‫البرنامج على حاسوب بطيء. ستجد أن السرعة في كل الحالات ثابتة. و هذا ما أردت أن أصل إليه في هذا الفصل. ليس فقط عمل‬ ‫رسوم متحركة على الشاشة و لكن أردت أن أجعل سرعة الحركة ثابتة في جميع الحالت. و طبعاً هذا ينطبق فقط على سرعة‬ ‫معينة للمعالجات. فمثلًا لا يمكن أن تعمل لعبة قوية على حاسوب صنع في الثمانينيات مثلًا أو في أوائل التسعينيات. لأنه ربما‬ ‫يكون هناك حاسوب قديم يأخذ وقت أكبر من الذي حددته في معالجات أوامر اللعبة. و حينئذ فإن دالة ‪ glutTimerFunc‬‬‫إذا وجدت أن الدالة التي سوف تستدعيها قد جاء وقت إستدعائها و لكن مازال هناك إستدعاء آخر لم ينتهي فإنها ستنتظر حتى‬ ‫ينتهي ثم تستدعيها مرة آخرى و لهذا ربما يحدث بعض البطء و إختلف السرعة في بعض الجهزة القديمة. و لكن في الغالب إذا إتبعت هذه الطريقة‬ ‫التي وصلنا إليها في النهاية سيضبط سرعة اللعبة جيداً إن شاء الله