مدخل لمكتبة pygame

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

Pygame هي أداة رائعة للمطورين. لا يتم إعداده ليكون متوافقًا بسهولة مع الأجهزة المحمولة ، وبسبب طبيعة Python ، فهي ليست رائعة للمنتجات التجارية بشكل عام ؛ ومع ذلك ، فإن نقاط قوتها تشمل سهولة الاستخدام وسهولة الوصول إليها. يعد تدفق سير عمل Pygame طريقة رائعة للمبتدئين لتعلم برمجة الألعاب على مستوى أعمق لأنه واضح ومباشر مع وجود قدر لا بأس به من العمق. فهي تساعدك لتبدأ في استخدام مكتبات أكبر وأفضل ما يتم استخدامها تجاريًا مثل libgdx مع Java ، وحتى إطار Löve في Lua.

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

ألقِ نظرة على الكود التالي والذي سيفعل شيئًا ما. تفضل، أنظر إليه، قم بتشغيله إذا كنت ترغب في ذلك. من المفترض أن ترى نافذة مشابهة للصورة أعلاه، ولكن يجب أن يتم التبديل بين الأسود والأبيض. كن راضيًا بعدم فهم أي منها، وكن سعيدًا إذا رأيت أي شيء تعرفه، ولكن كن متحمسًا لأننا سنتعلم كل ذلك.

import pygame
import sys
# Defines common colors
background_one = (0, 0, 0)  # black
background_two = (255, 255, 255)  # white

# Initializes all pygame functionality.
pygame.init()
# Set the size of the window, variables can be used for positioning
# within program.
window_width = 700
window_height = 400
# Creates the window and puts a Surface into "screen".
screen = pygame.display.set_mode((window_width, window_height))
# Sets title of window 
pygame.display.set_caption("Game Title Here")
# Used for timing within the program.
clock = pygame.time.Clock()
# Used for timed events.
milli_timer = 0
white_flash_time = 1000  # Milliseconds between the screen being filled white
black_flash_time = 500   # Milliseconds between the screen being filled black
# Main loop of the program.
while True:
    # Event processing here, stuff the users does.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        # When user presses a key.
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                print("UP")
            elif event.key == pygame.K_DOWN:
                print("DOWN")
    # Add the amount of milliseconds passed
    # from the last frame.
    milli_timer += clock.get_time()
    # Every 1000 milliseconds, fill it with white
    if milli_timer > white_flash_time:
        screen.fill(background_two)
        milli_timer = 0  # Reset timer
    # Every 500 milliseconds, fill it with black.
    elif milli_timer > black_flash_time:
        screen.fill(background_one)
    # Display all images drawn.
    # This removes flickering images and makes it easier for the processor.
    pygame.display.flip()
    # Defines the frame rate. The number is number of frames per second.
    clock.tick(20)

أولاً دعونا نلقي نظرة عامة على كيفية عمل اللعبة. يتم تشغيل اللعبة باستخدام حلقة، كل تشغيل خلال الحلقة يمثل إطارًا واحدًا. لكل إطار، يأخذ الكمبيوتر مدخلات من المستخدم، ويقوم بحسابات على منطق اللعبة، ويلون وحدات بكسل معينة على الشاشة بألوان معينة.

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

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

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

import pygame
import sys

عبارات الاستيراد الكلاسيكية. يتيح لك “import pygame” استخدام جميع دواله، وهو عباراة الاستيراد الوحيد الذي ستحتاجه في البداية ويبدأ كل الوصول إلى دوال Pygame بـ “pygame”. نقوم أيضا بإستيراد sys لسبب واحد، وهو إغلاق العملية باستخدام sys.exit() والذي سنناقشه قريبًا.

# Defines common colors
background_one = (0, 0, 0)  # black
background_two = (255, 255, 255)  # white

نحدد لونين هنا. تخزين الألوان البسيط في Pygame يحفظها في مجموعات أو مصفوفات من النموذج (أحمر، أخضر، أزرق) مع كون الأحمر والأخضر والأزرق عددًا صحيحًا من 0 إلى 255، كلما زاد العدد، زاد اللون. ولهذا السبب (0، 0، 0) أسود، لأنه لا يوجد لون، و(255، 255، 255) أبيض، لأنه يوجد كل الألوان. (هناك أيضًا فئة ألوان يمكنك استخدامها بحرية، ولكنها ليست ضرورية).

