لعبة الغزاة الفضائيين بإستخدام وحدة Turtle

في هذا الدرس، ستستخدم وحدة turtle في بايثون لبناء نسخة طبق الأصل من لعبة Space Invaders. لا تحتاج لعبة Space Invaders إلى أي مقدمة. تم إصدار اللعبة الأصلية في عام 1978 وهي واحدة من أكثر ألعاب الفيديو شهرة على الإطلاق. لقد حددت بلا شك نوع ألعاب الفيديو الخاص بها. في هذا الدرس، ستنشئ نسخة طبق الأصل أساسية من هذه اللعبة.

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

في هذا الدرس، سوف تتعلم كيفية:

  • تصميم وبناء لعبة فيديو كلاسيكية
  • استخدم وحدة turtle لإنشاء صور متحركة
  • إضافة تفاعل المستخدم في برنامج يعتمد على الرسومات
  • إنشاء حلقة لعبة للتحكم في كل إطار من اللعبة
  • استخدم الدوال لتمثيل الإجراءات الرئيسية في اللعبة

هذا الدرس مثالي لأي شخص على دراية بمواضيع بايثون الأساسية ويريد استخدامها لبناء لعبة فيديو كلاسيكية من الصفر. لست بحاجة إلى أن تكون على دراية بوحدة turtle للعمل من خلال هذا الدرس.

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

العرض التوضيحي: لعبة غزاة الفضاء باستخدام turtle

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

هذا هو الشكل الذي ستبدو عليه لعبة السلحفاة الخاصة بك عند إكمال هذا الدرس:

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

نظرة عامة على المشروع

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

لإنشاء لعبة turtle، عليك اتباع الخطوات التالية:

  • إنشاء شاشة اللعبة ومدفع الليزر
  • تحريك المدفع إلى اليسار واليمين باستخدام المفاتيح
  • أطلاق أشعة الليزر باستخدام مفتاح المسافة
  • إنشاء كائنات فضائية وتحريكها نحو أسفل الشاشة
  • تحديد متى يضرب الليزر كائنًا فضائيًا
  • إنهاء اللعبة عندما يصل الكائن الفضائي إلى القاع
  • إضافة التوقيت والنتيجة
  • ضبط معدل إطارات اللعبة

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

المتطلبات الأساسية

لإكمال هذا الدرس، يجب أن تكون على دراية بالمفاهيم التالية:

  • تكرار التعليمات البرمجية باستخدام حلقات for وحلقات while
  • استخدام عبارات if للتحكم فيما يحدث في ظروف مختلفة
  • تحديد الدوال لتغليف الكود
  • استخدام القوائم لتخزين عناصر متعددة

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

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

الخطوة 1: إعداد لعبة Turtle باستخدام شاشة ومدفع ليزر

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

حان الوقت لاتخاذ الخطوة الأولى في إعداد لعبتك باستخدام وحدة turtle وإنشاء الشاشة.

إنشاء الشاشة

للبدء، قم بإنشاء ملف جديد واستيراد وحدة turtle:

import turtle

turtle.done()

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

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

import turtle

window = turtle.Screen()
window.setup(0.5, 0.75)
window.bgcolor(0.2, 0.2, 0.2)
window.title("pyarabic Space Invaders")

turtle.done()

تستخدم الأرقام العائمة كحجج لـ .setup()، الذي يضبط حجم الشاشة كجزء من شاشة الكمبيوتر لديك. يبلغ عرض النافذة 50 بالمائة من عرض الشاشة لديك، ويبلغ ارتفاعها 75 بالمائة من ارتفاع الشاشة لديك. يمكنك تجربة استخدام قيم البكسل لتعيين أبعاد الشاشة باستخدام الأعداد الصحيحة بدلاً من الأرقام العائمة كحجج في .setup().

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

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

إنشاء مدفع الليزر

تمامًا كما لا يمكنك لعب لعبة بدون شاشة، لا يمكنك لعب لعبة Space Invaders بدون مدفع ليزر. لإعداد المدفع، يمكنك إنشاء كائن Turtle باستخدام turtle.Turtle(). يحتاج كل عنصر مستقل في اللعبة إلى كائن Turtle خاص به.

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

import turtle

window = turtle.Screen()
window.setup(0.5, 0.75)
window.bgcolor(0.2, 0.2, 0.2)
window.title("The Real Python Space Invaders")

# Create laser cannon
cannon = turtle.Turtle()
cannon.penup()
cannon.color(1, 1, 1)
cannon.shape("square")

turtle.done()

الآن أصبح المدفع عبارة عن مربع أبيض. يمكنك استدعاء cannon.penup() حتى لا يرسم كائن Turtle خطًا عند تحريكه. يمكنك رؤية المدفع عند تشغيل الكود:

ومع ذلك، فأنت تريد أن يكون المدفع في أسفل الشاشة. وإليك كيفية تعيين الموضع الجديد للمدفع:

