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


تعرفت في بداية رحلتك في البرمجة بلغة سي و سي++ أننا نستقبل المدخلات عن طريق الأمر ‪ .scanf‬و في بعض‬ ‫الأحيان عن طريق ‪ getch‬ إلا أن هذه لا محال لإستعمالها في برمجة الألعاب إذ أن الأمر ‪ getch‬يجعل البرنامج يتوقف حتى‬ ‫يتم ضغط أي زر ثم ترجع تلك الدالة قيمة ‪ ASCII‬لهذا الزر. و كذلك في دالة ‪ scanf‬فإنها تأخذ البيانات ثم تضغط أنت‬ ‫‪ Enter‬ كي يتم تمريرها و هذا لا نريده في برمجة اللعاب. في الواقع إن مكتبة ‪ glut‬وفرت لنا الوسيلة لذلك. تذكر في‬ ‫دالة ‪ glutTimerFunc‬و دالة ‪ glutDisplayFunc‬ كنا نجعل مكتبة جلوت تقوم بإستدعاء الدالة التي نذكرها لها‬ ‫في ظروف معينة مثل أن يتم تغيير على النافذة يحتاج إلى إعادة رسم أو أن يمر وقت معين أو أن يكون البرنامج في حالة‬ ‫سكون مثلًا. نفس المسألة موجودة في إستقبال المدخلات من المستخدم فسوف نقوم بإخبار مكتبة جلوت بأن تقوم بإستدعاء الدالة‬ ‫الفلانية إذا ما ضغط المستخدم على أحد أزرار لوحة المفاتيح و تمرر لنا الزر الذي تم ضغطه في إحدى البارامترات و كذلك‬ ‫الحال مع مؤشر الفأرة و تمرر لنا بارامترات توضح الموضع الجديد للمؤشر و نحو ذلك.
‫إستقبال مدخلات لوحة المفاتيح:‬
‫بإختصار , هذه الدالة ستقوم مكتبة جلوت بإستدعائها عندما يضغط المستخدم على أحد حروف لوحة المفاتيح. إنظر‬ ‫إلى الكود التالي:‬
void MyKeyboard(unsigned char Key , int x , int y)
{
switch (Key)
{
case 27:
‫أغلق البرنامج /////‬
break;
case ‘\t’:
‫… كود ////‬
break;
case ‘\n’:
‫… كود ////‬
}
}
‫إذا كنت على علم بجدول أسكي فأعتقد أنك قد فهمت كل شئ !! تبدوا الحالة الأولى التي إختبرنا فيها هل الزر الذي ضغطه المستخدم هو ‪ Escape‬أم لا. حيث أن الرقم 27 هو رقم الزر ‪ . Escape‬على أي حال هذه هي شكل الدالة التي سوف‬ ‫ننشأها لإستقبال المدخلات من المستخدم و هذه الدالة طبعاً يمكن أن نسميها بأي إسم و هي تستقبل ثلاث أشياء. الأول هو متغير‬ ‫من نوع ‪ unsigned char‬و هو متغير حرفي. و هو يعبر عن الآسكي الخاص بالزر الذي ضغط عليه المستخدم. و المتغير‬ ‫الثاني و الثالث هما محور السينات و الصادات X and Y Axis لمؤشر الماوس حينما ضغط المستخدم أحد الزرار و طبعاً قيمتهم ليست كما‬ ‫نستعملها في ‪ OpenGL‬تتراوح ما بين -1 إلى 1 بل إن قيمتهم بالبكسل أي إذا كان المؤشر أعلى يسار الشاشة فإن قيمة ‪x‬‬ ‫و ‪) y‬أو المتغير الثاني و الثالث حيث يمكن تسميته بأي إسم) هي صفر و صفر و ليس -1 و 1 و إذا كانت النافذة مثلً بطول‬ 150 في 300 و المؤشر في وسط النافذة فإنقيمة ‪ x‬ و ‪ y‬ هي 75 و 200 و ليس صفر و صفر. بعد ذلك في الدالة‬ ‫الرئيسية ‪ main‬ نقوم بإخبار مكتبة جلوت أننا نريد أن نستدعي تلك الدالة في حالة إذا ما ضغط المستخدم أي حرف من حروف‬ ‫لوحة المفاتيح.
‪glutKeyboardFunc(MyKeyboard‬‬);
‫عندئذ سيتم إستدعاء هذه الدالة التي أنشأناها و أخبرنا مكتبة جلوت أن تقوم بإستدعائها عندما يضغط المستخدم على أي حرف‬ ‫من لوحة المفاتيح و بالتالي فإن قيمة المتغير ‪ key‬ ستكون هي قيمة الآسكي للزر الذي تم ضغطه. و حينها يمكنك داخل الدالة‬ ‫أن تختبر قيمة ‪ key‬ لتعرف ما هو الزر الذي تم ضغطه و بالتالي تتخذ القرار الذي تريد. لنجرب الآن مثالً عملياً بسيطاًُ‬ ‫يتضمن تحريك مربع في الشاشة عن طريق لوحة المفاتيح. و لندمج ما درسناه في الفصل السابق مع هذا الفصل لنعمل برنامجاً‬ ‫فيه تحريك كائنات و مؤقتات و كذلك تفاعل مع المستخدم. ليكن البرنامج عبارة عن مربع يتحرك في النافذة تلقائياً و آخر‬ ‫نحركه بأزرار لوحة المفاتيح.‬
#include <GL/gl.h>
#include <GL/glut.h>
float X1=0,X2=0,Y1=0,Y2=0;
int Direction = 1;
int Win;
void Display(int V)
{
glutTimerFunc(50,Display,0);
glClear(GL_COLOR_BUFFER_BIT);
if (Direction == 1)
{
X1 += 0.05; Y1 += 0.05;
}
else
{
X1 -= 0.05; Y1 -= 0.05;
}
if (Y1 > 0.9)
Direction = 0;
else if (Y1 < -0.9)
Direction = 1;
glBegin(GL_QUADS);
glColor3f(1,0,1);
glVertex2f(X1-0.1,Y1-0.1);
glVertex2f(X1+0.1,Y1-0.1);
glColor3f(0,0,0);
glVertex2f(X1+0.1,Y1+0.1);
glVertex2f(X1-0.1,Y1+0.1);
glColor3f(0,1,1);
glVertex2f(X2-0.1,Y2-0.1);
glVertex2f(X2+0.1,Y2-0.1);
glColor3f(0,0,0);
glVertex2f(X2+0.1,Y2+0.1);
glVertex2f(X2-0.1,Y2+0.1);
glEnd();
glFlush();
glutSwapBuffers();
}
void KB(int K , int xx , int yy)
{
switch (K)

{
case ‘w’:
Y2 += 0.01;
break;
case ‘s’:
Y2 -= 0.01;
break;
case ‘a’:
X2 -= 0.01;
break;
case ‘d’:
X2 += 0.01;
break;
case 27:
glutDestroyWindow(Win);
}
}
void DUMMY() {}
int main(int argc,char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
Win = glutCreateWindow(“Keyboard Test”);
glutFullScreen();
glClearColor(0,0,0,1);
glutTimerFunc(50,Display,0);
glutDisplayFunc(DUMMY);
glutKeyboardFunc(KB);
glutMainLoop();
return 0;
}
‫و عندما نجرب البرنامج سنجد أنه بهذا الشكل. مربع يتحرك في بميل من أعلى اليمين إلى أسفل اليسار و مربع آخر ثابت و إذا‬ ‫ضغطت على الأزرار‬ w و s و d و a فإنه يتحرك معك.

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