ملاحظة: لاحظ أسماء المتغيرات. أنا لم أسميهم “أسود” أو “أبيض”. ماذا لو أردت تغييرها إلى الأحمر والأزرق؟ سيكون لدي خياران: تغيير أسماء المتغيرات إلى “أحمر” و”أزرق” وبالتالي يتعين علي تغيير كل تكرار لها في البرنامج، بدلاً من ذلك، قمت بتسميتها على اسم وظيفتها، بحيث يكون تغيير لون الخلفية أمرًا بسيطًا مثل تغيير قيمة الصف. هذا مفهوم برمجة عام جيد يجب أخذه في الاعتبار.
pygame.init()

هذه هي طريقة Pygame لإخبار الكمبيوتر “مرحبًا، ساعدني يا رجل، قم بإعدادي”. قد يحاول القراء الأكثر فضولًا تشغيل هذا البرنامج بدون هذا السطر ويلاحظون أنه لا يزال يعمل. وذلك لأن هذا البرنامج يستخدم وحدات بسيطة؛ ومع ذلك، بمجرد البدء في استخدام وحدات أخرى مثل pygame.mixer، يصبح من الضروري تهيئتها للعمل بشكل صحيح. من الممارسات الجيدة تضمينه افتراضيًا لتجنب نسيانه في المستقبل وينتهي الأمر بقضاء عدة ساعات في استكشاف أخطاء تثبيت Pygame وإصلاحها قبل أن تدرك أنك ببساطة تفتقد هذا السطر. (أنا بالتأكيد لا أتكلم من تجربة شخصية…).

window_width = 700
window_height = 400
screen = pygame.display.set_mode((window_width, window_height))
pygame.display.set_caption("Game Title Here")

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

clock = pygame.time.Clock()

هنا نسمي بشكل إبداعي متغير الساعة “clock“. تمكننا هذه الوحدة من التحكم في معدل الإطارات في لعبتنا، وتمكننا من إنشاء مؤقتات تعمل بشكل مستقل عن معدل الإطارات.

هذه السطور عبارة عن مؤقت بسيط أثناء العمل، لكن انظر أولاً إلى هذا المثال:

import pygame
import sys
background_one = (0, 0, 0)
background_two = (255, 255, 255)
pygame.init()
window_width = 700
window_height = 400
screen = pygame.display.set_mode((window_width, window_height))
pygame.display.set_caption("Bad Example")
clock = pygame.time.Clock()
# BAD COUNTER >:(
frame_counter = 0
# Main loop of the program.
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    #DON'T DO THIS >:(
    frame_counter += 1
    if frame_counter > 20:
        screen.fill(background_two)
        frame_counter = 0
    elif frame_counter > 10:
        screen.fill(background_one)
    pygame.display.flip()
    clock.tick(20)

الطريقة الساذجة لإنشاء مؤقت، كما هو موضح أعلاه، هي ببساطة إضافة 1 إلى عداد كل إطار، ولكن يجب أن نأخذ في الاعتبار الأنظمة المختلفة. ماذا لو كان شخص ما يلعب على جهاز بقيمة 3000 دولار يشغل لعبتنا بمعدل 10000 إطارًا في الثانية، مقارنةً بشخص اخترق محمصة الخبز ويلعب بمعدل 0.01 إطارًا في الثانية عن طريق حرق كل إطار على قطعة خبز؟ ستكون سرعة اللعبة مختلفة تمامًا بين الاثنين، ومختلفة تمامًا عن نظامك، مما يغير التجربة المقصودة تمامًا.

milli_timer = 0  # Millisecond counter
white_flash_time = 1000  # Milliseconds between the screen being filled white
black_flash_time = 500
... 
milli_timer += clock.get_time()
# Every 1000 milliseconds, fill it with white
if milli_timer > white_flash_time:
    screen.fill(background_two)
    milli_timer = 0  # Reset timer
# Every 500 milliseconds, fill it with black.
elif milli_timer > black_flash_time:
    screen.fill(background_one)

تُرجع “Clock.get_time()” مقدار المللي ثانية التي مرت منذ الإطار الأخير. باستخدام الوقت بين الإطارات، يعمل التوقيت بشكل مستقل عن مدى سرعة تشغيل اللعبة. 10,000 إطارًا في الثانية مع .002 مللي ثانية بين الإطارات: 10,000 * .002 = 20 يتم حسابها في كل ثانية. 1 إطارًا في الثانية مع 20 مللي ثانية بين الإطارات: 1 * 20 = 20 يتم حسابها في كل ثانية.

ومع ذلك، هناك خلل في طريقة هذا البرنامج للتبديل بين حالتي الخلفية البيضاء والسوداء بمعدلات إطارات منخفضة بشكل خاص، هل يمكنك اكتشافه؟ التعليق أدناه المشكلة والحل المحتمل.

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                print("UP")
            elif event.key == pygame.K_DOWN:
                print("DOWN")