import turtle

window = turtle.Screen()
window.setup(0.5, 0.75)
window.bgcolor(0.2, 0.2, 0.2)
window.title("The Real Python Space Invaders")

LEFT = -window.window_width() / 2
RIGHT = window.window_width() / 2
TOP = window.window_height() / 2
BOTTOM = -window.window_height() / 2
FLOOR_LEVEL = 0.9 * BOTTOM

# Create laser cannon
cannon = turtle.Turtle()
cannon.penup()
cannon.color(1, 1, 1)
cannon.shape("square")
cannon.setposition(0, FLOOR_LEVEL)

turtle.done()

يمكنك تحديد حواف الشاشة باستخدام window.window_height() وwindow.window_width() نظرًا لأن هذه القيم ستكون مختلفة في إعدادات الكمبيوتر المختلفة. يحتوي مركز الشاشة على الإحداثيات (0، 0). لذا، يمكنك تقسيم العرض والارتفاع على اثنين، للحصول على إحداثيات x وy للحواف الأربعة.

يمكنك أيضًا تعريف FLOOR_LEVEL على أنه 90 بالمائة من إحداثيات y للحافة السفلية. ثم ضع المدفع عند هذا المستوى باستخدام .setposition() بحيث يكون مرتفعًا قليلاً عن أسفل الشاشة.

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

# ...

# Draw cannon
cannon.turtlesize(1, 4)  # Base
cannon.stamp()
cannon.sety(FLOOR_LEVEL + 10)
cannon.turtlesize(1, 1.5)  # Next tier
cannon.stamp()
cannon.sety(FLOOR_LEVEL + 20)
cannon.turtlesize(0.8, 0.3)  # Tip of cannon
cannon.stamp()
cannon.sety(FLOOR_LEVEL)

turtle.done()

عندما تقوم بتشغيل هذا الكود، سوف ترى المدفع في أسفل الشاشة:

مهمتك التالية هي توفير بعض التفاعل حتى تتمكن من تحريك المدفع إلى اليسار واليمين.

الخطوة 2: تحريك المدفع باستخدام المفاتيح

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

للبدء، عليك أن تتعرف على التقنيات الأساسية المستخدمة في الرسوم المتحركة.

تحريك كائن Turtle

يمكنك ربط دالة بمفتاح باستخدام window.onkeypress(). يستدعي الضغط على المفتاح الدالة المرتبطة بهذا المفتاح. يمكنك تعريف دالتين لتحريك المدفع إلى اليسار واليمين، وربطهما بمفاتيح الأسهم اليسرى واليمنى على لوحة المفاتيح:

import turtle

CANNON_STEP = 10

# ...

def move_left():
    cannon.setx(cannon.xcor() - CANNON_STEP)

def move_right():
    cannon.setx(cannon.xcor() + CANNON_STEP)

window.onkeypress(move_left, "Left")
window.onkeypress(move_right, "Right")
window.onkeypress(turtle.bye, "q")
window.listen()
turtle.done()

هل لاحظت أنك استخدمت أسماء الدالتين move_left وmove_right بدون أقواس كوسيطتين لـ .onkeypress()؟ إذا أضفت أقواسًا، يستدعي البرنامج الدالة عندما ينفذ السطر باستخدام .onkeypress()، ويربط قيمة إرجاع الدالة بالمفتاح. تُرجع الدالتان move_left() وmove_right() قيمة None، لذا إذا أضفت أقواسًا، فستظل مفاتيح الأسهم غير نشطة.

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

يمكنك أيضًا توجيه التركيز إلى الشاشة باستخدام window.listen() حتى يتم جمع أحداث ضغط المفاتيح بواسطة البرنامج. بشكل افتراضي، لا تستخدم وحدة turtle موارد الحوسبة للاستماع إلى ضغطات المفاتيح أثناء تشغيل البرنامج. يؤدي استدعاء window.listen() إلى تجاوز هذا الإعداد الافتراضي. إذا لم تعمل المفاتيح عند تشغيل لعبة turtle، فتأكد من تضمينlisten() لأنه من السهل نسيانه عند استخدام .onkeypress().

الآن بعد أن أصبحت على دراية بربط المفاتيح، ستضيف ميزة لإنهاء البرنامج عن طريق ربط المفتاح Q بـ turtle.bye. وكما فعلت مع الدوال السابقة، لا تضيف أقواسًا إلى turtle.bye على الرغم من أنها دالة. تأكد من استخدام حرف q صغيرًا لأنه إذا استخدمت حرفًا كبيرًا في الكود، فستحتاج إلى الضغط على Shift+Q لإنهاء البرنامج.

الآن عند تشغيل البرنامج، ستتمكن من الضغط على مفاتيح الأسهم اليمنى واليسرى لتحريك كائن Turtle.

