إضافة شخصية العدو للعبة عبر مكتبة 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 and self.counter 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 and self.counter

تعليقات