-
المساهمات
24 -
تاريخ الانضمام
-
تاريخ آخر زيارة
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو رشا سعد
-
يتمحور المقال السادس من سلسلة (بناء لعبة من الصفر باستخدام بايثون) حول إضافة المنصات إلى عالم اللعبة لتتفاعل معها الشخصيات وتتنقل عبرها فهذه اللعبة أولًا وأخيرًا لعبة منصات، ولكن قبل أن نبدأ نذكرك بمقالات السلسلة. بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. المنصات في Pygame كائنات مثلها مثل الشخصيات، تُضاف إلى عالم اللعبة بالطريقة نفسها عبر برمجة كائن بايثون خاص بكل منصة وتحديد إحداثيات ظهورها على الشاشة، ويسهل هذا التشابه برمجة التفاعل بين الطرفين. برمجة كائن المنصة تمامًا مثل بناء كائن شخصية اللاعب فإن بناء كائن للمنصة يعني إنشاء صنف بلغة بايثون، سنسميه Platform ويتطلب إنشائه عددًا من المعلومات عن المنصة مثل نوعها وصورتها وموقع ظهورها على الشاشة، قد لا تملك هذه المعلومات جميعها في لحظة بناء الصنف ولا بأس في ذلك فستُمررها له فيما بعد بالطريقة نفسها التي اتبعناها عند تحريك اللاعب فلم نحدد السرعة حتى نهاية المقال. عرّف الصنف وفق الآتي: # x الإحداثي, y الإحداثي, imgw عرض الصورة, imgh ارتفاع الصورة, img ملف الصورة class Platform(pygame.sprite.Sprite): def __init__(self, xloc, yloc, imgw, imgh, img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images', img)).convert() self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc لابد أنك لاحظت تشابه التعليمات بين هذا الصنف وأصناف شخصيتي البطل والعدو، ومثلهما أيضًا سيؤدي استدعاؤه إلى إنشاء كائن المنصة وإظهار صورتها على الشاشة بالأبعاد المحددة ضمن الصنف وفي الموقع المحدد وفق الإحداثيات X و Y. أنواع المنصات هناك نوعان للمنصات وهي: المنصات المبنية بطريقة قطع البلاط تُذكرنا هذه المنصات بالألعاب الكلاسيكية الشهيرة مثل سوبر ماريو والقنفذ سونيك، ويعدّها البعض من أسهل طرق بناء المنصات من ناحية الصور المطلوبة -وإن لم تكن كذلك لجهة تعقيد الشيفرة والعمليات الحسابية- فكل ما تحتاجه هو مجموعة صور لقطع البلاط أو القرميد أو ما شابه، ومن ثم تبدأ بترتيبها بأشكالٍ مختلفة ضمن اللعبة، حوالي 8 أو 12 نمط تشكل بها الأرضية والمنصات العائمة وتكررها لتبني مستويات اللعبة. المنصات المبنية بطريقة الرسم اليدوي تعني هذه الطريقة رسم أو تصميم كل منصة بصورة منفصلة، وتتطلب منك بذل بعض الوقت والجهد في العمل على برامج التصميم الجرافيكي لإنشاء كل جزء من عالم اللعبة، إلاّ أنها بالمقابل تحتاج قدرًا أقل من العمليات الحسابية، إذ إن كافة الكائنات أو المنصات مجهزة وكاملة وكل ما عليك فعله هو إخبار بايثون بمكان إظهارها على الشاشة. لكل طريقة مزايا وعيوب وأسلوب معين في كتابة التعليمات البرمجية، لذا سنشرح الطريقتين في الفقرات اللاحقة حتى تتمكن من استخدام الطريقة التي تناسبك أو تدمج الطريقتين معًا في مشروعك. وضع مخطط المستوى تعدّ هذه المرحلة جزءًا مهمًا من تصميم المستوى للعبة، يمكنك البدء باستخدام الورقة والقلم، ارسم مربعًا يمثل نافذة اللعبة وعليه المحورين الأفقي والعمودي، ومن ثم ابدأ برسم المنصات التي تريدها واحدةً تلو الأخرى وثبت على كل منها الإحداثيات X و Y وكذلك أبعاد الصورة عرض وارتفاع، لا داعِ لتكون المواقع على الورقة دقيقة جدًا لكن اجعلها منطقية، فمثلًا لا يمكنك وضع 8 قطع قياس كل منها 100 بكسل في شاشة واحدة حجمها 720 بكسل. بعد إنجاز شاشتك الأولى، استمر بالرسم تجاه اليمين حتى تمثل الشاشات التالية التي سينتقل خلالها بطل اللعبة وهو يجتاز المستوى حتى يصل لنهايته. ولو رغبت بتعزيز المخطط بمزيدٍ من الدقة استخدم أوراق الرسم البياني المقسمة إلى مربعات متساوية، فهي مفيدة خصيصًا في رسم منصات البلاط، إذ يمكنك تمثيل كل بلاطة بمربع. الإحداثيات كل ما تعلمته في المدرسة عن نظام الإحداثيات الديكارتي ينطبق هنا على إحداثيات Pygame باستثناء أن نقطة التقاء المحاور أو نقطة الصفر 0,0 تقع في الزاوية العلوية اليسرى للشاشة بدلًا من الوسط مثلما اعتدت سابقًا في دروس الهندسة. يبدأ المحور X عند النقطة 0 في أقصى اليسار ويمتد لا نهائيًا نحو اليمين، كذلك يبدأ المحور Y عند النقطة 0 في أعلى الشاشة لكنه يمتد للأسفل. أبعاد الصور معرفة أبعاد الصور التي تمثل كلًا من الأبطال والأعداء والمنصات مهمة جدًا ليكتمل مخطط المستوى، فبدونها سيغدو مخططك دون فائدة تُذكر، فكيف تعرف هذه الأبعاد؟ لديك طريقتان، إما أن تفتح الصور باستخدام برنامج تصميم متخصص مثل كريتا وتستعلم عن خصائصها، أو تكتب برنامج بسيط بلغة بايثون يعطيك إياها وذلك بالاستفادة من الوحدة Pillow التي توفر مكتبة خاصة بالموضوع تسمى PIL، ولكن طبعًا بعد ذكر الوحدة ضمن ملف متطلبات المشروع requirements.txt وفق مايلي: pygame~=1.9.6 Pillow أنشئ ملفًا جديدًا ضمن Pycharm باسم identify، واكتب فيه: #!/usr/bin/env python3 # GNU All-Permissive License # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. from PIL import Image import os.path import sys if len(sys.argv) > 1: print(sys.argv[1]) else: sys.exit('Syntax: identify.py [filename]') pic = sys.argv[1] img = Image.open(pic) X = img.size[0] Y = img.size[1] print(X, Y) والآن اضغط على تبويب الطرفية الموجود في أسفل شاشة Pycharm، الذي يفتح لك نافذة الطرفية في بيئتك الافتراضية واكتب فيها التالي حتى تثبت الوحدة Pillo. (venv) pip install -r requirements.txt Requirement already satisfied: pygame~=1.9.6 [...] Installed Pillow [...] وعند انتهاء التثبيت شغل البرنامج البسيط الذي كتبناه للحصول على الأبعاد، وشغله من مسار مجلد اللعبة. (venv) python ./identify.py images/ground.png (1080, 97) بذلك تكون قد حصلت على أبعاد صورة الأرضية، وهي في حالتنا 1080 عرض و97 ارتفاع. كتل المنصة إذا رغبت بتصميم صورة خاصة لكل مُكَوِّن من مكونات اللعبة مثل المنصات والأرضية وغيرها، فهذا يعني أنك ستخصص ملفُا منفصلًا لكل مُكَوِّن مثل الظاهر في الصورة: ويمكنك إعادة استخدام كل منصة عدة مرات ضمن لعبتك، بشرط أن يتضمن كل الملف مُكَوِّن واحد فقط كما ذكرنا، فلا يجوز مثلًا أن تتواجد جميع المكونات المبينة أدناه في ملف واحد: وعلى فرض أنك أنشأتها جميعًا في ملف واحد كبير يتضمن كل مكونات الصورة، فلن تملك عندها أي طريقة تجعلك تُمييز المنصة عن خلفية اللعبة، لذا إما أن ترسم المكونات التي تريدها كلٌ في ملفه الخاص أو أن تقصها من الملف الكبير الذي ترغب بالوصول إليه وتحفظها في ملفات منفصلة. تنويه: يمكنك استخدام أحد برامج التصميم الجرافيكي جيمب أو كريتا أو إنكسكيب على سبيل المثال لا الحصر لتُصمم مكونات اللعبة. تظهر المنصات في بداية كل مستوى لذا عليك إضافة دالة المنصة platform إلى صنف المستوى Level، أما المنصة التي تشكل الأرضية سنفرد لها دالة خاصة فهي تتمتع ببعض الخصوصية إذ إنها تبقى ثابتة في بعض الألعاب بينما تطفو فوقها المنصات الأخرى، أو تتحرك في بعضها الآخر أو تُمرر في أثناء سير اللعبة. أضف إذًا الدوال التالية إلى صنف المستوى Level: def ground(lvl,x,y,w,h): ground_list = pygame.sprite.Group() if lvl == 1: ground = Platform(x,y,w,h,'block-ground.png') ground_list.add(ground) if lvl == 2: print("Level " + str(lvl) ) return ground_list def platform( lvl ): plat_list = pygame.sprite.Group() if lvl == 1: plat = Platform(200, worldy-97-128, 285,67,'block-big.png') plat_list.add(plat) plat = Platform(500, worldy-97-320, 197,54,'block-small.png') plat_list.add(plat) if lvl == 2: print("Level " + str(lvl) ) return plat_list الدالة الأولى في التعليمات السابقة هي دالة الأرضية ground، وتتطلب تحديد الإحداثيات X و Y لمعرفة موقع ظهور الأرضية على الشاشة وكذلك تحديد الأبعاد حتى يتبين للوحدة Pygame كيف ستمتد أرضيتك في كلا الاتجاهين، تستخدم هذه الدالة صنف المنصة Platform لإنشاء كائن الأرضية على الشاشة وإضافته إلى مجموعة الأرضيات ground_list. أما الدالة الثانية دالة المنصة platform فتماثل الأولى تمامًا باستثناء أنها تتضمن أكثر من عنصر في مجموعة المنصات، في حالتنا يوجد منصتين فقط لكن يمكنك إضافة القدر الذي تريده بشرط ضمها جميعًا لمجموعة المنصات plat_list وإلاّ فإنها لن تظهر على الشاشة. تعلمنا على طول مقالات السلسلة أن الدوال والأصناف لا تعمل ما لم تستدعيها في البرنامج وهذا ما ستفعله بكتابة هذه التعليمات ضمن مقطع الإعدادات في شيفرة لعبتك: ground_list = Level.ground(1, 0, worldy-97, 1080, 97) plat_list = Level.platform(1) واكتب أيضًا الأسطر التالية في الحلقة الرئيسية، مع العلم أن السطر الأول لمعرفة السياق فقط وهو موجود لديك مسبقًا: enemy_list.draw(world) # تحديث الأعداء ground_list.draw(world) # تحديث الأرضية plat_list.draw(world) # تحديث المنصات منصات البلاط يعد استخدام قطع البلاط لبناء عالم الألعاب أسلوبًا أسهل من استخدام الصور الكاملة كما عرضنا في الفقرة السابقة، فكل ما يترتب عليك هنا هو تجهيز أو رسم بعض الكتل مقدمًا ومن ثم استخدامها مرارًا وتكرارًا في كل منصات اللعبة. توفر مواقع مثل kenney.nl أو OpenGameArt.org مجموعاتٍ واسعة من قطع البلاط التي تتمتع برخص المشاع الإبداعي، يمكنك تحميلها واستخدامها، اعتمد مثالنا قطعًا من kenney.nl تبلغ مساحة كل قطعة 64 بكسل مربع، وفي حال اعتمدت قطعًا بأبعادٍ أخرى راعِ ذلك ضمن الشيفرة. لن يتغير صنف المنصة platform عمّا كتبنا في السابق، لكن الدوال platform و ground في صنف المستوى Level ستتضمن حلقاتٍ خاصة بحساب عدد الكتل اللازمة لبناء كل منصة. لنفترض أنك ترغب بإنشاء أرضية ثابتة عبر تكرار استخدام قطع البلاط على طول نافذة اللعبة، يمكنك إنجاز ذلك بعدة طرق إحداها إعداد قائمة يدوية بالإحداثيات X و Y لمواقع قطع البلاط على طول المسار المطلوب، وعندها سترسم حلقة التكرار قطعة بلاط في كل موقع، انظر السطر التالي لكن لا تكتبه في فهو مجرد مثال، إذ سنعتمد طريقة أخرى للتعامل مع الموضوع. # لا تكتب هذا السطر في شيفرة اللعبة فهو مجرد مثال gloc = [0,656,64,656,128,656,192,656,256,656,320,656,384,656] دقّق قليلًا في المصفوفة السابقة وستجد أن جميع القيم Y متماثلة وهي 656 دائمًا، بينما تزداد قيم X ازديادًا مطردًا بمقدار 64 في كل نقلة الذي يعادل عرض البلاطة الواحدة. أتمتة هذا النوع من التكرار المنتظم سهلة نوعًا ما فهي تتطلب بعض المنطق الرياضي والعمليات الحسابية لتتم آليًا. اكتب لذلك التعليمات التالية ضمن مقطع الإعدادات في برنامجك: gloc = [] tx = 64 ty = 64 i=0 while i <= (worldx/tx)+tx: gloc.append(i*tx) i=i+1 ground_list = Level.ground( 1,gloc,tx,ty ) يُجري بايثون من خلال التعليمات السابقة عملية قسمة يُقسم فيها عرض عالم اللعبة على عرض المربع الواحد أو قطعة البلاط الواحدة، وينتج بذلك مصفوفة تتضمن كافة قيم X اللازمة أما قيم Y فهي ثابتة لا تتغير طالما أن أرضية لعبتك مستوية. استخدم حلقة while لتستثمر المصفوفة السابقة في دالة بناء الأرضية فهي ستضيف بلاطة إلى الأرضية في الموقع المقابل لكل قيمة من قيم المصفوفة. وستصبح الدالة على الشكل التالي، اكتبها ضمن صنف المستوى Level: def ground(lvl,gloc,tx,ty): ground_list = pygame.sprite.Group() i=0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i],worldy-ty,tx,ty,'tile-ground.png') ground_list.add(ground) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return ground_list تتشابه هذه الدالة في الغرض مع دالة الأرضية ground التي تعاملنا معها في الفقرة السابقة إلّا أنها تعتمد هنا على الحلقة while. المبادئ نفسها تقريبًا لدالة المنصات المتحركة، وسنعطيك بعض النصائح الكفيلة بتبسيط الموضوع، فبدلًا من تحديد مساحة كل منصة بالبكسل، يمكنك الاستعاضة عن ذلك بتحديد نقطة بداية المنصة (أو الإحداثي X) ومدى ارتفاعها عن الأرضية (أي الإحداثي Y) وأيضًا عدد قطع البلاط التي تحتويها، وبذلك لن تحتاج لتحديد أبعاد كل منصة. برمجة هذه العملية معقدة بعض الشيء فهي تتضمن حلقتي while الواحدة ضمن الأخرى، إذ إن إنشاء كل منصة يستلزم تحديد ثلاث محددات مقابل كل قيمة من قيم المصفوفة، في حالتنا مثلًا لا يوجد سوى ثلاث منصات تعرفها التعليمات ploc.append لكن بوسعك إضافة القدر الذي تريده من المنصات، وستلاحظ أنها لن تظهر دفعة واحدة عند تنفيذ الشيفرة بل ستظهر المنصات الخاصة بالشاشة الحالية فقط ومع تمرير الشاشة أثناء اللعب ستظهر البقية تباعًا. def platform(lvl,tx,ty): plat_list = pygame.sprite.Group() ploc = [] i=0 if lvl == 1: ploc.append((200,worldy-ty-128,3)) ploc.append((300,worldy-ty-256,3)) ploc.append((500,worldy-ty-128,4)) while i < len(ploc): j=0 while j <= ploc[i][2]: plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'tile.png') plat_list.add(plat) j=j+1 print('run' + str(i) + str(ploc[i])) i=i+1 if lvl == 2: print("Level " + str(lvl) ) return plat_list إذًا فقد أنشأت الدالة المسؤولة عن بناء المنصات، وعليك الآن استدعائها داخل البرنامج وفق الأمر التالي: plat_list = Level.platform(1, tx, ty) وأخيرًا اكتب التالي في الحلقة الرئيسية حتى تظهر المنصات في عالم لعبتك (السطر الأول للحفاظ على السياق فقط فلا داعي لكتابته): enemy_list.draw(world) # تحديث الأعداء ground_list.draw(world) # تحديث الأرضية plat_list.draw(world) # تحديث المنصات شغل اللعبة الآن، ولاحظ توضع المنصات وشكلها، وعدّل ما تريد، علمًا أنك لن تجد المنصات التي لا تنتمي للشاشة الأولى، لا تقلق سنتجاوز ذلك في المقالات اللاحقة عبر تمرير عالم اللعبة. طبق المهارات التي تعلمتها في السابق لم يذكر هذا المقال أين يتوضع العدو أو كيف، لذا طبق المهارات التي تعلمتها سابقًا وأظهره على الأرضية أو فوق إحدى المنصات فهي الأماكن الأنسب له، أما البطل دعه حاليًا فهو محكوم بقوى خاصة بعالم الرسوم المتحركة تحاكي قوى الجاذبية في العالم الحقيقي ستتعلمها في مقالاتنا التالية. إليك الشكل النهائي للشيفرة حتى الآن: #!/usr/bin/env python3 # by Seth Kenlon # GPLv3 # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import pygame import sys import os ''' Variables ''' worldx = 960 worldy = 720 fps = 40 ani = 4 world = pygame.display.set_mode([worldx, worldy]) BLUE = (25, 25, 200) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ALPHA = (0, 255, 0) ''' Objects ''' # x location, y location, img width, img height, img file class Platform(pygame.sprite.Sprite): def __init__(self, xloc, yloc, imgw, imgh, img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images', img)).convert() self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.y = yloc self.rect.x = xloc class Player(pygame.sprite.Sprite): """ Spawn a player """ def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.images = [] for i in range(1, 5): img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def control(self, x, y): """ control player movement """ self.movex += x self.movey += y def update(self): """ Update sprite position """ self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = pygame.transform.flip(self.images[self.frame // ani], True, False) # moving right if self.movex > 0: self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in hit_list: self.health -= 1 print(self.health) class Enemy(pygame.sprite.Sprite): """ Spawn an enemy """ def __init__(self, x, y, img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images', img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): """ enemy movement """ distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance * 2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 class Level: def ground(lvl, gloc, tx, ty): ground_list = pygame.sprite.Group() i = 0 if lvl == 1: while i < len(gloc): ground = Platform(gloc[i], worldy - ty, tx, ty, 'tile-ground.png') ground_list.add(ground) i = i + 1 if lvl == 2: print("Level " + str(lvl)) return ground_list def bad(lvl, eloc): if lvl == 1: enemy = Enemy(eloc[0], eloc[1], 'enemy.png') enemy_list = pygame.sprite.Group() enemy_list.add(enemy) if lvl == 2: print("Level " + str(lvl)) return enemy_list # x location, y location, img width, img height, img file def platform(lvl, tx, ty): plat_list = pygame.sprite.Group() ploc = [] i = 0 if lvl == 1: ploc.append((200, worldy - ty - 128, 3)) ploc.append((300, worldy - ty - 256, 3)) ploc.append((500, worldy - ty - 128, 4)) while i < len(ploc): j = 0 while j <= ploc[i][2]: plat = Platform((ploc[i][0] + (j * tx)), ploc[i][1], tx, ty, 'tile.png') plat_list.add(plat) j = j + 1 print('run' + str(i) + str(ploc[i])) i = i + 1 if lvl == 2: print("Level " + str(lvl)) return plat_list ''' Setup ''' backdrop = pygame.image.load(os.path.join('images', 'stage.png')) clock = pygame.time.Clock() pygame.init() backdropbox = world.get_rect() main = True player = Player() # spawn player player.rect.x = 0 # go to x player.rect.y = 30 # go to y player_list = pygame.sprite.Group() player_list.add(player) steps = 10 eloc = [] eloc = [300, 0] enemy_list = Level.bad(1, eloc) gloc = [] tx = 64 ty = 64 i = 0 while i <= (worldx / tx) + tx: gloc.append(i * tx) i = i + 1 ground_list = Level.ground(1, gloc, tx, ty) plat_list = Level.platform(1, tx, ty) ''' Main Loop ''' while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() try: sys.exit() finally: main = False if event.type == pygame.KEYDOWN: if event.key == ord('q'): pygame.quit() try: sys.exit() finally: main = False if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(-steps, 0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(steps, 0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps, 0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps, 0) world.blit(backdrop, backdropbox) player.update() player_list.draw(world) enemy_list.draw(world) ground_list.draw(world) plat_list.draw(world) for e in enemy_list: e.move() pygame.display.flip() clock.tick(fps) ترجمة -وبتصرف- للمقال Put platforms in a Python game with Pygame لصاحبيه Seth Kenlon و Jess Weichler. اقرأ أيضًا المقال السابق: إضافة شخصية العدو للعبة عبر مكتبة Pygame في بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون دليلك الشامل إلى: أشهر لغات برمجة الألعاب
-
لطالما كانت الأسباب التي تدفع الناس للمساهمة في المشاريع الحرة ومفتوحة المصدر FOSS مثارًا للاهتمام، ومع ذلك فإن أحدث البحوث حولها يعود تاريخه لعشر سنوات سابقة أو ربما أكثر، وقد تغير العالم كثيرًا منذ ذلك الحين. لذا سنعرض في هذا المقال سبع أفكار من دراسة بحثية حديثة أثارت الموضوع مجددًا، وأعادت دراسة الأسباب القديمة، وسألت المساهمين الحاليين عن ما يحفزهم اليوم لتطوير مشاريع حرة مفتوحة المصدر، وخلُّصت في النتيجة لمجموعة أفكار من شأنها مساعدة مديري المجتمع مفتوح المصدر على تطوير مجتمعهم من جهة، وتفسير الأمر للمهتمين به من أفراد عاملين فيه أو منظمات داعمة أو دارسة له من جهةٍ ثانية، ففي النهاية معرفة الأسباب مهمة للتطوير، فمعرفة محفزات المساهمين تفيد الجميع في اتخاذ قرارات مؤثرة وداعمة لهم ولمشاريعهم. لمحة تاريخية عن البحوث التي تناولت محفزات المشاريع مفتوحة المصدر لننظر في أصول المصدر المفتوح حتى نعرف ما الذي جعل دراسة محفزات المساهمين فيه أمرًا مهمًا ومثيرًا للاهتمام. مُذ بدأت حركة البرمجيات مفتوحة المصدر عرفت بأنها تمردٌ، إذ تمردت على المألوف وتحدّت أسلوب الشركات التي تبيع البرمجيات وتعتمد حقوق النشر والرخص لتقييد حرية المستخدم والمطوّر، ولم يكن سهلًا على أحد فهم الأسباب التي أدت إلى إنتاج برمجيات بهذه الجودة العالية على أيدي مطوّرين بدأوا بتطويرها أساسًا لاستخدامهم الشخصي أو تطوّعوا بمهاراتهم في سبيل تحقيقها، فقد وجدت روح تعاونية مذهلة في صلب هذه الحركة أثارت اهتمام الجميع بما فيهم الشركات أيضًا، وبالنتيجة أصبحت البرمجيات مفتوحة المصدر بمثابة تحول فلسفي في مسار التطوير جعل طريقة التعاون هذه متاحة ومقبولة لدى أصحاب الأعمال. نشرت دراسة بحثية من أشمل الدراسات في العام 2012، تلخص المحفزات التي تدفع المساهمين في هذا المجال لكن المحفزات لابد تغيرت على مدى السنوات العشر الماضية، بالأخص مع تزايد اهتمام الشركات بالمشاريع مفتوحة المصدر ووجود موظفين بأجرٍ مدفوع يعملون حاليًا في هذا المجال، وأصبح من الضروري إعادة دراستها وتقييمها. تغير محفزات المساهمين تناولت دراستنا العلمية بعنوان الأسباب تتغير- إعادة نظر في دوافع المساهمين في المشاريع مفتوحة المصدر المحفزات التي تدفع الناس للمساهمة في المشاريع الحرة مفتوحة المصدر FOSS وما يدفعهم للاستمرار فيها بعد ذلك. هدفت هذه الدراسة في المقام الأول إلى البحث في التغيرات التي طرأت على دوافع المساهمين منذ العقد الأول من القرن الحادي والعشرين، ومن ثم إلى نقل البحث لسويةٍ أخرى تتناول تغير هذه الدوافع مع استمرار المساهمين في مساهمتهم. يستند البحث إلى استبيان أُجري في أواخر العام 2020 وأجاب عنه حوالي 300 مساهم في البرمجيات الحرة ومفتوحة المصدر. أهم سبعة محفزات للمساهمة في المشاريع مفتوحة المصدر نلخص أهم نتائج الدراسة فيما يلي. 1. تلعب الدوافع الجوهرية دورًا رئيسيًا بينت الدراسة أن أغلب الناس يساهمون في المشاريع الحرة ومفتوحة المصدر لأسبابٍ تتعلق بالمتعة بنسبة 91٪ والإيثار بنسبة 85٪ والقرابة بنسبة 80٪، أما عند تحليلنا للاختلافات بين دوافع الانضمام والاستمرار في المساهمة، وجدنا أن كلًا من الأيديولوجيا أو الاستخدام الشخصي أو البرامج التعليمية يمكن أن يشكلوا حافزًا لانضمامهم، لكنهم يستمرون لأسباب جوهرية أخرى هي المتعة والإيثار والسمعة وصلة القُربى. 2. السمعة والدافع المهني أقوى من المقابل المادي يسعى المساهمون للسمعة الطيبة بنسبة 68٪ وتدفعهم الرغبة في التطور المهني بنسبة 67٪، بينما أشار أقل من 30٪ منهم بأن دافعهم للانضمام كان ماديًا سعيًا لأجرٍ مدفوع، بموازنة هذه النتائج بالدراسات السابقة نجد أن السمعة اليوم تعد دافعًا أكثر أهمية مما كانت عليه في ما مضى. 3. ازدياد أهمية الجوانب الاجتماعية فقد ارتفعت نسب الرضا والاستمتاع بمساعدة الآخرين إلى 89٪ في التصنيف والقُربى إلى 80٪ موازنةً بالدراسات الاستقصائية التي أجريت في بدايات العقد الأول من القرن الحادي والعشرين. 4. تتغير محفزات الناس كلما زاد عهدهم بالمساهمة تباين محفزات الانضمام والاستمرار من أوضح نتائج الدراسة، فقد أعطى 155 مشارك من أصل 281 أي ما نسبته 55٪ أسبابًا متباينة بين ما انضموا أساسًا لأجله وما يدفعهم اليوم للاستمرار. لاحظ الشكل أدناه فهو يوضح التحولات في محفزات المشاركين بين ما قادهم للانضمام بدايةً وما يدفعهم للاستمرار اليوم، فحجم المربعات الموجودة على اليسار يمثل عدد المساهمين الذين قادهم هذا الحافز للانضمام وبدء المساهمة في البرمجيات الحرة والمفتوحة المصدر، أما تلك الموجودة على اليمين فتشير إلى دافع الاستمرار، وخطوط الوصل بين الطرفين تمثل التحولات من دافع لآخر، أما ثخن كل خط يتناسب مع عدد المساهمين الذين تحولوا من دافعٍ إلى آخر بين المرحلتين. 5. الاستخدام الشخصي مجرد مدخل انخفضت أهمية الحافز الناجم عن تطوير البرامج لغرض الاستخدام الشخصي منذ الأيام الأولى، وتحول أغلب من انضموا لهذا السبب، إلى محفزاتٍ أخرى مثل الإيثار والتعلم والمتعة وتبادل المنفعة والخبرة، راجع الشكل أعلاه ولاحظ ذلك. 6. تختلف المحفزات تبعًا للخبرة والعمر اتجهت حوافز المطورين ذوي الخبرة أكثر باتجاه الإيثار بمعدل 5.6 ضعفًا عن المبتدئين، والأجور بمعدل 5.2 ضعفًا، وكذلك الأيديولوجية 4.6 ضعفًا، بينما أعطى المبتدئون معدلاتٍ أعلى للأسباب المتعلقة بحياتهم المهنية بمعدل 10 أضعاف والتعلم بحوالي 5.5 ضعفًا والمتعة 2.5 ضعفًا. وبالنظر إلى التحولات في محفزات المشاركين وجدنا زيادةً كبيرة في التحول إلى الإيثار حوالي 120% لدى ذوي الخبرة، بينما انخفضت انخفاضًا طفيفًا حوالي 16% لدى المبتدئين. أما فئة المشاركين الشباب فقد انضم عددٌ قليلٌ منهم إلى البرمجيات الحرة والمفتوحة المصدر بدافع مهني، لكن الكثيرين تحولوا بعدها إلى الإيثار بزيادة بلغت 100٪. 7. المحفزات تختلف بين المبرمجين وسواهم أعطى المبرمجون أسبابًا تتعلق بالمتعة أكثر من غيرهم بنحو 4 أضعاف، أما غير المبرمجين فقد أجابوا بمحفزاتٍ تتعلق بالأيديولوجية بمعدلات تعادل 2.5 ضعفًا. تحفيز المساهمين بناء على طول عهدهم في المجال تساعد معرفة اختلاف المحفزات بين المساهمين الجدد وذوي الخبرة في اتخاذ القرارات والخطوات المناسبة لتحفيزهم والحفاظ عليهم ودعمهم بطرقٍ أفضل. فعلى سبيل المثال جذب مساهمين جدد والحفاظ عليهم ليصبحوا قوة عاملة في المستقبل يتطلب من القائمين على المجتمع الاستثمار في المشاريع التي تركز على مسيرتهم المهنية وجوانب المتعة والإيثار والقرابة والتعلم وهو الأكثر أهمية لفئة الشباب. أما بمرور الوقت وازدياد خبرة المساهمين سيميل معظمهم لحافز الإيثار، وعندها يتعين على المشاريع مفتوحة المصدر -التي تهدف إلى الاحتفاظ بالمساهمين ذوي الخبرة حتى يكونوا أعضاء أساسيين أو مشرفين لديها- أن تستثمر في الاستراتيجيات والأدوات التي توضح لهم كيف سيستفيد المجتمع من عملهم، وتدعم الميزات الاجتماعية على منصات الاستضافة لتجمع بين من يحتاج إلى المساعدة ومن يرغب بتقديمها، ومن ثم تهتم بتسليط الضوء على هذه المساعدات والأعمال الإيجابية وعلى مقدميها، وإظهار التقدير اللازم الذي يستحقونه على غرار النجوم الممنوحة للمشاريع مثلًا. كانت هذه بعض الأفكار المستخلصة من نتائج الدراسة التي نأمل أن تكون ملهمة لكل المهتمين والمؤثرين في هذا المجال ليستفيدوا منها ويبنوا عليها المزيد لتحفيز المساهمين الجدد والمتمرسين لما فيه دعم عجلة تطور البرمجيات مفتوحة المصدر ودفعها قدمًا إلى الأمام. أما في الختام نذكركم برابط الورقة البحثية التي اعتمدنا عليها والتقرير الكامل عنها مع الحفاظ على الحقوق الفكرية لكافة المساهمين فيها. ترجمة -وبتصرف- للمقال ?What motivates open source software contributors لأصحابه Igor Steinmacher و Georg Link و Anita Sarma و Gregorio Robles و Bianca Trinkenreich و Christoph Treude و Marco Gerosa و Igor Wiese. اقرأ أيضًا ما المقصود بمصطلح مفتوح المصدر (open source)؟ تراخيص البرامج مفتوحة المصدر ما هي البرمجيات مفتوحة المصدر؟ صيانة المشاريع مفتوحة المصدر الفرق بين البرمجيات الحرة ومفتوحة المصدر
-
وصلنا للمقال الخامس من سلسلة بناء لعبة من الصفر باستخدام بايثون، وقد تعلمنا في أجزائها الأربعة السابقة كيفية بناء لعبة فيديو باستخدام الوحدة Pygame وطورنا اللعبة بوجود بطل متحرك، والآن سنضيف إليها بعض الإثارة بإضافة شخصية شريرة للمشهد أو عدو ليواجه البطل فهذا تقليدي في عالم الألعاب، ومن ثم سنبني إطار عمل خاص بإضافة مستويات اللعبة. يمكنك مطالعة المقال ضمن السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. بناء شخصية العدو مشابه تمامًا لبناء شخصية البطل سنستخدم الوظائف نفسها، وفي حال لم تُحضّر له صورة حتى الآن يمكنك اختيارها من مجموعة الصور المجانية المتاحة على منصة OpenGameArt.org أو نزلها من الملف المرفق opp2_sprites، وننصحك بتعلم المزيد عن رخصة المشاع الإبداعي حتى لا تنتهك حقوق النشر في الصور التي تستخدمها. إنشاء شخصية العدو العملية مشابهة جدًا لعملية إنشاء كائن البطل، فلديك المفاتيح الرئيسة للعمل. أنشئ صنفًا خاصًا بشخصية العدو لإنتاج كائن الشخصية. أنشئ دالة update لتحديث كائن العدو في الحلقة الرئيسية. أنشئ دالة move لتمنح شخصية العدو حركة عشوائية. ابدأ بالصنف بالطريقة نفسها لصنف البطل، ومن ثم حدد الصورة أو مجموعة الصور التي تعتمدها لشخصية العدو واحفظها في المجلد images الخاص بالصور ضمن مجلد مشروعك (المجلد نفسه الذي يتضمن صور البطل)، وأعطها اسمًا دلاليًا مثل enemy.png المستخدم في حالتنا. بالتأكيد ستبدو اللعبة أفضل لو جعلت كافة العناصر تتحرك، لكننا سنترك شخصية العدو دون حركة في هذه المرحلة حتى نخفف عليك التعقيد، ولو رغبت بتحريكه استذكر خطواتنا في المقال السابق. اكتب التعليمات التالية لإنشاء صنف العدو في أعلى المقطع الخاص بالكائنات objects ضمن برنامجك: class Enemy(pygame.sprite.Sprite): """ Spawn an enemy """ def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y يمكنك استخدام الصنف نفسه لتوليد عدة أعداء، فكل ما عليك فعله هو استدعاء الصنف وإعطائه صورة لكل شخصية وإحداثيات ظهورها على الشاشة X و Y. تمامًا مثل صنف اللاعب، اكتب صنف العدو في مقطع الإعدادات setup ضمن البرنامج: enemy = Enemy(300,0,'enemy.png') # إنتاج العدو enemy_list = pygame.sprite.Group() # إنشاء مجموعة الأعداء enemy_list.add(enemy) # إضافة عدو للمجموعة أنتجت التعليمات السابقة كائنًا يسمى enemy إحداثيات موقعه هي 300 بيكسل على المحور X و 0 بيكسل على المحور Y، ما يعني أن زاويته العلوية اليسرى عند النقطة 0 وتمتد صورته للأسفل تبعًا لمساحتها، لذا تنبه لأحجام صور الأعداء والأبطال واضبط إحداثياتها لتكون مواضعها مناسبة على الشاشة، وولد الأعداء في مكانٍ يمكنك إيصال بطل اللعبة إليه ففي مثالنا وضعنا العدو في النهاية عند النقطة 0 بيكسل والبطل عند النقطة 30 بيكسل على المحور العمودي Y ليظهرا بنفس المستوى (وهذا بسبب افتقار اللعبة إلى الحركة العمودية)، علمًا أن الصفر على المحور Y يقع في الأعلى ما يعني أن الأرقام تزداد كلما اتجهت نحو أسفل الشاشة. ولاحظ أن صورة البطل مدمجة مع كود الصنف أي ثابتة hard coded في مثالنا، ذلك أن البطل وحيد في اللعبة، أما صور الأعداء متغيرة إذ قد ترغب باستخدام أكثر من نموذج لها، لذا حدد الصورة خلال عملية إنشاء الشخصية، والصورة المستخدمة في مثالنا هي enemy.png. رسم شخصية على الشاشة حاول تشغيل لعبتك الآن، ستعمل، لكن شخصية العدو لن تظهر على الشاشة، اعترضتنا هذه المشكلة قبلًا وبالتأكيد عرفت السبب فتعليمات إنشاء الشخصية لم تكتب ضمن الحلقة الرئيسية، تذكّر دومًا أن أي شيء تريد حدوثه باستمرار لابد من ذكره في حلقة التكرار، أما بدون ذلك فسيظهر العدو على الشاشة لمدة بسيطة جدًا لأجزاء من الثانية ويختفي دون أن تلحظه حتى. أضف السطر الأوسط من التعليمات التالية إلى الحلقة حتى تُحدّث ظهور مجموعة الأعداء enemy_list التي تتكون حاليًا من عدو واحد لكن يمكنك توسيعها بإضافة أعداء آخرين وسيظهرون جميعًا مع كل تكرار للحلقة. player_list.draw(world) enemy_list.draw(world) # تحديث مجموعة الأعداء pygame.display.flip() شغل اللعبة بعد التعديلات، وستجد شخصية العدو على الشاشة في الموقع الذي تحدده الإحداثيات X و Y. إضافة مراحل للعبة يخطط مطور الألعاب لمستويات أو مراحل اللعبة حتى وهي في مهدها، ورغم بساطة لعبتنا وعدم وجود أي مستوى فيها حتى الآن، إلّا أننا سنكتب التعليمات كما لو كنا نخطط لبناء عدة مستويات. فكر في ماهية المستوى الذي تعّده؟ عادةً عندما تلعب كيف تعرف أنك في مستوى معين أو أنك انتقلت لمستوى جديد؟ يمكننا القول أن المستوى هو مجموعة من العناصر المميزة، وفي ألعاب المنصات مثل لعبتنا يتكون المستوى من مجموعة منصات مرتبة ترتيبًا معينًا، بالإضافة إلى نوع خاص بالمستوى من الأعداء والغنائم وما شابه، أما عن طريقة بناء المستوى سننشئ له صنفًا خاصًا يبني المستوى حول اللاعب، ونعيد استخدامه بعد الانتهاء لبناء المستويات اللاحقة. انقل الآن التعليمات التي كتبتها سابقًا لبناء شخصية العدو ومجموعة الأعداء، إلى دالة جديدة واستدعيها مع كل بناء لمستوى جديد، ستحتاج بالطبع لبعض التعديلات لإنتاج أنواع مختلفة من الأعداء لكل مستوى. class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'enemy.png') # إنتاج العدو enemy_list = pygame.sprite.Group() # إنشاء مجموعة الأعداء enemy_list.add(enemy) # إضافة عدو لمجموعة الأعداء if lvl == 2: print("Level " + str(lvl) ) return enemy_list مهمة التعليمة return السابقة هي إرجاع قائمة تتضمن مجموعة الأعداء enemy_list التي استخدمتها مع كل تنفيذ للدالة Level.bad. نظرًا لأن إنشاء العدو أصبح جزءًا من إنشاء المستوى، سنجري تعديلًا على مقطع الإعدادات setup، فبدلًا من إنشاء العدو، سنحدد المستوى الذي ينتمي إليه وأين سيتم إنتاجه، وذلك وفق الأوامر التالية: eloc = [] eloc = [300,0] enemy_list = Level.bad( 1, eloc ) شغل اللعبة مجددًا وتأكد من صحة إنشاء المستوى ومن ظهور البطل والعدو. التصادم مع العدو لا تُعدّ شخصية العدو فاعلة ما لم تسبب ضررًا للاعب أو تؤثر عليه، والتصادم هو أشيع أنواع الضرر في الألعاب. أما بعد التصادم علينا فحص حالة البطل أو صحته وسنجري ذلك في صنف البطل، يمكنك بالطبع فحص صحة العدو بالطريقة نفسها لكننا سنكتفي بتتبع صحة اللاعب، وذلك من خلال تعريف متغير خاص يشير إلى صحة اللاعب وفق السطر الثاني من التعليمات التالية أما السطر الأول فذُكر للإشارة إلى السياق فقط. self.frame = 0 self.health = 10 وأيضًا أضف التعليمات التالية في الدالة update ضمن صنف اللاعب. hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in hit_list: self.health -= 1 print(self.health) تكشف هذه التعليمات التصادم باستخدام sprite.spritecollide إحدى دوال Pygame ضمن المتغير hit_list لحظة التلامس بين صندوق البطل -الذي تفحص صحته- وصندوق أي شخصية تنتمي لمجموعة العدو enemy_list، ويرسل عندها إشارة إلى الحلقة for تؤدي إلى خصم نقطة من صحة اللاعب في كل مرة يحدث فيها التصادم. ولأن هذه التعليمات مكتوبة في الدالة update لصنف اللاعب التي تُستدعى مع كل تكرار للحلقة الرئيسية، فإن Pygame ستتحق من التصادم مع كل نبضة ساعة. تحريك العدو يعدّ نمط العدو الثابت الذي بنيناه هنا مقبولًا في الألعاب مثل المسامير والمصائد التي تعرقل سير اللاعب، إلاّ أن العدو المتحرك يضيف صعوبةً وإثارة للعبة. سنبدأ بتحريك العدو مع مراعاة أمرين، الأول أنه حركته آلية تُدار من قبل البرنامج ولا يتحكم بها المستخدم مثل حركة البطل، والثاني أن بيئة اللعبة أو عالمها متحرك، فكيف سنجعل العدو يتحرك داخله ذهابًا وإيابًا وهو أساسًا يتحرك؟ لنفترض أنك ترغب ببرمجة حركة العدو ليتحرك 10 خطوات نحو اليمين ومن ثم 10 نحو اليسار، كائن العدو لا يملك في مواصفاته البرمجية ما يمكنه من عدّ الخطوات، لذا ستحتاج إلى تعريف متغير يتتبع خطواته ويعدها، ومن ثم تبرمج عدوك ليتحرك يمينًا أو يسارًا بناءً على عدد الخطوات التي أنجزها. لنبدأ إذًا بتعريف متغير العداد المسؤول عن عدّ الخطوات، عبر إضافة السطر الأخير من التعليمات التالية إلى صنف العدو: self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 # متغير العداد ثم ننشئ دالة الحركة move في صنف العدو أيضًا، وذلك عبر الحلقة if-else بالشروط الآتية، التي ستجعلها حلقة لا نهائية. تحرك يمينًا إذا كانت قيمة العداد بين 0 و 100. تحرك يسارًا إذا كانت قيمة العداد بين 100 و 200. صفّر العداد وأرجعه للقيمة 0 عندما تتجاوز قيمته العدد 200. ستحرك هذه الحلقة كائن العدو يمينًا ويسارًا إلى ما لا نهاية فلا يوجود قيمة للمتغير تجعلها تتوقف فهو دائمًا بين 0 و 100 أو بين 100 و 200. أما عن كيفية اختيار القيم التي ستشكل أقصى مسافة يمكن للكائن الوصول إليها فالموضوع نسبي، يتغير تبعًا لقياس شاشة العرض لديك وقياس منصة اللعبة، لذا ننصحك بالتجريب، ابدأ بأرقام صغيرة ولاحظ النتائج ثم زدها لتصل لغايتك، والأمر نفسه بالنسبة لسرعة الحركة، مثلًا جرب التالي في البداية ويمكنك تعديله حسب رضاك عن النتائج: def move(self): ''' enemy movement ''' distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 بعد كتابة هذه التعليمات ستعرض عليك بيئة التطوير Pycharm تحذيرًا يتضمن اقتراحًا لتبسيط موازنة السلاسل بهدف تحسين أسلوب كتابة التعليمات، يمكنك قبول الاقتراح وتعلم المزيد عن الكتابة المتقدمة في بايثون، أو تجاهل التحذير بأمان والاستمرار. السؤال الآن، هل سيتحرك العدو عند تشغيل اللعبة؟ أم أننا تحتاج لخطوة إضافية؟ الإجابة هي لا بالطبع، لن يتحرك ما لم تستدعِ الدالة move ضمن حلقة البرنامج الرئيسية، أضف السطرين الأخيرين من التعليمات التالية لتُنجز ذلك: enemy_list.draw(world) #حدّث العدو for e in enemy_list: e.move() شغل اللعبة وشاهد ما يحدث عند اصطدام بطل اللعبة بالعدو، ربما ستحتاج لبعض التعديلات مثل تغيير مكان إنتاج العدو لتصنع التصادم، وعندما يحدث ذلك، تفقد النقاط التي خُصمت من رصيد صحة اللاعب من خلال بيئة التطوير IDLE أو Pycharm. قد تلاحظ أن النقاط تُخصم من صحة اللاعب (أو من أدوار اللاعب) في كل لحظة من الاصطدام، ستتعلم معالجة ذلك مع تطور مهاراتك في بايثون، أما حاليًا جرّب أن تنتج المزيد من الأعداء ووأيًا كانوا لا تنسَ إضافتهم إلى مجموعة الأعداء enemy_list، ومن ثم حاول تغير طريقة حركتهم استخدم حركة مختلفة لكل عدو. الشكل النهائي لبرنامج اللعبة إليك الشكل النهائي لبرنامج اللعبة متضمنًا كامل التعليمات البرمجية التي استخدمناها خلال السلسلة، ليبقى مرجعًا لك: #!/usr/bin/env python3 # by Seth Kenlon # GPLv3 # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import pygame import sys import os ''' Variables ''' worldx = 960 worldy = 720 fps = 40 ani = 4 world = pygame.display.set_mode([worldx, worldy]) BLUE = (25, 25, 200) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ALPHA = (0, 255, 0) ''' Objects ''' class Player(pygame.sprite.Sprite): """ Spawn a player """ def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.health = 10 self.images = [] for i in range(1, 5): img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert() img.convert_alpha() img.set_colorkey(ALPHA) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def control(self, x, y): """ control player movement """ self.movex += x self.movey += y def update(self): """ Update sprite position """ self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > 3*ani: self.frame = 0 self.image = pygame.transform.flip(self.images[self.frame // ani], True, False) # moving right if self.movex > 0: self.frame += 1 if self.frame > 3*ani: self.frame = 0 self.image = self.images[self.frame//ani] hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in hit_list: self.health -= 1 print(self.health) class Enemy(pygame.sprite.Sprite): """ Spawn an enemy """ def __init__(self, x, y, img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images', img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 def move(self): """ enemy movement """ distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1 class Level(): def bad(lvl, eloc): if lvl == 1: enemy = Enemy(eloc[0], eloc[1], 'enemy.png') enemy_list = pygame.sprite.Group() enemy_list.add(enemy) if lvl == 2: print("Level " + str(lvl) ) return enemy_list ''' Setup ''' backdrop = pygame.image.load(os.path.join('images', 'stage.png')) clock = pygame.time.Clock() pygame.init() backdropbox = world.get_rect() main = True player = Player() # spawn player player.rect.x = 0 # go to x player.rect.y = 30 # go to y player_list = pygame.sprite.Group() player_list.add(player) steps = 10 eloc = [] eloc = [300, 0] enemy_list = Level.bad(1, eloc ) ''' Main Loop ''' while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() try: sys.exit() finally: main = False if event.type == pygame.KEYDOWN: if event.key == ord('q'): pygame.quit() try: sys.exit() finally: main = False if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(-steps, 0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(steps, 0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps, 0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps, 0) world.blit(backdrop, backdropbox) player.update() player_list.draw(world) enemy_list.draw(world) for e in enemy_list: e.move() pygame.display.flip() clock.tick(fps) ترجمة -وبتصرف- للمقال What's a hero without a villain? How to add one to your Python game لصاحبه Seth Kenlon. اقرأ أيضًا المقال السابق: تحريك شخصية في لعبة باستخدام Pygame بناء لعبة نرد بسيطة بلغة بايثون برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون إضافة لاعب إلى لعبة مطورة باستخدام بايثون ومكتبة Pygame
-
استخدمنا بايثون لبناء لعبة نرد بسيطة معتمدة على النص في مقالنا الأول من هذه السلسلة، وفي الثاني تعلمنا كيفية تجهيز بيئة لعبة رسومية من الصفر أيضًا بلغة بايثون، أما في المقال الثالث السابق أضفنا كائنًا أو بطلًا للعبة الفارغة، والآن سنتعلم استخدام Pygame لتحريك الكائن والتحكم به مباشرةً من خلال لوحة المفاتيح، بطريقة شبيهة نوعًا ما ببرمجة زر مغادرة اللعبة التي اتبعناها في المقال الثاني، إلّا أنها أكثر تعقيدًا. يمكنك مطالعة المقال ضمن السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. تدعم Pygame خياراتٍ أخرى للتحكم مثل الفأرة أو قبضة اللعب لن يغطيها هذا المقال، ولكن لا تقلق فمجرد فهمك لطريقة العمل مع لوحة المفاتيح سيفتح لك الباب لتعلُّم الخيارات الأخرى وتطبيقها بسهولة. إعداد مفاتيح التحكم بحركة الشخصية افتح السكربت الخاص باللعبة باستخدام بيئة التطوير المتكاملة IDLE أو PyCharm أو ما تفضل. والآن حاول أن تخمن، في أي مقطع من مقاطع السكربت ينبغي أن نكتب التعليمات البرمجية الخاصة بمفاتيح التحكم؟ وانتبه إلى أن اللعبة ستبقى في حالة ترقب دائمة لأوامر التحكم الواردة من لوحة المفاتيح، ما يعني أننا بصدد حدث سيتكرر دائمًا. لابد أنك أصبت، فهي بالفعل ستكتب في الحلقة الرئيسية وتذكر أن أي تعليمات لا توضع ضمن حلقة التكرار تُنفذ لمرة واحدة فقط، وقد لا تُنفذ أبدًا إن كانت ضمن وظيفة أو إجراء لم يُستدعى. إذًا، اكتب التعليمات التالية في الحلقة الرئيسية، ولاحظ أننا استخدمنا تعليمة print لاختبار صحة تنفيذها، إذ إن البرنامج الكلي لم يجهز بعد. while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() main = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): print('left') if event.key == pygame.K_RIGHT or event.key == ord('d'): print('right') if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): print('left stop') if event.key == pygame.K_RIGHT or event.key == ord('d'): print('right stop') if event.key == ord('q'): pygame.quit() sys.exit() main = False شاع عند الكثير استخدام الأزرار W و A و S و D في لوحة المفاتيح لتحريك شخصيات الألعاب بينما يفضل بعضهم الآخر أسهم الاتجاهات، لذا احرص على تضمين الخيارين في برنامجك. تنويه: راعي دومًا احتياجات وتفضيلات أوسع فئة ممكنة من المستخدمين عند تطوير أي تطبيق، خصوصًا إن كانت البرمجة هي مهنتك التي تكسب منها، فإعطاء خيارات متعددة للمستخدم يعدّ واحدًا من أفضل المؤشرات التي تدل على مهارة المبرمج وتمتع تطبيقه بقابلية الوصول وهي ميزة مهمة في تطوير التطبيقات. شغل لعبتك الآن باستخدام بايثون، وتفقد الخرج الظاهر في نافذة الطرفية عند ضغط الأزرار A أو W أو D أو أسهم الاتجاهات. $ python ./your-name_game.py left left stop right right stop jump ستلاحظ أن Pygame تكتشف ضغطات الأزرار على المفاتيح بصورة صحيحة وتفهمها فالتعليمات صحيحة إذًا، لذا دعنا ننتقل إلى الجزء الأصعب وهو تحريك الكائن. تحريك شخصية اللعبة يتطلب تحريك شخصية إنشاء خاصية تمثل الحركة على كل محور، وتعريف متغير يحمل القيمة صفر عندما لا يتحرك الكائن، وأيضًا تتبع الأطر حتى تظل دورة المشي ضمن المسار الصحيح. عرّف المتغيرات التالية ضمن الصنف الخاص بشخصية اللاعب وذلك بإضافة الأسطر الثلاثة الأخيرة فقط أما السطر الأول والثاني فهما لديك أساسًا وأعدنا كتابتها هنا حتى يكون السياق واضحًا. def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 # X الحركة بالاتجاه الأفقي self.movey = 0 # Y الحركة بالاتجاه العمودي self.frame = 0 # عدد الأطر لننتقل إلى برمجة الحركة بعد أن عرفنا المتغيرات. لا يُفترض بشخصية اللعبة أن تستجيب للضغط على أزرار لوحة المفاتيح طوال الوقت ففي بعض الأحيان لا يُطلب منه التحرك، فتعليمات التحكم إذًا هي جزء فقط من الأشياء التي يمكنه فعلها، ولتعمل هذه التعليمات باستقلالية عن بقية الكود سنضعها ضمن دالة خاصة فهذا أسلوب بايثون، والدوال في لغة بايثون تبدأ بالكلمة المفتاحية def وهي الحروف الأولى من كلمة define التي تعني تعريف. عرف إذًا دالة في الصنف الخاص باللاعب تكون مسؤولة عن التحريك عبر إضافة عدد معين من وحدات البكسل -سيحدد في أجزاء لاحقة من البرنامج- إلى الموضع الحالي للكائن، فالتحريك في Pygame يعني تحديد موضع جديد للكائن ليعيد بايثون رسمه فيه. def control(self,x,y): """ control player movement """ self.movex += x self.movey += y سنخصص لعملية تحديث الموضع دالة جديدة في صنف اللاعب تسمى update تُكتب بعد دالة التحكم control السابقة، وذلك وفق التعليمات التي سترد لاحقًا. لتُظهر كائنًا ما على أنه يمشي (أو يطير أو أيًا كان ما عليه القيام به)، عليك أن تغير موضعه على الشاشة بما يتناسب مع الزر الذي ضغطه المستخدم، أما الموضع الجديد فيُحدد بكل من الخاصية self.rect.x و self.rect.y وأيضًا بعدد وحدات البكسل المطبق movex و movey (وهذا العدد سيحدد لاحقًا ضمن الكود). def update(self): """ Update sprite position """ self.rect.x = self.rect.x + self.movex وبالمثل للموضع العمودي Y: self.rect.y = self.rect.y + self.movey لتحقق أثر الرسوم المتحركة، عدّل الأطر كلما تحرك الكائن واستخدم في كل مرة الإطار المناسب للحركة ليكون بمثابة صورة للاعب: # الحركة يسارًا if self.movex < 0: self.frame += 1 if self.frame > 3*ani: self.frame = 0 self.image = self.images[self.frame//ani] # الحركة يمينًا if self.movex > 0: self.frame += 1 if self.frame > 3*ani: self.frame = 0 self.image = self.images[self.frame//ani] عرّف الآن متغيرًا يحدد عدد وحدات البكسل التي سيتحرك الكائن بمقدارها عند بدء تنفيذ الدوال الخاصة بذلك، واكتب هذا المتغير في مقطع الإعدادات ضمن سكربت اللعبة وفق الآتي (مع العلم أننا أعدنا كتابة أول سطرين توضيحًا للسياق فقط، أما تعريف المتغير فهو في السطر الأخير). … player_list = pygame.sprite.Group() player_list.add(player) steps = 10 # عدد وحدات البكسل التي سيتغير بمقدارها موضع الكائن … الآن بعد أن جهزنا دالة التحكم بالحركة والمتغير الذي يحدد مقدارها، يمكننا تبديل التعليمة print باسم اللاعب (وهو player) متبوعًا باسم الدالة control. مع تحديد عدد الخطوات التي سيتحركها الكائن مع كل تكرار للحلقة سواء على المحور X الأفقي أو العمودي Y. if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(-steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(steps,0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps,0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps,0) if event.key == ord('q'): pygame.quit() sys.exit() main = False يشير المتغير steps لعدد وحدات البكسل التي تتحرك بموجبها الشخصية أو اللاعب عند الضغط على أحد أزرار التحكم بالحركة، فلو كان steps هو 10 بكسيل هذا يعني أننا سنضيف 10 خطوات لموضع اللاعب عند الضغط على الزر D مثلًا وفور تحرير الزر أو إيقاف الضغط عليه سنطرح 10 خطوات steps- من موضعه لإعادة زخم الكائن إلى الصفر (ويعرف الزخم Momentum في الفيزياء بأنه كمية تصف مقدار القوة المطلوبة لإيقاف جسم ما). جرب لعبتك الآن، لكنك لن تحظى بما تتوقع فما زالت تحتاج لبعض التعديلات. تعديل صورة الشخصية عند تحريكها ستجد بعد التجريب أن الكائن لم يتحرك، وذلك عائد لعدم استدعاء الدالة update في الحلقة الرئيسية، وهو ما سنقوم به عبر إضافة السطر الأول من التعليمات التالية. player.update() # تحديث موضع اللاعب player_list.draw(world) pygame.display.flip() clock.tick(fps) شغل اللعبة مجددًا وشاهد حركة الكائن على الشاشة يمينًا ويسارًا واستجابته لضغط الأزرار، علمًا أننا لم نبرمج الحركة العمودية حتى الآن فلها قوانين ووظائف خاصة تحكمها مثل الجاذبية، ربما نستعرضها في مقالٍ آخر. يتبقى لدينا مشكلة بسيطة هي أن الكائن لا يلتفت وفقًا للاتجاه الذي يسير فيه، فلو كان وجهه مصممًا أساسًا إلى اليمين في الصورة التي اخترتها، سيبدو وكأنه يسير للخلف عندما يضغط المستخدم على السهم الأيسر ليحرّكه يسارًا، وسيعطي ذلك انطباعًا سيئًا عن اللعبة، فالمستخدم يتوقع استدارة البطل مع اتجاه الحركة. توجيه الشخصية وفقا لاتجاه حركتها تتيح لك Pygame وظيفةً جاهزة لقلب الصور تدعى transform يمكنك استخدامها بكل سهولة عبر كلمة مفتاحية واحدة دون أن تحتاج لكتابة تعليمات معقدة وتعلم مبادئ خاصة للرسم على الشاشة حتى تنجز الموضوع، وهنا تكمن قوة استخدام أطر العمل فكل ما عليك فعله هو استدعاء الدالة المناسبة وترك Pygame تكمل الباقي. إذًا غايتنا هي أن يدير الكائن وجهه وجسده بما يتوافق مع اتجاه الحركة، في حالتنا صورة الشخصية متجهة أساسًا لليمين هذا يعني أننا سنستخدم دالة قلب الصور ضمن كتلة التعليمات التي تحرك الشخصية لليسار حتى نوحي للمشاهد باستدارته يسارًا. بناءً على توثيقات Paygame تحتاج الدالة pygame.transform.flip إلى ثلاثة وسطاء هم: ماذا ستقلب؟ وهل ستقلبه عموديًا؟ وهل ستقلبه أفقيًا؟ في حالتنا لو أعدت النظر للرسم الذي استخدمناه، ستجد أنه يحتاج للقلب أفقيًا ويقابل ذلك القيمة true، ولا يتطلب القلب عموديًا ما يعني تمرير القيمة false مقابل هذا الوسيط. حدّث الآن تعليماتك حتى تتضمن التالي: # الحركة يسارًا if self.movex < 0: self.frame += 1 if self.frame > 3*ani: self.frame = 0 self.image = pygame.transform.flip(self.images[self.frame // ani], True, False) لاحظ أن المتغير self.image ما زال يأخذ قيمه من قائمة صور الكائن التي حددناها للبرنامج، ولكنها تُدور أو تقلب في دالة القلب. انتهت خطواتنا في هذا المقال، سنعرض لك الشكل النهائي للكود كاملًا، باستطاعتك تعلم المزيد وتطوير مهاراتك باستكشاف مزايا التحكم الأخرى من Pygame فقد تكونت لديك فكرة جيدة عن الموضوع، جرب الفأرة أو قبضات التحكم وطور اللعبة وفق ما تريد. اللعبة الكاملة نعرض لك الشكل النهائي للكود كاملًا ليكون مرجعًا تستند إليه في تجاربك. #!/usr/bin/env python3 # by Seth Kenlon # GPLv3 # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from typing import Tuple import pygame import sys import os ''' Variables ''' worldx = 960 worldy = 720 fps = 40 ani = 4 world = pygame.display.set_mode([worldx, worldy]) BLUE = (25, 25, 200) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ALPHA = (0, 255, 0) ''' Objects ''' class Player(pygame.sprite.Sprite): """ Spawn a player """ def __init__(self): pygame.sprite.Sprite.__init__(self) self.movex = 0 self.movey = 0 self.frame = 0 self.images = [] for i in range(1, 5): img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert() img.convert_alpha() # optimise alpha img.set_colorkey(ALPHA) # set alpha self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() def control(self, x, y): """ control player movement """ self.movex += x self.movey += y def update(self): """ Update sprite position """ self.rect.x = self.rect.x + self.movex self.rect.y = self.rect.y + self.movey # moving left if self.movex < 0: self.frame += 1 if self.frame > 3*ani: self.frame = 0 self.image = pygame.transform.flip(self.images[self.frame // ani], True, False) # moving right if self.movex > 0: self.frame += 1 if self.frame > 3*ani: self.frame = 0 self.image = self.images[self.frame//ani] ''' Setup ''' backdrop = pygame.image.load(os.path.join('images', 'stage.png')) clock = pygame.time.Clock() pygame.init() backdropbox = world.get_rect() main = True player = Player() # spawn player player.rect.x = 0 # go to x player.rect.y = 0 # go to y player_list = pygame.sprite.Group() player_list.add(player) steps = 10 ''' Main Loop ''' while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() try: sys.exit() finally: main = False if event.type == pygame.KEYDOWN: if event.key == ord('q'): pygame.quit() try: sys.exit() finally: main = False if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(-steps, 0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(steps, 0) if event.key == pygame.K_UP or event.key == ord('w'): print('jump') if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps, 0) if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps, 0) world.blit(backdrop, backdropbox) player.update() player_list.draw(world) pygame.display.flip() clock.tick(fps) تابع المقال التالي لتتعلم إضافة أعداء إلى اللعبة ومحاكاة أثر الجاذبية في الحركة العمودية، ولا تنسَ تطوير مهاراتك في بايثون وهو الغرض من بناء هذه اللعبة. ترجمة -وبتصرف- للمقال Using Pygame to move your game character around لصاحبه Seth Kenlon. اقرأ أيضًا المقال السابق: إضافة لاعب إلى لعبة مطورة باستخدام بايثون ومكتبة Pygame تعرف على أشهر لغات برمجة الألعاب ناء لعبة رسومية باستخدام بايثون ووحدة الألعاب Pygame بناء لعبة نرد بسيطة بلغة بايثون برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون
-
يتميز تطوير التطبيقات باستخدام جانغو Django بالمرونة والسرعة، ويعدّ تجربةً جيدة للمطوّر، فتطبيق جانغو قابل للتطوير والتكيف مع المتغيرات بالإضافة إلى أنه يؤمن مجموعة متنوعة من إعدادات الأمان الأساسية التي تُسهل تحضير التطبيق لمرحلة الإنتاج ما بعد نشر التطبيق، إلّا أن إطلاقه للعلن وعمله الفعلي في بيئة الإنتاج يتطلب عوامل أمان أقوى، وله عدة طرق منها إعادة ترتيب ملفات إعدادات التطبيق وتجزئتها بناءً على تبعيتها لمتغيرات البيئة ما يسمح بإعادة ضبطها بسهولة، وأيضًا الاستفادة من dotenv لإخفاء الإعدادات السرية وضمان عدم تسريب أي تفاصيل قد تعرض مشروعك للخطر. للوهلة الأولى قد تبدو لك الحلول والاستراتيجيات التي نعرضها هنا مضيعةً للوقت، ولكن ما إن تنظمها في مخطط عمل واضح فإنها ستتحول إلى أداة مساعدة تضمن تطوير مشروعك بأمان ومستوى إنتاجية عالي دون أن تتنازل عن أحدهما في سبيل الآخر. ستتعلم في هذا المقال كيفية الاستفادة من مخطط العمل الموجه نحو التطوير الآمن لتطبيق جانغو من خلال ضبط ملفات الإعدادات المستندة إلى البيئة واستخدام dotenv وأيضًا إعدادات الأمان الأساسية المضمّنة في جانغو لتصل في النهاية إلى تطبيق جاهز للنشر بأمان بأي طريقة تختارها. متطلبات بيئة العمل ستحتاج المتطلبات التالية لتتمكن من التطبيق العملي للمقال: نسخة جاهزة من أي تطبيق جانغو، إن لم تتوفر لديك يمكنك إعدادها بإتباع خطوات المقال تثبيت إطار العمل Django وتهيئة بيئته البرمجية على Ubuntu، إذ استُعمل هنا التطبيق testsite المطوّر بموجبه. معرفة ببنية ملفات جانغو وإعداداته الأساسية، وننصحك بقراءة سلسلة بناء مدونة عبر جانغو. تنويه: تم تنظيم هذا المقال وعرضه بطريقة تلائم متطلبات التطبيق testsite فإن كنت تستخدم تطبيقًا آخر ستلاحظ بعض الاختلاف لكن لا بأس يمكنك تنفيذ الفقرات على حدة بما يناسب تطبيقك. الخطوة 1: إعادة هيكلة إعدادات جانغو سنُحلل الملف setting.py ونعيد ترتيبه ضمن مجموعة إعدادات منفصلة تستند إلى متغيرات البيئة، ما يعني أن أي نقل مستقبلي للتطبيق من بيئة إلى أخرى مثل نقله من بيئة التطوير إلى بيئة الإنتاج، سيتطلب تغيير بعض الإعدادات فقط بما يتلائم مع البيئة الجديدة. أنشئ مجلدًا جديدًا باسم settings ضمن المجلد الأب لمشروعك كمايلي: mkdir testsite/testsite/settings الاسم testsite هو اسم التطبيق كما ذكرنا في فقرة متطلبات بيئة العمل، أنشئ بعدها ثلاث ملفات بايثون ضمن المجلد settings كما يلي إذ ستحلّ هذه الملفات المنفصلة بدلًا من الملف setting.py: cd testsite/testsite/settings touch base.py development.py production.py تمامًا كما توحي الأسماء فالملف development.py سيتضمن الإعدادات التي ستحتاج إليها في أثناء التطوير، بينما سيحوي الملف production.py الإعدادات اللازمة لمرحلة الإنتاج وقد فصلنا إعدادات المرحلتين عن بعضهما بسبب اختلاف المتطلبات بينهما، فإعدادات مرحلة الإنتاج مثل توجيه الطلبات إلى HTTPS أو إضافة الترويسات أو استخدام قاعدة بيانات الإنتاج لن تنفع خلال مرحلة التطوير، أما الملف base.py فسيشمل الإعدادات المشتركة الأساسية التي سيرثها كلًا من development.py و production.py فهذ العملية ستساهم في عدم تكرار الإعدادات وتصميم كود نظيف لتطبيقك. طالما أن الملفات الثلاثة ستحمل كامل الإعدادات المطلوبة فلنزيل إذًا setting.py لضمان عمل جانغو بشكلٍ سليم. ضمن نفس المجلد settings نفذ الأمر التالي المتضمن تعديل اسم setting.py ليصبح base.py: mv ../settings.py base.py وضعنا بذلك الخطوط العريضة لعملية إعادة ترتيب الإعدادات تبعًا لمدى اعتمادها على متغيرات بيئة التشغيل، إلّا أنها غير مفهومة بعد بالنسبة للتطبيق وهو ما سيتم في الخطوة التالية. الخطوة 2: استخدام python-dotenv الحزمة python-dotenv هي إحدى اعتماديات التطبيق، مهمتها تحميل متغيرات البيئة من ملف خاص يدعى env.، وتثبيتها ضروري ليتمكن التطبيق من التعامل مع مجلد الإعدادات والملفات الجديدة التي عملنا عليها في الخطوة السابقة. لنبدأ التطبيق العملي: توجه إلى مسار الجذر لمشروعك: cd ../../ وثبت الاعتمادية python-dotenv: pip install python-dotenv ومن ثم اضبط إعدادات جانغو ليستخدم dotenv عبر تعديل الملفين manage.py الخاص ببيئة التطوير و wsgi.py الخاص ببيئة الانتاج كما يلي: افتح أولًا الملف manage.py باستعمال محرر النصوص: nano manage.py وأضف ضمنه التعليمات التالية: import os import sys import dotenv def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development') if os.getenv('DJANGO_SETTINGS_MODULE'): os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == '__main__': main() dotenv.load_dotenv( os.path.join(os.path.dirname(__file__), '.env') ) ثم أغلق الملف بعد حفظ التغييرات، وافتح الثاني: nano testsite/wsgi.py وأضف ضمنه التعليمات الناقصة المتعلقة بـ dotenv ليصبح بهذا الشكل: import os import dotenv from django.core.wsgi import get_wsgi_application dotenv.load_dotenv( os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env') ) os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development') if os.getenv('DJANGO_SETTINGS_MODULE'): os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE') application = get_wsgi_application() لا تنسَ حفظ التغييرات قبل إغلاق الملف. بموجب التعديلات التي أجريتها على الملفين: عند كل تشغيل لهما manage.py في التطوير والملف wsgi.py في الإنتاج، سيبحث جانغو عن الملف env. فإن وجده سيستخدم ملف الإعدادات الذي يوصي به env. وفي حال لم يجده يعتمد إعدادات التطوير افتراضيًا. لننشئ الآن الملف env. ضمن مجلد المشروع كما يلي: nano .env ونضيف ضمنه السطر التالي: DJANGO_SETTINGS_MODULE="testsite.settings.development" تنويه: ننصحك بإضافة env. على قائمة العناصر الموجودة في الملف gitingore. لتُحافظ عليه وتحميه من التعديل فهو في نهاية المطاف يتضمن كلمات المرور وإعدادات API وغيرها من المعلومات الحساسة المتعلقة بالتطبيق، وعندها ستنشئ ملف env. خاص بكل بيئة تنشر تطبيقك فيها، ولذا نوصيك بإنشاء نموذج ملف جاهز باسم env.example. ضمن مشروعك يمكنك تعديله بسهولة ليشكل env. جديد كلما دعت الحاجة. بناءً على السطر الذي أضفناه للملف env.، سيستخدم جانغو افتراضيًا testsite.settings.development وبالمثل إن غيرت قيمة المحدد DJANGO_SETTINGS_MODULE إلى testsite.settings.production فإنه سيبدأ باستخدام إعدادات بيئة الإنتاج. الخطوة 3: إنشاء ملفات الإعدادات لكل من بيئتي التطوير والإنتاج افتح الملف base.py وأضف ضمنه إعدادات التهيئة الخاصة بكل بيئة والتي ستعدلها لاحقًا في ملفي development.py و production.py المنفصلين، وتأكد أنك تملك معلومات الاتصال بقاعدة بيانات الإنتاج إذ ستحتاجها في الملف production.py. تنويه: إن تحديد ماهية إعدادات التهيئة الواجب تعديلها يختلف من مشروعٍ لآخر، وفي مثالنا سنعمل على الإعدادات الأكثر شيوعًا والتي تعد موجبة في معظم التطبيقات مثل إعدادات الأمان ومحددات الربط مع قواعد البيانات فهي تختلف عمومًا بين التطوير والإنتاج، وبناءً على خصوصية تطبيقك قد تكتفي بإعدادتنا أو قد تضيف إليها ما تحتاجه. لنبدأ بنقل الإعدادات من base.py إلى ملف إعدادات التطوير development.py الخاص بتطبيقنا testsite، لذا افتح ملف إعدادات التطوير: nano testsite/settings/development.py واكتب ضمنه تعليمة الاستيراد من base.py ومن بعدها الإعدادات الخاصة ببيئة التطوير وفق التالي: from .base import * DEBUG = True DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } كما ترى، الإعدادات الخاصة ببيئة التطوير لدينا هي التنقيح DEBUG الذي تم تفعيله بالقيمة True ومحدد قاعدة البيانات DATABASES الذي يشير إلى قاعدة البيانات المحلية وهي في حالتنا SQLite، وهذه الإعدادات لن تتماشى مع بيئة الإنتاج إذ إن الإنتاج يتطلب إلغاء تفعيل التنقيح والربط مع قاعدة بيانات الإنتاج. تنويه: يعود إلغاء تفعيل التنقيح في بيئة الإنتاج إلى أسبابٍ أمنية لضمان عدم تسريب أي معلومات سرية عن المشروع من الممكن أن تظهر في أثناء التنقيح، وتعرض تطبيقك للخطر مثل API أو KEY أو PASS أو SECRET أو SIGNATURE أو TOKEN، لذا احرص دائمًا على إلغاء تفعيل التنقيح DEBUG قبل نشر تطبيقك في بيئة الإنتاج. لننتقل للملف production.py: nano testsite/settings/production.py سنعتمد نفس الطريقة، ولكن مع إلغاء تفعيل التنقيح ووضع محددات قاعدة بيانات الإنتاج كما يلي: from .base import * DEBUG = False ALLOWED_HOSTS = [] DATABASES = { 'default': { 'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'), 'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')), 'USER': os.environ.get('SQL_USER', 'user'), 'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'), 'HOST': os.environ.get('SQL_HOST', 'localhost'), 'PORT': os.environ.get('SQL_PORT', ''), } } يمكنك استخدام dotenv مع وضع قيم افتراضية لضبط محددات قاعدة البيانات وفق المثال المقدم أعلاه، ولكن احرص على تبديل قيم المتغيرات بما يناسب قاعدة بيانات التطوير الخاصة بك. إذًا، فقد ضبطنا التطبيق ليعمل بنموذجين مختلفين من الإعدادات يتم القرار بينهما بناءً على قيمة المحدد DJANGO_SETTINGS_MODULE الموجود ضمن الملف env.، ففي حالتنا على سبيل المثال سيعني العمل بإعدادات الإنتاج أن DEBUG سيحمل القيمة False، وأن ALLOWED_HOSTS سيُحدد، أما التطبيق فسيبدأ باستخدام قاعدة بيانات مختلفة هي قاعدة بيانات الإنتاج (التي يُفترض أنك جهزتها مسبقًا على الخادم الخاص بك). الخطوة 4: ضبط إعدادات الأمان لتطبيق جانغو يتضمن جانغو إعدادات أمانٍ جاهزة لتضيفها إلى مشروعك، وهذه الإعدادات ضرورية لكل تطبيق يعمل في بيئة الإنتاج ويطلق للاستخدام العام. سيقتصر عملنا مع إعدادات الأمان على تهيئة ملف الإنتاج production.py فقط إذا لا يوصى باستخدامها في بيئة التطوير. أهم ما تتناوله هذه الإعدادات هو فرض استخدام HTTPS للعديد من مميزات مواقع الويب مثل ملفات تعريف الارتباط للجلسة وملفات تعريف الارتباط CSRF وتوجيه HTTP إلى HTTPS… إلخ، لذا فإن تنفيذ هذه الخطوة يفترض أن تطبيقك مجهز بخادم ويب وأن لديك اسم نطاق يشير إلى خادم التطبيق. افتح الملف production.py باستخدام محرر النصوص: nano production.py أضف ضمنه القيم الناقصة ليصبح بهذا الشكل: from .base import * DEBUG = False ALLOWED_HOSTS = ['your_domain', 'www.your_domain'] DATABASES = { 'default': { 'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'), 'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')), 'USER': os.environ.get('SQL_USER', 'user'), 'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'), 'HOST': os.environ.get('SQL_HOST', 'localhost'), 'PORT': os.environ.get('SQL_PORT', ''), } } SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True لنشرح المحددات بالترتيب: SECURE_SSL_REDIRECT: يوجه كافة الطلبات من HTTP إلى HTTPS بالطبع مع استثناء بعض الحالات المعفاة من ذلك، يستخدم التطبيق إذًا الاتصالات المشفرة ما يستلزم تهيئة شهادات TLS على الخادم، وفي حال كنت تستخدم Nginx أو أباتشي فسيكون هذا الضبط إضافيًا وغير ملزم إذ إن كلا الخادمين يقوم فعلًا بهذا التوجيه. SESSION_COOKIE_SECURE: يخبر هذا المحدد المتصفح بعدم إمكانية التعامل مع ملفات تعريف الارتباط خارج HTTPS. ما يعني أن كل ما يولّده مشروعك من ملفات تعريف ارتباط (مثل تلك الخاصة بعمليات تسجيل الدخول) ستعمل فقط عبر اتصالٍ مشفر. CSRF_COOKIE_SECURE: يشبه المحدد السابق ولكنه يُطبق على مفتاح CSRF الخاص بك. تحمي هذه الطريقة تطبيقك من هجمات تزوير الطلبات عبر المواقع من خلال تأكدها -باستخدام مفاتيح CSRF- من أن كافة النماذج المقدمة سواء لتسجيل الدخول أو الاشتراكات أو غيرها هي نماذج أصلية تم إنشاؤها بواسطة تطبيقك ولم يزوّرها طرفٌ ثالث. SECURE_BROWSER_XSS_FILTER: يضع هذا المحدد الترويسة X-XSS-Protection: 1; mode=block في كافة الردود الصادرة عن التطبيق ما لم تكن موجودة أساسًا، يمنع هذا الإعداد أي طرف ثالث من حقن سكربتات برمجية في مشروعك، فعلى سبيل المثال لو خزن مستخدمٌ ما سكربتًا في قاعدة بياناتك مستغلًا أحد الحقول العامة، فلن يعمل هذا السكربت عند استرجاعه وعرضه للمستخدمين الآخرين. يمكنك الحصول على مزيد من المعلومات حول الإعدادات الأمنية بالاطلاع على توثيقات جانغو. تحذير: توصي توثيقات جانغو بعدم الاعتماد كليًا على SECURE_BROWSER_XSS_FILTER وإغفال الجوانب الأخرى، بل عليك دائمًا التأكد من ضبط حقول الإدخال لتطبيقك لتقبل المدخلات الصالحة فقط. إعدادات إضافية تتعلق الإعدادات الإضافية الواردة أدناه بأمن النقل الصارم HSTS الذي يفرض استخدام SSL على كامل موقعك وفي كل الأوقات: SECURE_HSTS_SECONDS: يعين المدة الزمنية المحددة لاستخدام HSTS مقدرةً بالثانية، ففي حال ضبطت القيمة إلى ساعة واحدة (بالطبع حولها إلى ثواني قبل كتابتها) فهذا يعني أن متصفحك سيستخدم HTTPS حصرًا لمدة ساعة كاملة في كل مرة تزور فيها إحدى صفحات التطبيق، وإن زُرت أي جزء غير آمن من الموقع خلال هذه الساعة فإنك ستحصل على رسالة خطأ. SECURE_HSTS_PRELOAD: استخدامه مشروط بتعيين قيمة للمحدد السابق أي زمن HSTS، يوجه هذا النوع من الترويسات متصفح الإنترنت ليُحمّل موقعك مسبقًا أي ليُضيفه على قائمة خاصة مدمجة مع كود المتصفح وظيفتها فرض استخدام الاتصالات المشفرة مع المواقع الموجودة ضمنها (ومن بينها موقعك)، تعمل هذه الطريقة التي يصطلح على تسميتها Preload مع المتصفحات الشائعة مثل Firefox و Chrome، ولكن احذر في حال اعتمدتها لموقعك فلن يكون التخلي عن التشفير أمرًا سهلًا بعدها، إذ سيتطلب إزالة الموقع يدويًا من قائمة HSTS Preload وقد تمتد العملية لأسابيع. SECURE_HSTS_INCLUDE_SUBDOMAINS: تفعيله يعني تطبيق ترويسة HSTS على كافة النطاقات الفرعية تحت اسم نطاق الرئيس، فلو كان نطاقك your_domain فإن أي رابط فرعي تحته مثل unsecure.your_domain حتى لو كان لا يرتبط بتطبيق جانغو سيخضع للتشفير. تحذير: قد يؤدي الاستخدام الخاطئ لهذه الإعدادات الأمنية الإضافية إلى تعطيل موقعك لفترة لا يستهان بها، لذا تعلم المزيد عنها من توثيقات جانغو قبل تطبيقها في بيئتك الفعلية. الإعدادات التي عرضناها تعد أساسًا جيدًا لمعظم تطبيقات جانغو، ولكن ادرس بيئتك الفعلية وفكر كيف سينعكس تطبيق هذه الإعدادات على مشروعك. الخطوة 5: استخدام python-dotenv مع معلومات التطبيق السرية ستتعلم في هذا الجزء الأخير كيفية الاستفادة المثلى من حزمة python-dotenv لحماية معلومات تطبيقك الحساسة مثل المفتاح السري SECRET_KEY ورابط تسجيل الدخول إلى لوحة التحكم، في الواقع تعدّ هذه الآلية فكرةً ممتازة لمن يرغب برفع تطبيقه على GitHub أو GitLab أو أي منصة أخرى مشابهة فهي ستحمي بياناته السرية من النشر أمام العامة، وبدلًا من نشرها على المنصة سينشئ المطور ملف env. جديد يحدد من خلاله المتغيرات السرية لكل بيئة جديدة يستخدمها سواء محليًا أو على خادم. لنبدأ بإخفاء المفتاح السري SECRET_KEY، افتح الملف env.: nano .env واكتب ضمنه التالي: DJANGO_SETTINGS_MODULE="django_hardening.settings.development" SECRET_KEY="your_secret_key" ثم افتح الملف base.py: nano testsite/settings/base.py وعدّل ضمنه المفتاح السري SECRET_KEY ليأخذ قيمته من متغيرات البيئة المعرفة في env. وفق التالي: . . . SECRET_KEY = os.getenv('SECRET_KEY') . . . أما عمليتنا الأخيرة فهي إخفاء عنوان URL للوحة التحكم عبر إضافة سلسلة طويلة من المحارف العشوائية إليه، سيحميك ذلك هجمات القوة الغاشمة brute force التي تتعرض لها مواقع الويب بهدف فرض حقول تسجيل الدخول عنوةً ومحاولة تخمينها من قبل المهاجم. افتح الملف env. مجددًا: nano .env وأضف على محتوياته المحدد SECRET_ADMIN_URL: DJANGO_SETTINGS_MODULE="django_hardening.settings.development" SECRET_KEY="your_secret_key" SECRET_ADMIN_URL="very_secret_url" لنخفي الآن عنوان URL بالمحدد SECRET_ADMIN_URL، افتح الملف urls.py: nano /testsite/urls.py تنويه: لا تنسَ استبدال your_secret_key و very_secret_url بالسلاسل النصية التي تشكل مفتاحك السري وعنوان URL الخاص بك، وفي حال رغبت باستعمال سلاسل نصية عشوائية فإن بايثون يقدم لك مكتبة مميزة secrets.py لتوليدها، مع مجموعة مفيدة من الأمثلة لإنشاء برامج بسيطة تولد هذا النوع من السلاسل العشوائية الآمنة. ومن ثم عدّله ليصبح كما يلي: import os from django.contrib import admin from django.urls import path urlpatterns = [ path(os.getenv('SECRET_ADMIN_URL') + '/admin/', admin.site.urls), ] وبذلك يكون رابط لوحة التحكم لتطبيقك هو /very_secret_url/admin بدلًا من /admin/ فقط. خاتمة أعددنا في هذا المقال تطبيق جانغو سهل الاستخدام والنشر في بيئاتٍ مختلفة، ويستثمر ميزات python-dotenv للتعامل مع الإعدادات والأسرار، بالإضافة لكونه آمن ومجهز بضوابط الأمان الأساسية اللازمة لمرحلة الإنتاج، وفي حال طبقت المعايير التي ناقشناها والموصى بها فإنك ستحظى بتطبيقٍ آمن يحمل المزايا الأساسية التالية: تشفير كافة الاتصالات عبر SSL/HTTPS (مثل: النطاقات الفرعية وملفات تعريف الارتباط وملفات ارتباط CSRF). الحماية من هجمات البرمجة العابرة للمواقع XSS. الحماية من هجمات تزوير الطلبات عبر المواقع CSRF. إخفاء المفتاح السري للمشروع. إخفاء رابط لوحة التحكم ما يمنع هجمات القوة الغاشمة brute force. فصل إعدادات التطوير عن إعدادات الإنتاج. إن رغبت بمعرفة المزيد حول جانغو وإعداداته اطلع على توثيقاته الرسمية والقسم الخاص به على أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Harden the Security of Your Production Django Project لصاحبه Ari Birnbaum. اقرأ أيضًا إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات تثبيت إطار العمل جانغو على أوبنتو حزم بايثون الثمانية التي تسهل تعاملك مع Django تخصيص لوحة التحكم المرفقة مع Django
-
في مقال بناء لعبة نرد بسيطة بلغة بايثون استخدمنا بايثون لبرمجة لعبة نرد بسيطة معتمدة على النص، وفي مقالنا السابق تعلمنا كيفية تجهيز بيئة لعبة رسومية من الصفر أيضًا بلغة بايثون، والآن سنطوّر هذه اللعبة ونضيف إليها شخصية تلعب دور اللاعب أو بطل اللعبة. يمكنك مطالعة المقال ضمن السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. يُطلق اسم الكائن أو الشخصية sprite على الأفاتار أو أيقونة الشخصية التي يتحكم بها اللاعب في Pygame، إن كنت لم تجهز له صورةً بعد، فيمكنك تنزيل الصور walk.png ذوات الأرقام 0 و2 و4 و5 من موقع SuperTux الخاصة بلعبة Supertux وهي مفتوحة المصدر أو من الملفات المرفقة، ومن ثم إعادة تسميتها لتصبح hero1.png إلى hero4.png، أو استخدام كريتا أو إنكسكيب لتصميم صور مناسبة، أو الاستعانة بمنصة OpenGameArt مفتوحة المصدر لخياراتٍ أخرى، واحفظ كافة الصور التي ستعتمدها في المجلد images الموجود ضمن مجلد مشروعك، وإن لم يكن موجودًا أنشئ واحدًا. لتجعل لعبتك مثيرة وحقيقية استخدم كائنًا متحركًا، وتحتاج لذلك عدة رسومات للكائن في وضعيات مختلفة لتعطي وهم الحركة، أشهر الحركات المتعارف عليها في الرسوم المتحركة هي دورة المشي وتتطلب أربع صور لتحقق المطلوب. تنويه: كود اللعبة المستخدم هنا يصلح للكائنات الثابتة والمتحركة. سمي صورة كائن اللعبة بالاسم hero.png وإن رغبت بجعله متحركًا فسمي الصور الخاصة بدورة المشي بأرقامٍ متتابعة بدءً من hero1.png، واحفظها جميعًا في المجلد images الموجود ضمن مجلد مشروعك. إنشاء صنف بايثون يتطلب إظهار كائن بايثون Object على الشاشة إنشاء صنف class. إن كنت تستخدم كائنًا ثابتًا ليمثل شخصية بطل اللعبة، فاكتب التعليمات التالية في المقطع الخاص بكائنات بايثون في ملفك: ''' Objects ''' class Player(pygame.sprite.Sprite): """ Spawn a player """ def __init__(self): pygame.sprite.Sprite.__init__(self) self.images = [] img = pygame.image.load(os.path.join('images', 'hero.png')).convert() self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() ستُنشئ هذه التعليمات كائن بايثون افتراضي object يشير إلى بطل لعبتك، والكائنات في لغات البرمجة الكائنية تُشتق دومًا من الأصناف. توفر Pygame قوالب جاهزة للكائنات مثل: pygame.sprite.Sprite وهو ما يتيح لك اختيار الصورة التي تريدها لتمثل بطل لعبتك، وبدون هذه القوالب كنت ستحتاج لمعرفة ممتازة بلغة بايثون لتبرمج الكائن من الصفر، وهذه ميزة استخدام مكتبة جاهزة مثل Pygame. أما في حال أردت استخدام دورة المشي لإنشاء شخصية متحركة، فاحفظ صور وضعيات المشي الأربع لبطل اللعبة بأربع صور منفصلة سمّها hero1.png إلى hero4.png ضمن مجلد الصور ومن ثم استخدم حلقة التكرار لينتقل بايثون من صورة إلى أخرى، في الواقع أن يكون لكل صنف مهمة محددة لا تؤثر "بعالم اللعبة" هو واحد من أفضل ميزات البرمجة الكائنية، ففي حالتنا سيعطي الانتقال بين وضعيات المشي وهم الحركة للمشاهد، وسيحدث ذلك بغض النظر أن كل ما يجري في المحيط. ''' Objects ''' class Player(pygame.sprite.Sprite): """ Spawn a player """ def __init__(self): pygame.sprite.Sprite.__init__(self) self.images = [] for i in range(1, 5): img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert() self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() إحضار شخصية اللاعب إلى عالم اللعبة بعد أن جهزنا الصنف الذي يشير إلى بطل اللعبة، يتعين علينا استدعائه ضمن اللعبة ليظهر في عالمها، فلو حاولت تشغيل لعبتك الآن ستجدها فارغة كما كانت عليه في نهاية مقالنا السابق، فالكائن الذي يشير لبطل اللعبة لم يرتبط بها بعد ولم يُضاف إلى مجموعة كائنات Pygame. لتنجز ذلك اكتب التعليمات التالية في مقطع الإعدادات ضمن برنامجك: player = Player() # إنتاج اللاعب player.rect.x = 0 # X توجه إلى player.rect.y = 0 # Y توجه إلى player_list = pygame.sprite.Group() player_list.add(player) حاول الآن تشغيل اللعبة، لن تجد أي فرق، إذ إنك لن تلحظ بطل اللعبة فقد ظهر لأجزاء من الثانية فقط واختفى، تذكر ما شرحناه في مقالنا السابق لجهة أن إتاحة أي شيء باستمرار على الشاشة يستلزم إضافته إلى حلقة اللعبة الرئيسية، وبذلك سيرسمه بايثون مع كل تكرار للحلقة. أضف إذًا التعليمات التالية للحلقة: world.blit(backdrop, backdropbox) player_list.draw(world) # draw player pygame.display.flip() clock.tick(fps) ومن ثم شغل اللعبة، ستجد شخصية اللاعب فيها. ضبط قناة الشفافية ألفا لشخصية اللعبة اعتمادًا على الطريقة التي أنشأت بها كائن اللعبة ستظهر حوله مساحة ملونة تحيط به تسمى في الألعاب الحديثة صندوق التصادم hit box، هذه المساحة تشغلها القناة ألفا ويفترض أن تكون غير مرئية، لكن بايثون لا يملك التوجيهات الخاصة بذلك بعد. يمكنك إخبار بايثون باللون الذي تعدّه غير مرئي عبر ضبط إعدادات القناة ألفا باستخدام قيم النظام اللوني RGB، إذا كنت لا تعرف قيمة RGB التي يستخدمها رسمك للقناة ألفا فافتح الشخصية باستخدام أحد البرامج الاختصاصية مثل بينتا أو إنكسكيب واملأ المساحة الفارغة حوله بلونٍ مميز مثل #00ff00 الذي يكافئ اللون الأخضر "للشاشة الخضراء" ثم سجل هذه القيمة المكتوبة بالترميز الست عشري واكتبها في كود بايثون لتشكل قيمة القناة ألفا. أضف الآن السطرين التاليين إلى التعليمات الخاصة بإنشاء كائن اللعبة حتى تضبط القناة ألفا: img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert() img.convert_alpha() # تحسين ألفا img.set_colorkey(ALPHA) # تحديد قيمة ألفا إذا كنت تعتقد أن الصورة التي تعتمدها تملك بالفعل قيمة لقناة ألفا فاضبط المتغير ALPHA على القيمة 0 أو 255 فكلاهما من القيم الشائعة، ولكن لخبرتي مجال المونتاج -يقول كاتب المقال- أفضل إنشاء قناة ألفا وضبطها بنفسي لتكون قيمتها أكيدة دون لبس. أضف المتغير التالي في المقطع الخاص بالمتغيرات: ALPHA = (0, 255, 0) تكافئ القيمة 0,255,0 في نظام RGB اللون الأخضر، ويمكنك على الحصول قيم كافة الألوان باستخدام برنامج رسومات جيد مثل جيمب أو كريتا أو إنكسكيب، أو الاستعانة بأحد نظم اختيار الألوان الشهيرة مفتوحة المصدر مثل KColorChooser أو ColourPicker. إذا كانت الصور التي تعتمدها تملك قيمة مغايرة للقناة ألفا فاختر للمتغير قيمة مناسبة وفق ما يستلزم الأمر، ودائمًا القيم التي تضعها لألفا ستصبح غير مرئية أيًا كانت، ففي حال وضعت قيمة ألفا 000 مثلًا (وهي تعني اللون الأسود) فاجعل عندها خطوط رسمك التي تكون سوداء بالعادة باللون 111 وهو أقرب ما يمكن للأسود ولن يلاحظ المستخدم الفرق. شغل لعبتك الآن ولاحظ النتائج. أما الشكل النهائي للكود بعد كل هذه التعديلات سيكون كما يلي: #!/usr/bin/env python3 # by Seth Kenlon # GPLv3 # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from typing import Tuple import pygame import sys import os ''' Variables ''' worldx = 960 worldy = 720 fps = 40 # frame rate ani = 4 # animation cycles world = pygame.display.set_mode([worldx, worldy]) BLUE = (25, 25, 200) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ALPHA = (0, 255, 0) ''' Objects ''' class Player(pygame.sprite.Sprite): """ Spawn a player """ def __init__(self): pygame.sprite.Sprite.__init__(self) self.images = [] for i in range(1, 5): img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert() img.convert_alpha() # optimise alpha img.set_colorkey(ALPHA) # set alpha self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() ''' Setup ''' backdrop = pygame.image.load(os.path.join('images', 'stage.png')) clock = pygame.time.Clock() pygame.init() backdropbox = world.get_rect() main = True player = Player() # spawn player player.rect.x = 0 # go to x player.rect.y = 0 # go to y player_list = pygame.sprite.Group() player_list.add(player) ''' Main Loop ''' while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() try: sys.exit() finally: main = False if event.type == pygame.KEYDOWN: if event.key == ord('q'): pygame.quit() try: sys.exit() finally: main = False world.blit(backdrop, backdropbox) player_list.draw(world) pygame.display.flip() clock.tick(fps) تابع معنا المقال التالي لتضيف بعض الإثارة وتتعلم كيفية تحريك الكائن. ترجمة -وبتصرف- للمقال How to add a player to your Python game لصاحبه Seth Kenlon. اقرأ أيضًا المقال التالي: تحريك شخصية في لعبة باستخدام Pygame المقال السابق: بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب Pygame برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون تعرف على أشهر لغات برمجة الألعاب
-
شرحنا في المقال السابق كيفية بناء لعبة نرد بسيطة باستعمال لغة بايثون، وتعرفنا على وحدة السلحفاة Turtle الخاصة برسم الخطوط والأشكال البسيطة. وسنعرض في هذا المقال وحدة أخرى لكنها متقدمة ومتخصصة بالألعاب الرسومية كما يشير اسمها فهي تدعى PyGame، هذه الوحدة ليست مضمنة افتراضيًا في بايثون مثل وحدة السلحفاة، لذا عليك تنزيلها وتثبيتها بنفسك فلها مكتبةٌ خاصة. يمكنك مطالعة المقالات ضمن السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. يعتمد الأسلوب الحديث في تطوير بايثون على مفهوم البيئات الافتراضية، التي توفر لتطبيقك مساحة خاصة ومعزولة تتضمن كل ما يحتاج إليه، وتساعدك في إدارة المكتبات اللازمة له، ما يعني أن متطلبات التشغيل ستكون واضحة أمامك ويمكنك تحديدها بدقة لأي شخص آخر يرغب باستخدام التطبيق أو اللعبة حتى يثبتها لديه. تستطيع إدارة البيئة الافتراضية يدويًا لكن إدارتها وتنزيل حزمها من خلال بيئة تطوير متكاملة IDE التي تبقى الطريقة الأسهل والأكثر مرونة، وهذا ما طبقناه في حالتنا عبر بيئة PyCharm في المقال السابق المشار إليه أعلاه. مكتبة Pygame Pygame هي مكتبة أو وحدة بايثون، تتضمن مجموعة من الشيفرات الجاهزة الشائعة الاستخدام في مجال الألعاب، يمكنك الاستفادة منها عوضًا عن كتابتها من الصفر من جديد فهي توفر عليك إعادة اختراع العجلة كما يقال، تذكر وحدة السلحفاة التي جربّناها في مقالنا السابق وتخيل مثلًا أنك ستكتب الشيفرة البرمجية اللازمة لتنشئ القلم قبل أن ترسم به، كم ستكون العملية معقدة، وبالمثل في ألعاب الفيديو هنا تكمن فائدة Pygame. تحتاج اللعبة إلى عالم افتراضي أو بيئة تجري فيها الأحداث، في Pygame يمكنك إضافة ذلك بإحدى طريقتين: تعيين لون للخلفية. تعيين صورة للخلفية. وفي الحالتين لن تتفاعل شخصيات اللعبة مع الخلفية سواء أكانت صورة أو لون فهي مجرد بيئة جامدة للعبتك. كيفية استعمال Pygame أنشئ أولًا مشروعًا جديدًا، ومعه بطبيعة الحال مجلدًا خاصًا باللعبة لتُحفظ فيه كافة الملفات اللازمة لعملها فهذا التفصيل مهم. إنشاء المشروع الجديد عملية بسيطة في بيئة التطوير المتكاملة، ففي PyCharm المستخدمة في حالتنا، يكفي أن تضغط على قائمة ملف، وتختار "مشروع جديد" وتحدد بعدها اسمًا له (game_001 على سبيل المثال)، لاحظ أن مشروعك سيُحفظ ضمن مجلد خاص بمشاريع PyCharm. ولا تنسَ تفعيل خيار بناء البيئة الافتراضية باستخدام Virtualenv وخيار إنشاء الملف main.py مع الإبقاء على الخيارات الأخرى كما هي افتراضيًا. اضغط الآن على زر إنشاء ليظهر مشروعك الجديد في نافذة PyCharm وهو يتألف من بيئة افتراضية env (تجدها في العمود اليساري) وملف نموذجي main.py. امسح كافة المعلومات من الملف main.py لتكتب بداخله شيفرة برنامجك، عادةً ما تبدأ ملفات بايثون بكتابة نوع الملف ومن ثم اسمك والترخيص الذي تستخدمه، استخدم ترخيصًا مفتوح المصدر لتفسح المجال أمام بقية المطورين لتطوير لعبتك وتحسينها، انظر صيغة الملف المبينة أدناه: #!/usr/bin/env python3 # by Seth Kenlon ## GPLv3 # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. الآن ضمن الملف نفسه، حدد الوحدات التي تحتاج لاستخدامها، وانتبه فلن تكتب إشارة # قبل التعليمات هنا، فهذه الإشارة تعني في لغة بايثون أنك تترك تعليقًا أو ملاحظةً لك أو لغيرك من المطورين. import pygame # لاستيراد الوحدة pygame import sys # تسمح لبايثون باستخدام نظام التشغيل import os # لمساعدة بايثون في التعرف على نظام تشغيلك والتكيف معه ستلاحظ أن PyCharm يميز الكلمة pygame عن كل من os و sys ويعدّها خطأ، فهو لم يتعرف على هذه الوحدة بعد، إذ إنها غير مضمنة افتراضيًا مع بايثون كما ذكرنا في المقدمة، لذا ينبغي علينا تثبيتها قبل استخدامها. لتثبيتها يكفي أن تمرر مؤشر الفأرة فوق الكلمة وستظهر لك نافذة منبثقة تُطالبك بتثبيت الحزمة، انقر على رابط التثبيت وانتظر انتهاء العملية وستصبح Pygame جاهزة في بيئتك الافتراضية. أما لو أردت تثبيتها يدويًا بدون بيئة التطوير المتكاملة فاستخدم الأمر pip. تقسيم شيفرة اللعبة تقسيم الشيفرة البرمجية إلى مقاطع وإضافة التعليقات الكتلية عليها هو عملية تنظيمية تفيد المبرمج في التخطيط لبرنامجه ومعرفة ما ينبغي كتابته في كل مقطع، وتسهل عليه المراجعة والتعديل فيما بعد، علمًا أن هذه التعليقات غير مرئية لبايثون أي أنه يتجاهلها ولا ينفذها وهي تظهر في برنامجك لدى قراءة الشيفرة المصدرية فقط. ''' Variables ''' # ضع المتغيرات في هذا المقطع ''' Objects ''' # ضع هنا أصناف ودوال بايثون ''' Setup ''' # ضع كود التشغيل-لمرة-واحدة ''' Main Loop ''' # ضع هنا الحلقة الرئيسية للعبة لنضبط الآن قياس نافذة اللعبة، ويُراعى في ذلك اختلاف أحجام شاشات العرض بين الحواسيب والهواتف المحمولة وغيرها، لذا سنضع رقمًا وسطيًا يناسب معظم الشاشات، مع العلم بوجود طرق خاصة يتبّعها مطورو الألعاب الحديثة ليتكيف عرض اللعبة تلقائيًا مع حجم شاشة جهاز المستخدم، ولكن بصفتك في بداية الطريق يكفي أن تضع قياسًا واحدًا الآن كما يلي: ''' Variables ''' worldx = 960 worldy = 720 لننتقل للإعدادات الخاصة بمحرك Pygame، وتتمثل في ضبط معدل الأطر frame rate والساعة الداخلية internal clock (باستخدام الكلمة المفتاحية init). اكتب أولًا المتغيرات التالية في المقطع المخصص للمتغيرات: fps = 40 # معدل الأطر ani = 4 # دورات الحركة والآن التعليمات الخاصة بالساعة الداخلية ضمن مقطع الإعدادات: ''' Setup ''' clock = pygame.time.Clock() pygame.init() ضبط الخلفية اختر صورة مناسبة للعبتك أو صممها بأحد برامج التصميم الجرافيكي واحفظها باسم stage.png في المجلد images ضمن مجلد مشروعك. إليك بعض برامج التصميم المجانية التي يمكنك استخدامها على سبيل المثال لا الحصر: جيمب برنامج بسيط وسهل التعلم. كريتا برنامج احترافي يحاكي الطلاء وأدوات الرسم الحقيقية لينتج صورًا مميزة. إنكسكيب برنامج للرسوم المتجهة، توظف فيه الأشكال والخطوط ومنحنيات بيزير لرسم ما تريد. لا داعي لتكون صورة الخلفية معقدة في هذه المرحلة، إذ باستطاعتك تغييرها في أي وقت. والآن بعد أن جهزت الصورة اكتب التعليمات التالية ضمن مقطع الإعدادات: world = pygame.display.set_mode([worldx,worldy]) backdrop = pygame.image.load(os.path.join('images','stage.png')) backdropbox = world.get_rect() أما في حال رغبت بالاكتفاء بتلوين الخلفية فقط دون استخدام صورة، تكفيك هذه التعليمة: world = pygame.display.set_mode([worldx, worldy]) ولكن عليك أولًا تعريف الدرجات اللونية التي تفضلها للأحمر والأخضر والأزرق وفق النظام اللوني RGB، وكتابتها ضمن مقطع المتغيرات كما يلي: ''' Variables ''' BLUE = (25, 25, 200) BLACK = (23, 23, 23) WHITE = (254, 254, 254) اكتشاف الأخطاء بيئة التطوير PyCharm مفيدة في اكتشاف مخالفات قواعد كتابة اللغة، فهي تُميّز التحذيرات باللون الأصفر والأخطاء باللون الأحمر، وتعد صارمة بعض الشيء في هذا المجال. التحذيرات في معظمها مخالفات بسيطة لأسلوب بايثون، وهو أمرٌ ستتعلمه وتتجاوزه مع الزمن والخبرة، لذا يعد تجاهلها آمنًا ولن يؤثر على عمل برنامجك. أما الأخطاء ينبغي عليك تصحيحها بالتأكيد لأنها تعطل تنفيذ البرنامج، فعلى سبيل المثال تشدد PyCharm على وجود ما يدل على إدخال سطر جديد في نهاية السطر الأخير من الكود، لذا عليك الضغط على زر Enter أو Return في لوحة المفاتيح في نهاية الكود حتى يعمل. تشغيل اللعبة احفظ التغييرات وشغل اللعبة، عبر الضغط على زر "تشغيل الوحدة Run Module" من قائمة تشغيل في IDLE أو الضغط على "تشغيل ملف Run file" من شريط الأدوات في PyCharm. ويمكنك أيضًا التشغيل باستخدام نافذة الطرفية في يونيكس أو موجه سطر الأوامر في ويندوز بشرط أن تقوم بذلك وأنت في البيئة الافتراضية لبايثون. لكنك ستلاحظ بعد التشغيل أن لعبتك تعمل لمدة بسيطة جدًا أجزاء من الثانية فقط، وهو موضوع فقرتنا التالية. حلقة التكرار سيعمل البرنامج المكتوب بلغة بايثون لمرة واحدة فقط ما لم تعطه أوامر واضحة للتكرار، وبسبب سرعة معالجة الحواسيب فإن ذلك سيتم خلال أجزاء من الثانية حتى أن المستخدم لن يلحظ اللعبة، لنعالج الموضوع سنستخدم الحلقة While حتى تبقى اللعبة نشطة ومتاحة أمام المستخدم، وسنعيّن لهذه الحلقة متغير نسميه main ونعطيه قيمة ما، يضبط هذا المتغير تكرار الحلقة فهي ستتكرر باستمرار طالما أن قيمته لم تتغير، وبالمناسبة يطلق هذا النوع من الحلقات اسم الحلقة الرئيسية main loop. إذًا اكتب التعليمات التالية في برنامجك ضمن المقطع الخاص بالحلقة الرئيسية: ''' Main loop ''' while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() try: sys.exit() finally: main = False if event.type == pygame.KEYDOWN: if event.key == ord('q'): pygame.quit() try: sys.exit() finally: main = False ولا تنسَ الضغط على زر Enter أو Return في نهاية السطر الأخير، ليُضاف سطر فارغ في نهاية ملف البرنامج. حدّث أيضًا عالم اللعبة الافتراضي في الحلقة الرئيسية، بكتابة التعليمة التالية إن كنت تعتمد صورة للخلفية: world.blit(backdrop, backdropbox) أو التعليمة أدناه في حال كنت تستخدم الألوان فقط للخلفية: world.fill(BLUE) وأخيرًا أخبر الوحدة Pygame لتحدّث كل ما يظهر على الشاشة مع ساعتها الداخلية، وذلك وفق الأوامر الآتية: pygame.display.flip() clock.tick(fps) احفظ الملف وشغله، لتحظى بأكثر لعبة مملة رأيتها! وهذا طبيعي فلا شيء على الشاشة سوى خلفية اللعبة، لكن لا تقلق سنطوّرها تباعًا في المقالات اللاحقة، يمكنك الآن الضغط على q للخروج. تثبيت البيئة الافتراضية لبايثون تدير PyCharm المكتبات وتوفر لك متطلبات التشغيل المناسبة لبرنامجك، لكن المستخدم لن يشغل البرنامج أو اللعبة من PyCharm كما تفعل أنت، لذا يُعدّ تثبيت بيئتك الافتراضية خطوةً مهمة حتى أنها تعادل في الأهمية حفظ التعديلات على ملف البرنامج. استعرض قائمة الأدوات Tools واختر خيار "التزامن مع متطلبات بايثون" Sync Python Requirements، وستُحفظ عندها كافة اعتماديات المكتبات التي استخدمتها ضمن ملف نصي خاص يدعى requirements.txt. وفي المرة الأولى التي يعمل فيها هذا التزامن ستُطالبك PyCharm بتثبيت بعض الإضافات والاعتماديات اضغط على زر قبول، وسيتشكل عندها ملف المتطلبات الخاص بك requirements.txt ويُحفظ في مجلد مشروعك. الكود النهائي للعبة هذا ما سيبدو عليه كود اللعبة النهائي إلى الآن: #!/usr/bin/env python3 # by Seth Kenlon # GPLv3 # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import pygame import sys import os ''' Variables ''' worldx = 960 worldy = 720 fps = 40 # frame rate ani = 4 # animation cycles main = True BLUE = (25, 25, 200) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ''' Objects ''' # put Python classes and functions here ''' Setup ''' clock = pygame.time.Clock() pygame.init() world = pygame.display.set_mode([worldx, worldy]) backdrop = pygame.image.load(os.path.join('images', 'stage.png')) backdropbox = world.get_rect() ''' Main Loop ''' while main: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() try: sys.exit() finally: main = False if event.type == pygame.KEYDOWN: if event.key == ord('q'): pygame.quit() try: sys.exit() finally: main = False world.blit(backdrop, backdropbox) pygame.display.flip() clock.tick(fps) محطتنا التالية سنطوّر اللعبة في مقالنا القادم ونضيف بعض الشخصيات والحركة لعالمها الفارغ، لذا يمكنك البدء بتحضير بعض الرسومات المميزة منذ الآن والمتابعة معنا. ترجمة -وبتصرف- للمقال Build a game framework with Python using the Pygame module لصاحبه Seth Kenlon. اقرأ أيضًا المقال التالي: إضافة لاعب إلى لعبة مطورة باستخدام بايثون ومكتبة Pygame المقال السابق: بناء لعبة نرد بسيطة بلغة بايثون تعرف على أشهر لغات برمجة الألعاب برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
-
لغة بايثون هي لغة برمجة سهلة التعلم موازنةً باللغات الأخرى مثل لغة جافا ولغة C ولغة ++C، بالإضافة لكونها متعددة الأغراض فهي تستخدم في بناء تطبيقات سطح المكتب وألعاب الفيديو والرسومات ثلاثية الأبعاد والمواقع الإلكترونية وغيرها، زد على أنها لغة قوية برمجيًا وممتازة للتطبيقات المتقدمة، هذا كله جعلها خيارًا مثاليًا للجميع بصرف النظر عن عملهم وعمرهم وعدد سنوات خبرتهم، بالأخص للراغبين بتعلم البرمجة. هذا المقال جزءًا من سلسلة مقالات حول تصميم لعبة بلغة بايثون ومكتبة Pygame وإليك كامل مقالات السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. تثبيت بايثون ستحتاج إلى تثبيت بايثون الإصدار 3 لتتابع معنا خطوات العمل، لذا اتبع الخطوات المناسبة لنظام تشغيلك. إن كنت تستخدم نظام تشغيل لينكس، فستجد بايثون متوفرًا ضمنه على الأغلب، استعلم عن إصداره بالأمر التالي: python --version وانظر إلى الخرج فلو تبين لك أن الإصدار الموجود هو 2، أو أن بايثون غير مثبت على الإطلاق، حاول كتابة الأمر بالشكل التالي: python3 --version وإن حصلت على رسالة مفادها عدم التعرف على الأمر، فثبت بايثون 3 باستخدام مركز التطبيقات أو مدير الحزم الخاص بتوزيعة لينكس التي تعتمدها مثل apt في أوبونتو أو dnf في فيدورا. إن كانت توزيعتك هي فيدورا، ثبت بايثون 3 بالأمر التالي: sudo dnf install python3 اتبع الخطوات نفسها في نظام تشغيل macOS لتتأكد من وجود بايثون 3 في النظام، وإن لم يكن مثبتًا، حمله من موقع بايثون الرسمي ثم ثبته، وهنا لديك هذه الطريقة فقط إذ إن macOS لا يملك مدير حزم. أما لو كنت تستخدم نظام تشغيل ويندوز فحمل بايثون 3 من الموقع الرسمي في البداية، ويمكنك الاستعانة بالمقال كيفية تثبيت بايثون 3 وإعداد بيئته البرمجية على ويندوز 10 لهذا الغرض. عمومًا أيًا كان نظام التشغيل الذي تعتمده يمكنك الاستعانة بقسم تثبيت بايثون 3 وإعداد بيئتها البرمجية من المرجع الشامل لتعلم بايثون المتوفر باللغة العربية على أكاديمية حسوب. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن تشغيل بيئة التطوير المتكاملة IDE فعليًا كل ما تحتاج إليه لكتابة البرامج بلغة بايثون هو محرر شيفرات، ولكن استخدامك بيئة تطوير متكاملة IDE سيسهل عليك كتابة الشيفرة كثيرًا، فهذه البيئات تتضمن وظيفة محرر الشيفرات البسيط مع بعض الأدوات الإضافية الملائمة لكتابة شيفرة بايثون، ويتوفر العديد من الخيارات مفتوحة المصدر لبيئات التطوير المتكاملة، سنعرض منها IDLE 3 و PyCharm. بيئة التطوير IDLE 3 الأساسية هي بيئة التطوير الأساسية المضمنة مع بايثون، وتتميز هذه البيئة بتلوين أجزاء الشيفرة ما يسهل قراءتها واكتشاف الأخطاء على المبرمج، وأيضًا بإعطاء تلميحات لإكمال التعليمات البرمجية، بالإضافة إلى وجود زر تشغيل لتسهيل تشغيلها واختبارها. لبدء استخدام هذه البيئة في لينكس ونظام macOS يكفيك كتابة الأمر idle3 في نافذة الطرفية، أما لاستخدامها في ويندوز فابحث عن بايثون 3 في قائمة ابدأ وستجد هذه البيئة بين خياراتها، وفي حال لم تجد بايثون في قائمة ابدأ، افتح سطر الأوامر بكتابة cmd في خانة البحث، ومن ثم اكتب ضمنه السطر التالي: C:\Windows\py.exe وإن لم ينفع، أعد تثبيت بايثون وتأكد من تفعيل خيار إضافة بايثون إلى متغير البيئة PATH في أثناء عملية التثبيت. وإن لم ينفع ذلك أيضًا، استخدم لينكس ضمن ويندوز نفسه أو بتثبيت لينكس مع ويندوز، فهو في النهاية مجاني، وهذا الانتقال في البيئة لن يكلفك أكثر من حفظ ملفات بايثون خاصتك على قرص USB محمول ونقلها. بيئة التطوير Pycharm النسخة المجتمعية تعدّ Pycharm من أفضل بيئات التطوير مفتوحة المصدر المتوافقة مع بايثون، تتميز بتلوين الشيفرات لتسهيل قراءتها ولاكتشاف أخطاء الكتابة، وإكمال الأقواس وعلامات الاقتباس لضمان صيغ قواعدية سليمة، بالإضافة إلى ميزة ترقيم الأسطر المفيدة في أثناء تنقيح الأخطاء، وعلامات المسافة البادئة، وزر التشغيل لتسهيل اختبار الكود. لتستخدم هذه البيئة ثبّت النسخة المجتمعية من Pycharm، إن كان نظام تشغيلك هو لينكس فيمكنك تثبيتها باستخدام flatpak من منصة FlatHub أو تحميلها من الموقع الرسمي وتثبيتها يدويًا، أما لو كنت تعتمد ويندوز أو macOS فحمل نسخة البرنامج المناسبة لك من موقعه الرسمي، ومن ثم ثبتها. افتح البرنامج بعد التثبيت وأنشئ مشروعًا جديدًا. أخبر بايثون بما تريد تنفيذه الكلمات المفتاحية هي كلمات أساسية تُعرّف الدوال والأصناف وغيرها فهي إذًا الوسيلة التي تخبر بايثون بكل ما تريد تنفيذه. لتجرب إحداها، اكتب السطر التالي ضمن مشروعك الجديد الذي أنشأته على بيئة التطوير: print("Hello world.") والآن شغله بإحدى الطريقتين: إن كنت تستخدم IDLE استعرض قائمة التشغيل وحدد الخيار "تشغيل الوحدة Run Module". أما في PyCharm انقر فوق الزر "تشغيل ملف Run file" ضمن أزرار شريط الأدوات. تطبع print -الكلمة المفتاحية التي جربناها- النص المكتوب بين علامتي اقتباس الموجود ضمن القوسين كما هو أيًا كان. وننوه لك أن بايثون افتراضيًا لا تستخدم إلّا الكلمات المفتاحية الأساسية مثل print و help وتلك الخاصة بالعمليات الحسابية الأساسية وما شابه، ولكن التعليمة import تتيح للمبرمج استيراد المزيد من الوظائف واستخدامها. استخدام وحدة الرسم turtle في بايثون اكتب الأسطر التالية في ملفك (بعد حذف الموجود فيه)، ومن ثم شغله: import turtle turtle.begin_fill() turtle.forward(100) turtle.left(90) turtle.forward(100) turtle.left(90) turtle.forward(100) turtle.left(90) turtle.forward(100) turtle.end_fill() لاحظ الأشكال التي يمكنك رسمها باستخدام مكتبة الرسم turtle والتي تدعى سلحفاة. لتمسح كل الموجود في مساحة الرسم، استخدم: turtle.clear() حاول تخمين معنى الدالة من اسمها الأجنبي: turtle.color("blue") لابد أنك أصبت، سيصبح لون الخط أزرق بدل الأسود، فسهولة فهم التعليمات وتفسيرها هي إحدى ميزات بايثون. سنحاول الحصول على النتيجة نفسها بكتابة كود مختلف أكثر تعقيدًا، وذلك باستخدام الحلقة while التي ستوجه بايثون ليكرر عملية رسم الخط والانعطاف أربع مرات متتالية عوضًا عن إعادة كتابة السطر نفسه أربع مرات كما فعلنا في الفقرة السابقة، أما المتغير الذي سيحدد عدد مرات التكرار فيسمى العداد counter، ستتعلم المزيد عن المتغيرات لاحقًا، ولكن الآن اكتب الأسطر التالية ونفذها لترى كيفية تفاعل المتغير مع الحلقة: import turtle as t import time t.color("blue") t.begin_fill() counter=0 while counter < 4: t.forward(100) t.left(90) counter = counter+1 t.end_fill() time.sleep(2) تعلم بايثون عبر بناء لعبة بسيطة هدفنا أساسًا تعلم البرمجة عبر لغة بايثون، وتمهيدًا لاستخدامها في برمجة متقدمة تعتمد على الرسوميات، سنبدأ في هذا المقال بتعلم منطق اللعبة وكيفية تنظيم البيئة لبناء لعبة نرد بسيطة، يلعب فيها المستخدم مقابل الحاسوب ويلقي كل منهما النرد الافتراضي ليفوز صاحب القيمة الأعلى. التخطيط للعبة يبدأ تطوير البرمجيات عادةً بكتابة توثيق -ولو كان بسيطًا- للمتطلبات والأهداف يبين ما يفترض أن يكون عليه البرنامج، وفي لعبة النرد التي نعمل عليها سيتضمن التوثيق آلية العمل التالية. ابدأ اللعبة واضغط على زر Return أو Enter في لوحة المفاتيح لتبدأ دورة النرد. ستظهر النتيجة على الشاشة. ستُطالب بعدها بإعادة اللعب أو بالخروج. اللعبة بسيطة هذا صحيح، لكن توثيقها سيبين لك كمًّا لا بأس به من التفاصيل لتعمل عليها، مثل حاجتك لهذه المكونات. اللاعب: وهو العنصر البشري الذي سيستخدم اللعبة. AI: وترمز للذكاء الصنعي أي جهاز الحاسوب الذي يحاكي دور اللاعب الخصم، حتى يكون لديك فائز وخاسر. رقم عشوائي: الشائع في لعبة النرد استخدام نرد سداسي الأضلاع وبالتالي الرقم سيكون بين 1 و 6. عامل إجرائي: عملية رياضية بسيطة لموازنة رقمي اللاعبين ومعرفة الأكبر بينهما. رسالة فوز أو خسارة. مطالبة باتخاذ إجراء: إما اللعب مجددًا أو الخروج. بناء الإصدار الأول من لعبة النرد يتضمن الإصدار الأول من البرنامج الميزات الأساسية فقط، قلة هم من يبدأون تطوير برامجهم بميزات كاملة من أول إصدار. سنعرض مفهومين أساسيين قبل البدء. أولًا المتغير variable هو قيمة قابلة للتغيير، يستخدم لتخزين بعض المعلومات التي يحتاجها البرنامج وهو شائع الاستخدام في بايثون، فمثلًا المحرف x هو متغير في المعادلة التالية لأنه يشير إلى قيمةٍ ما أو ينوب عنها: x + 5 = 20 وثانيًا العدد الصحيح integer فيشمل الأعداد الصحيحة الموجبة والسالبة، مثلًا: 1، -1 ،14 ،10947. يتضمن إصدارنا الأول من اللعبة -والمسمى ألفا dice-alpha- متغيرين هما player و ai. لتبدأ بإنشائه، افتح مشروعًا جديدًا، وسمّه dice-alpha ثم اكتب ضمنه الكود التالي: import random player = random.randint(1,6) ai = random.randint(1,6) if player > ai : print("You win") # لاحظ المسافة البادئة else: print("You lose") شغل اللعبة، ولاحظ أن وظيفتها الأساسية تعمل جيدًا، لكنها لا تبدو لعبةً متكاملة حتى الآن، فاللاعب لا يعرف نتيجة رميته ولا رمية الخصم، بالإضافة إلى أنها تنتهي مباشرةً حتى لو رغب اللاعب بالاستمرار، في الواقع يعدّ هذا شائعًا في الإصدارات الأولى من البرامج التي تسمى الإصدارات ألفا، لكنك في نهاية المطاف حللت الإجراء المهم في اللعبة وهو رمي النرد وأصبحت متيقنًا من عمله، فيمكنك استكمال بقية الميزات فيما بعد. تحسين اللعبة سنبني الآن الإصدار الثاني وهو الإصدار بيتا، بإضافة بعض الميزات ليصبح برنامجنا أقرب للعبة فعلًا. 1. إظهار تفاصيل النتيجة في إصدارنا الأول أظهرنا للاعب عبارةً واحدة ليعرف إن فاز أو خسر، والآن سنطوّر الآلية ليعرف اللاعب مقدار العدد العشوائي الذي حصل عليه أي مقدار رميته للنرد وكذلك رمية الخصم. جرب إجراء التعديل التالي في برنامجك، ومن ثم شغله: player = random.randint(1,6) print("You rolled " + player) ai = random.randint(1,6) print("The computer rolled " + ai) بمجرد تشغيله ستحصل على خطأ، والسبب أن بايثون قد فهم برنامجك على أنه عملية حسابية تجمع حروف وأعداد، الحروف هي حروف العبارة التي ستُظهر النتيجة على الشاشة والعدد هو قيمة المتغير player أو المتغير ai، وهذا بالطبع غير منطقي وغير قابل للتطبيق. لتصحيح الخطأ سنجعل بايثون يتعامل مع قيمة المتغير بصفتها نصًا أو بالأحرى سلسلة نصية string بدلًا من معاملتها على أساس أنها عدد صحيح integer. نفذ إذًا التعديل التالي وأعد تشغيل اللعبة ولاحظ النتائج: player = random.randint(1,6) print("You rolled " + str(player) ) ai = random.randint(1,6) print("The computer rolled " + str(ai) ) 2. إضافة عامل التشويق أضفنا بعض التسلية للعبة بإظهار النتائج، والآن سنضيف بعض الإثارة بإبطاء اللعبة في الأجزاء المشوقة، أما وسيلتنا لذلك فهي الدالة time إحدى دوال بايثون. نفذ التالي وتفقد النتائج: import random import time player = random.randint(1,6) print("You rolled " + str(player) ) ai = random.randint(1,6) print("The computer rolls...." ) time.sleep(2) print("The computer has rolled a " + str(player) ) if player > ai : print("You win") # لاحظ المسافة البادئة else: print("You lose") 3. اكتشاف ثغرة التعادل لو ثابرت على اللعب لفترةٍ جيدة، ستكتشف أن لعبتك لا تمتلك التعليمات اللازمة للتعامل مع حالة التعادل أي الحالة التي يحصل فيها اللاعب والحاسوب على القيمة نفسها، وهذه تعدّ ثغرة أو خطأ منطقي في الكود المكتوب، وحتى نعالجها سنجعل بايثون يتحقق من كون القيمتين متساويتين ويعرض عندها نتيجة التعادل. سنستخدم إشارة يساوي مزدوجة == للتحقق من تساوي القيمتين، إذ إن إشارة يساوي مفردة تعني في بايثون إسناد قيمة ما لأحد المتغيرات، أما بخصوص إدراج خيار ثالث للنتيجة، فسنعتمد على الكلمة المفتاحية elif (اختصار لـ else if) وهي مفيدة في الحالات التي تحتاج فيها للمطابقة مع ثلاثة احتمالات أو أكثر ضمن التعليمات الشرطية، بدلاً من مجرد احتمالين أحدهما صحيح والآخر خاطئ. الآن عدّل برنامجك ليغدو كما يلي: if player > ai : print("You win") # لاحظ المسافة البادئة elif player == ai: print("Tie game.") else: print("You lose") وبعدها جرب اللعبة عدة مرات لتصادف حالة التعادل وتتأكد من عملها. يمكنك تعلم المزيد عن الدوال الشرطية في بايثون بالاطلاع على المقال كيفية كتابة التعليمات الشرطية في بايثون 3 ضمن دليل تعلم بايثون على أكاديمية حسوب. بناء الإصدار النهائي للعبة تفوق الإصدار بيتا وظيفيًا على الإصدار ألفا، وكان أكثر قربًا منه للألعاب الحقيقية، والآن سنبني إصدارنا النهائي الأكثر تكاملًا وستتعلم خلاله بناء دالتك الأولى في بايثون. الدالة function هي مجموعة من التعليمات البرمجية التي يتم استدعائها ضمن البرنامج بصورة كتلة واحدة متميزة، تفيد الدوال في تنظيم التطبيقات بالأخص تلك التي تتضمن عددًا كبيرًا من التعليمات البرمجية التي لا يُفترض أن تعمل في وقتٍ واحد وشروطٍ واحدة، والدوال هنا هي من تحدد ما الذي سيحدث وفي أي وقت وشرط سيحدث. عدّل الآن برنامجك ليصبح: import random import time def dice(): player = random.randint(1,6) print("You rolled " + str(player) ) ai = random.randint(1,6) print("The computer rolls...." ) time.sleep(2) print("The computer has rolled a " + str(ai) ) if player > ai : print("You win") elif player == ai: print("Tie game.") else: print("You lose") print("Quit? Y/N") cont = input() if cont == "Y" or cont == "y": exit() elif cont == "N" or cont == "n": pass else: print("I did not understand that. Playing again.") في هذا الإصدار سيُسأل اللاعب إن كان يريد الخروج من اللعبة، وفي حال أجاب بنعم عبر كتابة Y أو y، فإن بايثون سينهي استدعاء الدالة وتتم مغادرة اللعبة. إذًا فقد بنيت دالتك الأولى المسماة نرد dice لكنها لن تعمل ما لم تستدعيها ضمن البرنامج، وهذا ما ستفعله الحلقة While True. أضف التعليمات التالية في نهاية برنامجك، وانتبه للمسافات البادئة فقد أعدنا كتابة آخر سطرين في الكود السابق عن قصد ليكون السياق واضحًا أمامك: … # الحلقة الأساسية while True: print("Press return to roll your die.") roll = input() dice() هذه الحلقة هي أول ما سيعمل في برنامجك، وتتكرر دومًا ما لم يكسرها أمرٌ من بايثون، إذ إن شرط عملها محقق دائمًا وتلقائيًا فهو True، لاحظ تسلسل عملها فهي تطالب المستخدم أولًا باتخاذ إجراء حتى يبدأ اللعب، ومن ثم تستدعي الدالة dice، ومن بعدها بناءً على إجابة المستخدم ستستمر الحلقة بالعمل أو تتوقف. ومن الجدير بالذكر أن الحلقات هي واحدة من أشيع الطرق المستخدمة لبدء التطبيقات فهي تضمن بقاء التطبيق متاحًا أمام المستخدم لفترة كافية تمكنه من استخدام وظائفه المختلفة. يمكنك تعلم المزيد عن الحلقات التكرارية بالاطلاع على المقال كيفية إنشاء حلقات تكرار while في بايثون 3، والمقال كيفية استخدام تعابير break و continue و pass عند التعامل مع حلقات التكرار في بايثون 3 ضمن دليل تعلم بايثون على أكاديمية حسوب. ختامًا تعرفنا في هذا المقال على أساسيات البرمجة بلغة بايثون، وسنتعلم في مقالنا التالي كيفية بناء لعبة فيديو باستخدام الوحدة PyGame المتخصصة في هذا المجال. ترجمة -وبتصرف- للمقال Learn how to program in Python by building a simple dice game لصاحبه Seth Kenlon. اقرأ أيضًا المقال التالي: بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب Pygame تعرف على أشهر لغات برمجة الألعاب برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
-
بدأت رحلتنا في التعرف إلى زوليب Zulip عندما قررت الشركة تطوير منصتها الخاصة بالدردشة الفورية والانتقال لمنصة أخرى تجمع في صفاتها سهولة الاستخدام و البرمجيات مفتوحة المصدر، وهذه المحددات ومميزات عديدة أخرى قادتنا إلى زوليب وهو ما سنعرضه في هذا المقال. كيف توصلت شركتنا إلى ضرورة تطوير حلها للدردشة شركة Backdrop CMS هي شركة متخصصة بأنظمة إدارة المحتوى وهي جزء من مشروع دروبال التي تُعنى بتطوير المواقع الإلكترونية للشركات الصغيرة والمتوسطة الحجم والمؤسسات غير الربحية أي للفئات ذات الميزانيات المنخفضة. في البدايات خلال السنوات الخمس الأولى لعمل الشركة استخدمنا Gitter كأداة للدردشة وكانت تفي بالغرض فهي: مفتوحة المصدر. سهلة الاستخدام. تدعم تسجيل الدخول باستخدام حسابات GitLab و GitHub. يمكن رؤية محادثاتها دون شرط امتلاك حساب على المنصة. بمرور الوقت وتطور عمل الشركة وبوجود أدوات دردشة متخصصة مثل سلاك Slack بدأنا نشعر أن غيتّر Gitter قاصرة عن تلبية احتياجاتنا فهي على سبيل المثال لا توفر قنوات لتنظيم المحادثات كما أن تطبيق الهاتف الجوال الخاص بها أدنى من المستوى المطلوب ومن هنا برزت الحاجة للبحث عن بديل. وبالنظر لمحدودية مواردنا المالية والتضييق الذي تمارسه سلاك على الحسابات المجانية وهي بالمناسبة المنافس القوي الذي يستخدمه معظمنا خارج الشركة، كان التحدي الأكبر لنا العثور على منصة مفتوحة المصدر وسهلة الاستخدام لغير التقنيين وقادرة نوعًا ما على منافسة سلاك. لماذا اخترنا زوليب Zulip بحثنا في المنصات مفتوحة المصدر عن كثب لإيجاد بديل معقول لسلاك وزوليب كان الخيار الأنسب، حيث أن الاستضافة المجانية التي تمنحها منصة زوليب من باب الدعم " لبعض المنظمات الجديرة بذلك" كانت واحدة من أهم العوامل التي دفعتنا باتجاه اختيار التطبيق في ظل ميزانيتنا المحدودة وصعوبة تحملنا للأعباء الناجمة عن صيانة وإدارة خادم خاص بالتطبيق. يتمتع زوليب بميزة مبتكرة تسمح للمستخدم بإنشاء مواضيع ووسم المحادثات الواردة ضمن مجرى التدفق بها وهذه الآلية في تصنيف المحادثات شبيهة إلى حد ما بقنوات الدردشة الموجودة في التطبيقات المنافسة، وبذلك يعطيك زوليب الإمكانية لعرض المحادثات الواردة وفق تسلسلها الزمني في مجرى التدفق أي كما هي وهذا ما اعتدناه في غيتّر، أو تصفيتها تبعًا للمواضيع التي تنتمي إليها وعرض موضوع بعينه. في بداية تشغيل التطبيق تنوعت انطباعات المستخدمين عن هذه الميزة فالبعض عدّها نقطة قوة وأحب استخدامها بينما شكلت تعقيدًا للبعض الآخر وبالأخص للمستخدمين الجدد فالتآلف مع هذه الواجهة واعتيادها يتطلب وقتًا. ولكونها ميزة اختيارية فالمواضيع في حينه لم تأخذ طابعًا رسميًا أما اليوم وبعد مرور زمن على استخدام التطبيق أصبحت مواضيع زوليب أداة تنظيمية مهمة وأهميتها تزداد يومًا بعد يوم دون أن ننكر استمرار استهجانها من قبل المستخدمين الجدد وحتى البعض من ذوي الخبرة. أما بالنسبة لتطبيقات الجوال الخاصة بزوليب فهي جيدة ومستقرة الأداء كما أنها تدعم أنظمة أندرويد أو iOS. تطور استخدام زوليب في الشركة انتقلت محادثات الدعم من المنتدى العام Public online forum إلى قناة الدردشة في زوليب مما أثار قلقنا حول بعض النقاط منها كون المحادثات على زوليب غير مصنفة كما هي في المنتدى وغير مرئية للأشخاص الذين لا يملكون حسابات على المنصة وبالتالي لن يتمكنوا من الاستفادة منها. وسنعرض في هذا الإطار بعض الإحصاءات كمؤشر عن حجم الإقبال على زوليب، إذ يوجد لدينا حاليًا نحو 240 حسابًا على المنصة، ويردنا كمعدل وسطي حوالي 75 رسالة في اليوم من 13 زائر مختلف أما معدل سرعة الرد فيتراوح بين ساعة إلى ست ساعات كحد أقصى. في الواقع تعدد قنوات الاتصال بشكل كبير دون وجود كثافة حقيقية في أي منها من الممكن أن يكون عائقًا أمام نجاح أي مجتمع على الإنترنت. ما زلنا نسعى لإبقاء معظم المحادثات في مجرى التدفق دون تصنيفها كمواضيع مع بعض الاستثناءات فعلى سبيل المثال لدينا موضوع مخصص للرسائل الواردة باللغة الألمانية ومواضيع خاصة بالحوادث والبنية التحتية أو تلك العائدة للإدارة العليا أو المواضيع الأمنية وقد يضاف لها مستقبلًا مواضيع أخرى. وفي الختام تختلف ردود أفعال المستخدمين حول زوليب لكن بالنسبة لتجربتنا فنحن نوصي به لأنه أولًا وأخيرًا مشروع مفتوح المصدر. ترجمة -وبتصرف- للمقال How our community uses Zulip for its open source chat tool لصاحبه Tim Erickson. اقرأ أيضًا مدخل إلى منصة مايكروسوفت تيمز Microsoft Teams كيف تزيد الدردشة المباشرة من معدلات التحويل
-
تخزين الصور وملفات الفيديو باستخدام صور Google هي واحدة من أكثر طرق التخزين شيوعًا، وكونها الخيار الافتراضي لهواتف أندرويد فهذا يعزز من انتشارها، ولكن في حال رغبت بالتخلي عن هذه الطريقة أو بالأحرى التخلي عن استخدام كافة المنصات مغلقة المصدر المشابهة والانتقال لمنصات إدارة الصور مفتوحة المصدر الأكثر أمانًا، فتطبيق بيويجو Piwigo يعد خيارًا مناسبًا إذ هو مفتوح المصدر مع إمكانية استضافته ذاتيًا وهو ما سنتعرف عليه أكثر في هذا المقال. بيويجو مكتبة الصور مفتوحة المصدر ذات الاستضافة الذاتية بيويجو الحل مفتوح المصدر لإدارة الصور والفيديو. يدعم التطبيق نمطين من أنماط الاستضافة ذاتية وسحابية، فتُخزَّن البيانات في النمط الأول محليًا على حاسب المستخدم ويكون المسؤول الوحيد عن سلامتها وتوفير النسخ الاحتياطي لها، أما في حال استخدام النمط السحابي فستُخزّن كافة البيانات سحابيًا على مخدمات الشركة ومقرها فرنسا وستوفر الشركة عندها خدمة النسخ الاحتياطي كإحدى ميزات الاستضافة. ميزات بيويجو يضمن بيويجو خصوصية المستخدم فضلًا عن طريقة تصميمه المناسبة للأعمال الفردية والجماعية، ويتمتع بمجموعة واسعة من ميزات التحكم بالصور وإدارتها إدارة متخصصة نستعرض أهمها: توفير استضافة واسم نطاق فرعي خاص بكل مستخدم تحت النطاق الرئيس piwigo.com إمكانية تحميل الملفات دفعة واحدة. إنشاء ألبومات الصور. تحديد الصور وتعيينها ضمن ألبومات ومجموعات. مشاركة الصور باستخدام الروابط الخاصة بها. إدارة التحكم بالوصول للملفات بنمطين عام وخاص. القدرة على تنظيم المستخدمين ضمن مجموعات عمل لإدارة الصور والألبومات وهذه الميزة مفيدة للشركات وفرق العمل. التحليلات الأساسية اللازمة لتتبع استخدامية التطبيق ومعدل التخزين المستخدم. إمكانية إضافة الوسوم للصور والألبومات. يدعم العمل بالنمط الليلي. إمكانية التعديل على البيانات الوصفية للصور. محددات تصفية لسهولة البحث عن الصور. يدعم لواحق الصور JPG و JPEG و PNG و GIF فقط بالنسبة لاستضافة الأفراد، بينما يدعم كافة كافة أنواع الملفات في استضافة المنظمات. مساحة تخزين غير محدودة في استضافة الأفراد. يسمح باستخدام أسماء نطاقات مخصصة (حتى في حال اختيار الاستضافة السحابية). يتضمن مجموعة من الإضافات لتوسيع الوظائف. يدعم وجود نمط أو سمة عامة للاستخدام. قابل للعمل مع الهاتف المحمول بنظام Android أو iOS. علاوة على كل الميزات المذكورة يمتلك بيويجو خيارات إضافية أخرى قادرة على رفع إمكانيات إدارة الملفات وعلى تحسين كافة جوانب تجربة المستخدم. وللحصول على نظرة أشمل قمت باستخدام التطبيق باستضافة سحابية تجريبية مدتها 30 يوم مخصصة للأفراد وسأشارك بعضًا من آرائي حول التجربة لتكونوا فكرة عنه قبل استخدامه. استخدام بيويجو لإدارة الصور بدايةً أنشئ حسابك على المنصة وسيُطلب منك اختيار اسم نطاق فرعي تحت النطاق الرئيس لبيويجو وهذا الاسم سيولد الرابط الخاص بك. ومن ثم عليك تقييد صلاحيات الوصول لتكون محصورة بك فقط أو بمن تريد من المستخدمين فالمشاركة العامة للملفات تعني أن كل من يملك الرابط الخاص بك يستطيع الوصول إليها. كما يمكنك التوجه للوحة التحكم الخاصة بالتطبيق لتفقد نسبة استخدام التخزين ونشاطاتك بشكل عام. يوفر التطبيق مجموعة من الإضافات اثنتين منها مفعلة بشكل تلقائي الأولى خاصة بالوظائف الداخلية والثانية بهدف التصدي لمرسلي البريد العشوائي Spam. أما الإضافات الأخرى فهي تتطلب تفعيل يدوي ونذكر منها الإضافة الخاصة بتحسين الإدارة الدفعية أو بتمكين رسائل إدارة النظام أو ضبط عمليات التحميل وكذلك الإضافات المسؤولة عن تفعيل التعليقات على ألبومات الصور أو تحديد مدة صلاحية عرض هذه الألبومات. في الواقع إن تجربة المستخدم لهذا التطبيق جيدة جدًا رغم أنها قد لا تقدم الشكل العصري الأفضل لواجهة المستخدم لكنها سهلة الإدارة وتتمتع بالعديد من المزايا والإضافات التي تجعلها جديرة بالاستكشاف، لذا امنحها بعض الوقت واستعرض هذه الميزات مع الإضافات المتاحة لتتبين فوائدها وتوازنها مع صور Google وغيرها من منصات استضافة الصور فمن المؤكد أنك لن تحظى بإدارة مماثلة لصورك. * أما بالنسبة لبقية الميزات الموجودة فنذكر منها تمكنك من إنشاء وإدارة عدة حسابات للمستخدمين والتحكم بصلاحيات الوصول وإرسال الإشعارات (عبر البريد الالكتروني) بالإضافة إلى بعض عمليات الصيانة. ابدأ باستخدام بيويجو في الواقع التطبيق خيار جيد ويلبي كافة الاحتياجات بدءًا من المستخدم الراغب بتخزين وتنظيم الصور إلى المستخدمين المحتاجين لمنصة مشتركة لعرض وتخزين وإدارة الصور الخاصة بعملهم. إن اخترت الاستضافة الذاتية للتطبيق فاسترشد بالتوثيق الخاص به وبمنصة GitHub دون أن تنسى الاهتمام بالنسخ الاحتياطي لبياناتك فأنت المسؤول كليًا عنها في هذا النوع من الاستضافة. أما في حال فضلت الاستضافة السحابية المدفوعة فلديك نوعين استضافة للأفراد مع مساحة تخزينية غير محدودة لكنها تسمح برفع الصور فقط، واستضافة المنظمات وفيها تتناسب سعات التخزين المتاحة مع الأسعار إلاّ أنها بالمقابل تدعم رفع كافة أنواع الملفات، إذ تفترض مساحات التخزين غير المحدودة في استضافة الأفراد أن التطبيق لن يواجه أي مشكلة إلا في حال إساءة الاستخدام. قد يختار الجزء الأكبر من المستخدمين الاستضافة السحابية ضمن هذه المنصة رغم تكلفتها المرتفعة على حساب خدمات أخرى كصور Google ومثيلاتها وذلك نظرًا لما توفره ميزات تحكم وإدارة بالإضافة إلى دعمها رفع وتخزين الملفات بمختلف الصيغ وضمان خصوصية المستخدم. ترجمة -وبتصرف- للمقال Piwigo: An Open-Source Google Photos Alternative That You Can Self-Host لصاحبه Ankush Das. اقرأ أيضًا المرجع الشامل لاختيار الصور المناسبة لأغراض التسويق 10 أدوات وتطبيقات مجانية تسهل عليك تحسين الصور 50 تصميمًا مميزًا لمواقع إلكترونية نموذجية يحتذى بها
-
ازداد عدد الأطفال ممن يتلقون التعليم من المنازل في الآونة الأخيرة وهذا استدعى إيجاد طرق مبتكرة ومميزة للتعليم فقد تغيرت الصورة النمطية عن غرفة الصف وفي هذه المقالة سأشارك معكم كيف تعلمت البرمجة واكتشفتها سويًا مع طلابي. ما هي الشيفرة الأجهزة الإلكترونية على اختلاف أنواعها من هواتف ذكية وحواسيب وألعاب الفيديو وحتى الغسالات جميعها تستخدم الشيفرة code لتنفيذ مهامها، ومهما تطور علم الذكاء الاصطناعي وتعلم الآلة فالشيفرة تحتاج دائمًا لعقول بشرية تكتبها وهؤلاء هم المبرمجون. إذًا فالشيفرة يكتبها المبرمجون وتتكون من مجموعة خوارزميات هي بدورها لوائح من التعليمات المركبة والمنظمة بدقة فائقة لتبدو أشبه بالوصفة، وتحلل هذه الشيفرة وتترجم إلى لغة الآلة لينفذها الجهاز، وبالتالي المهارة الأولى الواجب تعلمها هي كتابة التعليمات وفق القواعد والترتيب الصحيح لتتمكن الحواسيب من فك تشفيرها ومعالجتها. ما هو سكراتش Scratch؟ سكراتش Scratch منصة خاصة بتعليم البرمجة للأطفال من عمر 8 سنوات فما فوق وذلك عبر لغة برمجة مرئية تعتمد تحريك اللبنات (الكتل) من خلال السحب والإفلات لتشكل مع بعضها رسومًا متحركة أو ألغاز أو ألعاب فيديو. يسهل سكراتش البرمجة على الصغار لكونه يعتمد على لبنات مرئية مبرمجة وجاهزة تتطلب التركيب فقط هو أشبه بتركيب المكعبات فهو لا يتطلب الكتابة الإملائية الصحيحة للتعليمات بخلاف البرمجة التقليدية. سكراتش أكثر من أداة تعليمية له بعد اجتماعي فهو يشجع المستخدمين على مشاركة مشاريعهم وإعادة دمجها معًا. كما أنه يوفر إمكانية العمل دون اتصال للحالات التي تملك مصادر إنترنت محدودة كبعض المنازل والغرف الصفية. تعلم مصطلحات البرنامج هذه الفقرة مهمة حتى لمن استخدم سكراتش قبلًا وذلك لتعلم مصطلحات البرنامج ومنطقه. يمكنك إنشاء لعبة فيديو باستخدام سكراتش عبر تحريك لبنات المقاطع البرمجية code blocks وتركيبها معًا لتكون سكريبت بعملية أشبه ما تكون بتركيب المكعبات، والسكريبت -نسميه أيضًا بالخوارزمية- هو لائحة من التعليمات المرتبة على شكل وصفة، أما الكائن sprite الظاهر في الصورة فهو شخصية مصغرة أو كيان ما ضمن اللعبة والمنصة هي خلفية اللعبة ويتم التحكم بكليهما عبر السكريبت. كل سكريبت ضمن البرنامج يبدأ بلبنة برمجية مميزة تسمى قبعة الحدث event hat وتتميز رسوميًا عن غيرها من اللبنات بأنها مدورة من الأعلى ومهمتها انتظار حدث معين ليبدأ ومن ثم إعطاء أمر التشغيل لكافة اللبنات التي تليها في السكريبت. لنبدأ البرمجة جرب التحديات التالية لتبدأ رحلتك البرمجية عبر سكراتش 1. التحدي الأول: تحريك الكائن لليمين المطلوب إنشاء خوارزمية تحرك الكائن لليمين عند الضغط على السهم الأيمن في لوحة المفاتيح. في البرمجة الحلول دائمًا متعددة وفي الصورة أدناه نعرض احتمالين لحل هذا التحدي: لنناقش الحل: ما الذي سيحدث لو غيرنا الرقم؟ ما الذي سيحدث إن وصل الكائن أثناء حركته إلى حافة الشاشة؟ ما هي اللبنة الواجب استخدامها حتى لا يختفي الكائن عند وصوله لحافة الشاشة؟ انظر التغيرات أدناه: ستلاحظ بعد التنفيذ أن الكائن يصل لحافة الشاشة ويبدأ بالاهتزاز إلى الأعلى والأسفل وهذا السلوك في السكريبت يدعى بالخطأ bug وتصحيحه يدعى بتنقيح الخطأ debugging، وفي حالتنا يتم التصحيح عبر اختيار الكائن والضغط على زر الاتجاهات واختر الأيقونة يمين/يسار أو الأيقونة الخاصة بمنع الدوران كما في الصورة أدناه: نفذ مجددًا هل تلاحظ أي أخطاء؟ إن توقف الاهتزاز عند حافة الشاشة فانتقل للتحدي التالي. إن كان الكائن في برنامجك يتحرك يسارًا عندما يصل لحافة الشاشة حتى وأنت تضغط السهم الأيمن فأنت إذًا جاهز للتحدي التالي. 2. التحدي الثاني: تحريك الكائن لليسار المطلوب إنشاء خوارزمية تحرك الكائن لليسار عند الضغط على السهم الأيسر في لوحة المفاتيح. بنفس طريقة التحدي الأول ننجز هذا التحدي لاحظ الحل في الصورة أدناه. بعد الانتهاء سمي مشروعك واحفظه عبر الخيار حفظ أو حفظ الآن ولا حاجة لاستخدام خيار حفظ باسم أو حفظ نسخة لأن ذلك يعني مضاعفة المشروع. لا تتوقف عند هذا الحد البرمجة تعني التجريب والمحاولة مجددًا حتى الوصول للنتيجة المطلوبة وهذا بالضبط ما يجعلها مادتي المفضلة في التدريس، ولا تتوقف عند هذه الأمثلة افترض مشكلة ما وحاول الوصول إلى حل لها اطلع على لبنات المقاطع البرمجية الموجودة وابني السكريبت اللازم. الأهم دائمًا أن لا تيأس وتذكر أنه لا يوجد إجابة خاطئة هنا فالموضوع كله يتعلق بالإبداع وإيجاد الحلول والاستمتاع. ترجمة -وبتصرف- للمقال Code your first algorithm in Scratch لصاحبه Jess Weichler. اقرأ أيضًا تعلم البرمجة مدخل إلى الخوارزميات البرمجة باستخدام سكراتش Scratch
-
محرر النصوص Vim (ويلفظ فيم) هو المحرر الأكثر استخدامًا اليوم فهو متوفر في معظم الأجهزة على حساب المحررات الأخرى مثل نانو Nano وإيماكس Emacs و Vscodium ولا تكاد تخلو منه أي جلسة اتصال SSH. وسنعرض في هذا المقال المخصص للأشخاص المتمكنين من أساسيات العمل على المحرر بعضًا من مزاياه المتقدمة والمفيدة التي تخفى حتى على من استخدامه لسنوات، ويفترض هذا المقال معرفة مسبقة بمحرر vim أما إن كنت حديث عهد بهذا المحرر ولا تعرف عنه الكثير، فننصحك بالرجوع أولًا إلى مقال تعرف على أساسيات Vim. إضافة الإشارات المرجعية يوفر لك المحرر فيم Vim إمكانية إضافة الإشارات المرجعية bookmarks على مقاطع النص لتمييزها وتسهيل التنقل بينها وهذه العملية مفيدة جدًا في حالة النصوص الكبيرة. إليك طريقة الاستخدام؛ لوضع إشارة مرجعية اكتب m (الحرف الأول من mark) ويليه اسم الإشارة المرجعية. لنفترض مثلًا أن لديك مقطعًا في النص يتحدث عن الأسماء names وتريد تمييزه بوضع إشارة مرجعية اسمها n فعليك إذن أن تكتب mn، وإن أردت العودة للمقطع الخاص بالأسماء من أي موضع آخر في النص يكفيك كتابة `n لتنتقل إلى موقع الإشارة المرجعية n تمامًا، أو كتابة 'n لتنتقل إلى بداية السطر الذي يحتوي هذه الإشارة المرجعية. في حال رغبت بحذف الإشارة المرجعية n استخدم الأمر: :delmarks n ولاستعراض كافة الإشارات المرجعية استخدم الأمر التالي: :marks مع ضرورة الإشارة إلى أن استخدام الأحرف الإنكليزية الصغيرة في تسمية الإشارات المرجعية يعني أن هذه الإشارات محلية تستخدم ضمن المستند الحالي فقط، أما تسميتها بالأحرف الكبيرة تعني أن الإشارة عامة ويمكن الاستدلال بها ضمن عدة مستندات نصية أي أن استخدامه ليس محصورًا بالمستند نفسه. تعريف الاختصارات الخاصة بالمستخدم يتيح لك هذا المحرر إمكانية إنشاء اختصارات للكلمات والعبارات الطويلة والتي قد تكون كتابتها مزعجة وبالأخص إن تكررت أكثر من مرة ضمن النص. عرف الاختصارات باستخدام الأمر ab: يليه الاختصار ومن ثم العبارة المراد اختصارها، فمثلًا إن أردت إنشاء اختصار للعبارة Acme Painted Fake Roadways, Incs إلى شيء مثل apfr فيمكنك فعل ذلك وفق مايلي: :ab apfr Acme Painted Fake Roadways, Inc والآن في كل مرة تكتب فيها الاختصار apfr متبوعًا بفراغ يبدله المحرر تلقائيًا إلى العبارة كاملة دون أن تحتاج لكتابتها. أما إن رغبت بإزالة أي اختصار، استخدم الأمر uab: متبوعًا بالاختصار مثال: :uab apfr وتجدر الإشارة إلى أن الاختصارات التي تعرفها ضمن جلسة العمل تعتبر مؤقتة وتزول بمجرد إغلاق الجلسة. الإكمال التلقائي للكلمات أثناء الكتابة في الواقع يجهل العديد من المستخدمين قدرة هذا المحرر على الإكمال التلقائي للكلمات المكتوبة سابقًا ضمن النص. لنفترض أنك تكتب نصًا يتضمن كلمة مثل Antarctica عدة مرات فبعد كتابتها لأول مرة ضمن النص يمكنك لاحقًا كتابة الحروف الأولى منها فقط مثلًا ant والضغط على Ctrl+P وسيظهر لك المحرر كافة الكلمات التي كتبتها سابقًا في النص وتبدأ بحروف ant وعندها استخدم زر tab للتنقل بين الاحتمالات واختر الكلمة التي تريدها وسيقوم المحرر استكمالها تلقائيًا مع العلم أن زيادة عدد الحروف المكتوبة سيضيق لائحة الكلمات المحتملة أكثر. تحديد نطاق تتيح هذه الميزة للمستخدم تحديد نطاق معين من الأسطر وتطبيق جملة من الإجراءات عليه بشكل موحد وفق مايلي. لتحديد نطاق معين تبدأ كتابة الأمر برقم أول سطر في النطاق المطلوب تحديده متبوعًا بفاصلة يليها رقم أخر سطر في النطاق وهذا السطر يكون مضمنًا (فتطبق عليه الإجراءات)، كما يمكنك استخدام رمز النقطة للإشارة إلى السطر الحالي، ورمز $ للإشارة إلى السطر الأخير، أما % تعني كامل المستند. نورد بعض الأمثلة (مع التأكيد على أن كتابة هذه التعليمات تتم والمحرر في نمط الأوامر): حذف الأسطر من 2 إلى 10: :2,10d حذف الأسطر من 25 حتى نهاية المستند: :25,$d حذف كافة الأسطر: :%d نسخ الأسطر من 5 إلى 10 ولصقها بعد السطر 15: :5,10t 15 قص الأسطر من 5 إلى 10 ولصقها بعد السطر 15: :5,10m 15 رفع الانتاجية عبر Vim مع هذا المحرر لدينا دائمًا أشياء جديدة ومفيدة نتعلمها فهو غني للغاية ومليء بالميزات الجديرة بالاستكشاف والتي تخفى عن مستخدمين خبروه لسنوات. ترجمة -وبتصرف- للمقال 4 Vim features to use to improve productivity لصاحبه Hunter Coleman. اقرأ أيضًا تعرف على أساسيات Vim - الجزء الأول تعرف على أساسيات Vim - الجزء الثاني إدارة إضافات vim