إن فكرة اللعبة بسيطة للغاية. في اللعبة هناك مجموعة من الطوب في الأعلى و الهدف هو تحطيم كل هذا الطوب. و في الأسفل عندك مضرب يسير في الإتجاه الأفقي و هناك كرة تقوم بتحطيم الطوب و يجب على المضرب أن يمنع الكرة من أن تنزل فيصدها فتقوم الكرة بالرجوع ناحية الطوب مرة آخرى و تحطم بعض الطوب ثم تنزل إلى الأسفل فيضربها المضرب حتى لا تسقط فترجع إلى الطوب و هكذا. و إذا سقطت الكرة يخسر اللاعب و إذا حطمت كل الطوب يكسب. هذا هو أساس اللعبة و يمكنك أن تقوم بإضافة بعض الأشياء مثل أن تضيف المزيد من الكرات أو المزيد من المحاولات أو تجعل بعض الطوب يتحطم بضربتان بدلًا من ضربة واحدة أو غير ذلك. و نحن نريد في البداية أن نقوم بالأساسي ثم إذا أردت إضافة بعض الأشياء الآخرى فلك ذلك.
و لنقم أولًا بترتيب أولوياتنا و عمل مخطط كروكي للعبة قبل الخوض في كتابة الكود. و يمكنك إعتبار هذا الكلام مجرد ما يدور في ذهن المبرمج حين يقوم بالتفكير في كتابة إحدى الألعاب. فأولًا مما تتكون اللعبة ؟؟ أو ما هي الكائنات التي عندنا ؟؟ تتكون من مضرب يمشي في الأسفل. إذاً نحن نحتاج إلى متغيرات تحمل مكان هذا المضرب. هل نحتاج إلى متغيران إحداهما لمحور السينات و الآخر لمحور الصادات ؟؟ لا لأن المضرب يسير في الإتجاه الفقي فقط و أما مكانه في محور الصادات فثابت لا يتغير. كيف سيتحرك هذا المضرب ؟؟ الأفضل تحريكه بمؤشر الفأرة. و بناءاً على ذلك فإن المتغير الذي يحمل مكان المضرب هو متغير مكان المؤشر في محور السينات. و لنقم الآن بوضع هذا الأمر في اللعبة ثم نفكر بعد ذلك في عمل الكرة. كل ما سنقوم بعمله في الكود هو إنشاء نافذة كما تعلمنا و نقوم بعمل الدالة التي ترصد تحرك المؤشر و تقوم بتحويل قيمة مكانه في محور السينات إلى الإحداثيات النسبية. و سنقوم بعمل دالة التوقيت و التي لن ترسم إلى مضرب في أسفل الشاشة حول النقطة التي يتحرك فيها المؤشر. و بالطبع سنقوم بإلغاء شكل المؤشر كما تعلمنا. و ها هو كود ما ذكرناه إلى الآن:
#include <GL/gl.h>
#include <GL/glut.h>
float Mx;
int H,W;
int Win;
void Mouse(int X, int Y)
{
Mx = (float)X/W*2-1;
}
void DispTimer(int V)
{
glutTimerFunc(10,DispTimer,0);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glColor3f(1,0,0);
glVertex2f(Mx-0.1,-0.87);
glColor3f(0,0,1);
glVertex2f(Mx-0.1,-0.93);
glColor3f(1,1,0);
glVertex2f(Mx+0.1,-0.93);
glColor3f(1,0,1);
glVertex2f(Mx+0.1,-0.87);
glEnd();
glFlush();
glutSwapBuffers();
}
void Resize(int Width, int Height)
{
W = Width;
H = Height;
glViewport(0,0,Width,Height);
}
void Keys(unsigned char K, int X, int Y)
{
if (K == 27)
glutDestroyWindow(Win);
}
void Dummy() {}
int main(int argc, char **argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
Win = glutCreateWindow(“Brix”);
glutFullScreen();
glutSetCursor(GLUT_CURSOR_NONE);
glClearColor(0.9,0.5,0,1);
glutReshapeFunc(Resize);
glutPassiveMotionFunc(Mouse);
glutMotionFunc(Mouse);
glutDisplayFunc(Dummy);
glutKeyboardFunc(Keys);
glutTimerFunc(10,DispTimer,0);
glutMainLoop();
return 0;
}
هذا الكود هو عبارة عن برنامج يقوم برسم المضرب الذي يتحرك في الأسفل عن طريق مؤشر الفأرة. و إذا قمت بتشغيل هذا الكود فسوف يظهر لك مثل هذه الصورة:

و الآن لنشرح هذا الكود حتى لا يكون فيه أي غموض. و كانت هذه أول سطور في البرنامج:
float Mx;
int H,W;
int Win;
المتغير الأول Mxسنضع فيه مكان المؤشر في محور السينات Xبالإحداثيات النسبية (راجع بداية الفصل الرابع) و سنستخدم هذا المتغير لكي نرسم المضرب حوله. و المتغير الثاني و الثالث Hو Wسنضع فيهما طول و عرض الشاشة و هذا لكي نقوم بتحويل إحداثيات البكسل إلى الإحداثيات النسبية. و أما المتغير الرابع Winفقد ذكرنا في الفصل الثالث أن دالة إنشاء النوافذ glutCreateWindowتقوم بإرجاع قيمة من نوع عدد صحيح و نحن نستعمل هذه القيمة و نأخذها عندنا في متغير حتى إذا أردنا أن نستدعي دالة إغلاق النافذة نمرر لها هذا الرقم (أو كان عندنا عدة نوافذ أنشأناها و نريد أن نتنقل بين النوافذ مثلاً). و المتغير Winهو الذي سنضع فيه هذا الرقم.
void Mouse(int X, int Y)
{
Mx = (float)X/W*2-1;
}
هذه الدالة هي الدالة التي سوف تستدعيها مكتبة جلوت إذا تحرك المؤشر و تمرر له مكانه بإحداثيات البكسل. و نحن هنا نقوم بتحويل إحداثيات المؤشر في محور السينات إلى إحداثيات نسبية و نضع مكان المؤشر في محور السينات في المتغير العام .Mx
void DispTimer(int V)
{
glutTimerFunc(10,DispTimer,0);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glColor3f(1,0,0);
glVertex2f(Mx-0.1,-0.87);
glColor3f(0,0,1);
glVertex2f(Mx-0.1,-0.93);
glColor3f(1,1,0);
glVertex2f(Mx+0.1,-0.93);
glColor3f(1,0,1);
glVertex2f(Mx+0.1,-0.87);
glEnd();
glFlush();
glutSwapBuffers();
}
هذه هي الدالة التي تقوم بعمليات الرسمة و تحديثها كل 10 ميلي ثانية. في أول سطر في الدالة قمنا بإخبار مكتبة جلوت بأن تقوم بإستدعاء الدالة بعد 10 ميلي ثانية لكي تقوم بتحديث الرسمة في وقت ثابت (راجع الفصل الثاني). ثم قمنا بتنظيف الشاشة في دالة ) glClearراجع الفصل الأول). ثم قمنا برسم مضلع من أربعة أضلاع. و قمنا برسمه حول نقطة .Mxإنظر إلى هذه الرسم التوضيحي الذي يوضح لك لماذا ذكرنا تلك الحداثيات في دالة :glVertex2f