‫و الآن لنشرح الكود الذي قمنا بكتابته و تجريبه. الفكرة تعتمد على أن هناك دالة تقوم مكتبة جلوت بإستدعائها كل 50 ميلي‬ ‫ثانية لعمل تحريكات بسرعة ثابتة (راجع الفصل الثاني). في هذه الدالة نقوم برسم مربعين و قد حددنا مكان المربعين و قلنا‬ ‫في المربع الأول أنه حول X1 ‬و Y1. ‬و هذين المتغيرين يحملان مكان المربع الأول. و تقوم نفس الدالة بتغيير قيمة هذين‬
‫المتغيرين طبعاً حتى ترسم المربع المرة القادمة في مكان مختلف (طبعاً هذه المتغيرات عمومية على كل البرنامج تم تعريفهم‬ ‫خارج الدالة). و هناك متغير آخر يشير إلى إتجاه حركة المربع (نحن نتحدث عن المربع الذي يتحرك بنفسه). و هو المتغير‬ ‫‪ .Direction‬إذا كانت قيمته 1 فإن المربع يتحرك إلى فوق و إلى اليمين و إذا كانت صفر فإنه سيتحرك إلى تحت و إلى‬ ‫اليسار. قمنا في الدالة بإختبار قيمة المتغير ‪ Direction‬فإذا كانت 1 فسنزيد قيمة X1 ‬وY1 ‬و إذا كانت غير ذلك‬ ‫فإننا ننقص قيمة المتغيرين. و طبعاً نختبر أيضاً هل مكان المربع في ال X1 و Y1‬قد تجاوز -0.9. فإذا كان كذلك عندها‬ ‫فإننا نقوم بجعل قيمة المتغير ‪ Direction‬بقيمة واحد و حينها فإن الدالة عندما تستدعى في المرة القادمة فسوف تجد أن‬ ‫المتغير ‪ Direction‬قيمته واحد و تبدأ في زيادة المتغيرين X1 ‬و Y1 ‬و حينها ستجد أن المربع أصبح يمشي في التجاه‬ ‫العكسي و هكذا. إنظر إلى الكود:‬
if (Direction == 1)
{
X1 += 0.05;
Y1 += 0.05;
}
else
{
X1 -= 0.05;
Y1 -= 0.05;
}
‫هنا نختبر هل قيمة المتغير تساوي 1 ؟ فإذا كانت كذلك فإننا نغير مكان المربع فنزيد X1 ‬و Y1 ‬بقيمة 0.05 و إن كان غير ذلك‬ ‫فإننا نقلل قيمة المتغيرين. و إنظر إلى هذا الكود :‬
if (Y1 > 0.9)
Direction = 0;
else if (Y1 < -0.9)
Direction = 1;
‫هنا نغير إتجاه سير المربع إذا تجاوز نقطة معينة كما هو واضح في الكود. ثم إنظر إلى أوامر رسم المربعين في هذا الكود:‬
glBegin(GL_QUADS);
glColor3f(1,0,1); glVertex2f(X1-0.1,Y1-0.1); glVertex2f(X1+0.1,Y1-0.1);
glColor3f(0,0,0); glVertex2f(X1+0.1,Y1+0.1); glVertex2f(X1-0.1,Y1+0.1);
glColor3f(0,1,1); glVertex2f(X2-0.1,Y2-0.1); glVertex2f(X2+0.1,Y2-0.1)
glColor3f(0,0,0); glVertex2f(X2+0.1,Y2+0.1); glVertex2f(X2-0.1,Y2+0.1);
glEnd();
‫قلنا لمكتبة الرسوم المفتوحة ‪ OpenGL‬ أننا نريد أن نرسم أشكال رباعية. في أول سطرين بعد ‪ glBegin‬ رسمنا أول‬ ‫نقطتين بلون و نقطتين آخرتين بلون آخر و النقطتين كانوا حول X1 ‬و Y1 ‬إذ أن محور السينات للمربع الول يتراوح ما بين‬ ‫0.1-X1 ‬و 0.1+X1 ‬و كذلك في محور الصادات. ثم قمنا برسم المربع الثاني حول نقطة X2 ‬و Y1 و لاحظ أننا عندما‬ ‫إنتهينا من رسم المربع الأول لم نقل ‪ glEnd‬ حيث أننا في المربع الثاني سنقوم برسم شكل رباعي أيضاً و نكرر نفس الشئ. عندما نحدد الشكل سواء كان شكل رباعي أو مثلث أو غير ذلك فإن‬
‫مكتبة الرسوم تأخذ أول أربع إستدعائات لدالة ‪ glVertex‬ على أنهم مربع ثم بعدها تأخذ أربع إستدعائات آخرى على أنهم‬ ‫مربع آخر و هكذا. و لسنا بحاجة إلى أن نقوم في كل مرة بإستدعاء ‪ glBegin‬إذا كنا سنرسم نفس الشكل. إل في حالات‬ ‫آخرى سنذكرنا لحقاً إن شاء الله. ثم قمنا في دالة ‪) KB‬بعد أن أخبرنا مكتبة جلوت بأن تستدعيها إذا ضغط المستخدم على أي‬ ‫حرف في لوحة المفاتح) بإختبار قيمة الحرف الذي ضغطه المستخدم فإختبرنا قيمة المتغير ‪:K‬‬
void KB(int K , int xx , int yy)
{
switch (K)
{
case ‘w’:
Y2 += 0.01;
break;
case ‘s’:
Y2 -= 0.01;
break;
case ‘a’:
X2 -= 0.01;
break;
case ‘d’:
X2 += 0.01;
break;
case 27:
glutDestroyWindow(Win);
}
}
‫و كما هو ملاحظ و مشاهد أننا قمنا بتغيير قيم مكان المربع الثاني عن طريق تغيير قيمة X1 و Y2 ‬حسب الذي ضغطه‬ ‫المستخدم. و كذلك قلنا أنه إذا ضغط المستخدم على الزر ‪ Escape‬ الذي رقمه 27 فإننا نغلق البرنامج. و لنقفز سريعاً‬ ‫إلى الدالة الرئيسية ‪ main‬حيث سأخبرك بشئ في مكتبة جلوت. إنظر إلى السطر التالي الذي كتبناه في الكود :‬
Win = glutCreateWindow(“Keyboard Test‬‬”);
‫لاحظ أن دالة ‪ glutCreateWindow‬تقوم بإرجاع قيمة من نوع عدد صحيح ‪ .int‬تحمل هذه القيمة رقم النافذة التي‬ ‫تم إنشائها. حيث أننا يمكننا أن نقوم بإنشاء عدة نوافذ في نفس البرنامج و نستدعي دالة ‪ glutCreateWindow‬عدة‬ ‫مرات و في كل مرة نضع أحدى المتغيرات لكي يستقبل رقم النافذة و إذا أردنا النتقال من نافذة إلى آخرى لكي نرسم عليها‬ ‫نقول ‪ glutSetWindow‬ ثم نعطيه قيمة النافذة التي نريدها و إذا أردنا أن‬ ‫نغلق نافذة نقول ‪ glutDestroyWindow‬و نعطيه قيمة النافذة التي نريد غلقها. إلى هنا نكون قد أعطينا مقدمة عن‬ ‫كيفية إستقبال مدخلات المستخدم. و الآن لنناقش بعض المشاكل التي قد تواجهنا في هذا الموضوع.‬
‫أولًا في الدالة التي سبق أن شرحناها و التي هي ‪ glutKeyboardFunc‬ نقوم بإعطائها الدالة التي نريد أن‬ ‫نستدعيها و مكتبة جلوت بدورها تقوم بإستدعاء الدالة التي طلبناها و تقوم بتمرير لها رقم آسكي للزر الذي تم ضغطه. و‬ ‫لكن ما رأيك بالزر ‪ Up‬و ‪ Down‬و ‪ Left‬ و ‪ Right‬و ‪ Control‬و ‪ Alt‬ ؟؟ و كذلك الزرار F1 ‬إلى F12‬ ؟؟ هل‬ ‫لها مكاناً في جدول آسكي ؟؟ حينها لن تنفعنا الدالة التي ذكرناها بل هناك دوال آخرى للتعامل مع تلك الزرار. في مكتبة جلوت تم تقسيم‬ ‫أزرار لوحة المفاتيح إلى ثلاثة أقسام. إما حرف أو زر خاص أو زر تغيير. أما الزرار التي لها رقم في جدول آسكي فنحن قد‬ ‫رأيناها في المثال السابق و قد عرفنا كيف نتعامل معها مثل ‪ Escape‬ و ‪ Tab‬و ‪ Backspace‬ و ‪ Enter‬ و بقية‬ ‫الحروف و الأرقام. أما الزرار الخاصة فهي مثل السهم و الأزرار منF1 ‬إلىF12 ‬و ‪ PageUp‬ و ‪ Home‬ و غيرها‬ ‫من الأزرار التي ليس لها رقم في جدول آسكي. و أما الزرار التغييرية ‪ Modifiers‬ فهي مثل ‪ Control‬و ‪ Shift‬ و‬ ‫‪ . Alt‬ و سميت بالزرار التغييرية ‪ Modifiers‬لنها عادةً ما يتم الضغط عليها مع زر آخر للوصول إلى وظيفة ثانوية‬ ‫للزر. و لذلك وضعت وحدها في دالة تقوم بإرجاع قيمة الزر التغييري المضغوط. لنها لو إعتبرناها أزرار عادية فربما مثلاً ‫نريد أن نجعل الوحش الذي نلعب به يبطئ الحركة إذا ضغطنا على الزر ‪ Control‬مع ‪ Up‬و يسرع في الحركة إذا ضغطنا على‬ ‫‪ Alt‬ مع ‪ Up‬و إذا ضغطنا على ‪ Up‬ فقط فإنه سيسير بالسرعة العادية. فإذا كانت الزرار التغييرية تأتي مع باقي الأزرار‬ ‫في دالة واحدة فإن ‪ Control‬سيأتي وحده و ‪ Up‬سيأتي وحده فلن نستطيع أن نعرف هل تم الضغط عليها مرة واحدة أم كل‬ ‫منهم على تم الضغط عليه وحده. و في المثال القادم سنوضح كيف سنعمل بالزرار الخاصة و التغييرية. ثم بعد ذلك سنشرح‬
‫الكود بأكمله. جرب تشغيل البرنامج التالي و تأمل الكود :‬
#include <GL/gl.h>
#include <GL/glut.h>
float X=0,Y=0;
int W;
void Display()
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glColor3f(1,1,1);
glVertex2f(X-0.1,Y-0.1); glVertex2f(X+0.1,Y-0.1);
glColor3f(1,0,0);
glVertex2f(X+0.1,Y+0.1); glVertex2f(X-0.1,Y+0.1);
glEnd();
glFlush();
glutSwapBuffers();
}
void Keyboard(int K , int x , int y)
{
int M;
float delta;
M = glutGetModifiers();
if (M == GLUT_ACTIVE_CTRL)
delta = 0.005;
else if (M == GLUT_ACTIVE_ALT)
delta = 0.15;
else delta = 0.05;
if (K == GLUT_KEY_UP)
Y += delta;
else if (K == GLUT_KEY_DOWN)
Y -= delta;
else if (K == GLUT_KEY_LEFT)
X -= delta;
else if (K == GLUT_KEY_RIGHT)
X += delta;
Display();
}
void Keyboard2(unsigned char K , int x , int y)
{
if (K == 27)
glutDestroyWindow(W);
}
int main(int argc , char** argv)
{
glutInit(&argc,argv);
W = glutCreateWindow(“Keyboard Test 2”);
glutFullScreen();
glutKeyboardFunc(Keyboard2);
glutSpecialFunc(Keyboard);
glutDisplayFunc(Display);
glutMainLoop();
}
‫هذا البرنامج يقوم برسم مربع على الشاشة. نحركه بأزرار السهم و نبطئ حركته بالزر ‪ Control‬ و نسرعها بالزر ‪.Alt‬‬ ‫أما عن ميكانيكية عمل البرنامج فقد قمنا في هذا البرنامج بعمل متغيرين عموميين ‪ X‬ و ‪ Y‬و هما يعبران عن مكان المربع و يتم‬ ‫تغيير قيمة المتغيرين في دالة الزرار الخاصة التي قلنا لمكتبة جلوت أن تقوم بإستدعائها في حالة إذا ضغط المستخدم على زر ‫من الأزرار التي ليس لها رقم في جدول آسكي. إنظر إلى تلك السطور في دالة ‪main‬‬:
‪glutKeyboardFunc(Keyboard‬‬2);
glutSpecialFunc(Keyboard‬‬);
‫قمنا بعمل دالة للأزرار التي لها آسكي و دالة للأزرار الخاصة التي ليس لها آسكي و كما نرى هنا قلنا لمكتبة جلوت أن تقوم‬ ‫بإستدعاء دالة Keyboard2 ‬إذا تم الضغط على أي زر له رقم في جدول آسكي. و قلنا فيها ‬أنه إذا‬ ‫كان الزر قيمته 27 (أي ‪ (Escape‬فإننا نغلق النافذة و من ثم نغلق البرنامج. كما قلنا لمكتبة جلوت أن تقوم بإستدعاء‬ ‫دالة ‪ Keyboard‬ إذا ضغط المستخدم على زر ليس له رقم في جدول آسكي في دالة ‪ .glutSpecialFunc‬و لاحظ أن‬ ‫دالة ‪ Keyboard‬ تستقبل في البارامترات ثلث أعداد صحيحة و أولها هو رقم الزر الذي تم ضغطه (طبعاً ليس رقمه في‬ ‫جدول آسكي) و ثانيها و ثالثها ال‪ x‬و ال‪ y‬ لمؤشر الفأرة. و نحن لا يتطلب منا أن نحفظ أرقام كل زر من هذه الأزرار و‬ ‫لكن نعبر عنها بثوابت. و كذلك عند طلب معرفة ما هو الزر التغييري المضغوط فإن دالة ‪ glutGetModifiers‬ ترجع لنا‬ ‫قيمة عدد صحيح. و نحن ل نحفظ رقم الزر ‪ Ctrl‬مثلً و لكن نعبر عنه بالثوابت. إنظر ماذا فعلنا في الدالة في هذا الكود :‬
void Keyboard(int K , int x , int y)
{
int M;
float delta;
M = glutGetModifiers();
if (M == GLUT_ACTIVE_CTRL)
delta = 0.005;
else if (M == GLUT_ACTIVE_ALT)
delta = 0.15;
else delta = 0.05;
if (K == GLUT_KEY_UP)
Y += delta;
else if (K == GLUT_KEY_DOWN)
Y -= delta;
else if (K == GLUT_KEY_LEFT)
X -= delta;
else if (K == GLUT_KEY_RIGHT)
X += delta;
Display();
}
‫إن المتغير ‪ delta‬هذا يعبر عن حجم الخطوة التي يأخذها المربع في المرة الواحدة و ستكون هذه القيمة كبيرة في حالة إذا‬ ‫ضغط المستخدم على الزر ‪ Alt‬و ستكون صغيرة إذا ضغط على ‪ Control‬ و ستكون متوسطة إذا لم يضغط على أي منهما. إنظر‬ ‫إلى السطور التي بعد أخذ قيمة ‪ glutGetModifiers‬ و وضعها في المتغير ‪ . M‬ثم بعد ذلك نقوم بإختبار البارامتر‬ ‫الأول الذي أسميناه ‪ K‬ و الذي يحمل قيمة الزر الذي ضغط عليه المستخدم. ثم بعد أن غيرنا قيم ‪ X‬ و ‪ Y‬قمنا بإستدعاء دالة‬ ‫‪ Display‬ لكي نقوم بتحديث الرسمة بعد أن غيرنا مكان المربع. و لحظ أننا في هذا المثال لم نقم بعمل ‪ Timer‬يجدد‬ ‫الصورة كل مدة ثابتة. لننا ليس لدينا أي تحريك إلا ما يقوم به المستخدم عن طريق لوحة المفاتيح. حيث أن عمل مؤقتات في‬ ‫هذا المثال ليس إلا تضييع للوقت فيما لا يفيد إذ أن رسم نفس الشياء كل مرة ليس له فائدة. فلنرسمها حين نغيرها فقط. و هذا‬ ‫الأمر لن نستعمله كثيراً في اللعاب. و إليك الن قائمة بالثوابت التي تعبر عن الأزرار في جلوت:‬
GLUT_KEY_F1 → GLUT_KEY_F‬‬12
‫‪GLUT_KEY_UP , GLUT_KEY_DOWN , GLUT_KEY_LEFT , GLUT_KEY_RIGHT‬‬
‫‪GLUT_KEY_PAGE_UP , GLUT_KEY_PAGE_DOWN‬‬
‫‪GLUT_KEY_HOME , GLUT_KEY_INSERT , GLUT_KEY_END‬‬
هذه كل الخيارات في أما عن الزر ‪ Delete‬فإن له رقم في جدول آسكي و يمكن التعبير عنه بالحروف و إذا أردنا إختباره‬ ‫نقول على سبيل المثال:‬
‪if (k == 127)
‫و أما القيم التي ترجعها دالة ‪ glutGetModifiers‬ فإنها كالتي:‬
‫‪GLUT_ACTIVE_CTRL , GLUT_ACTIVE_SHIFT , GLUT_ACTIVE_ALT‬‬
‫و تتبقى عندنا مشكلة في مسألة السرعة و أن السرعة تختلف من حاسوب لآخر و من ظروف لخرى. و قد ناقشنا بعض حلول‬ ‫مشكلة السرعة في الفصل السابق عندما أردنا عمل مؤقت يقوم برسم كل شئ و عمل كل إجرائات اللعبة كل مدة محددة من الزمن و‬ ‫الآن نستعرض مشكلتان في إستقبال مدخلت لوحة المفاتيح. أولهما أننا في بعض اللعاب نقوم بالعديد من العمليات داخل دالة ‫إستقبال مدخلت لوحة المفاتيح. مثلً في حالة أننا نحرك وحشاً من الوحوش فإنه يجب علينا أن نختبر هل هذه الحركة سيكون‬ ‫فيها تداخل مع الجدار مثلًا أو مع وحش آخر ؟؟ و هذه العملية غالباً ما تأخذ وقتاً معتبراً فربما تأخذ نصف ميلي ثانية في‬ ‫أحدى الجهزة و 2 ميلي ثانية في جهاز آخر و ربع ميلي ثانية في جهاز آخر. عندئذ سنعود إلى المشكلة القديمة و هي أن‬ ‫سرعة اللعبة ستتفاوت من ظروف لخرى. فإننا في هذه الحالة سنعالج الأمر معالجة آخرى غير تلك الطريقة التي تستعمل في‬ ‫المؤقتات إنها طريقة تختلف من برنامج لخر و من لعبة لخرى. فحسب إحتياجك يمكنك أن تضع الطريقة التي تناسبك. و لنعد‬ ‫إلى الرسم الزمني الذي رسمناه في الفصل الثاني لنرى كيف سنتعامل مع عامل الزمن. و سنوضع عدة طرق و أثرها في عامل‬ ‫الزمن حسب الرسم. إنظر إلى المثال الأول :‬

‫و الآن لنبدأ في شرح المثال. سوف يقوم هذا البرنامج بإجراء عمليات ثقيلة نتيجة لضغط المستخدم لحد الزرار. طبعاً‬ ‫نحتاج إلى مثل تلك الشياء في اللعاب عندما نقوم بإختبار ما إذا كان الكائن الذي نحركه سيتداخل مع كائن آخر إذا ما تحرك‬ ‫تلك الحركة أم لا. و طبعاً ربما يكون هناك أوامر أثقل من هذا. لذلك نقوم بعمل حيلة آخرى. إبتداءًا نحن قد ضمنا أن الأوامر‬
‫تي في دالة الزمن تنفذ بمعدل زمني جيد , لذلك سنقوم بعمل ما نريد من أوامر ثقيلة في دالة الزمن التي تتكرر كل مدة زمنية‬ ‫ثابتة. و دور دالة إستقبال المدخلات من المستخدم هي أن تعطي إشارة لدالة الزمن بأن تقوم بتنفيذ المطلوب فمثلً سنقوم بجعل‬ ‫دالة إستقبال المدخلت تقوم بتغيير قيمة متغير عام إلى قيمة معينة و في دالة الزمن نقوم نحن بإختبار قيمة هذا المتغير العلم‬ ‫Flag و من ثم ننفذ ما نريد. إنظر إلى الكود التالي:‬
#include <GL/glut.h>
#include <GL/gl.h>
float Px=0,Py=0,Vx=0,Vy=0;
GLuint Win;
void Display(int H)
{
glutTimerFunc(20,Display,0);
glClear(GL_COLOR_BUFFER_BIT);
Px += Vx;
Py += Vy;
Vx *= 0.9;
Vy *= 0.9;
glBegin(GL_QUADS);
glColor3f(0,1,1);
glVertex2f(Px-0.05,Py-0.05);
glColor3f(1,0,0);
glVertex2f(Px+0.05,Py-0.05);
glColor3f(0,0,1);
glVertex2f(Px+0.05,Py+0.05);
glColor3f(1,0.5,0.2);
glVertex2f(Px-0.05,Py+0.05);
glEnd();
glFlush();
glutSwapBuffers();
}
void Dummy() {}
void Keys(int Key,int X, int Y)
{
switch (Key)
{
case GLUT_KEY_UP:
Vy += 0.005;
break;
case GLUT_KEY_DOWN:
Vy -= 0.005;
break;
case GLUT_KEY_LEFT:
Vx -= 0.005;
break;
case GLUT_KEY_RIGHT:
Vx += 0.005;
}
}
void Keys2(unsigned char K, int X, int Y)
{
if (K == 27)
glutDestroyWindow(Win);
}
int main(int argc, char **argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
Win = glutCreateWindow(“Moving An Object”);
glClearColor(1,0,0.5,1);
glutFullScreen();
glutTimerFunc(20,Display,0);
glutDisplayFunc(Dummy);
glutSpecialFunc(Keys);
glutKeyboardFunc(Keys2);
glutMainLoop();
return 0;
}
‫في هذا البرنامج قمنا بعمل مربع يتحرك عن طريق أزرار السهم ‪ Up‬ و ‪ Down‬ و ‪ Left‬و ‪ .Right‬ و إذا قمت‬ ‫بتجريب البرنامج فستجد أن حركة المربع قد إختلفت عن ما قمنا به في المثلة السابقة. في المثلة السابقة كان المربع يتحرك‬ ‫خطوة واحدة ثابتة لكل ضغطة زر , أما هنا فستجد أن حركة المربع أصبحت أكثر واقية إذ أننا قد إستخدمنا بعض القوانين‬ ‫الفيزيائية الميكانيكية للحركة و التي هي العجلة و السرعة و الحتكاك. قمنا بعمل المتغيران ‪ Vx‬و ‪ Vy‬و الذان يعبران عن‬ ‫السرعة في إتجاه محور السينات و الصادات. و عند كل 20 ميلي ثانية عندما تأتي دالة ‪) Display‬و التي هي دالة المؤقت‬ ‫في هذا المثال) و تقوم بالمطلوب حيث تزيد المتغير ‪ Px‬و ‪ Py‬و الذان يعبران عن مكان المربع بقيمة ‪ Vx‬و ‪ Vy‬حتى يتغير‬ ‫مكانهما بمقدار السرعة. ثم بعد ذلك قمنا بضرب السرعة في 9.0 حتى تقل السرعة تدريجياً (يمكن أن تعتبره إحتكاك الهواء‬ ‫مثلًا). و تذكر أن المقصود من هذا المثال أن نجعل دالة إستقبال مدخلات المستخدم تقوم بعمل بسيط جداً حيث يكون هذا العمل‬ ‫عبارة عن إشارة تأخذها دالة المؤقت في العتبار و تقوم هي بالمطلوب. و هنا كان دور دالة إستقبال المدخلات هي القيام فقط‬ ‫بزيادة السرعة حيث أن مقدار الزيادة في السرعة هو العجلة. أما بقية الشياء من تغيير مكان المربع و قوانين الحتكاك فقد‬ ‫قامت بها دالة المؤقت ‪ .Display‬ و بعد أن تقوم بترجمة البرنامج و تشغيله جرب تحريك المربع عن طريق أزرار السهم و‬ ‫ستشعر بثقل الجسم الذي تحركه. إنظر إلى الصورة:‬

