اذهب إلى المحتوى

البحث في الموقع

المحتوى عن 'pygame'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 11 نتائج

  1. تجنب الأعداء والركض بعيدًا عنهم شيء والقتال ضدهم شيء آخر تمامًا، فهو يزيد التفاعل بين المستخدم واللعبة، وهو موضوع المقال الختامي من سلسلة بناء لعبة من الصفر باستخدام بايثون، إليك مقالات السلسلة بالترتيب قبل أن نبدأ: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. لديك تفصيلين تهتم بهما في هذه الحالة، الأول هو إنتاج العنصر الذي سيُرمى والثاني هو المعيار أو الشرط الذي سيبقيه فعالًا فالسهام والكرات النارية لابد ستختفي في النهاية قد تختفي عند حافة الشاشة مثلًا أو تظل صالحة لفترة زمنية محدودة ينتهي مفعولها بعدها وهكذا. بالإضافة إلى تحديد قدرة اللاعب على القذف فهو يستطيع أن يقذف عنصرًا واحدًا فقط في كل مرة، يرفع هذا الخيار من مستوى التحدي بإعطاء اللاعب فرصةً وحيدة في كل مرة لإصابة العدو، ويساهم من جهةٍ ثانية بتبسيط تعليماتك البرمجية. يمكنك بعد نهاية القراءة وتعلم الموجود هنا أن تسعى لتطوير البرنامج وتعطي لاعبك القدرة على قذف أكثر من عنصر في وقتٍ واحد. إنشاء صنف الكائن المقذوف ستجد الدالة __init__ ضمن تعليمات الصنف المبينة أدناه لإنتاج كائن بايثون للعنصر المقذوف، وهي الدالة نفسها المستخدمة سابقًا لإنتاج البطل و الأعداء. class Throwable(pygame.sprite.Sprite): """ إنتاج الكائن المقذوف """ def __init__(self, x, y, img, throw): 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.firing = throw الاختلاف الجوهري بين الدالة __init__ الموجودة في هذا الصنف والدالة نفسها الموجودة في صنف اللاعب Player وصنف العدو Enemy هو المتغير self.firing الذي يتتبع حالة الكائن المقذوف فيما إذا كان لا يزال فعالًا وظاهرًا على الشاشة أم انتهت صلاحيته، وتتغير قيمته تبعًا لذلك فهو يحمل القيمة 1 عندما ينشئ الكائن. قياس زمن فعالية الكائن المقذوف تمامًا مثل البطل والعدو يحتاج هذا الكائن إلى الدالة update لتحديث موقع ظهوره على الشاشة حتى يتحرك في الهواء منطلقًا باتجاه العدو بعد أن رماه البطل. أسهل طريقة لقياس زمن فعالية الكائن المقذوف هي وصوله إلى حافة الشاشة وخروجه عن مدى الرؤية، لكن عليك أن تحدد أي حافة ستراقب تبعًا لحركة هذا الكائن إن كانت أفقية أو عمودية. إذا كان البطل يقذفه أفقيًا مثل السهم أو الطاقة السحرية أو ماشابه فعليك مراقبة حافة الشاشة على المحور الأفقي وتحددها القيمة worldx. أما إذا كان يقذف الأشياء عموديًا أو بكلا الاتجاهين يتعين عليك مراقبة الحافة العمودية للشاشة باستخدام القيمة worldy. يفترض هذا المثال أن الجسم المقذوف يتحرك قليلًا للأمام ثم يسقط على الأرضية دون ارتداد فيتابع سقوطه إلى أسفل الشاشة، اطلع على التعليمات التالية وغيّر القيم الواردة فيها قليلًا لترى ما يناسب لعبتك أكثر: def update(self,worldy): ''' فيزياء المقذوفات ''' if self.rect.y < worldy: #المحور العمودي self.rect.x += 15 #سرعة حركته إلى الأمام self.rect.y += 5 #سرعة سقوطه إلى أسفل else: self.kill() #انتهاء فاعلية العنصر المقذوف self.firing = 0 #تحرير المتغير أو تصفيره ارفع قيم self.rect لتسريع حركة الكائن المقذوف. عندما يصل المقذوف إلى حافة الشاشة سيختفي أو بالأحرى يُدَمّر، وتتحرر الذاكرة المؤقتة التي أُشغلت بوجوده وتُضبط قيمة المتغير self.firing إلى الصفر ويتحرر تمهيدًا للرمية التالية. ضبط إعدادات الكائن المقذوف أنشئ في مقطع الإعدادات ضمن برنامج مجموعةً خاصة للكائنات التي يمكن أن يقذفها البطل، بصورةٍ مشابهة لمجموعات البطل والأعداء، وجهز كائنًا قابلًا للقذف غير فعال حتى يبدأ به البطل اللعبة، وإلّا فإن قذفته الأولى ستفشل. يفترض هذا المثال أن سلاح البطل هو الكرة النارية وتحدد مواصفاتها باستعمال المتغير fire ويمكنك في مستوياتٍ لاحقة تطوير السلاح وتغيير صورته ومواصفاته بالاعتماد على الصنف Throwable نفسه. اكتب التعليمات الآتية علمًا أن تعليمات أول سطرين لمعرفة السياق ولا حاجة لكتابتها مجددًا: player_list = pygame.sprite.Group() #للسياق player_list.add(player) #للسياق fire = Throwable(player.rect.x,player.rect.y,'fire.png',0) firepower = pygame.sprite.Group() لاحظ أن مكان إنتاج الكائن المقذوف مطابق لمكان إنتاج كائن البطل وهذا يعطي انطباعًا بأنه ينطلق من جعبة البطل، بعد إنتاج كرة النار الأولى يأخذ المتغير self.firing القيمة صفر ليتيح إمكانية القذف. إضافة القذف إلى الحلقة الرئيسية المبدأ نفسه على طول السلسلة اذكر ما تريد تنفيذه في الحلقة الرئيسية، أضف في البداية تعليمات التحكم التي ستُطلق الكرة النارية عندما يستخدم اللاعب الزر المحدد على لوحة المفاتيح. عند تحريك البطل استخدمنا الضغط على الزر لتبدأ حركة البطل وتحرير الزر أو رفع الضغط عنه ليتوقف البطل عن الحركة، لا يوجد توقف في عملية القذف فلن تحتاج أكثر من إشارة اختر واحدة منهما الضغط أو التحرير. اكتب التعليمات التالية وأول سطرين لتبيان السياق: if event.key == pygame.K_UP or event.key == ord('w'): player.jump(platform_list) if event.key == pygame.K_SPACE: if not fire.firing: fire = Throwable(player.rect.x,player.rect.y,'fire.png',1) firepower.add(fire) تُسند القيمة 1 للمتغير self.firing على عكس الكرة النارية الأولى غير الفعالة التي كُتبت سابقًا في مقطع الإعدادات. اكتب التعليمات الخاصة بتحديث الكائن المقذوف ورسمه على الشاشة، واحرص على كتابتها في الموقع الصحيح تمامًا كما هو مبين أدناه: enemy.move() # للسياق if fire.firing: fire.update(worldy) firepower.draw(world) player_list.draw(screen) # للسياق enemy_list.draw(screen) # للسياق لاحظ أن تعليمات التحديث تعمل فقط عندما يحمل المتغير self.firing القيمة 1، أما إذا كانت قيمته 0 فإن الشرط لن يتحقق ويتجاوز البرنامج هذه التعليمات دون أن ينفذها، أما لو كتبت هذه التعليمات دون شروط وحاولت تطبيقها بغض النظر عن قيمة المتغير سيفشل عمل اللعبة لأنها لن تجد كرةً نارية لترسمها أو تحدّث ظهورها على الشاشة. شغل اللعبة الآن وحاول استخدام السلاح. اكتشاف التصادم فور تجريب اللعبة ستلاحظ أن البطل يقذف سلاحه باتجاه العدو وقد يصيبه لكن دون أي تأثير، فالبرنامج لا يملك حتى الآن أي آلية تكتشف تصادم الجسم المقذوف بالعدو. الآلية المطلوبة مشابهة جدًا للمذكورة في صنف اللاعب Player، لذا أضف الدالة update التالية في صنف العدو Enemy: def update(self,firepower, enemy_list): """ اكتشاف التصادم مع الكرة النارية """ fire_hit_list = pygame.sprite.spritecollide(self,firepower,False) for fire in fire_hit_list: enemy_list.remove(self) التعليمات بسيطة فهي تتحقق من حدوث التصادم بين كائن العدو وكل كرة نارية firepower تنتمي لمجموعة كائنات الكرات النارية التي يقذفها البطل، وفي حال تبين حدوث التصادم يُزال العدو من مجموعة الأعداء ويختفي عن الشاشة. أضف السطر الأخير من التعليمات الآتية إلى كتلة تعليمات firing في الحلقة الرئيسية: if fire.firing: # للسياق fire.update(worldy) # للسياق firepower.draw(screen) # للسياق enemy_list.update(firepower,enemy_list) # تحديث العدو يتبقى لدينا أمر أخير للتعامل معه وهو اتجاه القذف. تغيير اتجاه القذف يقذف بطل اللعبة حاليًا كراته باتجاه اليمين فقط، فعندما ما عرفنا دالة التحديث في صنف القذف جعلناها تزيد عددًا من البكسلات لموقع الكرة وهي تتحرك على المحور X والزيادة على هذا المحور تعني التحرك يمينًا، فماذا لو استدار البطل وأراد القذف نحو اليسار؟ يتمثل الحل المتبع هنا في تعريف متغير جديد يحدد توجه البطل ومن ثم تحديد اتجاه القذف الأنسب وفقًا لقيمته. عرّف المتغير facing_right في صنف اللاعب وأعطه القيمة الابتدائية True إذ إن صورة اللاعب المستخدمة في هذا المثال تتجه افتراضيًا نحو اليمين: self.score = 0 self.facing_right = True # أضف هذا السطر self.is_jumping = True وبذلك تكون القيمة True مؤشرًا على اتجاه البطل إلى اليمين والقيمة False تشير لاتجاهه يسارًا، وينبغي التبديل بينهما في كل مرة يغير البطل اتجاه حركته، وأنسب مكان ذلك هو التعليمات الخاصة بالتحكم بحركة البطل عبر أزرار لوحة المفاتيح وفق ما يلي: if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps, 0) player.facing_right = False # أضف هذا السطر if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps, 0) player.facing_right = True # أضف هذا السطر عدّل أخيرًا الدالة update في صنف القذف Throwable لتجمع أو تطرح عددًا من وحدات البكسل إلى موقع الكرة النارية بناءً على اتجاه حركة البطل: if self.rect.y < worldy: if player.facing_right: self.rect.x += 15 else: self.rect.x -= 15 self.rect.y += 5 جرب اللعبة الآن وتفقد النتائج. حاول تطوير اللعبة وامنح البطل بعض النقاط مع كل إصابة موفقة للعدو. برنامج اللعبة كاملًا #!/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 pygame.freetype import sys import os ''' Variables ''' worldx = 960 worldy = 720 fps = 40 ani = 4 world = pygame.display.set_mode([worldx, worldy]) forwardx = 600 backwardx = 120 BLUE = (80, 80, 155) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ALPHA = (0, 255, 0) tx = 64 ty = 64 font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts", "amazdoom.ttf") font_size = tx pygame.freetype.init() myfont = pygame.freetype.Font(font_path, font_size) ''' Objects ''' def stats(score, health): myfont.render_to(world, (4, 4), "Score:"+str(score), BLUE, None, size=64) myfont.render_to(world, (4, 72), "Health:"+str(health), BLUE, None, size=64) class Throwable(pygame.sprite.Sprite): """ Spawn a throwable object """ def __init__(self, x, y, img, throw): 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.firing = throw def update(self, worldy): ''' throw physics ''' if self.rect.y < worldy: if player.facing_right: self.rect.x += 15 else: self.rect.x -= 15 self.rect.y += 5 else: self.kill() self.firing = 0 # 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.damage = 0 self.score = 0 self.facing_right = True self.is_jumping = True self.is_falling = True self.images = [] for i in range(1, 5): img = pygame.image.load(os.path.join('images', 'walk' + 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 gravity(self): if self.is_jumping: self.movey += 3.2 def control(self, x, y): """ control player movement """ self.movex += x def jump(self): if self.is_jumping is False: self.is_falling = False self.is_jumping = True def update(self): """ Update sprite position """ # moving left if self.movex < 0: self.is_jumping = True 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.is_jumping = True self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) if self.damage == 0: for enemy in enemy_hit_list: if not self.rect.contains(enemy): self.damage = self.rect.colliderect(enemy) if self.damage == 1: idx = self.rect.collidelist(enemy_hit_list) if idx == -1: self.damage = 0 # set damage back to 0 self.health -= 1 # subtract 1 hp ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.bottom = g.rect.top self.is_jumping = False # stop jumping # fall off the world if self.rect.y > worldy: self.health -=1 print(self.health) self.rect.x = tx self.rect.y = ty plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.is_jumping = False # stop jumping self.movey = 0 if self.rect.bottom <= p.rect.bottom: self.rect.bottom = p.rect.top else: self.movey += 3.2 if self.is_jumping and self.is_falling is False: self.is_falling = True self.movey -= 33 # how high to jump loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) self.rect.x += self.movex self.rect.y += self.movey 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 def update(self, firepower, enemy_list): """ detect firepower collision """ fire_hit_list = pygame.sprite.spritecollide(self, firepower, False) for fire in fire_hit_list: enemy_list.remove(self) 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((550, 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 def loot(lvl): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_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 fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 0) firepower = pygame.sprite.Group() eloc = [] eloc = [300, worldy-ty-80] enemy_list = Level.bad(1, eloc) gloc = [] 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) enemy_list = Level.bad( 1, eloc ) loot_list = Level.loot(1) ''' 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'): player.jump() if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == ord('a'): player.control(steps, 0) player.facing_right = False if event.key == pygame.K_RIGHT or event.key == ord('d'): player.control(-steps, 0) player.facing_right = True if event.key == pygame.K_SPACE: if not fire.firing: fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 1) firepower.add(fire) # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll for l in loot_list: l.rect.x -= scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll for l in loot_list: l.rect.x += scroll world.blit(backdrop, backdropbox) player.update() player.gravity() player_list.draw(world) if fire.firing: fire.update(worldy) firepower.draw(world) enemy_list.draw(world) enemy_list.update(firepower, enemy_list) loot_list.draw(world) ground_list.draw(world) plat_list.draw(world) for e in enemy_list: e.move() stats(player.score, player.health) pygame.display.flip() clock.tick(fps) ترجمة -وبتصرف- للمقال Add throwing mechanics to your Python game لصاحبيه Seth Kenlon و Jess Weichler. اقرأ أيضًا المقال السابق: تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
  2. عرض النتائج على الشاشة جزء مهم وحيوي من أي لعبة وهو موضوع المقال ما قبل الأخير من سلسلة بناء لعبة من الصفر باستخدام بايثون، سنعرض فيه نقاط اللاعب وصحته بناءً على ما اغتنمه من جوائز وما أصابه من ضرر جراء التصادم مع الأعداء. يمكنك مطالعة المقالات ضمن السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. اكتسبت على مدار السلسلة كل الأساسيات اللازمة لبناء لعبة فيديو بلغة بايثون وستضيف لها اليوم مهارة جديدة جوهرية لكل مبرمج أيًّا كان اختصاصه، فإضافةً لعرض النتائج على الشاشة بتنسيق وخط مناسبين ستتعلم قراءة توثيقات المكتبة أو اللغة البرمجية ومعرفة إمكاناتها وكيفية استخدام ميزاتها التي لم تتعامل معها قبلًا وهو فعليًا الهدف الحقيقي المقال. عرض النتائج في Pygame يتضمن برنامج اللعبة كل المتغيرات اللازمة لتتبع نقاط اللاعب التي كسبها من الجوائز وأيضًا صحته أو أدواره المتأثرة بالتصادم مع الأعداء، إلّا أن هذه المتغيرات تعمل في الخلفية ولا تُظهر أي نتائج على شاشة اللعبة لذا ينصب اهتمامنا هنا على طريقة العرض واختيار الخط المناسب لها. قراءة التوثيقات معظم وحدات بايثون ومن بينها Pygame لها توثيقات رسمية تشرحها وحتى الجزء البسيط من الوحدات الذي لا تشمله التوثيقات تشرحه Help دالة المساعدة من بايثون، والتوثيقات عمومًا هي توصيف لكافة دوال الوحدة وأصنافها وتكتب بلغة فنية دقيقة تحدد مثلًا أنواع المدخلات المطلوبة وما شابه، وقد تكون مربكة للبعض غير المعتاد عليها فهي لا تشبه السرد البسيط المتبع في المقالات التعليمية. بخصوص Pygame الوحدة التي نستخدمها تستطيع الوصول لتوثيقاتها الغنية عبر رابط في صفحتها الرسمية، ولكن قبل البحث في أي توثيق فكر أولًا بما تريد الحصول عليه، فهنا مثلًا نسعى لإظهار نقاط اللاعب وصحته على شاشة اللعبة، ومن بعدها حاول تخمين العناصر البرمجية المطلوبة لتحقيقه مثل المتغيرات والدوال وغيرها، وإن لم تتمكن من وصف احتياجك بهذه الدقة، صِفهُ بالعموم وببساطة، فهنا مثلًا تحتاج لنصوص ترسمها Pygame على الشاشة لتظهر النتائج، وربما شعرت بقدرتك على إظهارها بطريقة مشابهة لعرض المنصات والكائنات على شاشة اللعبة. تقنيًا لا يوجد ما يمنعك من فعل ذلك، واستخدام صور الأرقام بكل بساطة وعرضها على الشاشة لتمثل نتائج اللعبة عبر Pygame، قد لا تكون هذه الطريقة المثلى لتصل لهدفك لكنها صحيحة في نهاية المطاف وقد أوصلك إليها التفكير العام بأساليب عرض النتائج، بكل الأحوال لو تصفحت توثيقات Pygame ستلفت انتباهك على الفور وحدة الخط وتدعى Font وهي مختصة بتسهيل كتابة النصوص على الشاشة وتنسيق خطوطها. فهم رموز التوثيق الفني يبدأ توثيق وحدة الخط بالدالة ‎pygame.font.init‎(‎)‎ ويصفها التوثيق بأنها دالة التهيئة للوحدة، تُستدعى تلقائيًا بواسطة ()pygame.init المستدعاة أساسًا في برنامج اللعبة، واستخدامها شبيه بما تعلمته خلال السلسلة، إذ ستتمكن من استخدام دوالها وكتابة النتيجة على الشاشة بقراءة بسيطة للتوثيق ومراجعة لما تعلمته. ولو استمريت باستعراض الوحدات في توثيقات Pygame ستصادف الوحدة Pygame.freetype ومن الاسم يتضح ارتباطها بحالتنا وهي أفضل من وحدة الخط إذ يصفها التوثيق بأنها: وفي صفحة التوثيق نفسها المخصصة للوحدة ستجد بعض الأمثلة على طريقة استخدامها ومنها: import pygame import pygame.freetype السطر موجود لدينا في برنامج اللعبة وما نحتاجه هو السطر الثاني، أضفه إذًا لمجموعة تعليمات import لتصبح على الشكل التالي: import pygame import sys import os import pygame.freetype استخدام الخطوط في Pygame استنادًا للتوثيق تعرض الوحدتان النصوص على الشاشة باستعمال الخطوط الإفتراضية في Pygame أو أي خطوط أخرى تزودها بها، وستجد ضمن توثيق الوحدة pygame.freetype التوصيف الفني للدالة التالية: pygame.freetype.Font # إنشاء خط جديد انطلاقًا من أحد ملفات الخطوط المدعومة Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font pygame.freetype.Font.name # اسم ملف الخط pygame.freetype.Font.path # مسار ملف الخط pygame.freetype.Font.size # حجم الخط الافتراضي المستخدم في العرض وهي مسؤولة عن إنشاء كائن Object الخط في Pygame ومواصفاته ولو دققت فيها ستجدها مشابهة لتعليمات إنشاء كائنات اللعبة مثل الأبطال والأعداء فبدلًا من صورة الشخصية ستحدد هنا ملف الخط وبمجرد توفره يمكنك إنشاء كائن الخط باستخدام الدالة السابقة pygame.freetype.Font ومن ثم استدعائه للعرض على الشاشة. إدارة الخط بصفته أحد أصول اللعبة لتضمن عمل لعبتك بصورةٍ جيدة عليك تضمن الخط الذي تعتمده ضمن ملفات اللعبة فمجموعات الخطوط تختلف من حاسبٍ لآخر، لذا أنشئ مجلدًا خاصًا بالخطوط في المجلد الأب للعبتك تمامًا مثل مجلد الصور وضع فيه ملفات الخطوط التي تستخدمها. واحرص على عدم انتهاك حقوق الملكية الفكرية، فاستعمل خطوطًا مفتوحة المصدر أو تتمتع برخصة المشاع الإبداعي، فحتى استعمال أحد ملفات الخطوط المتوفرة على حاسبك الشخصي وجعله خطًا للعبتك لا يعد تصرفًا قانونيًا. وفيما يلي قائمة ببعض المواقع التي توفر خطوطًا مجانية واستخدامها قانوني يمكنك الاستفادة منها: Font Library. Font Squirrel. League of Moveable Type. حمّل الخط الذي أعجبك من هذه المواقع على شكل ملف مضغوط zip أو tar وفك ضغطه لاستخراج ملف الخط، ويكون عادةً بصيغة ttf. أو otf. ومن ثم ضعه في مجلد الخطوط الخاصة باللعبة. لا يوجد أي حاجة لتثبيت الخط في نظام التشغيل لديك بل يكفي وجوده في مجلد اللعبة لتستخدمه Pygame، وفي حال تضمن اسم الملف رموزًا أو فراغات أزلها وعدّل الاسم، ويفضل لو تختر له اسمًا بسيطًا ومختصرًا لتكتبه بسهولة في التعليمات البرمجية. تعليمات الخط في Pygame بناءً على توثيق الدالة pygame.freetype.Font يمكنك إنشاء كائن الخط عبر تحديد مسار ملف الخط المرغوب على الأقل المدخلات الأخرى فهي اختيارية وليست ملزمة. Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font أنشئ متغيرًا جديدًا اسمه myfont وأسند له خرج الدالة Font المذكورة وفق التعليمات الآتية وملف الخط الذي اعتمدنا عليه هنا هو amazdoom.ttf على سبيل المثال. font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf") font_size = tx pygame.freetype.init() myfont = pygame.freetype.Font(font_path, font_size) عرض النص على الشاشة في Pygame بعد إنشاء الخط عرف الدالة stats التالية في مقطع الكائنات ضمن برنامج اللعبة، مهمتها رسم النصوص المطلوبة على الشاشة وهي تتطلب ذكر النصوص التي تريد عرضها وتحديد لون معين لها من باستخدام النظام اللوني RGB بالطريقة نفسها التي اتبعناها عند إظهار خلفية عالم اللعبة ومنصاتها، مع ضرورة التنويه إلى أن هذه الدالة عامة ومستقلة ولا تتبع لصنف معين من أصناف برنامج اللعبة. def stats(score,health): myfont.render_to(world, (4, 4), "Score:"+str(score), BLACK, None, size=64) myfont.render_to(world, (4, 72), "Health:"+str(health), BLACK, None, size=64) والآن استدعيها في حلقة التكرار الرئيسية: stats(player.score,player.health) # draw text إذا شغلت اللعبة الآن ستحصل على خطأ فهي ما زالت بحاجة لبعض التعديلات. تفسير الأخطاء تفسير الأخطاء ومحاولة فهمها عملية مهمة جدًا للمبرمجين وتفيدهم في اصلاح مشاكل البرامج، والأخطاء في لغة بايثون دلالية وسهلة الفهم نوعًا ما لكنها مع ذلك تحتاج لتفسير لتُحلل ما يحدث. عند تشغيل اللعبة في هذه المرحلة ستحصل على هذا الخطأ: Traceback (most recent call last): File "/home/tux/PycharmProjects/game_001/main.py", line 41, in <module> font_size = tx NameError: name 'tx' is not defined يدّل الخطأ صراحةً على عدم تعريف المتغير tx رغم أن المتغير معرف وقد استخدمناه مررًا ضمن البرنامج، ويشير أيضًا إلى السطر رقم 41 لا تعني هذه الإشارة بالضرورة أن الخطأ موجود في السطر 41 بل تعني أن التنفيذ توقف عنده. لو رجعت إلى برنامج اللعبة وتفقدت تعريف المتغير tx ستجده معرفًا مع المغير ty في مقطع الإعدادات ضمن البرنامج وهو يأتي بعد السطر 41 في تسلسل الأسطر. اتضحت الصورة الآن علينا نقل تعريف المتغير إلى موضعٍ آخر ضمن البرنامج ترتيبه يسبق السطر 41 وبذلك يمرّ عليه بايثون قبل الوصول لهذا السطر ويُحل الخطأ. جميع البرامج معرضة للأخطاء وستواجه هذه الحالات دائمًا، قد تكون صعبة أو سهلة دوّن المعلومات التي يظهرها الخطأ وراجع البرنامج في ضوئها بعنايةً وستصل للحل حتى أمهر المبرمجين يواجهون هذه الأخطاء وكلما زاد إتقانك للغة بايثون زادت قدرتك على تفسير الأخطاء وحلها. تشغيل اللعبة شغل اللعبة وتفقد نجاح العملية. يزداد رصيد اللاعب من النقاط مع كل جائزة يغتنمها، وتنخفض مؤشرات سلامته أو صحته كلما اصطدم بعدو. لكنك ستكتشف مع تقدم اللعبة مشكلةً بسيطة أخرى، فنقاط صحة اللاعب تنخفض كثيرًا بطريقةٍ غير عادلة مع كل تصادم بالعدو، وسنعالجها في الفقرة القادمة. لا يعطل هذا النوع من الأخطاء عمل البرنامج فهو ليس من الأخطاء "القاتلة" إنما يعد من المشكلات البسيطة التي تعطي نتائج غير منطقية أو تزعج المستخدم. إصلاح عداد صحة البطل المشكلة في عداد صحة اللاعب أن نقاطه تتناقص مع كل نبضة ساعة يكون فيها العدو والبطل في حالة تصادم، وهذا غير عادلة طبعًا بالعدو بطيء الحركة سينقص صحة اللاعب بمقدار 200- نقطة أو ربما أكثر، يمكنك بكل بساطة زيادة رصيد الصحة الابتدائي للاعبك إلى رقم كبير نسبيًا مثل 10000 سيخفف هذا من أثر المشكلة لكنه لا يعد حلًا مثاليًا. فالحل الأفضل يكتشف حادثتين عوضًا عن واحدة، فلن يكتشف التصادم فقط إنما أيضًا انتهاء التصادم. عرّف المتغير الموجود في السطر الأخير ضمن صنف اللاعب ليمثل حالة التصادم بين البطل والعدو: self.frame = 0 self.health = 10 self.damage = 0 والآن استبدل كتلة التعليمات التالية في دالة التحديث update: for enemy in enemy_hit_list: self.health -= 1 #print(self.health) بهذه الكتلة: if self.damage == 0: for enemy in enemy_hit_list: if not self.rect.contains(enemy): self.damage = self.rect.colliderect(enemy) يوجد بعض التشابه بين كتلة االتعليمات المحذوفة والكتلة البديلة فكلاهما يؤديان نفس الغرض إلّا أن الجديدة أعقد فهي تعمل لمرة واحدة فقط عند انتهاء التصادم ولا تعمل باستمرار وتنقص النقاط باستمرار مثل التعليمات القديمة، وتسنخدم دالتين من Pygame نتعرف عليهما للمرة الأولى في هذه السلسلة. الدالة الأولى هي self.rect.contains مهمتها معرفة فيما إذا كان العدو متواجدًا داخل المربع المحيط باللاعب في لحظة التنفيذ. والدالة الثانية self.rect.colliderect تعطي المتغير self.damage القيمة 1 إذا كانت نتيجة الدالة الأولى محققة أي أن العدو داخل المربع المحيط بالبطل بغض النظر عن عدد مرات تحققها. وبدءًا من الآن سيُعدّ التصادم بين العدو والبطل لمدة ثلاث ثواني حادث تصادم واحد فقط من وجهة نظر Pygame ويخصم عنه مرة واحدة فقط. يمكننا دومًا اكتشاف هذه الدوال بقراءة توثيقات Pygame وقد وفرت الكثير من العناء والبرمجة، ننصحك دائمًا بقراءة التوثيقات ولا نعني بذلك قراءة كل توثيقات المكتبة أو اللغة وقراءة كل كلمة فيها لكن اهتم بقراءة توثيقات الوحدات الجديدة التي تستخدمها، اطلع على مزاياها سيوفر ذلك عليك إعادة اختراع العجلة كما يقال ويغنيك عن المحاولات المضنية لاكتشاف الحلول لمشاكل محلولة أساسًا. اكتب أخيرًا التعليمات التالية لتكتشف انتهاء حالة التصادم أو التلامس بين اللاعب والعدو وعندها فقط يطرح البرنامج نقطة واحدة من رصيد صحة اللاعب: if self.damage == 1: idx = self.rect.collidelist(enemy_hit_list) if idx == -1: self.damage = 0 # تصفير قيمة المتغير self.health -= 1 # إنقاص نقطة من صحة اللاعب لا تعمل التعليمات السابقة ما لم يحدث التصادم مع العدو فهي لا تتأثر بركض اللاعب في عالم اللعبة أو جمعه للجوائز والكنوز شرطها الوحيد هو تنشيط المتغير self.damage. وعندما تعمل فإنها تستخدم Self.rect.collidelist لتبيان إن كان تصادم البطل مع عدو ينتمي لمجموعة الأعداء ما زال مستمرًا أم أنه انتهى (ويعطي عنها collidelist القيمة 1-) وبمجرد انتهاء التصادم تعاد قيمة المتغير self.damage وتخصم نقطة من صحة اللاعب. شغل اللعبة الآن. يفتح لك إظهار النتائج خياراتٍ جديدة في لعبتك، يمكنك مثلًا إضافة نوع معين من الجوائز يعوض اللاعب بعضًا من نقاط صحته أو ربما تريد للاعبك أن يعود إلى بداية المستوى عندما يصل رصيد صحته إلى الصفر. كل ما عليك هو تجربة المبادئ التي تعلمتها لتكتشف هذه الأحداث المتنوعة وتطور عالم اللعبة وفق ما تريد. طور مهاراتك طوّر مهارتك في بايثون واستفد من التوثيقات، تعرف على مكاتب ووحداتٍ جديدة وابتكر ألعابًا أو تطبيقات جديدة فهذه اللغة متعددة الأغراض والبرمجة تحتاج للمثابرة، واجعل برامجك مفتوحة المصدر ليساهم معك مطورو بايثون من مختلف أنحاء العالم. سنضيف في المقال التالي بعض الآليات القتالية للعبة، وننهي هذا المقال بتذكيرك ببرنامج اللعبة كامًلا حتى الآن: #!/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 pygame.freetype import sys import os ''' Variables ''' worldx = 960 worldy = 720 fps = 40 ani = 4 world = pygame.display.set_mode([worldx, worldy]) forwardx = 600 backwardx = 120 BLUE = (80, 80, 155) BLACK = (23, 23, 23) WHITE = (254, 254, 254) ALPHA = (0, 255, 0) tx = 64 ty = 64 font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts", "amazdoom.ttf") font_size = tx pygame.freetype.init() myfont = pygame.freetype.Font(font_path, font_size) ''' Objects ''' def stats(score,health): myfont.render_to(world, (4, 4), "Score:"+str(score), BLUE, None, size=64) myfont.render_to(world, (4, 72), "Health:"+str(health), BLUE, None, size=64) # 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.damage = 0 self.score = 0 self.is_jumping = True self.is_falling = True 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 gravity(self): if self.is_jumping: self.movey += 3.2 def control(self, x, y): """ control player movement """ self.movex += x def jump(self): if self.is_jumping is False: self.is_falling = False self.is_jumping = True def update(self): """ Update sprite position """ # moving left if self.movex < 0: self.is_jumping = True 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.is_jumping = True self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) if self.damage == 0: for enemy in enemy_hit_list: if not self.rect.contains(enemy): self.damage = self.rect.colliderect(enemy) if self.damage == 1: idx = self.rect.collidelist(enemy_hit_list) if idx == -1: self.damage = 0 # set damage back to 0 self.health -= 1 # subtract 1 hp ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.bottom = g.rect.top self.is_jumping = False # stop jumping # fall off the world if self.rect.y > worldy: self.health -=1 print(self.health) self.rect.x = tx self.rect.y = ty plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.is_jumping = False # stop jumping self.movey = 0 if self.rect.bottom <= p.rect.bottom: self.rect.bottom = p.rect.top else: self.movey += 3.2 if self.is_jumping and self.is_falling is False: self.is_falling = True self.movey -= 33 # how high to jump loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) self.rect.x += self.movex self.rect.y += self.movey 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((550, 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 def loot(lvl): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_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, worldy-ty-80] enemy_list = Level.bad(1, eloc) gloc = [] 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) enemy_list = Level.bad( 1, eloc ) loot_list = Level.loot(1) ''' 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'): player.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) # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll for l in loot_list: l.rect.x -= scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll for l in loot_list: l.rect.x += scroll world.blit(backdrop, backdropbox) player.update() player.gravity() player_list.draw(world) enemy_list.draw(world) loot_list.draw(world) ground_list.draw(world) plat_list.draw(world) for e in enemy_list: e.move() stats(player.score, player.health) pygame.display.flip() clock.tick(fps) ترجمة -وبتصرف- للمقال Add scorekeeping to your Python game لصاحبيه Seth Kenlon و Jess Weichler. اقرأ أيضًا المقال السابق: إضافة الجوائز إلى اللعبة المطورة بلغة بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
  3. سيُمنح اللاعب بعضًا من الجوائز ليجمعها ويعزز نقاطه في هذا الإصدار من اللعبة المطورة باستخدام بايثون 3 والوحدة Pygame المتخصصة بألعاب الفيديو، والبداية مع روابط مقالات هذه السلسلة. بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. عرضت هذه السلسلة المبادئ الأساسية لتطوير ألعاب الفيديو باستخدام بايثون، ولابدّ أنها منحتك أساسًا جيدًا تنطلق منه لبناء لعبتك الخاصة، لا تتقيد حرفيًا بأسلوب السلسلة، بل استخدم المبادئ نفسها بطرقٍ جديدة، عدّلها لتبرمج عناصرك الخاصة وشروط اللعب التي تريدها. مقال اليوم هو خيرُ مثالٍ على ذلك، إذ ستلاحظ التشابه الكبير في المبادئ بين إضافة المنصات التي تعلمتها سابقًا وإضافة الجوائز، فكلاهما من كائنات اللعبة التي لا تخضع لتحكم خارجي من قبل المستخدم، إنما تُمرر مع عالم اللعبة خلال مستوياتها المتتابعة، وفي الحالتين تحتاج إلى طريقةٍ برمجية لاكتشاف التصادم بين كائن البطل وهذه الكائنات واحتساب تأثيره على نقاط اللاعب وصحته سواءً سلبًا أو إيجابًا. وقبل أن تبدأ جهّز صورًا تمثل الكنوز أو الجوائز التي يجمعها اللاعب، اختر صورًا لعملات معدنية أو جواهر أو ما شابه، يمكنك تحميل حزمة الصور نفسها من موقع kenney.nl التي أخذنا منها صور البلاطات المربعة لبناء منصات اللعبة فهي تتضمن صور جواهر ومفاتيح وكلاهما يفي بالغرض. بناء دالة الجوائز إضافة الجوائز والكنوز إلى لعبة مشابه جدًا لإضافة المنصات كما ذكرنا في المقدمة، في الواقع هو مشابه لدرجة تجعلنا نستدعي صنف المنصة نفسه Platform لإنتاجها. ابدأ بإنشاء دالة جديدة تحمل الاسم loot في صنف المستوى level إذ من المحتمل جدًا أن يتبدل نوع الجوائز بين مستوى وآخر، وتمامًا مثلما فعلنا مع المنصات العائمة والأرضية والأعداء أنشئ في هذه الدالة مجموعة خاصة بكائنات الجوائز سميها loot_list وأضف إليها الجوائز، ستُفيدك هذه المجموعة في عملية اكتشاف التصادمات. def loot(lvl): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(tx*9, ty*5, tx, ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_list استخدم هذا المثال أبعاد قطع البلاط ty و tx لتحديد إحداثيات ظهور الجوائز من خلال مضاعفاتها للسهولة فقط، ستفيدك هذه الطريقة إن خططت للمستوى على أوراق الرسم البياني مع تمثيل كل بلاطة بمربع أو كان المستوى الذي صممته طويلًا، لكن في النهاية يعود الخيار لك إذ يمكنك عوضًا ذلك كتابة إحداثيات كل جائزة صراحةً برقم مقدر بالبكسل فتصبح هذه الأرقام مدمجة مع برنامج اللعبة hard-coded. لا تنسَ أن باستطاعتك إظهار القدر الذي تريده من الجوائز المتنوعة لكن بشرط أن تضيفها جميعًا لمجموعة الجوائز. أما بالنسبة لوسطاء صنف المنصة Platform في هذه الحالة فهم إحداثيات موقع ظهور الجائزة X و Y بالإضافة إلى أبعاد صورة الجائزة، وننصحك بالتخطيط لأماكن توضع الجوائز منذ البداية وتضمينها في مخطط تصميم المستوى واختيار صور مربعة لتمثيلها تعادل أبعادها أبعاد بلاطات الأرضية والمنصات فهذا سيوفر عليك الكثير. استدعِ الآن دالة الجوائز في مقطع الإعدادات ضمن البرنامج وفق التالي: loot_list = Level.loot(1) وأخيرًا اذكرها ضمن الحلقة الرئيسية لتظهر على الشاشة: loot_list.draw(world) شغل اللعبة ولاحظ النتائج. ظهرت صورة الجائزة بعد تشغيل اللعبة، إلّا أنها لم تُحدث أي تأثير على اللاعب عندما اصطدم بها ولم تُمرر مع تحرك اللاعب وتمرير عالم اللعبة، وهذا هو موضوع فقراتنا اللاحقة. تمرير الجوائز مع عالم اللعبة تمامًا مثل المنصات ينبغي أن تُمرر الجوائز مع عالم اللعبة إلى الأمام والخلف تبعًا لحركة البطل. اكتب السطرين الأخيرين من التعليمات التالية لتُمرر الجوائز إلى الأمام: for e in enemy_list: e.rect.x -= scroll for l in loot_list: # تمرير الجوائز l.rect.x -= scroll # تمرير الجوائز واكتب السطرين الأخيرين من التالي لتُمررها للخلف: for e in enemy_list: e.rect.x += scroll for l in loot_list: # تمرير الجوائز l.rect.x += scroll # تمرير الجوائز تفقد اللعبة الآن واختبر التمرير بالاتجاهين، ولاحظ أن الجوائز أصبحت تتصرف وكأنها عنصر أصيل من عناصر عالم اللعبة وليست مجرد صورة. اكتشاف التصادم استخدم ما تعلمته سابقًا عند اكتشاف تصادم البطل مع الأرضية والمنصات العائمة والأعداء لتكتشف لتصادمه مع الجوائز، الطريقة نفسها تمامًا إلّا أن النتائج المترتبة على التصادم ستكون مختلفة فهي لن تتعلق بالجاذبية ولن تُنقص من صحة اللاعب وأدواره بل ستؤدي إلى زيادة نقاطه واختفاء الجائزة التي اصطدم بها من عالم اللعبة، وذلك عبر إزالتها من مجموعة كائنات الجوائز loot_list ما يعني أنها لن تظهر مجددًا في المرة القادمة التي تُعيد فيها الحلقة الرئيسية رسم عالم اللعبة، وهذا منطقي فالبطل اغتنمها وحاز على بعض النقاط بسببها. اكتب التعليمات التالية في الدالة update ضمن صنف اللاعب Player تمامًا فوق التعليمات المسؤولة عن اكتشاف التصادم مع المنصات، وانتبه للسطر الأخير فهو لتبيان السياق فقط وما من داعٍ لتعيد كتابته: loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) عرّف الآن المتغير الذي استخدمه لزيادة نقاط اللاعب بعد الاصطدام بالجائزة في الدالة __init__ الموجودة في صنف اللاعب وهي المسؤولة عن تحديد خصائصه المختلفة، وذلك بكتابة السطر الثالث من التعليمات أدناه: self.frame = 0 self.health = 10 self.score = 0 طبق المهارات التي تعلمتها في السابق طبق المهارات التي تعلمتها خلال هذه السلسلة وأضف الأعداء في أماكن مناسبة، خطط لذلك ومن ثم عدّل برنامج اللعبة، حاول أيضًا الاستفادة من المبادئ الأساسية، ووظفها بطرقٍ أخرى، وصمم ألعابًا متنوعة وإن كانت وحيدة المستوى في المرحلة كبداية، ولا تنسَ التخطيط الجيد للعبة قبل البرمجة، فكر بأوسع نطاق تريده وبرمج على هذا الأساس، يساعدك ذلك في الحصول على إصداراتٍ جيدة لا تتطلب كثيرًا من التعديلات. وفي ختام المقال نذكرك ببرنامج اللعبة كاملًا من بداية السلسلة. #!/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]) forwardx = 600 backwardx = 120 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.score = 0 self.is_jumping = True self.is_falling = True 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 gravity(self): if self.is_jumping: self.movey += 3.2 def control(self, x, y): """ control player movement """ self.movex += x def jump(self): if self.is_jumping is False: self.is_falling = False self.is_jumping = True def update(self): """ Update sprite position """ # moving left if self.movex < 0: self.is_jumping = True 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.is_jumping = True self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 # print(self.health) ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.bottom = g.rect.top self.is_jumping = False # stop jumping # fall off the world if self.rect.y > worldy: self.health -=1 print(self.health) self.rect.x = tx self.rect.y = ty plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.is_jumping = False # stop jumping self.movey = 0 if self.rect.bottom <= p.rect.bottom: self.rect.bottom = p.rect.top else: self.movey += 3.2 if self.is_jumping and self.is_falling is False: self.is_falling = True self.movey -= 33 # how high to jump loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False) for loot in loot_hit_list: loot_list.remove(loot) self.score += 1 print(self.score) plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) self.rect.x += self.movex self.rect.y += self.movey 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((550, 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 def loot(lvl): if lvl == 1: loot_list = pygame.sprite.Group() loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png') loot_list.add(loot) if lvl == 2: print(lvl) return loot_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) enemy_list = Level.bad( 1, eloc ) loot_list = Level.loot(1) ''' 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'): player.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) # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: e.rect.x -= scroll for l in loot_list: l.rect.x -= scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: e.rect.x += scroll for l in loot_list: l.rect.x += scroll world.blit(backdrop, backdropbox) player.update() player.gravity() player_list.draw(world) enemy_list.draw(world) loot_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 some loot in your Python platformer game لصاحبيه Seth Kenlon و Jess Weichler. اقرأ أيضًا المقال السابق: إضافة خاصية القفز والركض إلى لعبة في بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
  4. تناول المقال السابق من هذه السلسلة محاكاةً برمجية لقوى الجاذبية وتأثيرها على تحركات بطل اللعبة، وفي هذا المقال سيتوفر للبطل ما يعينه على مواجهة أثرها ولو للحظات معدودة عبر منحه خاصية القفز. سيَقفز اللاعب وعندما يصل لذروة قفزته ستُطبق عليه الجاذبية من جديد فيعود للسقوط، فما هي الآلية التي تحكم ذلك وكيف يميز البرنامج بين القفز والسقوط؟ الآلية المستخدمة هنا تعتمد على المتغيرات، إذ سنعرف بدايةً متغير خاص يراقب حالة اللاعب ويخبر بايثون بأن اللاعب يقفز الآن ليطبق عليه الجاذبية فيسقط بتأثيرها على أقرب كائن لموقعه من كائنات اللعبة. قبل أن نبدأ نذكرك بمقالات السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. ضبط متغيرات القفز سنضيف متغيرين جديدين إلى صنف اللاعب. يتتبع المتغير الأول حالة القفز عند اللاعب، ويحدد في كل لحظة إن كان الكائن يقفز أم يقف على أرضٍ صلبة. أما المتغير الثاني فيسحب كائن اللاعب إلى أرضية اللعبة. بناءً على ما سبق عرّف المتغيرات المنطقية التالية ضمن صنف اللاعب علمًا أن أول سطرين لتبيان السياق فقط ولا حاجة لإعادة كتابتها: self.frame = 0 self.health = 10 # أدناه المتغيرات الخاصة بالقفز self.is_jumping = True self.is_falling = False إنه الاستخدام الأول للمتغيرات المنطقية في هذه السلسلة والتي تسمى أيضًا بالمتغيرات البوليانية نسبةً إلى عالم الرياضيات جورج بول، تأخذ هذه المتغيرات إحدى قيمتين فقط لا ثالث لهما إما صحيح True أو خاطئ False، أي أن اللاعب يقفز أو لا يقفز وفقًا للمتغير الأول، ويسقط أو لا يسقط وفقًا للمتغير الثاني. لاحظ أننا أعطينا المتغير الأول is_jumping الذي يشير إلى القفز قيمةً ابتدائية هي True، ذلك أن كائن البطل في مثالنا يظهر بدايةً في سماء عالم اللعبة وينبغي سحبه إلى الأسفل مباشرةً ليبدأ اللعب، وكانت هذه أسهل الطرق لإنجاز ذلك رغم أنها لا توظف المتغير المنطقي توظيفًا مثاليًا لكنها تغنينا عن كتابة تعليمات خاصة تحدد موقع إنتاج البطل في كل مستوى، زد على أنها تُذكرنا بالألعاب الكلاسيكية التي يقفز فيها اللاعب إلى عالم اللعبة في لحظة البدء، فاعتمد هذه الخدعة البسيطة وسهّل برنامجك لا بأس في ذلك. أما المتغير الثاني is_falling فقد أُسندت له القيمة الابتدائية False ليهبط اللاعب إلى الأرضية فور بدء اللعبة. شروط تفعيل أثر الجاذبية القفز في العالم الحقيقي هو فعل مقاوم للجاذبية، وفي لعبتنا ستعمل الجاذبية في حالة واحدة فقط هي قفز اللاعب -أي عدم وقوفه على أرضٍ صلبة- وتتوقف في بقية الحالات، أما إن عملت باستمرار كما في المقال السابق فإن اللاعب سيعاني ارتدادًا دائمًا بسبب سحبه للاصطدام بالأرض. الجاذبية في ألعاب الفيديو هي دالة تُبنى خصيصًا في بعض الألعاب لتحاكي الجاذبية الأرضية الحقيقية، وليست ميزة مضمنة في محركات الألعاب مثل Pygame وهي بطبيعة الحال لا تُدار من قبلها، فبعض الألعاب المطورة باستعمال Pygame لا تحتاج للجاذبية مطلقًا وفقًا لقصتها وتصميمها، والسقوط ليس سقوطًا بالمعنى الفعلي للكلمة بل مجرد تحريك للكائن تفرضه الدالة، وحتى تمنح البطل قدرة القفز ومقاومة الجاذبية أو الاصطدام بالمنصات العائمة عليك بالتأكيد تعديل هذه الدالة وجعلها تنشط فقط عندما يقفز. عدّل إذًا دالة الجاذبية التي استخدمناها في المقال السابق لتصبح على الشكل التالي: def gravity(self): if self.is_jumping: self.movey += 3.2 تتسبب هذه الدالة في سقوط البطل إلى أسفل الشاشة مباشرةً متجاوزًا الأرضية والفقرة التالية ستُعالج ذلك من خلال اكتشاف تصادمه مع أرضية اللعبة. برمجة الأرضية الصلبة طبقنا في المقال السابق حيلةً بسيطة لمنع كائن البطل من السقوط بعيدًا إلى أسفل الشاشة وجعلناه يرتد إلى مسافة معينة تبقيه ضمن الشاشة، يمكننا تشبيه هذه الحيلة بإنشاء جدار غير مرئي بارتفاعٍ معين يمتد على عرض الشاشة من الأسفل، لا يسعنا فعليًا التمسك بهذه الطريقة فهي لا تحقق معيار الشيفرة النظيفة في البرمجة، ففي الشيفرة النظيفة تُمثل الكائنات بكائنات برمجية فعلية (ونعني بذلك الجدار الوهمي الذي ذكرناه)، أضف إلى ذلك أننا لابد سنحتاج في بعض الحالات إلى إسقاط بطلنا حتى أسفل عالم اللعبة ربما عقبًا له على قفزة خاطئة أو عند خسارة كل نقاطه أو ما شابه فهذه الحركة شائعة في تصميم الألعاب. اكتب التعليمات التالية في الدالة update ضمن صنف اللاعب: ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.bottom = g.rect.top self.is_jumping = False # إيقاف القفز # السقوط عبر عالم اللعبة if self.rect.y > worldy: self.health -=1 print(self.health) self.rect.x = tx self.rect.y = ty تتحقق هذه التعليمات من حدوث التصادم بين كائن الأرضية وكائن البطل وفق الأسلوب نفسه الذي استخدمناه للتحقق من التصادم بين البطل والعدو. وفي حال حدوث التصادم ستعدّل هذه التعليمات موضع البطل عبر تعديل قيمة self.rect.bottom التي تعني أخفض نقطة في صورة كائن البطل لتصبح مساوية لقيمة g.rect.top التي تعني قمة كائن الأرضية أو أعلى نقطة فيه، وبذلك يبدو البطل كأنه يقف تمامًا فوق أرضية اللعبة ويمنعه ذلك من السقوط إلى الأرضية فهو يقف عليها. علمًا أن هذه التفاصيل (مثل أخفض نقطة وأعلى نقطة في صورة الكائن) هي معلومات توفرها Pygame عن الكائنات عمومًا ويمكنك دائمًا الاستفادة منها. بعد تعديل موضع كائن البطل ستُغَيّر هذه التعليمات قيمة self.is_jumping إلى False ليعرف برنامج اللعبة أن البطل لم يعد في حالة قفز بعد الآن، وقيمة self.movey أيضًا إلى 0 حتى لا يخضع البطل لتأثير الجاذبية (في الواقع هي طرافة في فيزياء اللعبة أن تتوقف عن جذب الكائن إلى الأرض طالما أنه يقف عليها). أما مهمة if الشرطية في الجزء الأخير من التعليمات فهي اكتشاف القفزات الخاطئة لبطل اللعبة التي ينزل بموجبها تحت مستوى الأرضية، لتَخصم بناءً عليها نقطًا من صحته ومن ثم تعيد إنتاجه في أعلى يسار الشاشة باستخدام القيم ty و tx التي تمثل أبعاد بلاط الأرضية وقد استخدمناها للسهولة فقط في حين تستطيع تحديد الموضع الذي تريد، ويمكنك طبعًا عدم خصم نقاط من صحة اللاعب لقاء السقوط دون مستوى الأرضية فأنت من يحدد شروط اللعبة في نهاية المطاف. القفز في Pygame أنشئ أولًا دالة القفز jump لتقلب قيم المتغيرات is_falling و is_jumping. def jump(self): if self.is_jumping is False: self.is_falling = False self.is_jumping = True عمليًا الانطلاق الفعلي للقفز يحدث في الدالة update ضمن صنف اللاعب. if self.is_jumping and self.is_falling is False: self.is_falling = True self.movey -= 33 # مقدار علو القفزة تعمل هذه التعليمات بشرط تحقق أمرين أن تكون قيمة المتغير is_jumping هي True وقيمة المتغير is_falling هي False، وستُعَدّل عندها إحداثيات كائن البطل على المحور العمودي Y لتصبح 33- بكسل في فضاء عالم اللعبة وقيمتها سالبة بالطبع لأن الصفر في الأعلى والقيم الأصغر هي الأقرب إلى أعلى الشاشة، يعطيك هذا الرقم قفزةً جيدة لكن يمكنك تعديله لتحظى بقفزاتٍ أعلى أو أخفض. بعد تعديل الإحداثي العمودي تُغَيّر هذه التعليمات قيمة is_falling لتصبح True حتى لا تتراكم القفزات فيُطلق الكائن عاليًا في فضاء اللعبة. استدعاء دالة القفز أشرنا للقفز سابقًا في مقال تحريك شخصية اللعبة عن طريق PyGame وربطناه بالضغط على السهم العلوي من أسهم الاتجاهات على لوحة المفاتيح قبل إنشاء دالة القفز ودون استدعائها وقد اكتفينا حينها بطباعة عبارة تشير للقفز في نافذة الطرفية باستخدام print، والآن يمكننا استدعاء دالة القفز في الحلقة الرئيسية بدلًا من الطباعة. عدّل القيمة المقابلة للضغط على السهم العلوي في الحلقة الرئيسية لتصبح على الشكل التالي: if event.key == pygame.K_UP or event.key == ord('w'): player.jump() يمكنك استخدام زر آخر بدل السهم العلوي مثل زر المسافة إن رغبت بذلك أو الجمع بين الخيارين باستخدام تعليمة if بسيطة، علمًا أن الرمز الذي يعبر عن المسافة في Pygame هو: pygame.K_SPACE الهبوط على المنصات العائمة تعاملنا في فقرةٍ سابقة مع اصطدام اللاعب بأرضية اللعبة وما نتج عنه من إيقاف لعمل الجاذبية، والآن سنعيد الخطوات نفسها مع بقية المنصات العائمة، إذ إن أرضية اللعبة والمنصات في مثالنا لا تنتميان لمجموعة الكائنات نفسها بل لمجموعتين مختلفتين، مع العلم أنك تستطيع دمجها معًا في مجموعةٍ واحدة، والتعامل مع الأرضية بصفتها منصة عادية، لكن المقال اعتمد الخيار الأوسع الذي يسمح بتفضيلات إضافية، في النتيجة السماح لكائن البطل بالوقوف على إحدى المنصات يعني أن يكتشف البرنامج تصادمه مع كائن المنصة ويمنع الجاذبية من سحبه إلى الأسفل. اكتب التعليمات التالية ضمن الدالة update: plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.is_jumping = False # إيقاف القفز self.movey = 0 # اكتشاف محاولة الوصول للمنصة من الأسفل if self.rect.bottom <= p.rect.bottom: self.rect.bottom = p.rect.top else: self.movey += 3.2 تفحص هذه التعليمات كافة المنصات العائمة في اللعبة وتتحقق إن حدث أي تصادم بينها وبين كائن البطل، وفي حال ثبت حدوث التصادم تُعدّل قيمة متغير القفز إلى False وتُعطل حركة البطل على المحور العمودي. منصات اللعبة عائمة في الهواء ما يعطي الأريحية للاعب بالوصول إليها من الأعلى أو من الأسفل، عادةً ما تمنع الألعاب وصول البطل للمنصات من الأسفل وتسمح به من الأعلى فقط أي عندما يقفز البطل أعلى من مستوى المنصة، يمكنك تجربة الحالتين لكن هذا المثال تعامل مع المنصات على أنها سقوف والجزء الثاني من التعليمات يسمح للاعب بالقفز على المنصة طالما أن قفزته بلغت مسافةً أعلى من قمة المنصة ويمنعه عندما يحاول القفز عليها من الأسفل. يتحقق السطر الأول من التعليمة if الشرطية فيما إذا كانت قيمة أخفض نقطة من كائن البطل أقل من موقع المنصة ما يعني أن البطل أعلى منها على الشاشة لأن القيم سالبة، وإن تحقق ذلك يُسمح له بالهبوط على المنصة وتتعدل قيمة أخفض نقطة فيه لتصبح مساوية لقيمة قمة كائن المنصة، وإن لم يتحقق الشرط فيزداد الإحداثي Y لموقع لبطل مؤديًا إلى سقوطه نحو الأسفل بعيدًا عن المنصة العائمة التي كان يحاول الوصول إليها. السقوط إذا شغلت اللعبة الآن ستجد أن دالة القفز تعمل وبطلك يتنقل بين المنصات لكنه لا يسقط أرضًا بعد أن يهبط على أي منصة بل يبقى معلقًا في الهواء، والسبب في ذلك أننا عطلنا الجاذبية عند الهبوط على المنصة ولم نكتب أي تعليمة تعيدها للعمل ثانيةً عندما يصل البطل إلى حافة المنصة. عدّل دالة التحديث update في صنف اللاعب لتصبح مسؤولة عن تشغيل وإيقاف تشغيل الجاذبية أثناء حركة الكائن وفق التالي انتبه أنك ستحتاج لإضافة الأسطر التي تتضمن تعليقات فقط. if self.movex < 0: self.is_jumping = True # إيقاف تشغيل الجاذبية self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = pygame.transform.flip(self.images[self.frame // ani], True, False) if self.movex > 0: self.is_jumping = True # تشغيل الجاذبية self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] إذا فشل التحقق من حدوث تصادم مع إحدى المنصات خلال فترة تشغيل الجاذبية فإن البطل سيسقط أرضًا. شغل اللعبة الآن، ولاحظ أن الوظائف كافة تعمل جيدًا، يمكنك تغيير قيم بعض المتغيرات لتجرب تأثيرها. إليك شيفرة اللعبة كاملًا حتى هذه النقطة، أما في القسم التالي سنمنح البطل القدرة على الركض بحرية عبر تمرير عالم اللعبة جانبيًا. #!/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.is_jumping = True self.is_falling = True 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 gravity(self): if self.is_jumping: self.movey += 3.2 def control(self, x, y): """ control player movement """ self.movex += x def jump(self): if self.is_jumping is False: self.is_falling = False self.is_jumping = True def update(self): """ Update sprite position """ # moving left if self.movex < 0: self.is_jumping = True 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.is_jumping = True self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 # print(self.health) ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.bottom = g.rect.top self.is_jumping = False # stop jumping # fall off the world if self.rect.y > worldy: self.health -=1 print(self.health) self.rect.x = tx self.rect.y = ty plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.is_jumping = False # stop jumping self.movey = 0 if self.rect.bottom <= p.rect.bottom: self.rect.bottom = p.rect.top else: self.movey += 3.2 if self.is_jumping and self.is_falling is False: self.is_falling = True self.movey -= 33 # how high to jump self.rect.x += self.movex self.rect.y += self.movey 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((550, 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'): player.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.gravity() 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) تفعيل الركض ذهابًا وإيابا في عالم اللعبة سنعطي البطل في هذه المرحلة القدرة على الركض ذهابًا وإيابًا عبر تمرير عالم اللعبة جانبيًا. خططت في مقالٍ سابق مستوى للعبة يتضمن عدة منصات وعندما نفذته لم تظهر كافة المنصات على شاشة العرض فقد تجاوزت أبعادها وأعدادها مساحة الشاشة الواحدة، والتمرير الجانبي لعالم اللعبة أو ما يسمى side-scroller هو الحل الأمثل لهذه الحالة ولإظهار المستوى المخطط له كاملًا. تمامًا كما يوحي الاسم، ستتحرك المنصات جانبيًا كلما اقترب البطل من حافة الشاشة لتعطي المستخدم شعورًا يشبه مشاهدة الفيديو وكأن كاميرا متحركة تتنقل في عالم اللعبة وتصوره، يتطلب التمرير حجز منطقة ميتة على جانبي الشاشة الأيمن والأيسر وعندما يصل البطل إلى هذه المناطق سيبقى ثابتًا بينما يُمرر العالم جانبيًا من حوله وتتحرك المنصات. تثبيت نقاط التمرير الجانبي على الشاشة إن رغبت بالسماح للاعب بالتحرك إلى الأمام والخلف فستحتاج إلى نقطتي تمرير على جانبي الشاشة، أما لو سمحت له بالتقدم للأمام فقط تكفيك عندها نقطةٌ واحدة. سنحدد نقاط التمرير باستخدام المتغيرات بمسافة 100 أو 200 بيكسل عن كل جانب من جوانب الشاشة. عرّف إذًا متغيرات التمرير في مقطع المتغيرات ضمن برنامجك على الشكل التالي: forwardx = 600 backwardx = 230 والآن تحقق من اللحظة التي يصل فيها بطلك إلى نقاط التمرير ضمن الحلقة الرئيسية للبرنامج وذلك عبر تعليمة if الشرطية، وإذا وصل اللاعب إلى إحدى هذه النقاط حرك عندها عالم اللعبة يمينًا أو يسارًا. اكتب التعليمات التالية في حلقة التكرار الرئيسية للبرنامج وانتبه أن لا تتداخل مع الحلقة for الخاصة باكتشاف الضغط على أزرار لوحة المفاتيح، الأسطر الثلاثة الأخير هي للسياق فقط لن تحتاج لإعادة كتابتها: # تمرير عالم اللعبة إلى الأمام if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll # تمرير عالم اللعبة إلى الخلف if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll # تعليمات التمرير أعلى هذا السطر world.blit(backdrop, backdropbox) player.gravity() # فحص الجاذبية player.update() شغل اللعبة الآن وجرب التمرير. ستلاحظ بعد التشغيل أن التمرير الجانبي يعمل جيدًا والمنصات تتحرك مع البطل إلى الأمام والخلف أما العنصر الوحيد الذي لا يستجيب للتمرير فهو كائن شخصية العدو. اكتب التعليمات اللازمة لتمرير إن أردت أن يُمرر مثلهم، وتستطيع طبعًا تمريره للأمام فقط أو للخلف فقط أو بكلا الاتجاهين ليلاحق بطل اللعبة. تمرير العدو مع عالم اللعبة طبق التعليمات نفسها الخاصة بتمرير المنصات على شخصية العدو، والأفضل لك أن تطبقها على المجموعة التي تضم جميع كائنات العدو إذ إن اللعبة تتضمن أكثر من عدو، هذه ميزة جمع العناصر المتشابهة في مجموعات فهي تسهل على المبرمج تطبيق التعليمات عليها جميعًا دفعةً واحدة. أضف السطرين الأخيرين من التعليمات التالية ضمن الحلقة الرئيسية في كتلة التمرير إلى الأمام لتمرير العدو إلى الأمام: # تمرير عالم اللعبة إلى الأمام if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: # تمرير العدو e.rect.x -= scroll # تمرير العدو ومن ثم السطرين الأخيرين من هذه التعليمات إلى كتلة التمرير إلى الخلف ضمن الحلقة الرئيسية لتمرير العدو بالاتجاه الآخر: # تمرير عالم اللعبة إلى الخلف if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: # تمرير العدو e.rect.x += scroll # تمرير العدو والآن شغل اللعبة وتفقد النتائج. إليك الشكل النهائي لبرنامج اللعبة حتى هذه المرحلة ليبقى مرجعًا لديك: #!/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]) forwardx = 600 backwardx = 230 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.is_jumping = True self.is_falling = True 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 gravity(self): if self.is_jumping: self.movey += 3.2 def control(self, x, y): """ control player movement """ self.movex += x def jump(self): if self.is_jumping is False: self.is_falling = False self.is_jumping = True def update(self): """ Update sprite position """ # moving left if self.movex < 0: self.is_jumping = True 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.is_jumping = True self.frame += 1 if self.frame > 3 * ani: self.frame = 0 self.image = self.images[self.frame // ani] # collisions enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in enemy_hit_list: self.health -= 1 # print(self.health) ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False) for g in ground_hit_list: self.movey = 0 self.rect.bottom = g.rect.top self.is_jumping = False # stop jumping # fall off the world if self.rect.y > worldy: self.health -=1 print(self.health) self.rect.x = tx self.rect.y = ty plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False) for p in plat_hit_list: self.is_jumping = False # stop jumping self.movey = 0 if self.rect.bottom <= p.rect.bottom: self.rect.bottom = p.rect.top else: self.movey += 3.2 if self.is_jumping and self.is_falling is False: self.is_falling = True self.movey -= 33 # how high to jump self.rect.x += self.movex self.rect.y += self.movey 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((550, 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'): player.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) # scroll the world forward if player.rect.x >= forwardx: scroll = player.rect.x - forwardx player.rect.x = forwardx for p in plat_list: p.rect.x -= scroll for e in enemy_list: # enemy scroll e.rect.x -= scroll # enemy scroll # scroll the world backward if player.rect.x <= backwardx: scroll = backwardx - player.rect.x player.rect.x = backwardx for p in plat_list: p.rect.x += scroll for e in enemy_list: # enemy scroll e.rect.x += scroll # enemy scroll world.blit(backdrop, backdropbox) player.update() player.gravity() 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) ترجمة -وبتصرف- للمقال Add jumping to your Python platformer game والمقال Enable your Python game player to run forward and backward لصاحبيه Seth Kenlon و Jess Weichler. اقرأ أيضًا المقال السابق: محاكاة أثر الجاذبية في لعبة بايثون تصميم وبرمجة لعبة قفز باستخدام سكراتش إنشاء السلالم وختام اللعبة
  5. عالمنا الحقيقي مليء بالحركة، والفيزياء هي ما يحكم ويفسر كافة ظواهره المتعلقة بحركة المادة، أما عالم ألعاب الفيديو فهو خالٍ من المادة ما يعني أنه منطقيًا خالٍ من الفيزياء أيضًا، لذا ينبغي على مطوري الألعاب محاكاة الفيزياء في ألعابهم لمنحها لمسة واقعية، وأكثر المبادئ الفيزيائية أهمية في عالم الألعاب هي الجاذبية والتصادم. تعلمنا قليلًا عن التصادم عندما أضفنا شخصية العدو إلى اللعبة، لكننا سنتناوله بمزيدٍ من التعمق في هذا المقال والذي يليه فالجاذبية تفرض علينا ذلك، فما هي العلاقة بين الجاذبية والتصادم؟ ستجيبك عن ذلك تعليماتنا البرمجية اللاحقة. الجاذبية في العالم الحقيقي كما نعرفها جميعًا من دروس الفيزياء هي ميل الأجسام ذات الكتلة إلى التجاذب المتبادل فيما بينها، وكلما زادت كتلة الجسم زاد تأثير الجاذبية التي يمارسها على غيره من الأجسام، فكيف سينعكس ذلك في عالم الألعاب؟ في الواقع الأثر الأوضح للجاذبية الذي ينبغي علينا محاكاته في الألعاب هو ميل الأجسام للسقوط تجاه الكائن الذي يملك افتراضيًا أكبر كتلة في عالم اللعبة، وهو عالم اللعبة نفسه. قبل أن نبدأ نذكرك بمقالات السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame. تحريك شخصية اللعبة باستخدام PyGame. إضافة شخصية العدو للعبة. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame. محاكاة أثر الجاذبية في لعبة بايثون. إضافة خاصية القفز والركض إلى لعبة بايثون. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون. إضافة دالة الجاذبية سنستفيد من خاصية الحركة التي يملكها كائن اللاعب حتى نسحبه أو نحركه باتجاه أسفل شاشة العرض، بما يحاكي أثر الجاذبية الأرضية، وتذكر أن صفر المحور العمودي في Pygame يقع في الأعلى ما يعني أن الإحداثيات الأكبر هي أكثر قربًا لأسفل الشاشة. الجاذبية انتقائية في ألعاب المنصات فالمبرمج يختار أثرها ولن تؤثر على كل شيء كما في العالم الحقيقي، فلو فعلت ذلك لرأيت منصات اللعبة بأكملها تسقط إلى الأرضية، لذا سنطبق أثر الجاذبية على شخصيتي اللاعب والعدو فقط. أضف أولاً دالة الجاذبية في صنف اللاعب: def gravity(self): self.movey += 3.2 # معدل سرعة سقوط اللاعب الدالة بسيطة فقد ضبطنا حركة اللاعب العمودية لتكون باتجاه أرضية اللعبة دائمًا بغض النظر إن كان أساسًا بحالة حركة أو سكون، أي أنه سيسقط دائمًا وهذا بالفعل معنى الجاذبية الأرضية، واستخدمنا لذلك خاصية الحركة العمودية التي يمتلكها اللاعب. اكتب دالة الجاذبية في الحلقة الرئيسية ليصبح أثرها دائمًا كما في السطر الأول من التعليمات أدناه وبذلك سيُطبقها بايثون على اللاعب مع كل نبضة ساعة. player.gravity() # تحقق من الجاذبية player.update() شغل لعبتك الآن وشاهد النتائج، سيسقط اللاعب سقوطًا حادًا وسريعًا من سماء عالم اللعبة إلى أسفل الشاشة، ربما ترغب بتغيير مقدار سرعة السقوط لتحظى بنتيجة أفضل، لكن أثر الجاذبية يعمل وهذا يكفي في هذه المرحلة. تعيين أرضية الجاذبية ليقف عندها اللاعب بعد السقوط دائمًا ما يلي السقوط إجراءٌ معين في ألعاب الفيديو، قد يكون مسح اللاعب عن شاشة اللعبة وإعادة إنتاجه في مكانٍ مختلف، أو خصم بعض النقاط من رصيده أو ما شابه، بالنتيجة أيًا كان الإجراء الذي تريده فينبغي أولًا إيجاد طريقة تجعل البرنامج يكتشف حادثة السقوط ويقرر إن سقط اللاعب واختفى عن الشاشة، أم أن ذلك لم يحدث، ويُتخذ بعدها الإجراء المطلوب، أما أنسب تعليمة لهذا النوع من الكشف فهي تعليمة if الشرطية. ستتحقق إذًا فيما إذا سقط لاعبك أم لا، وماهو مدى سقوطه، فلو سقط بعيدًا جدًا متجاوزًا الأرضية إلى أخفض نقطة في الشاشة فالوضع غير مقبول ويحتاج لبعض التعديلات فمنطيقًا كان عليه الاستقرار على أرضية اللعبة. لنبسط الموضوع اضبط موقع اللاعب عند 20 بكسل، وعدّل دالة الجاذبية لتصبح: def gravity(self): self.movey += 3.2 # معدل سرعة السقوط if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty ومن ثم شغل اللعبة ولاحظ أن اللاعب يسقط من الأعلى ويتوقف عند أسفل الشاشة، حتى أنك قد لا تتمكن من رؤيته خلف كائن الأرضية، سنعالج الموضوع بإضافة ty- ثانية إلى موقعه الجديد تجعله يرتد أعلى بعد اصطدامه بأسفل الشاشة فيظهر على مسافة معينة ضمن الشاشة، وذلك وفق الأوامر التالية: def gravity(self): self.movey += 3.2 # how fast player falls if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty-ty يحتاج بطل اللعبة إلى طريقة أو قوة دفع تساعده على مقاومة أثر الجاذبية ليجتاز المستويات ويتابع اللعب هذه القوة هي القفز سيناقشها المقال التالي مع توسع إضافي في تصادمه مع الأرضية والمنصات المختلفة. حاول بنفسك تطبيق أثر الجاذبية على كائن شخصية العدو، وانظر الشكل النهائي لكود اللعبة متضمنًا كل فعلناه في السلسلة حتى الآن: #!/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 gravity(self): self.movey += 3.2 if self.rect.y > worldy and self.movey >= 0: self.movey = 0 self.rect.y = worldy-ty-ty 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.gravity() 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) كان هذا المقال السابع من سلسلة بناء لعبة من الصفر باستخدام بايثون 3 و Pygame. ترجمة -وبتصرف- للمقال Simulate gravity in your Python game لصاحبيه Seth Kenlon و Jess Weichler. اقرأ أيضًا المقال السابق: إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame إنشاء الجاذبية في اللعبة النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
  6. يتمحور المقال السادس من سلسلة (بناء لعبة من الصفر باستخدام بايثون) حول إضافة المنصات إلى عالم اللعبة لتتفاعل معها الشخصيات وتتنقل عبرها فهذه اللعبة أولًا وأخيرًا لعبة منصات، ولكن قبل أن نبدأ نذكرك بمقالات السلسلة. بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب 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 في بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون دليلك الشامل إلى: أشهر لغات برمجة الألعاب
  7. لغة بايثون هي لغة برمجة سهلة التعلم موازنةً باللغات الأخرى مثل لغة جافا ولغة 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 تعرف على أشهر لغات برمجة الألعاب برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
  8. وصلنا للمقال الخامس من سلسلة بناء لعبة من الصفر باستخدام بايثون، وقد تعلمنا في أجزائها الأربعة السابقة كيفية بناء لعبة فيديو باستخدام الوحدة 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
  9. استخدمنا بايثون لبناء لعبة نرد بسيطة معتمدة على النص في مقالنا الأول من هذه السلسلة، وفي الثاني تعلمنا كيفية تجهيز بيئة لعبة رسومية من الصفر أيضًا بلغة بايثون، أما في المقال الثالث السابق أضفنا كائنًا أو بطلًا للعبة الفارغة، والآن سنتعلم استخدام 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 بناء لعبة نرد بسيطة بلغة بايثون برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون
  10. في مقال بناء لعبة نرد بسيطة بلغة بايثون استخدمنا بايثون لبرمجة لعبة نرد بسيطة معتمدة على النص، وفي مقالنا السابق تعلمنا كيفية تجهيز بيئة لعبة رسومية من الصفر أيضًا بلغة بايثون، والآن سنطوّر هذه اللعبة ونضيف إليها شخصية تلعب دور اللاعب أو بطل اللعبة. يمكنك مطالعة المقال ضمن السلسلة: بناء لعبة نرد بسيطة بلغة بايثون. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب 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 برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون تعرف على أشهر لغات برمجة الألعاب
  11. شرحنا في المقال السابق كيفية بناء لعبة نرد بسيطة باستعمال لغة بايثون، وتعرفنا على وحدة السلحفاة 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 المقال السابق: بناء لعبة نرد بسيطة بلغة بايثون تعرف على أشهر لغات برمجة الألعاب برمجة لعبة حجرة ورقة مقص باستخدام لغة بايثون النسخة العربية الكاملة من كتاب البرمجة بلغة بايثون
×
×
  • أضف...