هذا ليس السلوك الذي تريده تمامًا. لاحظ كيف يتحرك جزء واحد فقط من الرسم عند الضغط على مفاتيح الأسهم. هذا هو الشكل الذي يمثل كائن Turtle. أما بقية الرسم فهو النتيجة لإستدعاءات .stamp() في قسم التعليمات البرمجية حيث ترسم مدفع الليزر.

تحريك مدفع الليزر بأكمله

في كل مرة تحرك فيها مدفع الليزر، يمكنك مسح الرسم السابق وإعادة رسم المدفع في الموقع الجديد. لتجنب التكرار، يمكنك نقل الكود لرسم المدفع إلى دالة draw_cannon(). في الكود التالي، معظم الأسطر في draw_cannon() هي نفس الأسطر التي كانت لديك في الإصدار السابق، لكنها الآن مسننة:

# ...

def draw_cannon():
    cannon.clear()
    cannon.turtlesize(1, 4)  # Base
    cannon.stamp()
    cannon.sety(FLOOR_LEVEL + 10)
    cannon.turtlesize(1, 1.5)  # Next tier
    cannon.stamp()
    cannon.sety(FLOOR_LEVEL + 20)
    cannon.turtlesize(0.8, 0.3)  # Tip of cannon
    cannon.stamp()
    cannon.sety(FLOOR_LEVEL)

def move_left():
    cannon.setx(cannon.xcor() - CANNON_STEP)
    draw_cannon()

def move_right():
    cannon.setx(cannon.xcor() + CANNON_STEP)
    draw_cannon()

window.onkeypress(move_left, "Left")
window.onkeypress(move_right, "Right")
window.onkeypress(turtle.bye, "q")
window.listen()

draw_cannon()

turtle.done()

عند استدعاء cannon.clear()، يتم حذف جميع الرسومات التي رسمها cannon من الشاشة. يمكنك استدعاء draw_cannon() في كل من move_left() وmove_right() لإعادة رسم المدفع في كل مرة تحركه فيها. يمكنك أيضًا استدعاء draw_cannon() في القسم الرئيسي من البرنامج لرسم المدفع في بداية اللعبة.

يتحرك مدفع الليزر بالكامل عند الضغط على مفاتيح الأسهم اليسرى أو اليمنى. تقوم الدالة draw_cannon() بحذف المستطيلات الثلاثة التي تشكل المدفع وإعادة رسمها.

ومع ذلك، فإن حركة المدفع ليست سلسة وقد تتعطل إذا حاولت تحريكه بسرعة كبيرة يمكنك إصلاح هذه المشكلة من خلال التحكم في وقت رسم العناصر على الشاشة في وحدة turtle.

التحكم في وقت عرض العناصر

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

تستغرق هذه الإجراءات بعض الوقت، وإذا ضغطت على مفتاح السهم أثناء عملية الرسم هذه، يتحرك كائن Turtle قبل أن يتوفر له الوقت الكافي لإكمال الرسم.

تصبح هذه المشكلة أكثر وضوحًا عند حدوث المزيد من الأحداث في اللعبة. الآن هو الوقت المناسب لإصلاح هذه المشكلة. يمكنك منع عرض أي تغييرات على الشاشة عن طريق ضبط window.tracer(0).

سيظل البرنامج يغير إحداثيات كائن Turtle واتجاهه، لكنه لن يعرض التغييرات على الشاشة. يمكنك التحكم في وقت تحديث الشاشة عن طريق استدعاء window.update(). يمنحك هذا التحكم في وقت تحديث الشاشة.

يمكنك استدعاء window.tracer(0) مباشرة بعد إنشاء الشاشة وwindow.update() في draw_cannon():

import turtle

CANNON_STEP = 10

window = turtle.Screen()
window.tracer(0)

# ...

def draw_cannon():
    # ...
    window.update()

# ...

الآن لم تعد هناك أي مشكلات عند تحريك المدفع، ولم يعد موضع cannon الأولي من المركز إلى الأسفل مرئيًا.

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

منع مدفع الليزر من مغادرة الشاشة

يتم استدعاء الدالتين move_left() و move_right() في كل مرة يضغط فيها اللاعب على أحد مفاتيح الأسهم لتحريك مدفع الليزر إلى اليسار أو اليمين. للتحقق مما إذا كان مدفع الليزر قد وصل إلى حافة الشاشة، يمكنك إضافة عبارة if. بدلاً من استخدام LEFT و RIGHT، وهما حواف الشاشة، ستتضمن ميزابًا لإيقاف المدفع قبل وصوله إلى الحواف:

# ...

LEFT = -window.window_width() / 2
RIGHT = window.window_width() / 2
TOP = window.window_height() / 2
BOTTOM = -window.window_height() / 2
FLOOR_LEVEL = 0.9 * BOTTOM
GUTTER = 0.025 * window.window_width()