هذه هي الحلقة الرئيسية، فهي لا نهاية لها لأن السبب الوحيد لانتهاءها هو إغلاق اللعبة نفسها. يمكنك أيضًا استخدام متغير منطقي إذا كنت تستمتع بالغرابة.

السطر 2 في المقطع أعلاه هو أمر كبير، وهو ما يسمح للمستخدم بالتفاعل مع اللعبة. ترجع الدالة pygame.event.get()‎ قائمة بجميع المفاتيح التي تم الضغط عليها للأسفل أو الأعلى، وأزرار الخروج المحددة، وغيرها من المفاتيح الموجودة في الإطار المحدد. للحصول على نوع الحدث المطلوب، يمكنك استعراض جميع الأحداث والتحقق لمعرفة ما إذا كان هذا هو نوع الحدث الذي تحتاجه. السطران (3) و (6) يفعلان ذلك. فيما يلي قائمة بجميع أنواع الأحداث الأخرى. يتم تنشيط حدث pygame.QUIT عندما يضغط المستخدم على الزر X الموجود في النافذة. يؤدي pygame.quit() إلى إيقاف تشغيل اللعبة، ولأننا نتعامل بلطف مع أجهزة الكمبيوتر لدينا، فإننا نستدعي sys.exit() للمساعدة في عملية التنظيف.

pygame.KEYDOWN هو حدث للضغط على المفتاح. يوجد داخل هذا العبارة بنية if أخرى تعمل على العثور على المفتاح الفعلي الذي تم الضغط عليه، والذي يتم تخزينه داخل events.key.

تذكر أنني قلت في الإطار المحدد، لذلك يتم تنشيطه فقط لإطار واحد عند الضغط على مفتاح، ثم يتم نسيانه.
screen.fill(background_two)
...
screen.fill(background_one)

هذه الوظيفة تشرح نفسها إلى حد ما، فهي تملأ كائن السطح باللون الذي تمرره إليه. في هذه الحالة نقوم بملء “screen”، وتذكر أن محتويات الشاشة مرسومة على الشاشة، مما يؤدي إلى…

pygame.display.flip()

السطر الذي يحول البرنامج من كونه جهاز محاكاة لفتح النوافذ إلى لعبة. هذه هي الطريقة التي تعمل على تحديث الشاشة بـ “screen”، لتكشف عن الرسومات التي قمنا بها. تخيل أنها واحدة من تلك الجدران الدوارة السرية التي تدور في غرفة أخرى. على الجانب المفتوح للجمهور يوجد الإطار المعروض حاليًا، بينما يشير الجانب الخلفي إلى عمليتنا السرية للرموز والأرقام والسحر حيث نرسم الإطار التالي عليه. عندما يتم استدعاء pygame.display.flip()، فإننا نقوم بقلب هذا الجدار، وتحديث العرض والسماح بمشاهدة المشهد الجديد بينما نبدأ العملية على الظهر الجديد.

clock.tick(20)

هذه الدالة بسيطة، العدد الصحيح الذي تمرره إليه هو عدد الإطارات في الثانية التي تريد أن تستهدفها اللعبة. لاحظ أن معدل الإطارات في الثانية (FPS) لن يتجاوز الرقم أبدًا، ولكنه قد ينخفض ​​إذا كان الكمبيوتر بطيئًا جدًا.

الآن بعد أن أصبح لدينا فهم جيد لكل سطر في هذا الملف، فلنجعل محفز النوبات من الدرجة الثانية هذا يفعل شيئًا ما.

جعل الأشياء تتحرك

class Entity(pygame.sprite.Sprite):
    """Inherited by any object in the game."""
    def __init__(self, x, y, width, height):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        # This makes a rectangle around the entity, used for anything
        # from collision to moving around.
        self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
class Paddle(Entity):
    """
    Player controlled or AI controlled, main interaction with
    the game
    """
    def __init__(self, x, y, width, height):
        super(Paddle, self).__init__(x, y, width, height)
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(entity_color)

نظرة أولية على هذه الفئات، سيكون Entity ببساطة هو الفئة الأكثر افتراضية لأي شيء يظهر في اللعبة. لاحظ أنها ترث فئة pygame.sprite.Sprite، واستخدام فئة Sprite هذه يمكّن من استخدام مجموعات الكائنات التي تعتبر مهمة جدًا للتعامل مع الكيانات في لعبتنا، وسنصل إليها لاحقًا. لاحظ أيضًا السطر الأول من __init__()، فهذه مجرد دالة التهيئة ل Sprite. بخلاف ذلك، فإن الكيان هو مجرد مستطيل محدد بالإحداثيات والحجم. تأتي فئة pygame.Rect هذه مع الكثير من الأدوات المفيدة، بدءًا من تحريك Entity وحتى اكتشاف الاصطدامات مع الآخرين.

