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

تحريك شخصية في لعبة باستخدام Pygame


رشا سعد

استخدمنا بايثون لبناء لعبة نرد بسيطة معتمدة على النص في مقالنا الأول من هذه السلسلة، وفي الثاني تعلمنا كيفية تجهيز بيئة لعبة رسومية من الصفر أيضًا بلغة بايثون، أما في المقال الثالث السابق أضفنا كائنًا أو بطلًا للعبة الفارغة، والآن سنتعلم استخدام Pygame لتحريك الكائن والتحكم به مباشرةً من خلال لوحة المفاتيح، بطريقة شبيهة نوعًا ما ببرمجة زر مغادرة اللعبة التي اتبعناها في المقال الثاني، إلّا أنها أكثر تعقيدًا.

يمكنك مطالعة المقال ضمن السلسلة:

  1. بناء لعبة نرد بسيطة بلغة بايثون.
  2. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame.
  3. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame.
  4. تحريك شخصية اللعبة باستخدام PyGame.
  5. إضافة شخصية العدو للعبة.
  6. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame.
  7. محاكاة أثر الجاذبية في لعبة بايثون.
  8. إضافة خاصية القفز والركض إلى لعبة بايثون.
  9. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون.
  10. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة.
  11. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون.

تدعم 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.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...