البحث في الموقع
المحتوى عن 'لغة برمجة processing'.
-
تسهّل لغة بروسيسنج Processing من عملية إنشاء النماذج الأولية للتطبيقات المرئية. إذ يصبح بناء لعبة بسيطة أسهل مما قد تتصور بفضل تركيباتها البرمجية سهلة الاستخدام وبعض الحسابات. هذا هو الجزء الثالث من سلسلة لغة بروسيسنج Processing. قدمنا في الجزأين الأول والثاني شرحًا تفصيليًا أساسيًا للغة بروسيسنج Processing. والخطوة التي سنشرحها اليوم والتي ستعزز لك تعلم بروسيسنج Processing هي بكل بساطة التعرف على مزيد من الأمثلة البرمجية العملية. سنوضح في هذا المقال كيفية استخدام بروسيسنج Processing لتنفيذ لعبتك الخاصة، خطوة بخطوة. سيتم شرح كل خطوة بالتفصيل. وبعد ذلك، سننقل اللعبة إلى الويب. قبل أن نبدأ التطبيق العملي، إليك شيفرة الشعار الوارد في تمرين شاشة توقف DVD الذي شرحناه في المقال السابق. الدرس التعليمي لبروسيسنج Processing: لعبة بسيطة اللعبة التي سنبنيها في هذا الدرس التعليمي باستخدام Processing هي مزيج بين كل من الألعاب الإلكترونية التالية "Flappy Bird" و "Pong" و "Brick Breaker". وسبب اختيار هذه اللعبة هو احتواؤها على معظم المفاهيم التي يصادفها المبتدئون عند تعلم برمجة الألعاب. وتشمل هذه المفاهيم الجاذبية، والتصادمات، والحفاظ على النقاط، والتعامل مع شاشات مختلفة وتفاعل لوحة المفاتيح/الفأرة. ولعبة "Flappy Pong" تحتوي على كل هذه المفاهيم. العب الآن! See the Pen Flappy Pong by Hsoub Academy (@HsoubAcademy) on CodePen. ليس من السهل بناء ألعابٍ معقدة دون استخدام مفاهيم البرمجة بالكائنات OOP، فألعاب المنصات ذات المستويات المتعددة، ومتعددة اللاعبين تجعل الشيفرة معقدة جدًا والبرمجة كائينة التوجه تساعدك على تنظيمها. وقد بذلنا قصارى جهدنا لجعل هذا الدرس التعليمي منظمًا وبسيطًا. ننصحك بقراءة كامل المقال، والحصول على الشيفرة البرمجية الكاملة ومحاولة فهمها، واللعب به بمفردك لمعرفة منطق اللعب ثم البدء في التفكير في لعبتك الخاصة بأسرع ما يمكن، ثمّ البدء في تنفيذها. لذا دعونا نبدأ. بناء لعبة Flappy Pong الخطوة 1: تهيئة ومعالجة الشاشات المختلفة الخطوة الأولى هي تهيئة مشروعنا. سنكتب كتل الإعداد والرسم كالمعتاد، وما من شيء جديد في هذه الخطوة، ثم سنتعامل مع الشاشات المختلفة (شاشة البداية، شاشة اللعب، شاشة الخروج إلخ). فكيف نجعل بروسيسنج Processing تعرض الصفحة الصحيحة في الوقت الصحيح؟ يعدّ تحقيق هذه المهمة بسيطًا نسبيًا. إذ إننا سننشئ متغيرًا عامًا يخزن معلومات الشاشة النشطة حاليًا. ثم، سنرسم محتويات الشاشة الصحيحة اعتمادًا على هذا المتغير. في كتلة الرسم، ستكون لدينا تعليمة "if" تفحص المتغير وتعرض محتويات الشاشة وفقًا لذلك. كلما أردنا تغيير الشاشة، لذلك سنغيّر هذا المتغير إلى معرف الشاشة التي نريد عرضها. وهكذا يكون مظهر شيفرتنا البرمجية: /********* VARIABLES *********/ // نتحكم بالشاشة التي نريد تنشيطها من خلال تحديث وضبط متغيّر gameScreen // نعرض الشاشة الصحيحة بحسب قيمة هذا المتغير // // 0: الشاشة الأساسية // 1: شاشة اللعبة // 2: شاشة نهاية اللعبة int gameScreen = 0; /********* SETUP BLOCK *********/ void setup() { size(500, 500); } /********* DRAW BLOCK *********/ void draw() { // يعرض محتوى الشاشة الحالية if (gameScreen == 0) { initScreen(); } else if (gameScreen == 1) { gameScreen(); } else if (gameScreen == 2) { gameOverScreen(); } } /********* SCREEN CONTENTS *********/ void initScreen() { // الشيفرة البرمجية للشاشة الرئيسية } void gameScreen() { // الشيفرة البرمجية لشاشة اللعبة } void gameOverScreen() { // الشيفرة البرمجية لشاشة انتهاء اللعبة } /********* INPUTS *********/ public void mousePressed() { // إذا كنا على الشاشة الرئيسية عند النقر، ابدأ اللعبة if (gameScreen==0) { startGame(); } } /********* OTHER FUNCTIONS *********/ // هذا التابع يحدد المتغيرات الضرورية لبدء اللعبة void startGame() { gameScreen=1; } قد تبدو الشيفرة معقدة في البداية، ولكن كل ما فعلناه هو بناء الهيكل الأساسي وفصل الأجزاء المختلفة باستخدام كتل التعليقات. كما ترى، نحدد تابعًا مختلفًا لعرض كل شاشة. ونتحقق ببساطة من قيمة متغير gameScreen في كتلة الرسم الخاصة بنا، ونستدعي التابع المقابل. في الجزء void mousePressed(){...}، نستمع إلى نقرات الفأرة وإذا كانت الشاشة النشطة هي 0، الشاشة الأولية، نستدعي تابع startGame() الذي يبدأ اللعبة. ويغيّر السطر الأول من هذا التابع متغير gameScreen إلى 1 شاشة اللعبة. إذا فهمت ذلك، فالخطوة التالية هي تنفيذ شاشتنا الأولية. وللقيام بذلك، سنحرر تابع initScreen(). الذي يبدأ من هنا : void initScreen() { background(0); textAlign(CENTER); text("Click to start", height/2, width/2); } و الآن، في شاشة البداية، أضفتا خلفية سوداء ونص بسيط يظهر في منتصف الشاشة مكتوب عليه "انقر للبدء Click to start". ومع ذلك، عند النقر على الشاشة، لا يحدث أي شيء، وهذا متوقع لأننا لم نكتب بعد أي كود لتنفيذ محتوى اللعبة بعد الضغط على الزر. حالياً، لا تحتوي الدالة ()gameScreen على أية تعليمات، ولذلك عند الانتقال لشاشة اللعبة، فإن المحتوى السابق (أي النص) لا يزال يظهر لأننا لم نضع تعليمة ()background لإعادة رسم الخلفية وإخفاء المحتويات السابقة. لهذا السبب، يستمر النص في الظهور حتى بعد الانتقال، تمامًا كما كان يحدث في مثال الكرة المتحركة التي كانت تترك أثراً خلفها.لذلك دعونا نمضي قدمًا ونبدأ في تنفيذ شاشة اللعبة. void gameScreen() { background(255); } بعد هذا التغيير، ستلاحظ أن الخلفية تتحول إلى بيضاء ويختفي النص. الخطوة 2: إنشاء الكرة وتطبيق الجاذبية والآن سنبدأ العمل على شاشة اللعبة. سننشئ كرتنا أولًا. يجب أن نحدد متغيرات لإحداثياتها ولونها وحجمها لأننا قد نرغب في تغيير هذه القيم لاحقًا. على سبيل المثال، إذا أردنا زيادة حجم الكرة عندما يسجل اللاعب أعلى النقاط، تصبح اللعبة أكثر صعوبة. سنحتاج إلى تغيير حجمه، لذلك يجب أن يكون متغيرًا. وسنحدد سرعة الكرة أيضًا، بعد أن نطبق الجاذبية. أولًا نضيف ما يلي: ... int ballX, ballY; int ballSize = 20; int ballColor = color(0); ... void setup() { ... ballX=width/4; ballY=height/5; } ... void gameScreen() { ... drawBall(); } ... void drawBall() { fill(ballColor); ellipse(ballX, ballY, ballSize, ballSize); } عرّفنا الإحداثيات كمتغيرات عامّة، وأنشأنا تابعًا لرسم الكرة التي استدعيناها من التابع gameScreen، الشيء الوحيد الذي يجب الانتباه إليه هنا هو أننا هيّئنا الإحداثيات، ولكننا عرفناها في setup(). السبب وراء قيامنا بذلك هو أننا أردنا أن تبدأ الكرة من مسافة ربع من اليسار وخمس من الأعلى. ليس هناك سبب محدد وراء رغبتنا في ذلك، إلا أنّ هذه نقطة جيدة لبدء الكرة. لذلك نحن بحاجة للحصول على width وheight للرسم ديناميكيًا. يتم تعريف حجم الرسم في setup() بعد السطر الأول. لم يتم ضبط width وheight قبل تنفيذ setup()، ولهذا السبب لم نتمكن من تحقيق ذلك إذا حدّدنا المتغيرات في الأعلى. الجاذبية الآن تطبيق الجاذبية هو الجزء السهل في الواقع. إذ لا يوجد سوى عدد قليل من الحيل. وهذا التطبيق أولًا: ... float gravity = 1; float ballSpeedVert = 0; ... void gameScreen() { ... applyGravity(); keepInScreen(); } ... void applyGravity() { ballSpeedVert += gravity; ballY += ballSpeedVert; } void makeBounceBottom(float surface) { ballY = surface-(ballSize/2); ballSpeedVert*=-1; } void makeBounceTop(float surface) { ballY = surface+(ballSize/2); ballSpeedVert*=-1; } // ابقِ الكرة داخل الشاشة void keepInScreen() { // تصل الكرة إلى الأرض if (ballY+(ballSize/2) > height) { makeBounceBottom(height); } // تضرب الكرة السقف if (ballY-(ballSize/2) < 0) { makeBounceTop(0); } } والنتيجة هي: تمهّل يا عالم الفيزياء! أعلم أن هذه ليست الطريقة التي تعمل بها الجاذبية في الحياة الواقعية. بدلًا من ذلك، هي عملية رسوم متحركة أكثر من أي شيء آخر. المتغير الذي عرفناه gravity هو مجرد قيمة رقمية float حتى نتمكن من استخدام القيم العشرية، وليس فقط الأعداد الصحيحة - نضيفها إلى ballSpeedVert في كل حلقة. و ballSpeedVert هي السرعة الرأسية للكرة، التي تضاف إلى محور Y للكرة (ballY) في كل حلقة. نشاهد إحداثيات الكرة ونتأكد من بقائها في الشاشة. إن لم نفعل ذلك، ستسقط إلى ما لا نهاية. في الوقت الحالي، تتحرك الكرة رأسيًا فقط. لذلك نشاهد حدود الأرضية والسقف للشاشة. نتحقق مما إذا كانتballY (+ نصف القطر) أقل من الارتفاع باستخدام التابع keepInScreen()، وبالمثل ballY (- نصف القطر) أكبر من0. إذا لم تتحقق الشروط، نجعل الكرة ترتد (من الأسفل أو الأعلى) باستخدام تابعيّ makeBounceBottom() وmakeBounceTop(). لجعل الكرة ترتد، نحرك الكرة إلى الموقع المحدد الذي يجب أن ترتد فيه ونضرب السرعة العمودية (ballSpeedVert) في-1 (الضرب في -1 يغير الإشارة). عندما تحتوي قيمة السرعة على علامة سالب، فإن إضافة الإحداثي Y تجعل السرعة (ballY + (-ballSpeedVert، وهي ballY - ballSpeedVert. لذا تغيّر اتجاه الكرة على الفور بنفس السرعة. بعد ذلك، عندما نضيف الجاذبية إلى ballSpeedVert وتكون قيمة ballSpeedVert سالبة، فإنها تبدأ في الاقتراب من 0، وتصبح في النهاية 0، وتبدأ في الزيادة مرة أخرى. وهذا يجعل الكرة ترتفع، وترتفع بشكل أبطأ، إلى أن تتوقف وتبدأ بالسقوط. هناك مشكلة في عملية الرسوم المتحركة لدينا، رغم ذلك، إذ إنّ الكرة تستمر في الارتداد. إذا كان هذا سيناريو حقيقيًا، لكانت الكرة ستواجه مقاومة الهواء والاحتكاك في كل مرة تلمس فيها سطحًا. هذا هو السلوك الذي نريده لعملية الرسوم المتحركة في لعبتنا، لذا فإن تنفيذ ذلك أمر سهل. ونضيف ما يلي: ... float airfriction = 0.0001; float friction = 0.1; ... void applyGravity() { ... ballSpeedVert -= (ballSpeedVert * airfriction); } void makeBounceBottom(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); } void makeBounceTop(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); } والآن ستبدو عملية التحريك الخاصة بنا بهذا الشكل: كما يوحي الاسم، friction هو الاحتكاك السطحي و airfriction هو احتكاك الهواء. لذا من الواضح أن friction يجب أن يحدث في كل مرة تلمس فيها الكرة أي سطح. ومع ذلك، يجب أن نطبّق airfriction باستمرار. وهذا ما فعلناه. والآن ننفّذ تابع applyGravity() على كل حلقة، لذلك نحذف 0.0001 بالمئة من قيمتها الحالية من ballSpeedVert في كل حلقة. ثمّ ننفذ تابعي makeBounceBottom() و makeBounceTop() عندما تلمس الكرة أي سطح. لذا، في تلك الطرق، فعلنا نفس الشيء، ولكن هذه المرة باستخدام friction. الخطوة 3: إنشاء مضرب تنس الآن نحتاج إلى مضرب تنس للكرة لكي ترتدّ عليه. ويجب أن نتحكم بالمضرب. والآن لنتحكم به من خلال الفأرة. هذه هي الشيفرة البرمجية: ... color racketColor = color(0); float racketWidth = 100; float racketHeight = 10; ... void gameScreen() { ... drawRacket(); ... } ... void drawRacket(){ fill(racketColor); rectMode(CENTER); rect(mouseX, mouseY, racketWidth, racketHeight); } لقد عرّفنا اللون، الطول والعرض الخاصين بالمضرب كمتغير عام، لأننا قد نريدهم أن يتغيروا أثناء اللعب. طبّقنا التابع drawRacket() الذي ينفّذ ما يوضحه اسمه (يرسم المضرب). ضبطنا وضع rectMode على المركز، بحيث يكون مضربنا محاذيًا لمركز المؤشر. الآن بعد أن أنشأنا المضرب، علينا أن نجعل الكرة ترتد عليه. ... int racketBounceRate = 20; ... void gameScreen() { ... watchRacketBounce(); ... } ... void watchRacketBounce() { float overhead = mouseY - pmouseY; if ((ballX+(ballSize/2) > mouseX-(racketWidth/2)) && (ballX-(ballSize/2) < mouseX+(racketWidth/2))) { if (dist(ballX, ballY, ballX, mouseY)<=(ballSize/2)+abs(overhead)) { makeBounceBottom(mouseY); // يتحرك المضرب إلى الأعلى if (overhead<0) { ballY+=overhead; ballSpeedVert+=overhead; } } } } وهذه هي النتيجة: إذن ما يفعله watchRacketBounce() هو التأكد من اصطدام المضرب والكرة. هناك شيئان يجب التحقق منهما هنا، وهما إذا ما كانت الكرة والمضرب مصطفين رأسيًا وأفقيًا. تتحقق عبارة if الأولى مما إذا كان إحداثي X للجانب الأيمن من الكرة أكبر من إحداثي X للجانب الأيسر من المضرب (والعكس صحيح). إذا كان الأمر كذلك، فإن العبارة الثانية تتحقق مما إذا كانت المسافة بين الكرة والمضرب أصغر من أو تساوي نصف قطر الكرة (مما يعني أنهما يتصادمان). لذا، إذا استوفيت هذه الشروط، فسيتم استدعاء تابع makeBounceBottom() وترتد الكرة على مضربنا (عند mouseY، حيث يوجد المضرب). هل لاحظت مقدار المتغير overhead الذي يتم حسابه بواسطة mouseY - pmouseY؟ يخزّن المتغيّران pmouseX و pmouseY إحداثيات الفأرة في الإطار السابق. نظرًا لأن الفأرة يمكن أن تتحرك بسرعة كبيرة، فهناك احتمال كبير أننا قد لا نكتشف المسافة بين الكرة والمضرب بشكل صحيح بين الإطارات إذا كان الفأر يتحرك نحو الكرة بسرعة كافية. لذا، سنأخذ اختلاف إحداثيات الماوس بين الإطارات ونأخذ ذلك في الاعتبار أثناء اكتشاف المسافة. كلما تحركت الفأرة بشكل أسرع، كانت المسافة الأكبر مقبولة. نستخدم أيضًا overhead لسبب آخر. نكتشف الاتجاه الذي تتحرك به الفأرة من خلال التحقق من علامة overhead. إذا كان overhead سالبًا، فهذا يعني أن الفأرة كانت في مكان ما أسفل الإطار السابق، لذا فإن الفأرة (المضرب) يتحرك نحو الأعلى. في هذه الحالة، نريد إضافة سرعة إضافية للكرة وتحريكها أبعد قليلًا من الارتداد العادي لمحاكاة تأثير ضرب الكرة بالمضرب. إذا كان overhead أقل من 0، فإننا نضيفه إلى ballY و ballSpeedVert لجعل الكرة ترتفع إلى أعلى وأسرع. لذا، كلما أسرع المضرب في ضرب الكرة، كلما تحركت إلى أعلى وأسرع. الخطوة 4: الحركة الأفقية والسيطرة على الكرة في هذا القسم، سنضيف الحركة الأفقية إلى الكرة. ثمّ، سنحقّق التحكم بالكرة أفقياً بدون مضرب. لنبدأ: ... // سنبدأ بـ 0، لكننا نعطي 10 للاختبار فقط تعويم الكرة SpeedHorizon = 10؛ float ballSpeedHorizon = 10; ... void gameScreen() { ... applyHorizontalSpeed(); ... } ... void applyHorizontalSpeed(){ ballX += ballSpeedHorizon; ballSpeedHorizon -= (ballSpeedHorizon * airfriction); } void makeBounceLeft(float surface){ ballX = surface+(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } void makeBounceRight(float surface){ ballX = surface-(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } ... void keepInScreen() { ... if (ballX-(ballSize/2) < 0){ makeBounceLeft(0); } if (ballX+(ballSize/2) > width){ makeBounceRight(width); } } وستكون النتيجة كالتالي: الفكرة هنا هي نفسها التي نفذناها للحركة الرأسية. أنشأنا متغير السرعة الأفقي، ballSpeedHorizon. لقد أنشأنا تابعاً لتطبيق السرعة الأفقية على ballX وإزالة احتكاك الهواء. أضفنا عبارتي if إضافيتين إلى تابع keepInScreen() والتي ستراقب الكرة وهي تضرب الحواف اليسرى واليمنى للشاشة. وأخيرًا، أنشأنا تابعي makeBounceLeft() وmakeBounceRight() للتعامل مع الارتدادات من اليسار واليمين. الآن بعد أن أضفنا السرعة الأفقية إلى اللعبة، نريد التحكم في الكرة بالمضرب. كما هو الحال في لعبة أتاري الشهيرة Breakout وفي جميع ألعاب كسر الطوب الأخرى، يجب أن تتحرك الكرة يسارًا أو يمينًا وفقًا للنقطة التي تضربها على المضرب. يجب أن تمنح حواف المضرب الكرة سرعة أفقية أكبر بينما لا ينبغي أن يكون للوسط أي تأثير. الشيفرة البرمجية أولا: void watchRacketBounce() { ... if ((ballX+(ballSize/2) > mouseX-(racketWidth/2)) && (ballX-(ballSize/2) < mouseX+(racketWidth/2))) { if (dist(ballX, ballY, ballX, mouseY)<=(ballSize/2)+abs(overhead)) { ... ballSpeedHorizon = (ballX - mouseX)/5; ... } } } النتيجة هي: إن إضافة هذا السطر البسيط إلى watchRacketBounce() أدى المهمة. ما فعلناه هو أننا حددنا مسافة النقطة التي تضربها الكرة من مركز المضرب باستخدام ballX - mouseX. ثم نجعلها بالسرعة الأفقية. كان الفرق الفعلي كبيرًا جدًا، لذا أجرينا بعض المحاولات واكتشفنا أن عُشر القيمة تبدو طبيعية أكثر. الخطوة 5: إنشاء الجدران بدأ رسمنا يبدو وكأنه لعبة مع كل خطوة. في هذه الخطوة، سنضيف جدران تتحرك نحو اليسار، تمامًا كما في Flappy Bird: ... int wallSpeed = 5; int wallInterval = 1000; float lastAddTime = 0; int minGapHeight = 200; int maxGapHeight = 300; int wallWidth = 80; color wallColors = color(0); // تقوم قائمة المصفوفات هذه بتخزين بيانات الفجوات بين الجدران. يتم رسم الجدران الفعلية وفقًا لذلك // [gapWallX, gapWallY, gapWallWidth, gapWallHeight] ArrayList<int[]> walls = new ArrayList<int[]>(); ... void gameScreen() { ... wallAdder(); wallHandler(); } ... void wallAdder() { if (millis()-lastAddTime > wallInterval) { int randHeight = round(random(minGapHeight, maxGapHeight)); int randY = round(random(0, height-randHeight)); // {gapWallX, gapWallY, gapWallWidth, gapWallHeight} int[] randWall = {width, randY, wallWidth, randHeight}; walls.add(randWall); lastAddTime = millis(); } } void wallHandler() { for (int i = 0; i < walls.size(); i++) { wallRemover(i); wallMover(i); wallDrawer(i); } } void wallDrawer(int index) { int[] wall = walls.get(index); // للحصول على إعدادات جدار الفجوة int gapWallX = wall[0]; int gapWallY = wall[1]; int gapWallWidth = wall[2]; int gapWallHeight = wall[3]; // رسم الجدران الفعلية rectMode(CORNER); fill(wallColors); rect(gapWallX, 0, gapWallWidth, gapWallY); rect(gapWallX, gapWallY+gapWallHeight, gapWallWidth, height-(gapWallY+gapWallHeight)); } void wallMover(int index) { int[] wall = walls.get(index); wall[0] -= wallSpeed; } void wallRemover(int index) { int[] wall = walls.get(index); if (wall[0]+wall[2] <= 0) { walls.remove(index); } } وهذه نتيجة ما نفّذنا: قد تبدو الشيفرة البرمجية طويلةً ومخيفةً، إلا أننا نعدك أنّه ما من شيءٍ صعب. الشيء الأول الذي يجب ملاحظته هو ArrayList. بالنسبة لأولئك الذين لا يعرفون ما هي ArrayList، فهي مجرد تطبيق للقائمة التي تعمل كالمصفوفة، ولكنها تملك بعض الميزات الإضافية. إذ إنها قابلة لتغيير الحجم، وتحتوي على توابع مفيدة مثل (list.add (index، و(list.get(index و(list.remove(index. نحتفظ ببيانات الجدار كمصفوفات صحيحة داخل قائمة المصفوفات. البيانات التي نحتفظ بها في المصفوفات مخصصة للفجوة بين جدارين. تحتوي المصفوفات على القيم التالية: [gap wall X, gap wall Y, gap wall width, gap wall height] ترسم الجدران الفعلية بناءً على قيم جدار الفجوة. لاحظ أنه يمكننا التعامل مع كل هذه الأمور بشكل أفضل باستخدام الفئات، ولكن نظرًا لأن استخدام البرمجة كائنية التوجه OOP ليس ضمن نطاق البرنامج التعليمي للمعالجة، فهذه هي الطريقة التي سنتعامل بها. لدينا طريقتان أساسيتان لإدارة الجدران هما wallAdder() وwallHandler. يضيف تابع wallAdder() ببساطة جدرانًا جديدة في كل مللي ثانية من wallInterval إلى قائمة المصفوفات. لدينا متغير عام lastAddTime الذي يخزن الوقت الذي تمت فيه إضافة الجدار الأخير (بالمللي ثانية). إذا كانت قيمة المللي ثانية الحالية millis() ناقص آخر مللي ثانية تمت إضافتها lastAddTime أكبر من قيمة الفاصل الزمني الخاصة بنا wallInterval، فهذا يعني أن الوقت قد حان لإضافة جدار جديد. يتم بعد ذلك إنشاء متغيرات الفجوة العشوائية بناءً على المتغيرات العامة المحددة في الأعلى. ثم تتم إضافة جدار جديد (مصفوفة عدد صحيح تخزن بيانات جدار الفجوة) إلى قائمة المصفوفات ويتم تعيين lastAddTime على المللي ثانية الحالية millis(). تنفذ حلقات wallHandler() عبر الجدران الحالية الموجودة في قائمة المصفوفات. ولكل عنصر في كل حلقة، يستدعي كلاً من (wallRemover(i و (wallMover(i و (wallDrawer(i حسب قيمة فهرس قائمة المصفوفات. يقوم هذا التابع بما يوحي اسمه. يرسم التابع wallDrawer() الجدران الفعلية بناءً على بيانات فجوة الجدار. فهو يلتقط مصفوفة بيانات الجدار من قائمة المصفوفات، ويستدعي التابع rect() لرسم الجدران إلى المكان الذي ينبغي أن تكون فيه بالفعل. يلتقط تابع wallMover() العنصر من قائمة المصفوفات، وتغير موقعه X بناءً على المتغير العام wallSpeed. أخيرًا، يزيل التابع wallRemover() الجدران من قائمة المصفوفات الموجودة خارج الشاشة. لأننا إن لم نفعل ذلك، لكانت بروسيسنج Processing قد تعاملت معهم كما لو كانوا لا يزالون في الشاشة. وكان من الممكن أن يكون ذلك خسارة فادحة في الأداء. لذا، عند إزالة جدار من قائمة المصفوفات، لا يتم رسمه في الحلقات اللاحقة. التحدي الأخير المتبقي هو اكتشاف الاصطدامات بين الكرة والجدران. void wallHandler() { for (int i = 0; i < walls.size(); i++) { ... watchWallCollision(i); } } ... void watchWallCollision(int index) { int[] wall = walls.get(index); // الحصول على إعدادات جدار الفجوة int gapWallX = wall[0]; int gapWallY = wall[1]; int gapWallWidth = wall[2]; int gapWallHeight = wall[3]; int wallTopX = gapWallX; int wallTopY = 0; int wallTopWidth = gapWallWidth; int wallTopHeight = gapWallY; int wallBottomX = gapWallX; int wallBottomY = gapWallY+gapWallHeight; int wallBottomWidth = gapWallWidth; int wallBottomHeight = height-(gapWallY+gapWallHeight); if ( (ballX+(ballSize/2)>wallTopX) && (ballX-(ballSize/2)<wallTopX+wallTopWidth) && (ballY+(ballSize/2)>wallTopY) && (ballY-(ballSize/2)<wallTopY+wallTopHeight) ) { // يصطدم بالجدار العلوي } if ( (ballX+(ballSize/2)>wallBottomX) && (ballX-(ballSize/2)<wallBottomX+wallBottomWidth) && (ballY+(ballSize/2)>wallBottomY) && (ballY-(ballSize/2)<wallBottomY+wallBottomHeight) ) { // يصطدم بالجدار السفلي } } يستدعى التابع watchwallcollision لكل جدار في كلّ حلقة. ثم نلتقط إحداثيات جدار الفجوة، ونحسب إحداثيات الجدران الفعلية (العلوية والسفلى) ونتحقق مما إذا كانت إحداثيات الكرة تصطدم بالجدران. الخطوة 6: تنفيذ شريط الصحة Health Bar الآن بعد أن أصبح بإمكاننا اكتشاف اصطدامات الكرة بالجدران، يمكننا تحديد آليات اللعبة. بعد ضبط للعبة، تمكنت من جعل اللعبة قابلة للعب إلى حد ما. ولكن مع ذلك، كان الأمر صعبًا للغاية. وكان أول ما فكرت به في اللعبة أن أجعلها مثل Flappy Bird، عندما تلمس الكرة الجدران، تنتهي اللعبة. ولكن بعد ذلك أدركت أنه سيكون من المستحيل اللعب. لذا هذا ما فكرت به: يجب أن يكون هناك شريط صحة أعلى الكرة. أي يجب أن تفقد الكرة صحتها أثناء ملامستها للجدران. وبهذا المنطق، ليس من المنطقي أن نجعل الكرة ترتد عن الجدران. لذلك عندما تكون الصحة 0، يجب أن تنتهي اللعبة ويجب أن ننتقل إلى اللعبة على الشاشة. لننفذ شريط صحة مرتبط بالكرة، حيث سيقل تدريجيًا عند كل اصطدام بالجدران. وعندما يصل إلى الصفر، ستنتهي اللعبة كما يلي: int maxHealth = 100; float health = 100; float healthDecrease = 1; int healthBarWidth = 60; ... void gameScreen() { ... drawHealthBar(); ... } ... void drawHealthBar() { // جعلها بلا حدود: noStroke(); fill(236, 240, 241); rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth, 5); if (health > 60) { fill(46, 204, 113); } else if (health > 30) { fill(230, 126, 34); } else { fill(231, 76, 60); } rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth*(health/maxHealth), 5); } void decreaseHealth(){ health -= healthDecrease; if (health <= 0){ gameOver(); } } وهذا تنفيذٌ سريع لما قمنا به: لقد أنشأنا متغيرًا عامًا health يحفظ قيمة صحة الكرة. ثم أنشأنا تابع drawHealthBar() كي يرسم مستطيلين أعلى الكرة. الأول هو شريط الصحة الأساسي الكامل، والآخر هو الشريط النشط الذي يُظهر الصحة الحالية حيث يتغير حجمه بناءً على مقدار الصحة المتبقية، ويحسب باستخدام (healthBarWidth*(health/maxHealth، وهي نسبة الصحة الحالية مقارنة بشريط الصحة الكامل. ثمّ، يتم تعيين ألوان التعبئة لهذا الشريط وفقًا لقيمة الصحة ويعرض أخيرًا الدرجة Score التي حصلت عليها: ... void gameOverScreen() { background(0); textAlign(CENTER); fill(255); textSize(30); text("Game Over", height/2, width/2 - 20); textSize(15); text("Click to Restart", height/2, width/2 + 10); } ... void wallAdder() { if (millis()-lastAddTime > wallInterval) { ... // تمت إضافة قيمة أخرى في نهاية المصفوفة int[] randWall = {width, randY, wallWidth, randHeight, 0}; ... } } void watchWallCollision(int index) { ... int wallScored = wall[4]; ... if (ballX > gapWallX+(gapWallWidth/2) && wallScored==0) { wallScored=1; wall[4]=1; score(); } } void score() { score++; } void printScore(){ textAlign(CENTER); fill(0); textSize(30); text(score, height/2, 50); } كنا بحاجة للتسجيل عندما تمر الكرة بالحائط. لكننا بحاجة إلى إضافة درجة واحدة كحد أقصى لكل جدار. بمعنى، إذا مرت الكرة بالحائط ثم عادت ومرت مرةً أخرى، فلا ينبغي إضافة نتيجة أخرى. ولتحقيق ذلك، أضفنا متغيرًا آخر إلى مصفوفة جدار الفجوة داخل قائمة المصفوفات. يخزن المتغير الجديد 0 إذا لم تتجاوز الكرة هذا الجدار بعد و1 إذا تجاوزت ذلك الجدار. بعد ذلك، عدلنا تابع watchWallCollision(). أضفنا شرطًا لإطلاق تابع Score() ووضع علامة على الجدار كمرور عندما تمر الكرة بجدار لم تتجاوزه من قبل. نحن الآن قريبون جدًا من نهاية تطوير لعبتنا. آخر ما عليك فعله هو برمجة النقر على نص click to restart الظاهر على الشاشة. إلا أنّنا بحاجة إلى تعيين كافة المتغيرات التي استخدمناها إلى قيمتها الأولية، وإعادة تشغيل اللعبة. ها هو. ... public void mousePressed() { ... if (gameScreen==2){ restart(); } } ... void restart() { score = 0; health = maxHealth; ballX=width/4; ballY=height/5; lastAddTime = 0; walls.clear(); gameScreen = 0; } لنضف الآن بعض الألوان. تهانينا لديك الآن لعبة Flappy Pong متكاملة، جرب تنفيذها واللعب بها، وفي حال واجهت أي أي مشكلة يمكنك إيجاد الشيفرة البرمجية الكاملة لمعالجة اللعبة هنا نقل شيفرة معالجة اللعبة إلى الويب باستخدام p5.js تعرّف p5.js على أنها مكتبة للغة جافاسكريبت p5.js ذات بنية مشابهة للغة برمجة بروسيسنج Processing . وهي ليست مكتبة قادرة على تنفيذ شيفرة بروسيسنج Processing الموجودة ببساطة فبدلًا من ذلك، تتطلب p5.js كتابة تعليمات جافا سكريبت فعلية، على غرار منفذ جافا سكريبت لبروسيسنج Processing المعروف باسم Processing.js. مهمتنا هي تحويل شيفرة بروسيسنج Processing إلى جافا سكريبت باستخدام p5.js API. تحتوي المكتبة على مجموعة من الوظائف وبناء جملة مشابهة للمعالجة، وعلينا إجراء تغييرات معينة على التعليمات البرمجية الخاصة بنا لجعلها تعمل في جافا سكريبت - ولكن نظرًا لأن كلًّا من بروسيسنج Processing وجافا سكريبت يشتركان في أوجه التشابه مع جافا، فهي أقل انتقالًا مفاجئًا مما يبدو. حتى لو لم تكن مطور جافا سكريبت، فإن التغييرات بسيطة جدًا ويجب أن تكون قادرًا متابعتها بسهولة. أولًا، نحتاج إلى إنشاء ملف Index.html بسيط وإضافة p5.min.js إلى الترويسة. نحتاج أيضًا إلى إنشاء ملف آخر يسمى flappy_pong.js الذي سيحتوي على الشيفرة المحولة الخاصة بنا. <html> <head> <title>Flappy Pong</title> <script tyle="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.19/p5.min.js"></script> <script tyle="text/javascript" src="flappy_pong.js"></script> <style> canvas { box-shadow: 0 0 20px lightgray; } </style> </head> <body> </body> </html> يجب أن تكون استراتيجيتنا أثناء تحويل الشيفرة بنسخ كل الشيفرات ولصقها في flappy_pong.js ثم تنفيذ جميع التغييرات. وهذا ما فعلنا، إليك الخطوات التي اتخذناها لتحديث الشفيرة: جافا سكريبت هي لغة لغة ديناميكية الأنواع (إذ لا توجد تصريحات عن نوع المتغير مثل int و float) لذلك نحتاج إلى تغيير جميع تصريحات المتغيرات إلى var. لا يوجد void في جافا سكريبت. لذا يجب علينا تغيير جميع هذه الكلمات إلى الكلمة المفتاحية function. نحتاج إلى إزالة تصريحات النوع من الوسائط في توقيعات الدوال (على سبيل المثال نغير void wallMover(var index { لتكون function wallMover(index { . لا يوجد ArrayList في جافا سكريبت. لكن يمكننا تحقيق الشيء ذاته باستخدام مصفوفات جافا سكريبت. نُنشئ التغييرات التالية: غيّر ArrayList<int[]> walls = new ArrayList<int[]>(); إلى var walls = []; غيّر walls.clear(); إلى walls = []; غيّر walls.add(randWall); إلى walls.push(randWall); غيّر walls.remove(index); إلى walls.splice(index,1); غيّر walls.get(index); إلى walls[index] غيّر walls.size() إلى walls.length غيّر تصريح المصفوفة var randWall = {width, randY, wallWidth, randHeight, 0}; إلى var randWall = [width, randY, wallWidth, randHeight, 0]; أزل كل كلمات public. انقل كل تصريحات color(0) إلى داخل الدالة setup() لأن color() لن تُعرَّف قبل استدعاء setup(). تغيير size(500, 500); إلى createCanvas(500, 500); أعد تسمية الدالة gameScreen(){ إلى شيء آخر مثل function gamePlayScreen(){ لأن لدينا بالفعل متغير عام يسمى gameScreen. عندما كنا نعمل مع لغة بروسيسنج، كان أحدها دالة والأخرى متغير int. ولكن جافاسكريبت تخلط بينها لأنها غير مكتوبة النوع. يحدث الشيء نفسه للدالة score(). غيّرنا اسمها إلى addScore(). يمكن العثور على شيفرة جافا سكريبت الكاملة التي تغطي كل شيء في هذا الدرس التعليمي باستخدام Processing من هنا. الخلاصة إلى هنا نكون قد انتهينا من هذه السلسلة التي شرحنا فيها كيفية إنشاء لعبة بسيطة جدًا. ومع ذلك، فإن ما قمنا به في هذا المقال هو مجرد غيض من فيض. باستخدام لغة برمجة بروسيسنج Processing، حيث يمكنك تحقيق أي شيء بها تقريبًا، ويمكن القول أنها أفضل أداة لبرمجة ما كل ما تتخيله، والهدف الأساسي من هذه السلسلة التعليمية هي إثبات أن البرمجة ليست بهذه الصعوبة بدلًا من تدريس بروسيسنج Processing وبناء اللعبة الخاصة بك. إن صناعة لعبتك الخاصة ليس مجرد حلم. وأردنا أن نوضح لك أنه مع القليل من الجهد والحماس، يمكنك القيام بذلك بسهولة. ترجمة -بتصرف- لمقال Ultimate Guide to the Processing Language Part II: Building a Simple Game لكاتبه Oguz Gelal. اقرأ أيضًا المقال السابق: دليلك للغة برمجة Processing | الجزء الثاني: الرسم والتفاعل مع دخل المستخدم دليلك الشامل إلى برمجة الألعاب مطور الألعاب: من هو وما هي مهامه مبادئ كتابة جافا سكريبت متسقة ومفهومة مشروع لعبة منصة باستخدام جافاسكربت
-
بعد أن تعرّفنا في مقالتنا السابقة على لغة برمجة بروسيسنج وبيئة التطوير الخاصة بها وهيكلة البرنامج، سنتابع في الجزء الثاني التعرف على أبرز توابع الرسم الموجودة فيها وكيفية استخدامها، بالإضافة للتفاعل مع دخل المستخدم. رسم الأشكال والنصوص الآن سنتحدث عن بعض الأمور البصرية بما أننا أصبحنا نعرف كيفية ضبط المشروع باستخدام كتلة التهيئة ونعرف كيفية عمل رسم كتلة الرسم، كما سنتعلم الأمور المسلية في بروسيسنج processing مثل كيفية رسم الأشكال. قبل البدء يجب أن نفهم محاور الإحداثيات، إذ يجب تحديد إحداثيات كل شكل يتم رسمه على الشاشة في بروسيسنج processing. الواحدات مقدرة بالبيكسل والمبدأ (نقطة البداية) هي الزاوية اليسرى العلوية، يجب أن تحدد الإحداثيات بالنسبة لهذه النقطة. شيء آخر يجب أن تعرفه هو أن لكل شكل نقطة مرجعية مختلفة، مثلًا النقطة المرجعية للدالة react() هي الزاوية اليسرى العلوية، أما بالنسبة لدالة ellipse() فهي المركز. ويمكنك تغيير النقطة المرجعية باستخدام التوابع مثل rectMode() و ellipseMode() التي سنشرحها في قسم الخصائص والإعدادات. سنقتصر على توفير رؤية عامة أساسية للغة بروسيسنج ولن نتطرق لأي أشكال معقدة مثل الأشعة والأشكال ثلاثية البعد، فالأشكال ثنائية البعد تكفي لإنشاء لعبتنا، رأينا في الصورة أمثلة عن كيفية رسم الأشكال، كل شكل لديه صياغة خاصة به ليُنشئ، والفكرة الأساسية هي إما تحديد أبعاده أو حجمه أو كلاهما، التالي هو بعض الأمثلة التي يجب أن تتعلمها (تعني القيم x و y الإحداثيات مقدرة بالبيكسل لكل من المحاور الإحداثية x و y، وتعني القيم h و w العرض والطول مقدرة بالبيكسل أيضًا). الدالة point(): تستخدم لرسم نقطة بسيطة تحتاج قيمة إحداثيات واحدة، وتستخدم كما يلي: point(x, y) point(x, y, z) // في حالة الأبعاد الثلاثية الدالة line() - لإنشاء سطر، يمكنك إنشاء سطر بتحديد نقطة البداية والنهاية، وتستخدم كما يلي: line(x1, y1, x2, y2) line(x1, y1, z1, x2, y2, z2) // في حالة الأبعاد الثلاثية الدالة triangle() - لإنشاء المثلثات، وتستخدم كما يلي: triangle(x1, y1, x2, y2 ,x3 ,y3) الدالة quad() - لإنشاء المضلعات الرباعية، وتستخدم كما يلي: quad(x1, y1, x2, y2 ,x3, y3, x4 ,y4) الدالة rect(): تستخدم لرسم المربعات والأشكال المستطيلة. وتقع النقطة المرجعية في في الزاوية اليسارية العليا (راجع الصورة السابقة)، تستخدم الدالة كما يلي: rect(x, y, w, h) rect(x, y, w, h, r) // تعني r نصف القطر مقدرًا بالبيكسل لجعل زوايا المربع مدورة rect(x, y, w, h, tl, tr, br, bl) // أنصاف الأقطار للزاويتين العلوية اليسرى واليمنى والسفلية اليمنى واليسرى على الترتيب مقدرة بالبيكسل الدالة ellipse(): تستخدم لرسم قطع ناقص ومن أجل رسم الدوائر إذ يجب تحديد قيم متساوية للعرض والارتفاع، إن النقطة المرجعية هي افتراضيًا في المنتصف. وتستخدم كما يلي: ellipse(x, y, w, h) الدالة arc(): ترسم قوس، وتستخدم كما يلي: arc(x, y, w, h, start, stop) تشير المتغيرات start و stop في هذه الحالة إلى زاوية بداية ونهاية رسم القوس مقدرة بالراديان، ويمكن استخدام القيم التالية PI و HALF_PI و HALF_PI و TWO_PI لحساب الأقواي والمسارات الدائرية بسهولة أكبر. كما تستخدم أيضًا على النحو التالي: arc(x, y, w, h, start, stop, mode) يحدد المتغير mode هنا طريقة تصيير أو رسم القوس وتمرر قيمته كسلسلة نصية وتكون الخيارت المتاحة لقيم ذلك المتغير هي "OPEN" لرسم القوس فقط دون إغلاق الأطراف، و "PIE" لرسم قوس ووصل طرفيه بالمركز حيث نحصل على ما يشبه قطعة من فطيرة و "CHROD" لرسم القوس مع وصل طرفيه بخط مستقيم. إن إظهار النصوص على الشاشة يشابه إظهار الأشكال عن طريق تحديد إحداثيات مكان إظهار النص الدالة text() تظهر النصوص، وتستخدم كما يلي: text(c, x, y) // يشير المعامل c إلى المحرف، ويتم إظهار أي محرف أبجدي text(c, x, y, z) // في حال العمل في الأبعاد الثلاثة text(str, x, y) // تعني str السلسلة النصية المراد إظهارها text(str, x, y, z) // في حال العمل في الأبعاد الثلاثة text(num, x, y) // تعني num القيمة الرقمية المراد إظهارها text(num, x, y, z) // في حال العمل في الأبعاد الثلاثة الإعدادات والخصائص أول شيء يجب معرفته في هذا القسم هو المنطق المستخدم في ضبط خصائص الكائن، ومن الأمثلة عن ذلك هي لون التعبئة، و لون الخلفية، والحدود، وسماكة الحدود، ولون الحدود، ومحاذاة الشكل، وأنماط الحدود ...إلخ. تذكر عند ضبط الخاصية أن الشيفرة تُنفّذ من الأعلى إلى الأسفل، لنقل أنك تريد ضبط خاصية التعبئة اللونية "fill" باللون الأحمر، وبالتالي كل الكائنات التي ستُرسم بعدها ستكون تعبئتها حمراء حتى نعيد تعريف الخاصية، والأمر نفسه ينطبق أيضًا على باقي الخصائص. لكن الجدير بالملاحظة هنا أن بعض الخصائص لا تستبدل بعضها البعض. على سبيل المثال، خاصية لا تستبدل خاصية لون الحد stroke قيمة خاصية التعبئة fill لأنهما يعملان مع بعضهما. يسهل الشكل التالي فهم منطق عمل الخاصيات . لاحظ كيف تؤثر أوامر fill و stroke على جميع الأشكال المرسومة بعدها حتى تقوم بتغييرها أو إلغائها. كما رأيت في الشكل أعلاه يضبط السطر الأول لون التعبئة للأحمر، ويضبط السطر الثاني لون خط الحدود للأزرق، وبالتالي لدينا خاصتان فعالتان هنا وهما التعبئة باللون الأحمر ولون خط الحدود بالأزرق، وبالتالي سيكون كل كائن يرسم في الأسطر التالية من الكود بلون تعبئة حمراء وخط حد أزرق (إذا كان ذلك ممكنًا فبعض الأشكال قد لا تحتوي على حدود)، تمعّن بالشكل لتفهم المنطق بشكل أوضح. فيما يلي نشرح بعض الخصائص والإعدادات المتداولة في التصميم. إعدادات التصميم الدالة fill(): تضبط نوع اللون لتعبئة الكائنات، تستخدم هذه الدالة لتلوين النصوص، ويكفي مبدئيًا تعلم الاستخدامات التالية: fill(r, g, b) // قيم الأحمر والأخضر والأزرق كأعداد صحيحة fill(r, g, b, a) // قيمة alpha إضافية، قيمتها العظمى هي 225 الدالة noFill(): تستخدم لضبط لون التعبئة ليكون شفاف الدالة stroke(): تستخدم لضبط نوع لون خط الحدود للكائنات، يمكن تطبيق خاصية الحد للخطوط والحدود حول الكائنات، ويكفي مبدئيًا تعلم الاستخدامات التالية: stroke(r, g, b) // قيم الأحمر والأخضر والأزرق كأعداد صحيحة stroke(r, g, b, a) // قيمة alpha إضافية، قيمتها العظمى هي 225 الدالة noStrok(): تًزيل الحد الدالة strokeWeight(): تضبط سماكة خط الحد، وفيما يلي مثال على طريقة الاستخدام: strokeWeight(x) // إن قيمة x هو عدد صحيح يمثل عرض الخط مقدرة بالبيكسل الدالة background(): تضبط لون الخلفية، ويكفي مبدئيًا تعلم الاستخدامات التالية: background(r, g, b) // تمرر قيم الأحمر والأخضر والأزرق كأعداد صحيحة. background(r, g, b, a) // قيمة alpha إضافية، وقيمتها العظمى هي 225 إعدادات المحاذاة الدالة ellipseMode(): تستسخدم لضبط موضع النقطة المرجعية للقطع الناقص، وهذا مثال عن طريقة الاستخدام: ellipseMode(mode) يملك المعامل mode القيم الممكنة التالية التي تتحكم في كيفية رسم القطع الناقص: CENTER(default) يحدد المركز كنقطة المرجعية وتكون قيم w و h هي عرض وارتفاع القطع الناقص ككل. RADIUS تأخذ المركز كالنقطة المرجعية، ولكن في هذا النمط تكون قيم w و h المحددة هي نصف العرض ونصف الارتفاع (كما في حالة نصف قطر الدائرة بدل القطر بالكامل) CORNER تحدد الزاوية اليسرى العلوية كنقطة مرجعية، وتكون قيم w و h هما قيمتي العرض والارتفاع. CORNERS تحدد المعاملين الأول والثاني (x و y) كموقع الزاوية اليسرى العلوية، والمعاملين الثالث والرابع (w و h) كموقع الزاوية اليسرى السفلية للقطع الناقص، لذا في هذا النمط قيم العرض و الطول غير مهمتان، ولفهم أفضل يمكن اعتبار الدالة بالشكل التالي ellipse(x_tl,y_tl,x_br,y_br) لتكون أكثر منطقية. الدالة rectMode(): تحدد مكان النقطة المرجعية لمحاذاة المستطيلات، وفيما يلي طريقة الاستخدام: rectMode(mode) يملك المعامل mode لديه المعاملات التالية : CENTER يحدد المركز كنقطة المرجعية للمستطيل وتكون قيم w و h المحددة هي عرض وارتفاع المستطيل. RADIUS تأخذ المركز كالنقطة المرجعية، ولكن في هذا النمط تكون قيم w و h المحددة هي نصف العرض ونصف الارتفاع. CORNER تحدد الزاوية اليسرى العلوية كنقطة مرجعية وقيم w و h المحددة هي عرض وارتفاع المستطيل هذه هي القيمة الافتراضية للمتغير. CORNERS تحدد المعاملين الأول والثاني (x و y) كموقع الزاوية اليسرى العلوية، والمعاملين الثالث والرابع (w و h) كموقع الزاوية اليسرى السفلية للمستطيل، لذا في هذا النمط تكون قيم العرض و الطول غير مهمين، ولفهم أفضل يمكن اعتبار الدالة بالشكل التالي rect(x_tl,y_tl,x_br,y_br) لتكون أكثر منطقية. إعدادات متعلقة بالنص الدالة textSize(): تحدد حجم الخط، وطريقة الاستخدام كالتالي: testSize(size) قيمة حجم الخط كعدد صحيح دالة الدالة textLeading(): تحدد ارتفاع الخط للنصوص، الاستخدام: الدالة textLeading(lineheight): تحدد قيمة الفراغ بين الأسطر مقدر بالبيكسل الدالة textAlign(): تحدد أين هي النقطة المرجعية لمحاذاة النصوص، وهذا مثال على طريقة الاستخدام: textAlign(alignX)// تستخدم alignX للمحاذاة الأفقية وتأخذ القيم LEFT,CENTER,RIGHT textAlign(alignX,alignY) //تستخدم alignY للمحاذاة الشاقولية وتأخذ القيم TOP,BOTTOM,CENTER,BASELINE التحريك تعلمنا حتى الآن كيفية رسم الكائنات والنصوص، ولكنها كلها كائنات ثابتة لا تتحرك؛ سنتعلم الآن تحريكها عن طريق إعطاء الإحداثيات قيم متغيرة بدلًا من أعداد صحيحة ثابتة ويمكننا زيادتها أو إنقاصها، لاحظ الشيفرة التالية // تهيئة x و y إلى القيمة 0 int x=0; int y=0; void setup(){ size(800,600); background(255); // ضبط لون الخلفية للون الأبيض } void draw(){ fill(255,0,0); // لون التعبئة أحمر stroke(0,0,255); // لون خط الحد هو الأزرق ellipseMode(CENTER); // نقطة المرجع هي المركز ellipse(x, y, 20, 20); // رسم القطع الناقص // زيادة x و y x+=5; y+=5; } هل تستطيع معرفة كيفية إدارة عملية التحريك؟ لقد ضبطنا قيم x و y كمتغيرات عامة بقيم أولية 0، وأنشأنا في حلقة الرسم القطع الناقص، ثم ضبطنا لون التعبئة للأحمر، ولون خط الحد للأزرق وضبطنا قيم الإحداثيات x و y، إذ تغير الكرة مكانها عندما نزيد قيمتي x و y ولكن هناك مشكلة في الشيفرة هل تستطيع معرفتها كاختبار لك؟ حاول تجربتها: الهدف هنا أن تعرف كيفية عمل الحلقات في لغة بروسيسنج، تذكر المثال الذي أوردناه في قسم كتلة الرسم في المقال السابق ولماذا حصلنا على "111…" بدلًا من "1234.." إنه السبب ذاته الذي جعل الكرة تترك أثرًا، حيث في كل مرة تكرّر الكرة تزداد قيمة x و y بقيمة 5 وتُرسم الكرة نحو اليمين والأسفل بمقدار 5 بيكسل، ولكن تبقى الكرة المرسومة من التكرار السابق، كيف نستطيع إزالتها؟ لإزالة أثر الكرة نزيل ببساطة background(255) من كتلة التهيئة ونضعها في أول سطر من كتلة الرسم، فعندما كانت شيفرة الخلفية في كتلة التهيئة نُفذت الشيفرة مرة واحدة في البداية لجعل الخلفية بيضاء، ولكن ذلك لا يكفي يجب إعادة ضبط الخلفية للأبيض في كل حلقة لتغطية الكرات المرسومة من الحلقات السابقة. بما أن الخلفية هي السطر الأول فهذا يعني تنفيذه أولًا وتصبح الطبقة الأساسية، وتصبح الخلفية بيضاء في كل حلقة وتُرسم العناصر عليها لذا لا يبقى أي أثر. تعتمد فكرة التحريك في لغة البرمجة بروسيسنج على التلاعب بالشكل وتعديله برمجيًا لتغيير مكانه، ولكن قد تتساءل كيف نستطيع عمل أشياء أكثر تعقيدًا مثل إبقاء الكرة على الشاشة أو تطبيق الجاذبية؟ هذا ما سنتعلمه في المقال التالي عن تطبيق عملي نجرب من خلاله إنشاء لعبة كاملة وقابلة للعب ومسليّة. التفاعل مع الفأرة ولوحة المفاتيح إن التفاعل مع الفأرة ولوحة المفاتيح أمر سهل في لغة بروسيسنج، إذ توجد توابع تُستدعى لكل حدث وما يُنفّذ كل ما يتم كتابته داخلها عند حصول الحدث، و هناك أيضًا متغيرات عامة مثلmousePressed و keyPressed يمكن استخدامها في كتلة الرسم للاستفادة من الحلقة، التالي هي بعض التوابع مع شرحها void setup() { size(500, 500); } void draw() { if (mousePressed) { // تُنفَّذ الشيفرات هنا طالما زر // الفأرة مضغوط if (mouseButton == LEFT){ // تُنفَّذ الأسطر طالما // زر الفأرة المنقور هو زر الفأرة // اليسار } } if (keyPressed) { // تُنفَّذ الشيفرات هنا طالما هناك مفتاح // مضغوط على لوحة المفاتيح if (key == CODED) { // تتحقق هذه تعليمة if هذه إذا كان المفتاح // مُتَعرف عليه من لغة بروسيسنج if (keyCode == ENTER) { // تُنفَّذ الأسطر إذا كان المفتاح // هو مفتاح enter } } else{ // تُنفَّذ هذه الأسطر إذا لم يتم التعرف // على السطر من لغة بروسيسنج } } } void mousePressed() { // تنفذ هذه الشيفرات مرة عندما تنقر الفأرة // لاحظ أن المتغير mouseButton // مستخدم هنا } void keyPressed() { // تنفذ هذه الشيفرات مرة عندما يضغط المفتاح // لاحظ أن المتغيرات key و keyCode // مستخدمة هنا } كما ترى من السهل التحقق إذا ما كانت الفأرة قد تم نقرها أو هناك زر تم الضغط عليه، ولكن هناك خيارات أكثر للمتغيرات mousePressed و keyCode، إن الخيارات المتاحة للمتغير mousePressed هي LEFT و RIGHT و CENTER، وهناك العديد من الخيارات المتاحة للمتغير keyCode مثل (UP و DOWN و LEFT و RIGHT و ALT و CONTROL و SHIFT و DELETE ...إلخ.) هناك أمر وحيد يجب معرفته في متغيرات الفأرة وهو كيفية الحصول على إحداثيات مؤشر الفأرة، وهو أمر سنستخدمه كثيرًا، فللحصول على إحداثيات المؤشر يمكنك استخدام المتغيرين mouseX و mouseY مباشرة في كتلة draw()، وأخيرًا وليس آخرًا هناك العديد من الدوال التي يجب أن تتطلع عليها موثقة في مراجع لغة بروسيسنج. الخاتمة يجب أن تصبح لغة بروسيسنج Processing مألوفة لك الآن، ولكن إذا توقفت هنا ستضيع كل المعلومات، من الأفضل لك أن تستمر في التدريب و تجرب الأشياء التي تعلمتها. إليك تمرينين لمساعدتك على التدرب، يجب أن تحاول أقصى جهدك في تنفيذهما بنفسك ويمكنك البحث عن الحل إذا وجدت الأمور صعبة. ستكون شيفرة حل أول تمرين متاحة، إلا أن الاطلاع عليها يجب أن تكون خيارك الأخير إذا لم تستطع التقدم. التمرين الأول عليم رسم 4 كرات بألوان مختلفة تبدأ من 4 زوايا الشاشة وتنتقل إلى الوسط بسرعات مختلفة، ويجب أن تتوقف الكرة عندما تنقر على أحد الكرات وتستمر في ضغط الزر، وعندما تزيل الضغط تعود الكرة إلى مكانها الأساسي وتستمر في الحركة، أي شيء كالتالي تحقق من الشيفرة هنا بعد تجربة هذا التمرين التمرين الثاني هل تذكر شاشة توقف DVD المشهورة التي يرتد بها شعار DVD حول الشاشة وننتظر بشوق أن تصطدم في الزاوية؟ أريد منك تكرار شاشة التوقف هذه باستخدام مستطيل بدلًا من شعار DVD، عند تشغيل التطبيق يجب أن تكون الشاشة سوداء وينطلق المستطيل من موقع عشوائي، وفي كل مرة يصطدم بزاوية يجب على المستطيل تغيير لونه (وبالطبع اتجاهه). وعند تحريك الفأرة يجب على المستطيل الاختفاء وأن تصبح الشاشة بيضاء، لن تحصل على الحل في هذه المقالة يجب عليك أن تحاول كل جهدك أن تنفذها وسنقدم الشيفرة في المقال التالي. ترجمة -بتصرف- لمقال Guide to the Processing Language Part I: Fundamentals لكاتبه Oguz Gelal. اقرأ أيضًا المقال السابق: دليلك للغة برمجة بروسيسينج Processing | الجزء الأول: الأساسيات المقال التالي: دليلك للغة برمجة بروسيسينج Processing | الجزء الثالث: بناء لعبة بسيطة كيف تصبح مبرمج ألعاب فيديو ناجح دليلك الشامل إلى برمجة الألعاب بناء لعبة ورق في جافا تعرف على أفضل برنامج تصميم الألعاب الإلكترونية
-
يٌعدَ إنشاء النماذج الأولية بسرعة وإنتاج نتائج بصرية سريعة من الميزات المهمة في العديد من لغات وأطر البرمجة. ومع ذلك، تتميز بعض اللغات بجعل هذه الإمكانيات من أهدافها الأساسية، مثل لغة البرمجة بروسيسنج Processing المبنية على جافا. تُتيح لغة Processing للمستخدمين كتابة الشيفرات ضمن بيئة تصميم رسومية، حيث تم تطويرها خصيصًا لتقديم نتائج بصرية فورية. في هذه المقالة، نقدم مقدمة بسيطة عن لغة البرمجة بروسيسنج وتقنياتها الفريدة." فإذا كنت تشعر بالملل وتريد أن تفرغ إبداعك في بناء مشروع مبهر بصريًا أو شيء فني، أو لربما تريد تعلم البرمجة وعمل شيء جميل بأسرع وقت؟ عليك بلغة برمجة بروسيسنج Processing، ويمكن القول أن هذه اللغة واحدة من أكثر اللغات متعة، فهي لغة مباشرة وسهلة التعلم والاستخدام بالإضافة لكونها أداة قوية، فاستخدامها يمنحك الشعور بأنك ترسم على لوحة بيضاء بأسطر برمجية، ولا يوجد قوانين صارمة تحد إبداعك، الحد الوحيد هنا هو خيالك. ما سنتعلمه في هذه السلسلة من المقالات هو التعرف على لغة البرمجة بروسيسنج Processing وتصميم ألعاب بسيطة باستخدامها. تتكون هذه المقالات من ثلاثة أقسام: في المقال الأول سنتحدث عن اللغة Processing ونوفر نظرة عامة عنها. في المقال الثاني نوفر دليلًا شاملًا للغة Processing وبعض النصائح حولها. في المقال الثالث سننشئ لعبة بسيطة خطوة بخطوة كما سنحوّل شيفرة اللعبة إلى لغة جافاسكريبت JavaScript باستخدام P5js لتعمل على متصفح الويب. ماذا يجب أن تعرفه أولًا يجب عليك أن تعرف مبادئ البرمجة الأساسية لفهم ومتابعة هذه المقالة بسهولة، لأننا لن نتحدث عنها، ولن نتحدث عن مفاهيم برمجية معقدة أيضًا لذا يجب أن يكون لديك معرفة بأساسيات البرمجة. وسنتطرق في بعض الأجزاء عن مفاهيم برمجية ذات مستوى منخفض low-level مثل مفهوم البرمجة كائنية التوجه Object Oriented Programming أو OOP اختصارًا، ولكن هذه المفاهيم ليست أساسية للعمل على برمجة اللعبة، إنما هي موجهة للقراء المهتمين ببنية اللغة البرمجية وبالتالي يمكنك تخطي هذه الأجزاء إذا لا ترغب بمعرفتها. عدا ذلك ستحتاج إلى حافز تعلم اللغة والشغف لبرمجة لعبة إلكترونية خاصة بك. كيفية المتابعة من الجيد دومًا التعلم عن طريق التجريب، فكلما تعمقت في لعبتك كلما تمكنت من إتقان لغة بروسيسنج Processing. لذا نقترح بداية بأن تعمل على تجريب كل خطوة في البيئة الخاصة بك. لدى لغة بروسيسنج Processing بيئة تطوير متكاملة (أي محرر شيفرة) سهل الاستخدام، إنه الشيء الوحيد الذي تحتاج لتنزيله وتثبيته للمتابعة. ويمكن تنزيله من هنا بعدها ستكون جاهزًا لتبدأ! ما هي لغة برمجة بروسيسنج Processing؟ يضم هذا القسم مقدمة تقنية مختصرة عن لغة البرمجة Processing وهيكليتها وبعض الملاحظات على عملية التصريف compiling والتنفيذ. ستتحدث التفاصيل عن معلومات متقدمة عن البرمجة وبيئة جافا. يمكنك تخطي قسم "أساسيات Processing" إذا كنت لا تريد معرفة التفاصيل البرمجية النظرية وترغب في البدء بشكل عملي في برمجة اللعبة الخاصة بك. إن لغة بروسيسنج Processing هي لغة برمجة بصرية تسمح لك بالرسم عن طريق الشيفرة، ولكنها ليست لغة برمجة بحد ذاتها، فهي تدعى "شبيهة جافا Java-esque" أي أنها مبنية على منصة جافا ولكنها ليست لغة جافا، وهي تُعالج الشيفرة وتحوَّل بكاملها إلى شيفرة جافا عند التنفيذ. إن صنف جافا PApplet هو الصنف الأساسي لكل رسومات بروسيسنج Processing. لنأخذ كتلتي شيفرة بروسيسنج Processing كمثال. public void setup() { // توضع شيفرات التهيئة هنا } public void draw() { // توضع شيفرات الرسم هنا } سوف تتحول كل من كتلتي الشيفرة في الأعلى إلى شيء يشبه التالي: public class ExampleFrame extends Frame { public ExampleFrame() { super("Embedded PApplet"); setLayout(new BorderLayout()); PApplet embed = new Embedded(); add(embed, BorderLayout.CENTER); embed.init(); } } public class Embedded extends PApplet { public void setup() { // توضع شيفرات التهيئة هنا } public void draw() { // توضع شيفرات الرسم هنا } } سترى أنه تم تغليف كتلة شيفرة بروسيسنج Processing بصنف ممتد extended class من PApplet الخاص بلغة جافا. لذا يجري التعامل مع كل الأصناف المعرفة في شيفرة بروسيسنج كأنها أصناف داخلية inner classes. إن معرفة أن بروسيسنج Processing مبنية من لغة جافا له العديد من الميزات خصوصًا إذا كنت مطور جافا، ليس فقط لكون الصياغة مألوفة، ولكن هذه المعرفة تمنحنا القدرة على تضمين شيفرة جافا والمكاتب وملفات JAR في رسوماتك واستخدام تطبيقات بروسيسنج Processing مباشرة من Eclipse إذا أردت أخذ بعض الوقت لتهيئته. شيء واحد لا تستطيع عمله هو استخدام مكونات AWT و Swing في رسومات بروسيسنج لأنها تتضارب مع طبيعة لغة بروسيسنج ولكن لا تقلق لن نتطرق لذلك في هذه المقالة. أساسيات بروسيسنج Processing تتألف شيفرة لغة بروسيسنج Processing من قسمين أساسيين، هما كتلتَي التهيئة والرسم. تُنفذ كتل التهيئة عندما تُنفَّذ الشيفرة وتستمر كتل الرسم بالتنفيذ دومًا. إن الفكرة الأساسية خلف بروسيسنج Processing هي تنفيذ ما يتم كتابته في كتلة الرسم 60 مرة في الثانية من الأعلى إلى الأسفل لحين إغلاق البرنامج. سنبني كل شيء اعتمادًا على هذه الفكرة، وسنحرك الكائنات ونعرف النتيجة ونتوقع الاصطدامات ونطبق الجاذبية ونعمل أي شيء باستخدام هذه الخاصية. حلقة التحديث هذه هي نبض المشروع. سنشرح كيفية الاستفادة من ذلك لجعل الشيفرة تنبض بالحياة في الأقسام اللاحقة، ولكن أولًا لنتحدث عن بيئة التطوير المتكاملة لبروسيسنج Processing. بيئة التطوير المتكاملة لبروسيسنج Processing IDE نزّل بيئة التطوير المتكاملة لبروسيسنج Processing إذا لم تفعل ذلك لحد الآن. سنحدد بعض المهام السهلة لتنفذها لوحدك خلال المقالة ويمكنك أن تجربها فقط إذا كانت لديك بيئة التطوير المتكاملة.القسم التالي هو مقدمة عن بيئة التطوير المتكاملة لبروسيسنج Processing وهي بسيطة ومفهومة لذا سنختصر الشرح حولها. يقوم زرَّا "إيقاف Stop" و"تنفيذ Run" بما تتوقعه، فعند نقر زر "تنفيذ Run" تُصرَّف الشيفرة وتُنفَّذ. وتعمل برامج بروسيسنج Processing دون توقف إذا لم تُقاطِع تنفيذها. ويمكنك إنهاء التنفيذ برمجيًا أو يمكنك استخدام زر "إيقاف Stop". الزر الذي يشبه الفراشة علي يمين زر "إيقاف Stop" و "تنفيذ Run" هو المُنقّح Debugger، واستخدام المُنقِّح يحتاج لمقالة خاصة وهو خارج نطاق هذه المقالة ويمكن تجاهله الآن. أما اللائحة المنسدلة بجانب زر المُنقح فهي تستخدم عند إضافة وتهيئة الأنماط، إذ تقدم الأنماط Mods بعض الوظائف وتسمح لك بكتابة شيفرة لأندرويد وبايثون وما إلى ذلك. إن الأنماط هي أيضًا خارج نطاق المقالة ويمكنك تجاهلها أيضًا. النافذة في محرر الشيفرة هي مكان تنفيذ الرسوم، وهي سوداء في الصورة السابقة لأننا لم نحدد بعد خاصية مثل الحجم أو لون الخلفية ولم نرسم أي شيء. لا يوجد شيء آخر للتحدث عنه في محرر الشيفرة إنه ببساطة مكان لكتابة الشيفرة. وهناك عدد أسطر -لم تحوي إصدارات بروسيسنج Processing السابقة عليه- الصندوق الأسود السفلي هو الطرفية Console، سنستخدمها لطباعة الأشياء لتنقيح الأخطاء بسرعة. تظهر تبويبة الأخطاء Errors بجانب الطرفية الأخطاء عند ظهورها. هذه ميزة مفيدة تأتي مع إصدار بروسيسنج Processing 3.0. فقد كانت تُطبع الأخطاء في الإصدارت السابقة إلى الطرفية وكانت هناك صعوبة في تتبعها. كتلة التهيئة Setup Block تُنفَّذ كتلة التهيئة كما تحدثنا سابقًا عند تنفيذ البرنامج، ويمكن استخدامها للضبط ولأشياء تريد تنفيذها مرة واحدة مثل تحميل الصور والأصوات. فيما يلي مثال يوضح كتلة تهيئة، نفذ الشيفرة في البيئة الخاصة بك وشاهد النتائج بنفسك: public void setup() { // حجم المشروع هو 600x800 // واستخدم محرك التصيير P2D size(800, 600, P2D); // يمكن استخدام هذه الدالة بدلًا من size() // fullScreen(P2D); // سيكون لون خلفية المشروع هو الأسود //افتراضيًا، إذا لم يحدد غير ذلك background(0); // يمكن استخدام هذه لضبط صورة الخلفية //يجب أن يكون حجم المشروع بحجم الصورة // background(loadImage("test.jpg")); // يتم تعبئة الأشكال والكائنات باللون الأحمر افتراضيًا // مالم يُشار عكس ذلك fill(255,0,0); // يكون للأشكال والكائنات حد أبيض افتراضيًا // مالم يُشار عكس ذلك stroke(255); } سنشرح التوابع المتعلقة بالتصميم (مثل الخلفية والتعبئة وثخانة الخط) في المقال التالي ما تحتاج معرفته الآن هو كيفية تأثير الضبط والإعدادات على المشروع بأكمله بالإضافة لفهم التوابع التالية. تستخدم الدالةsize() -كما يشير اسمها- لضبط حجم الرسم. يجب أن تكون في أول كتلة شيفرة التهيئة، ويمكن استخدامها بالطرق التالية: size(width,height); size(width, height, renderer); يمكن تحديد عرض وارتفاع القيم بالبيكسل. وتقبل دوال الحجم معامل أو وسيط ثالث هو المُصيّر أو العارض renderer المستخدم لتحديد أي محرك تصيير سيتم استخدامه. والمصير الافتراضي هو P2D. والمُصيّرات المتوافرة هي P2D (للمعالجة ثنائية البعد) و P3D (للمعالجة ثلاثية البعد، ويجب استخدامه إذا تضمنت الرسومات رسوميات ثلاثية البعد) و PDF (حيث ترسم الرسوميات ثنائية البعد مباشرة في ملف PDF). لمزيد من المعلومات انقر هنا . ستستخدم كل من P2D و P3D العتاد الرسومي المتوافق مع OpenGL. يمكن استخدام دالة الشاشة الكاملة ()fullScreen بدلًا من دالة size() من إصدار بروسيسنج Processing 3.0 ، وكما في دالة size() يجب أن تكون في أول سطر من كتلة التهيئة أيضًا ويكون استخدامها كالتالي: fullScreen(); fullScreen(display); fullScreen(renderer); fullScreen(display, renderer); إذا استخدمتها بدون أي معامل سيكون رسم بروسيسنج Processing على الشاشة كاملة وسيُنفذ على شاشتك الأساسية. يشير معامل display للإشارة إلى الشاشة التي سيُنفذ الرسم عليها. مثلًا إذا وصلت شاشات خارجية لحاسوبك يمكنك ضبط متغير الإظهار إلى 2 (أو 3 أو 4 إلخ.) وسيُنفذ الرسم هناك. تم شرح معامل renderer في قسم size() في الأعلى. كتلة الإعدادات Settings Block أضيفت هذه الميزة في إصدارات بروسيسنج Processing الجديدة، وهي كتلة شيفرة مثل التهيئة والرسم وتفيد عند تعريف توابع size() و fullScreen() بمعاملات متغايرة. من الضروري أيضًا تعريف size() وخصائص التصميم الأخرى مثل smooth() في كتلة الشيفرة هذه إذا كنت تستخدم بيئات غير بيئة التطوير المتكاملة الخاصة ببروسيسنج Processing مثل Eclipse، ولكن لن تحتاجه في معظم الحالات وبالتأكيد ليس في هذه المقالة. كتلة الرسم Draw Block لا يوجد شيء مميز للتحدث عن كتلة الرسم إلا أنها نبض المشروع، فكل شيء يحصل فيها، فهي قلب البرنامج الذي ينبض 60 مرة في الثانية. تحوي كتلة الشيفرة هذه كل منطق الشيفرة فكافة الأشكال والكائنات..إلخ مكتوبة هنا. إن كل الشيفرة التي سنتحدث عنها في المقالة ستكون في كتلة الرسم لذا من الضروري أن تفهم كيفية عمل هذه الكتلة. لنقدم عرض عن ذلك. جرب التالي: لاحظ أننا يمكن أن نطبع أي شيء في الطرفية باستخدام التابعَين print() أو println(). تطبع دوال الطباعة إلى الطرفية لكن println تطبع وتبدأ بسطر جديد في النهاية لذا تُطبع كل println() في سطر مختلف. شاهد كتلة الشيفرة التالية وحاول أن تعرف ما سيُطبع في الطرفية: void setup(){ } void draw(){ int x = 0; x += 1; print(x+" "); } إذا قلت "1234…."، فهذا جواب متوقع، ولكنه للأسف جوال خاطئ! هذه واحدة من أوجه سوء الفهم في بروسيسنج processing. تذكر أن الكتلة تُنفَّذ بشكل متكرر. فعندما عرّفت المتغير هنا فسيُعرَّف في كل حلقة مرة تلو مرة. وفي كل تكرار قيمة x هي 0 وتزداد بمقدار 1 ويتم طباعتها في الطرفية لذا ستكون النتيجة "111…" هذا المثال واضح نوعًا ما، ولكن قد يكون أكثر صعوبة عندما تتعقد الأمور. لا نريد أن يُعاد تعريف x لذا كيف يمكننا تحقيق ذلك وأن نحصل على النتيجة "1234…"؟ الجواب هو عن طريق استخدام المتغيرات العامة global variables. لذا نعرف المتغير خارج كتلة الرسم كي لا يُعاد تعريفه في كل تكرار، أيضًا سيكون نطاق المتغير قابل للوصول داخل مشروعك. لاحظ الشيفرة التالية: int x = 0; void setup(){ } void draw(){ x += 1; print(x+" "); } ربما تتساءل كيف يمكن أن يعمل متغير معرف خارج النطاق؟ ولماذا لم نستخدم كتلة setup() طالما تُنفَّذ مرة في البداية؟ إن الجواب متعلق بالبرمجة كائنية التوجه والنطاق scope. تذكر كيف شرحنا أن شيفرة بروسيسنج processing تتحول إلى جافا وكيف تُغلف بصنف جافا؟ إن المتغيرات المكتوبة خارج كتل setup() و draw() تُغلَّف أيضًا. واستخدام x+=1 هو نفس استخدام this.x+=1، ويؤدي الغرض ذاته في حالتنا، لا يُعرَّف متغير باسم x في نطاق draw() ويتم البحث في النطاق الخارجي الذي هو نطاق this. وسبب عدم تعريفنا المتغير x في قسم setup() هو: إذا فعلنا بذلك سيُعرف نطاق x في نطاق دالة setup ولن تستطيع كتلة draw() الوصول إليه. الخلاصة تعرّفنا في الجزء الأول من هذه السلسلة على لغة بروسيسنج وميزاتها، ومن ثم بيئة التطوير وهيكل البرنامج فيها. سنتعرّف في الجزء القادم من السلسلة على التوابع المُستخدمة في الرسم وعلى معاملاتها المختلفة. ترجمة -بتصرف- لمقال Guide to the Processing Language Part I: Fundamentals لكاتبه Oguz Gelal. اقرأ أيضًا المقال التالي: دليلك للغة برمجة Processing | الجزء الثاني: الرسم والتفاعل مع دخل المستخدم تعرف على أهمية صناعة الألعاب الإلكترونية كيف تصبح مبرمج ألعاب فيديو ناجح دليلك الشامل إلى برمجة الألعاب بناء تطبيقات كاملة باستعمال مكتبة جافا إف إكس JavaFX بناء لعبة ورق في جافا