# ...

def move_left():
    new_x = cannon.xcor() - CANNON_STEP
    if new_x >= LEFT + GUTTER:
        cannon.setx(new_x)
        draw_cannon()

def move_right():
    new_x = cannon.xcor() + CANNON_STEP
    if new_x <= RIGHT - GUTTER:
        cannon.setx(new_x)
        draw_cannon()

# ...

الآن، لن يتمكن مدفع الليزر من مغادرة الشاشة. ستستخدم تقنيات مماثلة في الخطوات التالية عند إضافة الليزر والكائنات الفضائية إلى لعبة turtle الخاصة بك.

الخطوة 3: إطلاق أشعة الليزر باستخدام مفتاح المسافة

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

للبدء، سوف تستخدم كائنات Turtle لإنشاء وتخزين أشعة الليزر.

إنشاء الليزر

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

# ...

lasers = []

def draw_cannon():
    # ...

def move_left():
    # ...

def move_right():
    # ...

def create_laser():
    laser = turtle.Turtle()
    laser.penup()
    laser.color(1, 0, 0)
    laser.hideturtle()
    laser.setposition(cannon.xcor(), cannon.ycor())
    laser.setheading(90)
    # Move laser to just above cannon tip
    laser.forward(20)
    # Prepare to draw the laser
    laser.pendown()
    laser.pensize(5)

    lasers.append(laser)

# Key bindings
window.onkeypress(move_left, "Left")
window.onkeypress(move_right, "Right")
window.onkeypress(create_laser, "space")
window.onkeypress(turtle.bye, "q")
window.listen()

draw_cannon()

turtle.done()

يرتبط مفتاح المسافة بـ create_laser()، والذي ينشئ كائن Turtle جديدًا لتمثيل الليزر، ويحدد سماته الأولية، ويضيفه إلى قائمة جميع الليزر. لتجنب تعقيد الكود، يقوم هذا البرنامج التعليمي ببعض التبسيطات، والتي يمكنك القراءة عنها في القسم التالي:

هناك نوعان من التبسيطات لمنع التعليمات البرمجية من أن تصبح معقدة للغاية:

تم ترميز ارتفاع المدفع إلى 20 بكسل. تُستخدم هذه القيمة في draw_cannon() لوضع المستطيلات التي تشكل المدفع، وفي create_laser() لوضع نقطة بداية الليزر عند طرف المدفع. بشكل عام، يجب أن تحاول تجنب ترميز القيم بشكل ثابت. يمكن تعريف هذه القيمة كنسبة مئوية من ارتفاع الشاشة، والتغييرات المناسبة التي تم إجراؤها على draw_cannon() لتعيين عرض وارتفاع المستطيلات الثلاثة المستخدمة لرسم المدفع.

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

يقوم البرنامج بإنشاء كائنات Turtle جديدة لكل ليزر عند الضغط على مفتاح المسافة. يمكنك إضافة print(len(lasers)) في create_laser() للتأكد من أن البرنامج ينشئ ليزرًا جديدًا عند الضغط على مفتاح المسافة.

في القسم التالي، ستنقل أشعة الليزر في كل إطار من اللعبة. ثم ستنشئ حلقة لعبة لتشمل كل ما يحدث في كل إطار.

إنشاء حلقة اللعبة لتحريك الليزر

يمكن تقسيم بنية اللعبة إلى ثلاثة أجزاء:

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

لقد تعاملت بالفعل مع جزء كبير من عملية تهيئة لعبة turtle هذه. والآن حان الوقت للعمل على حلقة اللعبة.

تعتمد بنية حلقة اللعبة على المنصة التي تستخدمها. في وحدة turtle، ستحتاج إلى كتابة حلقة while خاصة بك لتحديث حالة الكائنات في اللعبة والتحقق من الأحداث، مثل الاصطدامات بين الكائنات. ومع ذلك، تتم إدارة الأحداث الأخرى من خلال طرق turtle مثل .onkeypress(). يقوم برنامج turtle بتشغيل حلقة حدث، والتي تبدأ عند استدعاء turtle.done() وتتعامل مع الأحداث مثل ضغطات المفاتيح.

يمكنك مقارنة حلقة اللعبة التي ستحتاجها عند العمل مع turtle، بهياكل حلقة اللعبة في حزم ألعاب بايثون الأخرى مثل pygame وarcade.

في لعبة turtle هذه حتى الآن، العنصر الوحيد الذي يتحرك هو المدفع، والذي يمكنك تحريكه باستخدام مفاتيح الأسهم. لا يتحرك المدفع في الأوقات الأخرى.

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

import turtle

CANNON_STEP = 10
LASER_LENGTH = 20
LASER_SPEED = 10

# ...

