الفصل الثالث
إستقبال مدخلات لوحة المفاتيح
سنتحدث إن شاء الله في هذا الفصل عن كيفية التفاعل مع المستخدم. بمعني أننا في الأمثلة و البرامج السابقة كنا نقوم بعمل رسومات و تحركات ثابتة. أما في هذا الفصل فسنجعل البرنامج يتفاعل مع المستخدم و يقوم بالتعديل في الرسم بناءاً على مدخلات المستخدم. فمثلًا تضغط بأزرار لوحة المفاتيح على الزر 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(Keyboard2);
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_F12
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فإن له رقم في جدول آسكي و يمكن التعبير عنه بالحروف و إذا أردنا إختباره نقول على سبيل المثال:
و أما القيم التي ترجعها دالة 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تذكر أنك يجب عليك أن تقوم بحل مشاكلك بنفسك لن المبرمج الحقيقي ليس الذي يحفظ أسماء الدوال و التصنيفات و الثوابت بل المبرمج الحقيقي المحترف هو من يستطيع أن يربط ذلك كله بإتقان و يتحكم في البرنامج و يدير الذاكرة جيداً حتى لو كان يرجع إلى بعض المراجع ليتذكر أسماء الدوال.
المفضلات