يتوسع Paddle على Entity من خلال إعطاء الكائن صورة. الصورة هي ما يتم رسمه على الشاشة، من خلال “screen” المتغير القديم. نحن فقط نصنع سطحًا عن طريق تمرير العرض والارتفاع ومن ثم ملئه باللون الأبيض، وسيتم رسم الصورة على إحداثيات مستطيل الكائن. تحقق من الأشياء الأخرى التي يمكن لـ Surfaces القيام بها هنا.

لا يقوم Paddle بأي شيء بمفرده، لذا سنمنحه بعض الدوال:

class Player(Paddle):
    """The player controlled Paddle"""
    def __init__(self, x, y, width, height):
        super(Player, self).__init__(x, y, width, height)
        # How many pixels the Player Paddle should move on a given frame.
        self.y_change = 0
        # How many pixels the paddle should move each frame a key is pressed.
        self.y_dist = 5
    def MoveKeyDown(self, key):
        """Responds to a key-down event and moves accordingly"""
        if (key == pygame.K_UP):
            self.y_change += -self.y_dist
        elif (key == pygame.K_DOWN):
            self.y_change += self.y_dist
    def MoveKeyUp(self, key):
        """Responds to a key-up event and stops movement accordingly"""
        if (key == pygame.K_UP):
            self.y_change += self.y_dist
        elif (key == pygame.K_DOWN):
            self.y_change += -self.y_dist
    def update(self):
        """
        Moves the paddle while ensuring it stays in bounds
        """
        # Moves it relative to its current location.
        self.rect.move_ip(0, self.y_change)
        # If the paddle moves off the screen, put it back on.
        if self.rect.y < 0:
            self.rect.y = 0
        elif self.rect.y > window_height - self.height:
            self.rect.y = window_height - self.height
...
for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            player.MoveKeyDown(event.key)
        elif event.type == pygame.KEYUP:
            player.MoveKeyUp(event.key)

ستلاحظ أولاً إضافة بعض الدوال. انظر إلى بنية الحدث (والتي ستكون بالفعل في حلقة while True)، وسترى أنه عند الضغط على مفتاح أو عدم الضغط عليه، يتم تمرير هذا المفتاح إلى دالتي MoveKeyDown وMoveKeyUp الخاصتين بالمشغل. عند الضغط على السهم لأعلى، تتم إضافة y_change إلى…y_dist سلبي؟ يبدو هذا غريبًا، خاصة إذا لم تكن لديك خبرة سابقة في تطوير الألعاب. يعمل نظام الإحداثيات الموجود على النافذة بطريقة تشبه المستوى الديكارتي المقلوب فوق المحور السيني. تستمر قيم X في الزيادة من اليسار إلى اليمين، لكن قيم Y تزداد من الأسفل إلى الأعلى، لذا فإن الأصل (0، 0) يقع في أعلى اليسار. لذلك، لكي ترتفع، فإنك تتحرك فعليًا في اتجاه سلبي.

ولمواجهة هذا الأمر، في MoveKeyUp، عندما يتم تحرير السهم لأعلى، تتم إضافة y_change إلى y_dist مرة أخرى للعودة إلى 0.

في دالة update، والتي يتم استدعاؤها لكل إطار، نستخدم rect.move_ip(x, y) لتحريك وحدات البكسل x المستقيمة في الاتجاه x والبكسلات y في الاتجاه y. الأسطر التالية تتأكد فقط من بقاء paddle على الشاشة.

إليك ما يبدو عليه “مضرب اللاعب”.

ملاحظة: يقوم المستطيل بتخزين الإحداثيات التي تحتوي على أعداد صحيحة فقط، لذا من أجل الحركة الدقيقة يتم التخلص من أي قيم عشرية، وهذا يتطلب وسائل أخرى للتعامل مع الإحداثيات.

الآن، إن وجود مجداف وحيد على الشاشة ليس هو اللعبة الأكثر إثارة، لذلك دعونا نضيف الكرة.