def create_laser():
    laser = turtle.Turtle()
    laser.penup()
    laser.color(1, 0, 0)
    laser.hideturtle()
    laser.setposition(cannon.xcor(), cannon.ycor())
    laser.setheading(90)
    # Move laser to just above cannon tip
    laser.forward(20)
    # Prepare to draw the laser
    laser.pendown()
    laser.pensize(5)

    lasers.append(laser)

def move_laser(laser):
    laser.clear()
    laser.forward(LASER_SPEED)
    # Draw the laser
    laser.forward(LASER_LENGTH)
    laser.forward(-LASER_LENGTH)

# ...

# Game loop
while True:
    # Move all lasers
    for laser in lasers:
        move_laser(laser)
    window.update()

turtle.done()

تقوم بتعريف الدالة move_laser()، التي تقبل كائن Turtle. تقوم الدالة بمسح الرسم السابق لليزر وتحريك كائن Turtle للأمام بمقدار يمثل سرعة الليزر. هذه هي أول ثلاث استدعاءات لـ laser.forward().

لرسم الليزر على الشاشة، يمكنك استخدام تقنية مختلفة. عندما أنشأت المدفع، استخدمت شكل كائن Turtle. لرسم الليزر، ارسم خطًا باستخدام كائن Turtle.

ألق نظرة على create_laser() ولاحظ أنك قمت باستدعاء laser.hideturtle() لإخفاء الصورة المتحركة، وlaser.pendown() وlaser.pensize(5) لتمكين السلحفاة من رسم خطوط بعرض خمسة بكسلات عندما تتحرك. الآن، ستحتاج إلى إعادة كائن Turtle إلى موضعه الأولي في move_laser() حتى يكون جاهزًا للإطار التالي.

تحتوي حلقة while على حلقة for تتكرر عبر جميع أشعة الليزر وتنقلها للأمام. يمكنك أيضًا إضافة window.update() في حلقة اللعبة لتحديث العرض مرة واحدة في كل إطار. في هذه المرحلة، يمكنك أيضًا إزالة window.update() من draw_cannon() لأنك لم تعد بحاجة إليها.

عندما تقوم بتشغيل هذا الكود وتضغط على مفتاح المسافة، ستشاهد أشعة الليزر تنطلق من المدفع:

بعد ذلك، ستتعرف على ما يجب فعله بالليزر عندما يغادر الجزء العلوي من الشاشة.

إزالة أشعة الليزر التي تخرج من الشاشة

عندما يخرج الليزر من الشاشة، فلن تراه بعد الآن. ولكن، لا يزال كائن السلحفاة موجودًا في قائمة الليزر ولا يزال يتعين على البرنامج التعامل مع هذا الليزر. وحتى عندما يكون الليزر خارج مجال الرؤية، لا يزال البرنامج يحرك الليزر إلى الأعلى، حتى ما لا نهاية وما بعد ذلك! بمرور الوقت، ستحتوي قائمة الليزر على عدد كبير من الكائنات التي لم يعد لها أي دور في اللعبة.

عندما يحرك البرنامج ليزرًا، فإن عنق الزجاجة في الأداء هو عملية رسم الليزر على الشاشة. يعد تحديث إحداثيات y لكائن Turtle حلاً سريعًا. عندما يترك الليزر الشاشة، لا يحتاج البرنامج إلى رسمه بعد الآن. لذلك، ستحتاج إلى إطلاق العديد من الليزر قبل أن ترى أي تباطؤ ملحوظ في اللعبة. ومع ذلك، من الجيد دائمًا إزالة الكائنات التي لم تعد بحاجة إليها.

لإزالة هذه الكائنات عند خروجها من الشاشة، قم بإزالتها من قائمة lasers، وهذا يمنع حلقة اللعبة من تحريكها للأمام. ولكن لأن وحدة turtle تحتفظ بقائمة داخلية لجميع كائنات Turtle، فإذا قمت بإزالة الليزر من هذه القائمة الداخلية باستخدام turtle.turtles()، فسيتم أيضًا إزالة الكائنات من الذاكرة:

# ...

# Game loop
while True:
    # Move all lasers
    for laser in lasers.copy():
        move_laser(laser)
        # Remove laser if it goes off screen
        if laser.ycor() > TOP:
            laser.clear()
            laser.hideturtle()
            lasers.remove(laser)
            turtle.turtles().remove(laser)
    window.update()

turtle.done()

لاحظ كيف تتكرر حلقة for عبر نسخة من الليزر، حيث أنه من الأفضل عدم تكرار قائمة يتم تغييرها داخل نفس حلقة for.

في هذه المرحلة، يمكنك إضافة print(len(turtle.turtles())) و print(len(lasers)) إلى حلقة اللعبة للتأكيد على إزالة الليزر من هذه القوائم عند مغادرتها الشاشة.

عمل جيد! أنت جاهز لإنشاء كائنات فضائية ونقلها إلى أسفل الشاشة.