فكما ترى , هذه هي الإحداثيات التي ذكرناها لدالة .glVertex2fفكما ترى فإن إحداثيات المضرب في محور الصادات ثابتة على عكس محور السينات فإنه يعتمد على مكان المؤشر. و هذا ما يجعل المضرب يتحرك مع المؤشر. و تذكر أننا حين نريد أن نرسم مضلع من أربع أضلاع و نمرر لدالة glBeginالثابت GL_QUADSفإننا نقوم بإستدعاء دالة glVertex2f أربع مرات و في كل مرة نذكر لها إحداثيات نقطة من نقاط هذا المضلع و ربما قمنا بتغيير لون هذه النقطة و مكتبة الرسوم المفتوحة بدورها تقوم بعمل تدريجات ألوان في المضلع حتى تصل النقاط المختلفة الألوان ببعضها.
void Resize(int Width, int Height)
{
W = Width;
H = Height;
glViewport(0,0,Width,Height);
}
هذه الدالة ستقوم مكتبة جلوت بإستدعائها حينما يتم تغيير حجم النافذة و قد قمنا بجعلها تضع المقاسات الجديدة في متغيرات عمومية. ثم بعد ذلك إستدعينا دالة glViewportحتى نعيد ضبط الإحداثيات النسبية على المساحة التي مررناها للدالة (راجع الفصل الرابع).
void Keys(unsigned char K, int X, int Y)
{
if (K == 27)
glutDestroyWindow(Win);
}
و هذه الدالة ستقوم مكتبة جلوت بإستدعائها حين يضغط المستخدم على زر له رقم في جدول آسكي (راجع الفصل الثالث). و في هذه الدالة قمنا بإختبار رقم الزر الذي تم تمريره للدالة فإذا كان 27 (رقم زر الـ (Escapeفإننا نقوم بإغلاق النافذة و قد قمنا بتمرير المتغير Winالذي قلنا أنه يحمل رقم النافذة التي أنشأناها حتى ننهي البرنامج حين يضغط المستخدم على الزر .Escapeو تذكر أننا نعمل على شاشة FullScreenلذلك يجب أن نوفر طريقة للغلق.
void Dummy() {}
int main(int argc, char **argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
Win = glutCreateWindow(“Brix”);
glutFullScreen();
glutSetCursor(GLUT_CURSOR_NONE);
glClearColor(0.9,0.5,0,1);
glutReshapeFunc(Resize);
glutPassiveMotionFunc(Mouse);
glutMotionFunc(Mouse);
glutDisplayFunc(Dummy);
glutKeyboardFunc(Keys);
glutTimerFunc(10,DispTimer,0);
glutMainLoop();
return 0;
}
قمنا بإنشاء دالة فارغة و أسميناها Dummyو ذلك حتى تقوم مكتبة جلوت بإستدعائها حين يحدث شئ على النافذة يتطلب إعادة الرسم و نحن قد آثرنا ان نقوم بتحديث الرسم كل جزء محدد من الثانية فلسنا بحاجة لهذا الأمر و لكن بعض المترجمات تتعسف في هذا الأمر و ترغمك أن تقوم بعمل دالة لعادة الرسم (راجع الفصل الثاني). أما في الدالة الرئيسية فقد قمنا بإنشاء نافذة و وضعنا رقمها في المتغير العام Winثم قمنا بإستدعاء دالة glutSetCursorلكي نقوم بإلغاء شكل المؤشر (السهم التقليدي). و قمنا بتحديد لون الخلفية في دالة ) glClearColorراجع الفصل الأول). و بعد ذلك أخبرنا مكتبة جلوت بأن تقوم بإستدعاء دالة Resizeحين يتغير حجم النافذة و دالة Mouseحين يتحرك المؤشر. و قد ذكرناها مرتين حتى نخبر مكتبة جلوت بأن تستدعيها في حالة الضغط على أي زر و في حالة عدم الضغط على أي زر (راجع الفصل الرابع) و دالة Keysحين يضغط المستخدم على أي زر (راجع الفصل الثالث) ثم قمنا بتشغيل المؤقت و هو الدالة التي أسميناها .DispTimer
الآن و بعد أن قمنا بعمل المضرب يتبقى عندنا الكرة و الطوب. و لنبدأ أولًا بالكرة. الكرة تتحرك في أي إتجاه لذلك سنعمل لها متغيرين واحد لمكانها في محور Xو الآخر لمكانها في محور .Yإذاً سنقوم في دالة التوقيت برسم كرة عند قيمة المتغيرين الذان يحملان مكان الكرة. هذه القيمتين هل هما ثابتتين أم متغيريتين ؟؟ بالطبع متغيريتين إذ أنهما لو كانتا ثابتتين ستبدو الكرة ثابتة. لذلك يجب علينا أن نقوم بشئ يجعل قيمة المتغيرين حتى تبدوا الكرة و كأنها تتحرك. نفترض أن عندنا متغير إسمه Bxو Byيعبران عن مكان الكرة. و سنفترض أن عندنا متغير يعبر عن إتجاه الكرة. بمعنى أن الكرة ربما تتحرك متجهه إلى أعلى يسار النافذة أو إلى أسفل يمينها أو غير ذلك. و هذا الإتجاه يتغير دائماً فمثلًا عندما تصطدم الكرة بالمضرب و قد كانت سائرة ناحية أسفل يمين الشاشة فإنها ترتد و تسير ناحية أعلى يمين الشاشة. كما أنها قد تصطدم بالجدار فإذا إصطدمت به ترتد في التجاه الآخر. لنقم بحفظ إتجاه الكرة الأفقي في متغير و إتجاهها الرأسي في متغير آخر و على حسب هذا المتغير يتم تغيير مكان الكرة في Bxو .Byيمكن مثلًا أن نضع مثل هذا الكود في الدالة التي تستدعى كل 10 ميلي ثانية:
if (Dx == 1)
Bx += 0.01;
else
Bx -= 0.01;
if (Dy == 1)
By += 0.01;
else
By -= 0.01;
هذا كان مجرد كود إبتدائي لعمل تحريك للكرة حيث إعتبرنا أن هناك متغيرين لتحديد إتجاه الكرة و قد أسميناهم Dxو Dy و إذا كان Dyبقيمة 1 فإن المتغير Byسوف يزيد فهكذا ستسير الكرة إلى العلى. و إن لم يكن بقيمة 1 فإن الكرة تسير إلى الأسفل. و كذلك الحال في الإتجاه الأفقي. أما بالنسبة لتصادم الكرة بالجدران فيمكن أن نختبر قيمة Bxو Byلكي نعرف مكان الكرة ثم نقوم بتغيير إتجاهها عن طريق تغيير قيمة Dyو .Dxبالطبع فإن الكرة إذا إصطدمت بأعلى النافذة أو بإحدى جوانبها فسوف ترتد أما إذا نزلت إلى الأسفل فإنها تسقط و يخسر اللاعب. كما أنها إذا إصطدمت بالمضرب فإنها ترتد إلى الأعلى. بالنسبة للإصطدام بالجدران يمكن كتابة مثل هذا الكود:
if (Bx > 1)
Dx = 0;
else if (Bx < -1)
Dx = 1;
if (By > 1)
Dy = 0;
حيث أن القيمة 1 بالنسبة لمحور السينات Xتعني أقصى يمين النافذة و القيمة -1 تعني أقصى يسار النافذة و نحن قد قمنا بإختبار القيمة فإذا تجاوزت تلك الحدود نقوم بتغيير الإتجاه. و كذلك الحال بالنسبة للحافة العليا للنافذة. أما عندما نقوم بعمل نفس الشئ عندما تصطدم الكرة مع المضرب فإنظر إلى هذا الرسم التوضيحي لقانون التصادم:

سنقوم في الكود بإختبار ما إذا كانت الكرة مصطدمة مع المضرب أم لا. عندما يكون هناك إصطدام للكرة مع المضرب فإن نقطة Bxيجب أن تكون بين النقاط 0.1- Mxو 0.1+ Mxو التي هي حدود المضرب. كما أن نقطة Byو يجب أن تكون أعلى من نقطة -0.39 و أعلى من النقطة -0.78 و التي هي أسفل المضرب و سطحه. سنقوم بتحويل هذا الكلام إلى كود لكي نقوم بجعل الكرة ترتد بعد أن تكون قد ضربت بالمضرب. و سنتكلم في الفصول القادمة عن التصادم بشئ من التفصيل:
if (Bx < Mx+0.1 && Bx > Mx-0.1 && By < -0.87 && By > -0.93)
Dy = 1;
و بعد أن قمنا بحساب كل شئ يتعلق بحركة الكرة لنقوم برسم الكرة. و قد قلنا بأن مكانها عند المتغير Bxو .Byو حتى لا نشتت القارئ فإننا لن نتكلم عن رسم الكرة في هذا التطبيق بل سنؤجله إلى الفصل القادم حين نتحدث عن مكتبة الرسوم بشئ من التفصيل. لذلك سنرسم الكرة كأنها مربع عادي حول نقطة Bxو Byهذه المرة:
glBegin(GL_QUADS);
glColor3f(1,0.8,0.9);
glVertex2f(Bx-0.02,By+0.02);
glVertex2f(Bx-0.02,By-0.02);
glVertex2f(Bx+0.02,By-0.02);
glVertex2f(Bx+0.02,By+0.02);
glEnd();
و الآن لنضيف كل هذا إلى الكود الأصلي و نرى ما التأثير الذي سيحدث. فكل ما سوف نضيفه إلى الكود هو أننا سنقوم بتعريف المتغيرات التي ذكرناها و هي Bxو Byو Dxو Dyو هي متغيرات عمومية.
float Bx = -1, By = 0;
int Dx = 0, Dy = 0;
و أما عن القيم الفتراضية التي وضعناها لهذه المتغيرات فهي تعني أن الكرة في بداية اللعبة سوف تكون في منتصف يسار النافذة كما نرى في القيم الإفتراضية للمتغيرين Bxو .Byو سوف يكون إتجاه سيرها إلى اليمين و إلى الأسفل كما نرى في القيم الفتراضية للمتغيرات Dxو .Dyثم في دالة الرسم قمنا بإضافة السطور الخاصة بالتحريك و تغيير مكان الكرة و كل الإصطدامات بالضافة إلى كود رسم الكرة. و بعد التعديل يفترض أن تجد اللعبة بهذا الشكل:

و الآن لنقم بعمل الخطوة الأخيرة. ألا و هي الطوب. و بما أننا ربما نضع الكثير من الطوب فربما نحتاج إلى عمل حلقة تكرارية Loopعلى كل طوبة لرسمها و فحص ما إذا كانت الكرة قد إصطدمت بها أم لا. يجب أن يكون هناك متغير لكل طوبة يوضح هل هذه الطوبة قد تحطمت أم لا فإذا كانت قد تحطمت لا نقوم برسمها و أما إذا كانت مازالت لم تحطم فنقوم برسمها. و يجب كذلك أن يكون هناك متغير لمكان الطوبة. و مادام عندنا الكثير من المتغيرات فلنقم بعمل تركيب Structureنضع فيه بيانات الطوبة الواحدة. إنظر إلى هذا الكود:
typedef struct
{
float X,Y;
int Damaged;
} Brick;
ثم نعرف من هذا التركيب عدد من الطوب في مصفوفة :Array
و يجب في بداية البرنامج أن نقوم بتعبئة هذه المصفوفة بالبيانات اللازمة. طبعاً سنجعل كل الطوب غير محطم و كذلك سنضع إحداثيات كل طوبة. نتخيل مثلًا أن عندنا ثلاث صفوف من الطوب ففي الصف العلوي لدينا على سبيل المثال عشر طوبات و الذي يليه فيه ثمان طوبات و الذي يليه فيه ست طوبات و الذي يليه فيه أربعة. إذاً فالمجموع الكلي للطوب هو ثمان و عشرين طوبة و الآن لنقم أولًا بعمل حلقة تكرارية لجعل كل الطوب غير محطم و لنفترض أن المتغير Damagedإذا كان يحمل قيمة 1 فيدل على أن الطوبة محطمة و إذا كان يحمل القيمة صفر فيدل على أن الطوبة غير محطمة. ضع هذا الكود في الدالة :main
for (int r = 0 ; r < 28 ; r++)
B[r].Damaged = 0;
هكذا قد جعلنا كل الطوب غير محطم في بداية البرنامج. و أما ما يتعلق بإحداثيات الطوب فإنظر إلى هذه الصورة للتوضيح:

كما ترى في الصورة فإننا قد جعلنا طول الطوبة هو 0.2 و عرضها 0.1 و الآن إنظر إلى الصف الأول من الطوب. سوف نقوم بعمل حلقة تكرارية على الصف الأول أي من أول الرقم 0 في المصفوفة إلى الرقم 9 و يكون معنا متغير يحمل نقطة Xو يزيد هذا المتغير في كل مرة بمقدار 0.2 حتى نصل إلى إحداثيات الطوبة الأخيرة في الصف:
float tmpX = -1;
for (int r = 0 ; r < 10 ; r++)
{
B[r].X = tmpX;
B[r].Y = 1;
tmpX += 0.2;
}
و الآن بعد أن نصل إلى آخر طوبة سوف تأخذ إحداثيات 0.8 لمحور السينات و 1 لمحور الصادات. كذلك الحال في بقية الطوب و طبعاً سنقوم بعمل حلقة تكرارية لكل صف من الصفوف و نعطي المتغير tmpXالقيمة المناسبة في كل مرة:
tmpX = -0.8;
for (int r = 10 ; r < 18 ; r++)
{
B[r].X = tmpX;
B[r].Y = 0.9;
tmpX += 0.2;
}
tmpX = -0.6;
for (int r = 18 ; r < 24 ; r++)
{
B[r].X = tmpX;
B[r].Y = 0.8;
tmpX += 0.2;
}
tmpX = -0.4;
for (int r = 24 ; r < 28 ; r++)
{
B[r].X = tmpX;
B[r].Y = 0.7;
tmpX += 0.2;
}
و بعد أن قمنا بملأ بيانات الطوب في بداية البرنامج لنقم الآن برسم الطوب ثم بعد ذلك نقوم بإكتشاف تصادم الطوب مع الكرة و كيف ستنعكس الكرة نتيجة لهذا التصادم. بالطبع سوف نقوم بعمل حلقة التكرارية على كل طوبة ثم نقوم بإختبار قيمة المتغير Damagedللطوبة فنحدد هل نرسمها أم لا. و سيكون الكود في دالة الرسم بهذا الشكل:
glBegin(GL_QUADS);
for (int r = 0 ; r < 28 ; r++)
{
if (B[r].Damaged == 0)
{
glColor3f(1,0,0);
glVertex2f(B[r].X,B[r].Y);
glColor3f(0,1,0);
glVertex2f(B[r].X,B[r].Y-0.1);
glColor3f(1,0,0);
glVertex2f(B[r].X+0.2,B[r].Y-0.1);
glColor3f(0,1,0);
glVertex2f(B[r].X+0.2,B[r].Y);
}
}
glEnd();
و هكذا قمنا برسم كل الطوب الغير محطم حيث أننا قد مررنا على كل الطوب و إختبرنا قيمة المتغير Damaged لمعرفة هل هذه الطوبة محطمة أم لا ثم رسمناها و لاحظ أننا قد رسمناها باللون الأحمر يتدرج إلى الأخضر. و طبعاً قد تتسائل لماذا وضعنا دالة glBeginو glEndخارج الحلقة التكرارية. و الجواب على ذلك باننا قد قلنا قبل ذلك أننا عندما نقوم برسم مضلع من أربع أضلع GL_QUADSفإن مكتبة الرسوم تأخذ كل أربع إستدعاءات لدالة glVertexعلى أنها مضلع منفرد. و كذلك الحال في GL_TRIANGLESفإن مكتبة الرسوم ستأخذ كل ثلث إستدعاءات على أنهم مثلث منفرد بإستثناء GL_POLYGONالتي ترسم مضلعات بدون تحديد عدد معين فإنه يجب عليك أن تقوم في كل مرة ترسم فيها شكل خماسي على سبيل المثال أن تقوم بإستدعاء glBeginو glEndفي كل مرة لأن مكتبة الرسوم لا تدري كم مضلعاً تريد.
و الآن ندخل في كتابة أوامر إصطدام الطوب بالكرة. بالطبع سنقوم في الحلقة التكرارية السابقة -التي سبق أن جعلناها ترسم الطوب- بجعلها تقوم بإختبار هل الكرة مصطدمة مع الطوبة أم لا فإذا لم تكن مصطدمة فلن يحدث شئ و إن كانت مصطدمة فإنها تعلم على الطوبة بأنها مصطدمة فتقوم بتغيير قيمة المتغير Damagedلتكون 1 (و ذلك حتى لا نقوم برسمها مرة آخرى و حتى لا نقوم بإختبار التصادم معها في كل مرة) و بعد ذلك نقوم بجعل الكرة تنعكس. إنظر إلى الصورة التوضيحية لقانون تصادم المستطيلات و المربعات:

كيف نعرف أن هذين المربعين قد تقاطعا ؟؟ إذا توفرت الشروط الآتية: النقطة X1 في مربع 2 أقل من النقطة X2 في مربع 1 و النقطة X2 في مربع 2 أكبر من النقطة X1 في مربع 1. و النقطة Y1 في مربع 2 فوق النقطة Y2 في مربع 1 و النقطة Y2 في مربع 2 تحت النقطة Y1 في مربع 1. بالنسبة للكرة و الطوب فإن النقطة X1 في الكرة هي 0.02- Bxو النقطة X2 هي 0.02+ Bxو النقطة Y1 هي 0.02- Byو النقطة Y2 هي 0.02+ Byو في الطوب النقطة X1 هي B[r].Xو النقطة X2 هي 0.2+ B[r].Xو النقطة Y1 هي B[r].Yو النقطة Y2 هي 0.1+ B[r].Yو هكذا نكون مستعدين لإختبار تصادم الطوبة بالكرة. إنظر إلى كود إكتشاف التصادم بعد أن أضفناه إلى الحلقة التكرارية:
glBegin(GL_QUADS);
for (int r = 0 ; r < 28 ; r++)
{
if (B[r].Damaged == 0)
{
if (B[r].Y > By-0.02 && B[r].Y-0.1 < By+0.02 && B[r].X < Bx+0.02 && B[r].X+0.2 > Bx-0.02)
{
if (Dx == 1)
Dx = 0;
else
Dx = 1;
if (Dy == 1)
Dy = 0;
else
Dy = 1;
B[r].Damaged = 1;
}
glColor3f(1,0,0);
glVertex2f(B[r].X,B[r].Y);
glColor3f(0,1,0);
glVertex2f(B[r].X,B[r].Y-0.1);
glColor3f(1,0,0);
glVertex2f(B[r].X+0.2,B[r].Y-0.1);
glColor3f(0,1,0);
glVertex2f(B[r].X+0.2,B[r].Y);
}
}
glEnd();
قمنا بإضافة كود إكتشاف التصادم كما شرحناه ثم قمنا بجعل الكرة تعكس إتجاهها تماماً فتعود من حيث جائت ثم يتم تغيير قيمة Damagedللطوبة لكي تصبح محطمة فلا ترسم بل لا يتم التعامل معها أصلًا. و هكذا نكون قد إنتهينا من كل شئ تقريباً في اللعبة. بعد التشغيل ستجد اللعبة كهذه الصورة:

بعد أن تقوم بتجريب اللعبة ستجد أن كل شئ يعمل كما ينبغي إلا أننا قد نسينا أن نضع اللمسة الأخيرة في اللعبة و هي الفوز و الخسارة. فبعد أن تقوم بتحطيم كل الطوب لا يحدث شئ و تظل اللعبة تعمل كما أن الكرة لو سقطت لا يحدث شئ لذلك نريد أن نضيف اللمسة الأخيرة على اللعبة. و الأمر بسيط لا يتعدى أكثر من أن نضع شرطاً واحداً في دالة DispTimerلكي نختبر مكان الكرة في محور الصادات هل تجاوز النقطة -1 أم لا فإن لم تتجاوزها لا يحدث شئ و أما إن تجاوزتها فإن اللعبة تنتهي و يتم إعلام اللاعب بطريقة أو بآخرى أنه قد خسر. و بالطبع فإننا لم ندخل بعد في الكلم عن تحميل الصور حتى نظهر للاعب رسالة تفيده بأنه قد خسر بل كل ما عرفناه في الرسم هو كيف نرسم أشكال هندسية أولية فقط. لذلك سنقوم بشئ بسيط و هو أن اللعبة سوف تبدأ من جديد فيعود كل الطوب غير محطم و يعود المضرب في منتصف الشاشة و تأتي الكرة في منتصف يسار الشاشة كما كان في البداية. و كذلك الحال في الفوز. و الفوز يتم حين يحطم اللعب كل الطوب. و لكي نعرف هل كل الطوب قد تم تحطيمه أم لا يمكننا أن نقوم بعمل عداد يزيد كلما ضربنا طوبة و حين يصل العداد إلى 28 يفوز اللعب. سنقوم أولًا بعمل دالة تقوم بإرجاع كل شئ في اللعبة إلى طبيعته. و هو أن يتم إستعادة الطوب و إرجاع الكرة إلى مكانها و وضع المضرب في منتصف النافذة. و قد أسميناها .ResetGameإنظر إلى هذه السطور من الكود:
void ResetGame()
{
for (int r = 0 ; r < 28 ; r++)
B[r].Damaged = 0;
Bx = -1;
By = 0;
Dx = 1;
Dy = 0;
Mx = 0;
glutWarpPointer(W/2,H/2);
}
و هكذا قمنا بإعادة كل شئ إلى طبيعته. و الآن لنقم بإستدعاء تلك الدالة عندما تنزل الكرة إلى أقل من -1 في محور الصادات أو إذا حطم اللعب كل الطوب. بالنسبة للكرة إذا نزلت سنكتب هذا الشرط في دالة

ispTimer
if (By < -1)
ResetGame();
أما عن إذا ما تحطم كل الطوب فإننا سوف نقوم بعمل عداد يزيد كلما حطمنا طوبة. سنقوم بتعريف متغير عمومي نعطيه قيمة صفر في البداية و يزيد كلما حطمنا طوبة فإذا وصل إلى 28 تأكدنا أن اللعب قد حطم كل الطوب:
و في دالة DispTimerسنقوم في الحلقة التكرارية على الطوب بإضافة هذا السطر بعد 1 = :B[r].Damaged
و بالطبع سنقوم في دالة إعادة اللعبة إلى طبيعتها بتصفير هذا المتغير هو الآخر. و سنقوم بتعديل شرط إستدعاء دالة إعادة كل شئ إلى طبيعته إلى التي:
if (By < -1 || Toob >= 28)
ResetGame();
و الآن قم بإجراء كل التغييرات التي وضعناها و جرب أن تلعب اللعبة. سترى أنها قد أصبحت لعبة متكاملة. و إذا نظرت في الكود ستجد أن كل شئ فيه قد تعلمناه في الدروس السابقة. إذاً فالمسآلة ليست بالتعقيد الذي ربما تصوره البعض في البداية. فبرمجة الألعاب لا تحتاج أكثر من أن تكون على علم بالرسم و الجرافكس بالإضافة إلى بعض الأفكار. يمكنك أن تقوم بعد ذلك بعمل بعض اللعاب الآخرى بناءاً على ما تعلمناه. يمكنك أن تقوم بعمل لعبة Snakeالشهيرة مثلًا و عبارة عن ثعبان يمشي و يأكل التفاح و إذا آكل تفاحة يزداد طوله و يجب عليه ألا يصطدم بنفسه و طبعاً كلما طال كلما أصبحت العملية أكثر تعقيداً و صعوبة. فالأمر لا يحتاج أكثر من أن تكون قادر على توليد الأفكار الجديدة و التفكير في حل المشكلة التي أمامك. و لهذا الأمر قمنا بعمل هذه التطبيقات بعد كل مجموعة من الفصول حتى نعطي القارئ أفكار لبعض الأشياء التي يمكن أن يعملها بما قد تعلمه.
المفضلات