class Ball(Entity):
    """
    The ball!  Moves around the screen.
    """
    def __init__(self, x, y, width, height):
        super(Ball, self).__init__(x, y, width, height)
        self.image = pygame.Surface([width, height])
        self.image.fill(entity_color)
        self.x_direction = 1
        # Positive = down, negative = up
        self.y_direction = 1
        # Current speed.
        self.speed = 3
    def update(self):
        # Move the ball!
        self.rect.move_ip(self.speed * self.x_direction,
                          self.speed * self.y_direction)
        # Keep the ball in bounds, and make it bounce off the sides.
        if self.rect.y < 0:
            self.y_direction *= -1
        elif self.rect.y > window_height - 20:
            self.y_direction *= -1
        if self.rect.x < 0:
            self.x_direction *= -1
        elif self.rect.x > window_width - 20:
            self.x_direction *= -1

في هذه الفئة، يتم استخدام x_direction وy_direction للإمساك إذا كانت الكرة تتحرك يمينًا أو يسارًا ولأعلى أو لأسفل على التوالي. سيكونون دائمًا 1 أو -1. إن ضرب هذا في السرعة في move_ip() في دالة update يجعلها تتحرك بكسلات السرعة لكل إطار في الاتجاه الصحيح. تشبه هياكل if تلك الموجودة في فئة Player، حيث تُبقي الكرة على الشاشة، ولكن في هذه الحالة عن طريق إلغاء الاتجاه اعتمادًا على الجانب الذي تضربه. في الأساس، تتحرك الكرة باستمرار حول الشاشة، وترتد من الجوانب.

وهنا لدينا الكرة.

يحتاج أي لاعب عظيم إلى خصم عظيم، لذا فلنفعل ذلك أيضًا.

class Enemy(Paddle):
    """
    AI controlled paddle, simply moves towards the ball
    and nothing else.
    """
    def __init__(self, x, y, width, height):
        super(Enemy, self).__init__(x, y, width, height)
        self.y_change = 4
    def update(self):
        """
        Moves the Paddle while ensuring it stays in bounds
        """
        # Moves the Paddle up if the ball is above,
        # and down if below.
        if ball.rect.y < self.rect.y:
            self.rect.y -= self.y_change
        elif ball.rect.y > self.rect.y:
            self.rect.y += self.y_change
        # The paddle can never go above the window since it follows
        # the ball, but this keeps it from going under.
        if self.rect.y + self.height > window_height:
            self.rect.y = window_height - self.height

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

ball = Ball(window_width / 2, window_height / 2, 20, 20)
player = Player(20, window_height / 2, 20, 50)
enemy = Enemy(window_width - 40, window_height / 2, 20, 50)
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(ball)
all_sprites_list.add(player)
all_sprites_list.add(enemy)

بالانتقال إلى أسفل الفئات الموجودة في الملف المصدر، سترى إعدادًا من المفترض أن يكون مألوفًا جدًا. تتضمن الإضافات إنشاء اصناف الكرة واللاعب والعدو فعليًا باستخدام متغيرات window_width وwindow_height لوضعها بشكل صحيح. ثم أدناه نقوم بإنشاء all_sprites_list وهو sprite.Group(). فئة Group هذه هي السبب وراء وراثة Entity لفئة Sprite لأنها تمكننا من تخزين جميع كياناتنا في مجموعة Sprite.

ملاحظة: تستخدم المجموعات add() بدلاً من append() لإضافة عناصر إلى القائمة.
for ent in all_sprites_list:
        ent.update()
screen.fill(background)
all_sprites_list.draw(screen)

يمكن تكرار القائمة تمامًا مثل أي مصفوفة أخرى في Python، ولكل Entity في القائمة دالة التحديث الخاصة به. لدى sprite.Group دالة draw(). يتكرر استدعاء هذه الدالة من خلال كل من الكائنات الموجودة فيها، ويأخذ قيمة صورة الكائن، ويرسمها عند الإحداثيات المستقيمة للكائن. يتطلب الأمر أن يكون لكل Sprite صورة ومستقيم، ولكن مع هذا الإعداد الصحيح، فإنه يتعامل مع رسم جميع الصور على سطح الشاشة.

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

  • أولاً، اجعل الكرة ترتد فعليًا عن المجداف، وراجع الوثائق الخاصة بمجموعات الكائنات أو المستطيلات للعثور على إجابة.
  • بعد ذلك، إذا لاحظت تحرك العدو بسرعة أكبر من سرعة الكرة، فلن يخطئ العدو أبدًا. كيف يمكنك أن تجعل العدو يخطئ أحيانًا؟
  • ثالثاً، كيف يمكنك حساب وعرض نتيجة اللعبة، أو حتى إضافة قائمة رئيسية؟
  • وأخيرًا، حاول التوسع في مفهوم Pong البسيط وإضافة ميزات إضافية مثل تعزيزات الطاقة.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading

Scroll to Top