‫و هكذا نكون قد إنتهينا من الكلام عن إستقبال مدخلات لوحة المفاتيح. تذكر أنه كان عندنا ثلاث دوال لفعل ذلك. و‬ ‫كل دالة تقوم بإستقبال نوع من أزرار لوحة المفاتيح. فأزرار لوحة المفاتيح -كما قسمتها مكتبة جلوت- إما أزرار لها رقم في‬ ‫جدول آسكي كالحروف و الأرقام و بعض الزرار مثل ‪ Space Bar‬ و ‪ Enter‬و ‪ Tab‬و ‪ Escape‬و غيرها. و هناك‬ ‫أزرار ليس لها رقم في جدول آسكي يتم التعامل معها عن طريق ثوابت معرفة في مكتبة جلوت. و هناك أزرار تغييرية و هي‬ ‫الزر ‪ Control‬ و ‪ Shift‬ و ‪ .Alt‬تذكر أنك يجب عليك أن تقوم بحل مشاكلك بنفسك لن المبرمج الحقيقي ليس الذي يحفظ ‫أسماء الدوال و التصنيفات و الثوابت بل المبرمج الحقيقي المحترف هو من يستطيع أن يربط ذلك كله بإتقان و يتحكم في‬ ‫البرنامج و يدير الذاكرة جيداً حتى لو كان يرجع إلى بعض المراجع ليتذكر أسماء الدوال.‬