الخطوة 4: إنشاء الكائنات الفضائية ونقلها

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

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

  • تظهر كائنات فضائية جديدة كل بضع ثوانٍ بدلاً من ظهورها عندما يضغط اللاعب على مفتاح.
  • يظهر الفضائيون في الجزء العلوي من الشاشة في موضع x عشوائي بدلاً من ظهورهم على طرف المدفع.
  • شكل ولون الكائنات الفضائية يختلف عن الليزر.

بخلاف هذه الاختلافات الطفيفة، فإن بقية الكود سوف يعكس الكود الذي كتبته لإنشاء الليزر وتحريكه.

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

توليد كائنات فضائية جديدة

للبدء، قم بتعريف الدالة الجديدة create_alien() وقائمة لتخزين الكائنات الفضائية:

import random
import turtle

# ...

lasers = []
aliens = []

# ...

def create_alien():
    alien = turtle.Turtle()
    alien.penup()
    alien.turtlesize(1.5)
    alien.setposition(
        random.randint(
            int(LEFT + GUTTER),
            int(RIGHT - GUTTER),
        ),
        TOP,
    )
    alien.shape("turtle")
    alien.setheading(-90)
    alien.color(random.random(), random.random(), random.random())
    aliens.append(alien)

#  ...

تضع الكائنات الفضائية في أعلى الشاشة في موضع عشوائي على شكل حرف X. وتواجه الكائنات الفضائية الأسفل. كما يمكنك تعيين قيم عشوائية لمكونات الألوان الأحمر والأخضر والأزرق بحيث يكون لكل كائن فضائي لون عشوائي.

تظهر الكائنات الفضائية على فترات زمنية منتظمة في لعبة السلحفاة هذه. يمكنك استيراد وحدة time وتعيين مؤقت لتوليد كائن فضائي جديد:

import random
import time
import turtle

CANNON_STEP = 10
LASER_LENGTH = 20
LASER_SPEED = 10
ALIEN_SPAWN_INTERVAL = 1.2  # Seconds

# ...

# Game loop
alien_timer = 0
while True:
    # ...

    # Spawn new aliens when time interval elapsed
    if time.time() - alien_timer > ALIEN_SPAWN_INTERVAL:
        create_alien()
        alien_timer = time.time()
    window.update()

turtle.done()

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

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

تحريك الكائنات الفضائية

هذا هو المكان الذي ستنقل فيه جميع الكائنات الفضائية الموجودة في قائمة الكائنات الفضائية في اللعبة:

import random
import time
import turtle

CANNON_STEP = 10
LASER_LENGTH = 20
LASER_SPEED = 10
ALIEN_SPAWN_INTERVAL = 1.2  # Seconds
ALIEN_SPEED = 2

# ...

# Game loop
alien_timer = 0
while True:
    # ...

    # Move all aliens
    for alien in aliens:
        alien.forward(ALIEN_SPEED)
    window.update()

turtle.done()

الآن يتحرك الكائنات الفضائية باستمرار إلى الأسفل:

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

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

الخطوة 5: تحديد وقت إصابة الكائنات الفضائية بالليزر

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

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

من المؤكد أن هذا ليس الخيار الأكثر كفاءة، لكنه جيد بما فيه الكفاية لهذه النسخة المبسطة من الغزاة الفضائيين.

في حلقة اللعبة، لديك بالفعل حلقة تتكرر عبر قائمة الليزر. يمكنك إضافة حلقة أخرى للتكرار عبر كل كائن فضائي لكل ليزر:

# ...

while True:
    # Move all lasers
    for laser in lasers.copy():
        move_laser(laser)
        # Remove laser if it goes off screen
        if laser.ycor() > TOP:
            laser.clear()
            laser.hideturtle()
            lasers.remove(laser)
            turtle.turtles().remove(laser)
        # Check for collision with aliens
        for alien in aliens.copy():
            if laser.distance(alien) < 20:
                # TODO Remove alien and laser
                ...

# ...

تتكرر حلقة for الجديدة عبر نسخة من الكائنات الفضائية، وهي متداخلة داخل حلقة for التي تتكرر عبر أشعة الليزر. يمكنك استخدام aliens.copy() لأنك ستزيل الكائنات الفضائية من هذه القائمة في بعض الإطارات. تأكد من تجنب تغيير القائمة التي تستخدمها للتكرار.

يعرض الكود أعلاه تعليق # TODO بدلاً من كتلة الكود المطلوبة. يجب أن يقوم الكود الذي تحتاج إلى كتابته هنا بتنفيذ الإجراءات التالية:

  • قم بإزالة الليزر من الشاشة ومن الذاكرة.
  • قم بإزالة الكائن الفضائي من الشاشة ومن الذاكرة.

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

# ...
laser.clear()
laser.hideturtle()
lasers.remove(laser)
turtle.turtles().remove(laser)

