Pygame: إنشاء لعبتك الأولى بلغة Python
أنشئ لعبتك الأولى بلغة Python باستخدام Pygame: حلقة اللعبة، والـ sprites، والتصادمات، والأصوات، والنصوص والنتيجة، مع الشيفرة الكاملة للعبة التقاط الأشياء.
InSkillCoach
Pygame: إنشاء لعبتك الأولى بلغة Python
تُعد Pygame المكتبة الأسهل للوصول إليها لإنشاء ألعاب ثنائية الأبعاد بلغة Python. فهي تتولى العرض والصوت ولوحة المفاتيح والفأرة والتصادمات، وتبقى بسيطة بما يكفي ليبني المبتدئ لعبة كاملة في بضع ساعات. في هذا الدليل ستتعلم اللبنات الأساسية في Pygame، ثم تجمعها في لعبة صغيرة كاملة لالتقاط الأشياء، تجد شيفرتها الكاملة مرفقة مع الشرح.
التثبيت
تُثبَّت Pygame باستخدام pip:
pip install pygame
# Vérification : lance un jeu d'exemple fourni avec la bibliothèque
python -m pygame.examples.aliens
إذا فُتحت نافذة اللعبة التجريبية، فكل شيء جاهز.
النافذة وحلقة اللعبة
تقوم كل لعبة على البنية نفسها: حلقة لا نهائية تعالج في كل دورة الأحداث (لوحة المفاتيح، الفأرة، الإغلاق)، ثم تحدّث حالة اللعبة، ثم تعيد رسم الشاشة.
import pygame
# Initialisation de tous les modules Pygame
pygame.init()
# Création de la fenêtre (largeur, hauteur)
ecran = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Mon premier jeu")
# Horloge pour contrôler la vitesse du jeu
horloge = pygame.time.Clock()
en_cours = True
while en_cours:
# 1. ÉVÉNEMENTS : traiter les entrées de l'utilisateur
for event in pygame.event.get():
if event.type == pygame.QUIT: # clic sur la croix de la fenêtre
en_cours = False
# 2. UPDATE : mettre à jour la logique du jeu (positions, score...)
# 3. DRAW : tout redessiner
ecran.fill((30, 30, 60)) # fond bleu nuit (R, G, B)
pygame.display.flip() # afficher le résultat à l'écran
# 4. Limiter le jeu à 60 images par seconde
horloge.tick(60)
pygame.quit()
نقطتان أساسيتان:
- تنقل
pygame.display.flip()إلى الشاشة كل ما رُسم خلال دورة الحلقة. وبدون هذا الاستدعاء لن يظهر أي شيء. - تجعل
horloge.tick(60)الحلقة تنتظر بالقدر اللازم فقط لتدور 60 مرة في الثانية. وبدونها ستعمل اللعبة بسرعة مختلفة على كل جهاز.
رسم الأشكال والصور
توفر Pygame دوال رسم أساسية وتحميل الصور:
# Formes géométriques
pygame.draw.rect(ecran, (255, 0, 0), (100, 100, 50, 80)) # rectangle rouge
pygame.draw.circle(ecran, (0, 255, 0), (400, 300), 40) # cercle vert
pygame.draw.line(ecran, (255, 255, 255), (0, 0), (800, 600), 3) # ligne blanche
# Chargement et affichage d'une image
# convert_alpha() accélère l'affichage et conserve la transparence PNG
joueur_img = pygame.image.load("joueur.png").convert_alpha()
ecran.blit(joueur_img, (200, 150)) # blit = copier l'image à une position
# Redimensionner une image
joueur_img = pygame.transform.scale(joueur_img, (64, 64))
يبدأ نظام الإحداثيات من الزاوية العلوية اليسرى: تزداد قيمة x نحو اليمين، وتزداد قيمة y نحو الأسفل.
التعامل مع لوحة المفاتيح والفأرة
توجد طريقتان متكاملتان:
# Approche 1 : les événements (parfaits pour les actions ponctuelles)
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
print("Saut !") # déclenché une seule fois par appui
if event.type == pygame.MOUSEBUTTONDOWN:
print(f"Clic en {event.pos}") # position (x, y) du clic
# Approche 2 : l'état des touches (parfait pour le déplacement continu)
touches = pygame.key.get_pressed()
if touches[pygame.K_LEFT]:
x -= 5 # tant que la flèche gauche est maintenue
if touches[pygame.K_RIGHT]:
x += 5
# Position actuelle de la souris
souris_x, souris_y = pygame.mouse.get_pos()
تذكّر القاعدة: استخدم الأحداث للإجراءات اللحظية (إطلاق النار، القفز، الإيقاف المؤقت)، واستخدم get_pressed() للحركة السلسة.
الـ Sprites والمجموعات
الـ sprite هو كائن في اللعبة يجمع بين صورة (self.image) وموضع (self.rect). وتتيح المجموعات تحديث ورسم عشرات الـ sprites بسطر واحد:
import pygame
class Joueur(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
# Image : ici un simple carré bleu de 50x50 pixels
self.image = pygame.Surface((50, 50))
self.image.fill((50, 150, 255))
# Le rect stocke la position et sert aux collisions
self.rect = self.image.get_rect(center=(400, 550))
self.vitesse = 7
def update(self):
"""Appelée à chaque image : déplace le joueur au clavier."""
touches = pygame.key.get_pressed()
if touches[pygame.K_LEFT]:
self.rect.x -= self.vitesse
if touches[pygame.K_RIGHT]:
self.rect.x += self.vitesse
# Empêcher de sortir de l'écran
self.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))
# Utilisation avec un groupe
tous_les_sprites = pygame.sprite.Group()
tous_les_sprites.add(Joueur())
# Dans la boucle de jeu :
tous_les_sprites.update() # appelle update() de chaque sprite
tous_les_sprites.draw(ecran) # dessine chaque sprite à son rect
التصادمات
توفر Pygame عدة مستويات من الكشف عن التصادم:
# Entre deux rectangles
if joueur.rect.colliderect(ennemi.rect):
print("Touché !")
# Entre un sprite et un groupe
# dokill=True : les sprites touchés sont retirés du groupe
objets_touches = pygame.sprite.spritecollide(joueur, groupe_objets, dokill=True)
for objet in objets_touches:
score += 1
# Entre deux groupes (renvoie un dictionnaire)
collisions = pygame.sprite.groupcollide(groupe_tirs, groupe_ennemis, True, True)
الأصوات
# Le mixer doit être initialisé (pygame.init() le fait automatiquement)
son_bonus = pygame.mixer.Sound("bonus.wav") # effet court, chargé en mémoire
son_bonus.set_volume(0.5) # volume de 0.0 à 1.0
son_bonus.play()
# Musique de fond (lue en continu depuis le fichier)
pygame.mixer.music.load("musique.ogg")
pygame.mixer.music.play(-1) # -1 = boucle infinie
النص والنتيجة
يُرسم النص على ثلاث خطوات: إنشاء خط، ثم توليد صورة للنص (render)، ثم عرضها (blit):
# None = police par défaut, 36 = taille en points
police = pygame.font.Font(None, 36)
score = 12
# render(texte, antialiasing, couleur)
texte_score = police.render(f"Score : {score}", True, (255, 255, 255))
ecran.blit(texte_score, (10, 10))
لعبة كاملة: التقاط الأشياء
لنجمع كل شيء في لعبة قابلة للّعب: تتساقط التفاحات من أعلى الشاشة، وتحرّك سلّة بالأسهم لالتقاطها. كل تفاحة تلتقطها تمنحك نقطة، وكل تفاحة تفوتك تكلّفك حياة. الشيفرة مستقلة بذاتها (لا صور خارجية: كل شيء يُرسم بالأشكال).
import random
import pygame
# ---------- Étape 1 : constantes et initialisation ----------
LARGEUR, HAUTEUR = 800, 600
FPS = 60
pygame.init()
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption("Attrape-Pommes")
horloge = pygame.time.Clock()
police = pygame.font.Font(None, 36)
# ---------- Étape 2 : les sprites ----------
class Panier(pygame.sprite.Sprite):
"""Le panier contrôlé par le joueur, en bas de l'écran."""
def __init__(self):
super().__init__()
self.image = pygame.Surface((100, 25))
self.image.fill((160, 100, 40)) # marron
self.rect = self.image.get_rect(midbottom=(LARGEUR // 2, HAUTEUR - 10))
self.vitesse = 9
def update(self):
touches = pygame.key.get_pressed()
if touches[pygame.K_LEFT]:
self.rect.x -= self.vitesse
if touches[pygame.K_RIGHT]:
self.rect.x += self.vitesse
self.rect.clamp_ip(ecran.get_rect()) # rester dans l'écran
class Pomme(pygame.sprite.Sprite):
"""Une pomme qui tombe du haut de l'écran à vitesse aléatoire."""
def __init__(self):
super().__init__()
self.image = pygame.Surface((28, 28), pygame.SRCALPHA)
pygame.draw.circle(self.image, (220, 40, 40), (14, 14), 14)
self.rect = self.image.get_rect(
midtop=(random.randint(20, LARGEUR - 20), -30)
)
self.vitesse = random.randint(3, 7)
def update(self):
self.rect.y += self.vitesse
# ---------- Étape 3 : état du jeu ----------
panier = Panier()
tous_les_sprites = pygame.sprite.Group(panier)
pommes = pygame.sprite.Group()
score = 0
vies = 3
# Événement personnalisé : faire apparaître une pomme toutes les 800 ms
APPARITION_POMME = pygame.USEREVENT + 1
pygame.time.set_timer(APPARITION_POMME, 800)
# ---------- Étape 4 : boucle de jeu ----------
en_cours = True
while en_cours:
# Événements
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == APPARITION_POMME and vies > 0:
pomme = Pomme()
pommes.add(pomme)
tous_les_sprites.add(pomme)
if vies > 0:
# Update
tous_les_sprites.update()
# Pommes attrapées : +1 point chacune
attrapees = pygame.sprite.spritecollide(panier, pommes, dokill=True)
score += len(attrapees)
# Pommes manquées (sorties de l'écran) : -1 vie chacune
for pomme in list(pommes):
if pomme.rect.top > HAUTEUR:
pomme.kill() # retire le sprite de tous ses groupes
vies -= 1
# Draw
ecran.fill((25, 35, 55))
tous_les_sprites.draw(ecran)
texte = police.render(f"Score : {score} Vies : {vies}", True, (255, 255, 255))
ecran.blit(texte, (10, 10))
if vies <= 0:
fin = police.render("PARTIE TERMINÉE - Fermez la fenêtre", True, (255, 80, 80))
rect_fin = fin.get_rect(center=(LARGEUR // 2, HAUTEUR // 2))
ecran.blit(fin, rect_fin)
pygame.display.flip()
horloge.tick(FPS)
pygame.quit()
احفظ هذه الشيفرة في الملف attrape_pommes.py ثم شغّله: اللعبة قابلة للّعب فورًا. وإليك بعض أفكار التحسين للتمرّن: تسريع التفاحات مع مرور الوقت، أو إضافة صوت عند التقاط كل تفاحة، أو حفظ أفضل نتيجة في ملف وعرضها، أو استبدال الأشكال بصور حقيقية.
أفضل الممارسات
- التزم ببنية الأحداث / التحديث / الرسم: لا ترسم أبدًا في مرحلة المنطق، ولا تعدّل الحالة في مرحلة الرسم.
- استخدم دائمًا
clock.tick(FPS)للحصول على سرعة متطابقة على جميع الأجهزة. - استدعِ
convert()أوconvert_alpha()بعد كلpygame.image.load(): فالعرض يصبح أسرع بوضوح. - فئة sprite واحدة لكل نوع من الكائنات: تبقى الشيفرة مقروءة وتتولى المجموعات التحديث والرسم.
- استخدم
sprite.kill()لإزالة الـ sprite من جميع مجموعاته بشكل سليم، بدلًا من إدارة القوائم يدويًا. - اجمع الثوابت (الأبعاد، الألوان، السرعات) في أعلى الملف: فيصبح ضبط توازن اللعبة أمرًا بسيطًا.
الخاتمة
أصبحت تعرف الآن جميع الآليات الأساسية في Pygame: حلقة اللعبة التي تضبط إيقاعها الساعة، ورسم الأشكال والصور، والتعامل مع لوحة المفاتيح، والـ sprites المنظمة في مجموعات، والتصادمات، والصوت، وعرض النتيجة. وتجمع لعبة التقاط الأشياء كل هذه العناصر في أقل من 120 سطرًا؛ وهي أساس متين يمكنك تطويره كما تشاء.
لمواصلة التعلم، استكشف التوثيق الرسمي لـ Pygame وخُض تحديًا كلاسيكيًا: أعد إنشاء Pong أو Snake أو لعبة تحطيم الطوب. فبناء ألعاب صغيرة كاملة هو أسرع طريق للتقدم.
نبذة عن InSkillCoach
خبير في التدريب والتقنيات
مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.
الشهادات:
- AWS Certified Solutions Architect – Professional
- شهادات Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
التعليقات
التعليقات مدعومة عبر GitHub Discussions
سجّل الدخول باستخدام GitHub للمشاركة في النقاش