يجب عليك تجنب تكرار نفس أسطر التعليمات البرمجية. لذا يمكنك وضع هذه الأسطر في دالة واستدعائها مرتين. ومع ذلك، فهذه هي نفس الخطوات المطلوبة لإزالة الكائن الفضائي من اللعبة. لذلك، يمكنك كتابة دالة يمكنها إزالة أي كائن فضائي واستخدامه لليزر والكائنات الفضائية:

تقبل الدالة remove_sprite() كائن Turtle وقائمة، وتمسح Turtle وأي رسومات مرتبطة بها. يمكنك استدعاء الدالة remove_sprite() مرتين لليزر: مرة عندما يغادر الليزر الشاشة ومرة ​​أخرى عندما يضرب كائنًا فضائيًا. يمكنك أيضًا استدعاء الدالة remove_sprite() مرة واحدة للكائنات الفضائية في حلقة اللعبة.

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

يجب أن تبدو لعبتك الآن بهذا الشكل:

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

الخطوة 6: إنهاء اللعبة

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

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

# ...

# Game loop
alien_timer = 0
game_running = True
while game_running:
    # ...

    # Move all aliens
    for alien in aliens:
        alien.forward(ALIEN_SPEED)
        # Check for game over
        if alien.ycor() < FLOOR_LEVEL:
            game_running = False
            break
    window.update()

turtle.done()

عندما يكون إحداثي y للكائن الفضائي أقل من مستوى الأرض، يمكنك تعيين العلم المنطقي game_running على False وإيقاف حلقة for. يشير العلم إلى حلقة while للتوقف عن التكرار.

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

# ...

# Game loop
alien_timer = 0
game_running = True
while game_running:
    # ...

    window.update()

splash_text = turtle.Turtle()
splash_text.hideturtle()
splash_text.color(1, 1, 1)
splash_text.write("GAME OVER", font=("Courier", 40, "bold"), align="center")

turtle.done()

يمكنك إنشاء سلحفاة جديدة لكتابة نص على الشاشة. اللعبة مكتملة تقريبًا الآن:

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

الخطوة 7: أضف التوقيت والنتيجة

في هذه الخطوة، ستضيف التوقيت وتعرض عدد الكائنات الفضائية التي ضربها اللاعب.

للبدء، عليك إنشاء مؤقت وعرضه.

إنشاء مؤقت

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

# ...

# Create laser cannon
cannon = turtle.Turtle()
cannon.penup()
cannon.color(1, 1, 1)
cannon.shape("square")
cannon.setposition(0, FLOOR_LEVEL)

# Create turtle for writing text
text = turtle.Turtle()
text.penup()
text.hideturtle()
text.setposition(LEFT * 0.8, TOP * 0.8)
text.color(1, 1, 1)

# ...

# Game loop
alien_timer = 0
game_timer = time.time()
game_running = True
while game_running:
    time_elapsed = time.time() - game_timer
    text.clear()
    text.write(
        f"Time: {time_elapsed:5.1f}s",
        font=("Courier", 20, "bold"),
    )

    # ...

تكتب الوقت المنقضي في الزاوية اليمنى العليا من الشاشة في بداية كل إطار. في الوسيطة الأولى في .write()، تستخدم مواصفة التنسيق :5.1f لعرض الوقت المنقضي بإجمالي خمسة أحرف ورقم واحد بعد النقطة العشرية.

أضف النتيجة

أنشئ متغيرًا آخر للاحتفاظ بالنتيجة. قم بزيادة النتيجة في كل مرة يضرب فيها اللاعب كائنًا فضائيًا وعرض النتيجة أسفل الوقت المنقضي:

# ...

# Game loop
alien_timer = 0
game_timer = time.time()
score = 0
game_running = True
while game_running:
    time_elapsed = time.time() - game_timer
    text.clear()
    text.write(
        f"Time: {time_elapsed:5.1f}s\nScore: {score:5}",
        font=("Courier", 20, "bold"),
    )

    # ...
        # Check for collision with aliens
        for alien in aliens.copy():
            if laser.distance(alien) < 20:
                remove_sprite(laser, lasers)
                remove_sprite(alien, aliens)
                score += 1
                break

    # ...

تُظهر لعبة السلحفاة هذه الآن عدد الكائنات الفضائية التي تم ضربها والوقت المنقضي في كل إطار من اللعبة:

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

الخطوة 8: تحسين حركة المدفع

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

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

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

  • 1 يمثل الحركة إلى اليمين
  • -1 يمثل الحركة إلى اليسار
  • 0 يشير إلى أن المدفع لا يتحرك

يمكنك بعد ذلك تغيير هذه القيمة في move_left() و move_right()، واستخدامها للتحكم في حركة المدفع في حلقة اللعبة:

import random
import time
import turtle

CANNON_STEP = 3

# ...

# Create laser cannon
cannon = turtle.Turtle()
cannon.penup()
cannon.color(1, 1, 1)
cannon.shape("square")
cannon.setposition(0, FLOOR_LEVEL)
cannon.cannon_movement = 0  # -1, 0 or 1 for left, stationary, right

# ...

def move_left():
    cannon.cannon_movement = -1

def move_right():
    cannon.cannon_movement = 1

# ...

# Game loop
alien_timer = 0
game_timer = time.time()
score = 0
game_running = True
while game_running:
    time_elapsed = time.time() - game_timer
    text.clear()
    text.write(
        f"Time: {time_elapsed:5.1f}s\nScore: {score:5}",
        font=("Courier", 20, "bold"),
    )

    # Move cannon
    new_x = cannon.xcor() + CANNON_STEP * cannon.cannon_movement
    if LEFT + GUTTER <= new_x <= RIGHT - GUTTER:
        cannon.setx(new_x)
        draw_cannon()

    #  ...

لا تزال مفاتيح الأسهم اليسرى واليمنى مرتبطة بنفس الوظائف، move_left() وmove_right(). ومع ذلك، تقوم هذه الوظائف بتغيير قيمة سمة البيانات cannon.cannon_movement.

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

السبب الذي يجعلك بحاجة إلى استخدام سمة بيانات بدلاً من متغير قياسي هو أنه يمكنك تعديل قيمته داخل الدوال. إذا قمت بتعريف متغير قياسي لهذه القيمة، فستحتاج الدوال move_left() و move_right() إلى إرجاع هذه القيمة. لكن هذه الدوال مرتبطة بمفاتيح، ويتم استدعاؤها من داخل .onkeypress()، لذا لا يمكنك الوصول إلى أي بيانات تعيدها.

يمكنك أيضًا تقليل قيمة CANNON_STEP في التعديل الأخير لإبطاء المدفع عند تحركه. يمكنك اختيار القيمة المناسبة لنظامك.

عند تشغيل هذا الكود، ستلاحظ أنه يمكنك ضبط المدفع للتحرك إما إلى اليسار أو اليمين، ويمكنك تغيير اتجاهه. ولكن بمجرد أن يبدأ المدفع في الحركة، فإنه لا يتوقف أبدًا. لا تقم أبدًا بتعيين cannon.cannon_movement إلى 0 مرة أخرى في الكود. بدلاً من ذلك، تريد أن تعود هذه القيمة إلى 0 عند تحرير مفاتيح الأسهم. استخدم .onkeyrelease() لتحقيق ذلك:

# ...

def move_left():
    cannon.cannon_movement = -1

def move_right():
    cannon.cannon_movement = 1

def stop_cannon_movement():
    cannon.cannon_movement = 0

# ...

# Key bindings
window.onkeypress(move_left, "Left")
window.onkeypress(move_right, "Right")
window.onkeyrelease(stop_cannon_movement, "Left")
window.onkeyrelease(stop_cannon_movement, "Right")
window.onkeypress(create_laser, "space")
window.onkeypress(turtle.bye, "q")
window.listen()

# ...

الآن يمكنك الضغط مع الاستمرار على مفاتيح الأسهم لتحريك المدفع. عندما تحرر مفاتيح الأسهم، يتوقف المدفع عن الحركة:

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

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

الخطوة 9: ضبط معدل إطارات اللعبة

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

في هذه الخطوة، سوف تقوم بتثبيت مدة الإطار عن طريق اختيار معدل الإطارات.

هنا يمكنك تعيين معدل الإطارات بالإطارات في الثانية، وحساب الوقت لإطار واحد بهذا المعدل:

import random
import time
import turtle

FRAME_RATE = 30  # Frames per second
TIME_FOR_1_FRAME = 1 / FRAME_RATE  # Seconds

# ...

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

import random
import time
import turtle

FRAME_RATE = 30  # Frames per second
TIME_FOR_1_FRAME = 1 / FRAME_RATE  # Seconds

CANNON_STEP = 10
LASER_LENGTH = 20
LASER_SPEED = 20
ALIEN_SPAWN_INTERVAL = 1.2  # Seconds
ALIEN_SPEED = 3.5

# ...

# Game loop
alien_timer = 0
game_timer = time.time()
score = 0
game_running = True
while game_running:
    timer_this_frame = time.time()

    # ...

    time_for_this_frame = time.time() - timer_this_frame
    if time_for_this_frame < TIME_FOR_1_FRAME:
        time.sleep(TIME_FOR_1_FRAME - time_for_this_frame)
    window.update()

# ...

هذا هو الوقت المناسب لضبط معلمات اللعبة لتناسب تفضيلاتك، مثل القيم التي تحدد سرعة المدفع والليزر والكائنات الفضائية.

وهنا النسخة النهائية من اللعبة:

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


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

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

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

Scroll to Top

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

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

Continue reading