آموزش ساخت بازی Flappy Bird با Python و Pygame

پروژه بازی‌سازی دو بعدی برای مبتدی‌ها

پروژه ۵ دقیقه مطالعه
فهرست مطالب
این مطلب از سایت tool.hamidvalad.ir گرفته شده است

معرفی — چه کاری قرار است انجام دهیم؟

در این آموزش قدم‌به‌قدم یک نسخهٔ ساده از بازی Flappy Bird را با استفاده از python و pygame می‌سازیم. هدف این آموزش این است که مبتدی‌ها با مفاهیم پایهٔ بازی‌سازی دو بعدی آشنا شوند: رسم تصویر در صفحه، حرکت اشیاء، برخورد (collision)، و پخش صدا.

پیش‌نیازها (کتابخانه‌ها و ابزارها)

  • + Python 3.7
  • pip (مدیر بستهٔ پایتون)
  • pygame — کتابخانهٔ اصلی برای رسم و کار با صدا
  • ماژول‌های استاندارد پایتون: random, time, os

راه‌اندازی محیط (Virtual Environment)

Terminal
mkdir flappy_project
cd flappy_project
python -m venv flappy_project_env
flappy_project_env\Scripts\activate  # Linux/Mac source flappy_project_env/bin/activate
pip install pygame

بعد از اجرای دستورات بالا، محیط مجازی فعال است و پکیج‌ها داخل همان محیط نصب می‌شوند.

چگونه بازی را اجرا کنیم

  1. اگر از virtual environment استفاده می‌کنید آن را فعال کنید.

  2. دستور فعال سازی محیط مجازی
    Terminal
    flappy_project_env\Scripts\activate  # Linux/Mac source flappy_project_env/bin/activate
                    
  3. در پاورشل یا ترمینال دستور زیر را اجرا کنید:

  4. Terminal
    py flappy_bird.py        # یا python flappy_bird.py
    

کد کامل پروژه

این کد را در فایلی به نام flappy_bird.py ذخیره کنید.

Python
import pygame
import random
import time
from pygame.locals import *

# تنظیمات بازی
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
SPEED = 20
GRAVITY = 2.5
GAME_SPEED = 15

GROUND_WIDTH = 2 * SCREEN_WIDTH
GROUND_HEIGHT = 100
PIPE_WIDTH = 80
PIPE_HEIGHT = 500
PIPE_GAP = 150

# مسیر فایل‌های صوتی
wing_sound = 'assets/audio/wing.wav'
hit_sound = 'assets/audio/hit.wav'

# راه‌اندازی صدا
pygame.mixer.init()

class Bird(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        
        # تصاویر پرنده
        self.images = [
            pygame.image.load('assets/sprites/bluebird-upflap.png').convert_alpha(),
            pygame.image.load('assets/sprites/bluebird-midflap.png').convert_alpha(),
            pygame.image.load('assets/sprites/bluebird-downflap.png').convert_alpha()
        ]
        
        self.speed = SPEED
        self.current_image = 0
        self.image = self.images[0]
        self.mask = pygame.mask.from_surface(self.image)
        
        self.rect = self.image.get_rect()
        self.rect[0] = SCREEN_WIDTH / 6
        self.rect[1] = SCREEN_HEIGHT / 2

    def update(self):
        # انیمیشن بال زدن
        self.current_image = (self.current_image + 1) % 3
        self.image = self.images[self.current_image]
        
        # اعمال جاذبه
        self.speed += GRAVITY
        self.rect[1] += self.speed

    def bump(self):
        # پرش پرنده
        self.speed = -SPEED

    def begin(self):
        # انیمیشن در صفحه شروع
        self.current_image = (self.current_image + 1) % 3
        self.image = self.images[self.current_image]

class Pipe(pygame.sprite.Sprite):
    def __init__(self, inverted, xpos, ysize):
        pygame.sprite.Sprite.__init__(self)
        
        self.image = pygame.image.load('assets/sprites/pipe-green.png').convert_alpha()
        self.image = pygame.transform.scale(self.image, (PIPE_WIDTH, PIPE_HEIGHT))
        
        self.rect = self.image.get_rect()
        self.rect[0] = xpos
        
        if inverted:
            # لوله بالایی
            self.image = pygame.transform.flip(self.image, False, True)
            self.rect[1] = - (self.rect[3] - ysize)
        else:
            # لوله پایینی
            self.rect[1] = SCREEN_HEIGHT - ysize
        
        self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        # حرکت لوله به چپ
        self.rect[0] -= GAME_SPEED

class Ground(pygame.sprite.Sprite):
    def __init__(self, xpos):
        pygame.sprite.Sprite.__init__(self)
        
        self.image = pygame.image.load('assets/sprites/base.png').convert_alpha()
        self.image = pygame.transform.scale(self.image, (GROUND_WIDTH, GROUND_HEIGHT))
        self.mask = pygame.mask.from_surface(self.image)
        
        self.rect = self.image.get_rect()
        self.rect[0] = xpos
        self.rect[1] = SCREEN_HEIGHT - GROUND_HEIGHT
    
    def update(self):
        # حرکت زمین به چپ
        self.rect[0] -= GAME_SPEED

def is_off_screen(sprite):
    # بررسی خروج از صفحه
    return sprite.rect[0] < -(sprite.rect[2])

def get_random_pipes(xpos):
    # تولید لوله‌های تصادفی
    size = random.randint(100, 300)
    pipe = Pipe(False, xpos, size)  # لوله پایین
    pipe_inverted = Pipe(True, xpos, SCREEN_HEIGHT - size - PIPE_GAP)  # لوله بالا
    return pipe, pipe_inverted

# شروع بازی
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Flappy Bird')

# بارگذاری تصاویر پس‌زمینه
BACKGROUND = pygame.image.load('assets/sprites/background-day.png')
BACKGROUND = pygame.transform.scale(BACKGROUND, (SCREEN_WIDTH, SCREEN_HEIGHT))
BEGIN_IMAGE = pygame.image.load('assets/sprites/message.png').convert_alpha()

# ایجاد گروه‌ها
bird_group = pygame.sprite.Group()
bird = Bird()
bird_group.add(bird)

ground_group = pygame.sprite.Group()
for i in range(2):
    ground = Ground(GROUND_WIDTH * i)
    ground_group.add(ground)

pipe_group = pygame.sprite.Group()
for i in range(2):
    pipes = get_random_pipes(SCREEN_WIDTH * i + 800)
    pipe_group.add(pipes[0])
    pipe_group.add(pipes[1])

clock = pygame.time.Clock()

# صفحه شروع
begin = True
while begin:
    clock.tick(15)

    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
        if event.type == KEYDOWN:
            if event.key == K_SPACE or event.key == K_UP:
                bird.bump()
                pygame.mixer.music.load(wing_sound)
                pygame.mixer.music.play()
                begin = False

    # نمایش صفحه شروع
    screen.blit(BACKGROUND, (0, 0))
    screen.blit(BEGIN_IMAGE, (120, 150))

    # بروزرسانی زمین
    if is_off_screen(ground_group.sprites()[0]):
        ground_group.remove(ground_group.sprites()[0])
        new_ground = Ground(GROUND_WIDTH - 20)
        ground_group.add(new_ground)

    bird.begin()
    ground_group.update()

    # رسم اجزا
    bird_group.draw(screen)
    ground_group.draw(screen)
    pygame.display.update()

# حلقه اصلی بازی
while True:
    clock.tick(15)

    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
        if event.type == KEYDOWN:
            if event.key == K_SPACE or event.key == K_UP:
                bird.bump()
                pygame.mixer.music.load(wing_sound)
                pygame.mixer.music.play()

    # نمایش پس‌زمینه
    screen.blit(BACKGROUND, (0, 0))

    # مدیریت زمین
    if is_off_screen(ground_group.sprites()[0]):
        ground_group.remove(ground_group.sprites()[0])
        new_ground = Ground(GROUND_WIDTH - 20)
        ground_group.add(new_ground)

    # مدیریت لوله‌ها
    if is_off_screen(pipe_group.sprites()[0]):
        pipe_group.remove(pipe_group.sprites()[0])
        pipe_group.remove(pipe_group.sprites()[0])
        pipes = get_random_pipes(SCREEN_WIDTH * 2)
        pipe_group.add(pipes[0])
        pipe_group.add(pipes[1])

    # بروزرسانی همه اجزا
    bird_group.update()
    ground_group.update()
    pipe_group.update()

    # رسم همه اجزا
    bird_group.draw(screen)
    pipe_group.draw(screen)
    ground_group.draw(screen)
    pygame.display.update()

    # بررسی برخورد
    if (pygame.sprite.groupcollide(bird_group, ground_group, False, False, pygame.sprite.collide_mask) or
            pygame.sprite.groupcollide(bird_group, pipe_group, False, False, pygame.sprite.collide_mask)):
        pygame.mixer.music.load(hit_sound)
        pygame.mixer.music.play()
        time.sleep(1)
        break

فایل‌ها و ساختار پوشه‌ها (assets)

پس از ذخیرهٔ کد بالا، پوشهٔ assets را ایجاد کنید و فایل‌های زیر را در آن قرار دهید:

Directory Structure
flappy_project/
├── flappy_bird.py
└── assets/
    ├── sprites/
    │   ├── bluebird-upflap.png
    │   ├── bluebird-midflap.png
    │   ├── bluebird-downflap.png
    │   ├── pipe-green.png
    │   ├── base.png
    │   ├── background-day.png
    │   └── message.png
    └── audio/
        ├── wing.wav
        └── hit.wav

اگر این تصاویر و صداها را ندارید، می‌توانید از تصاویر مشابه استفاده کنید یا فایل‌های پروژهٔ نمونه را دانلود کنید.

پوشه کامل (assets) را از اینجا دریافت کنید

توضیح بخش‌های مهم کد (گام‌به‌گام)

  • راه‌اندازی Pygame: با pygame.init و ساخت صفحه با display.set_mode.
  • کلاس‌ها: سه کلاس اصلی داریم: Bird (پرنده)، Pipe (لوله‌ها)، Ground (زمین). هرکدام از این‌ها از pygame.sprite.Sprite ارث می‌برند تا مدیریت و رسم ساده‌تر شود.
  • ماسک بر مبنای تصویر: با استفاده از pygame.mask.from_surface برخورد دقیق‌تری بین پیکسل‌ها انجام می‌شود.
  • حلقهٔ بازی: حلقهٔ شروع (نمایش پیام شروع) و حلقهٔ اصلی بازی که رویدادها را پردازش، موقعیت‌ها را بروز و صفحه را بازنقش می‌کند.
  • تولید لوله‌ها: با تابع get_random_pipes اندازهٔ تصادفی برای لوله‌ها تولید می‌کنیم و هر بار که لوله از صفحه خارج شد، یک جفت جدید اضافه می‌کنیم.
  • صداها: با pygame.mixer.music.load صداهای ضربه و بال‌زدن پخش می‌شوند (در صورت نبود فایل، برنامه بدون صدا کار می‌کند).

عیب‌یابی و نکات

نکات خوب

  • ساختار ساده و خلوت؛ مناسب برای یادگیری اولیه کلاس‌ها و اسپرایت‌ها.
  • استفاده از ماسک برخورد برای دقت بالا.
  • تقسیم وظایف بین کلاس‌ها (قابل توسعه و خواناتر).

خطاهای رایج هنگام اجرا و روش رفع آن‌ها❗

  • ❌ FileNotFoundError:
    اگر خطایی مشابه FileNotFoundError: No file 'assets/sprites/bluebird-upflap.png' دریافت کردید، یعنی پوشهٔ assets یا مسیر تصاویر اشتباه است. مطمئن شوید فایل‌ها دقیقاً در مسیر assets/sprites/ و assets/audio/ قرار گرفته باشند.
  • ❌ ModuleNotFoundError: No module named 'pygame'
    این خطا نشان می‌دهد که pygame نصب نیست. دستور زیر را اجرا کنید:
    Terminal
    pip install pygame
    
    اگر از محیط مجازی استفاده می‌کنید (Virtual Environment)، ابتدا آن را فعال کنید:
    Terminal
    flappy_project_env\Scripts\activate  # Linux/Mac source flappy_project_env/bin/activate
    
  • ⚠️ صدای بازی پخش نمی‌شود:
    ممکن است فرمت صدا پشتیبانی نشود. از فرمت WAV برای فایل‌های صوتی استفاده کنید (مثل wing.wav و hit.wav). همچنین بررسی کنید مسیرها درست نوشته شده باشند.
  • ⚠️ صفحه شروع کار نمی‌کند:
    اگر صفحه‌ی "Press Space to Start" نمایش داده نمی‌شود، احتمالاً نام تصویر message.png اشتباه است یا در مسیر صحیح قرار ندارد.

💡 پیشنهاد آموزشی: اگر با ارورهای بالا برخورد کردید، سعی کنید خطا را درک کنید نه فقط برطرف — هر پیام خطا یک فرصت یادگیری است.

منبع اصلی و دریافت کد

این پروژه با الهام از نسخهٔ اصلی بازی Flappy Bird ساخته شده و کد منبع اولیه از مخزن زیر گرفته شده است:

اگر می‌خواهید نسخهٔ اصلی و فایل‌های assets را دریافت کنید، می‌توانید با دستور زیر مخزن را کلون کنید:

Terminal
git clone https://github.com/LeonMarqs/Flappy-bird-python.git
cd Flappy-bird-python

python -m venv flappy_project_env
flappy_project_env\Scripts\activate
pip install pygame

پس از کلون کردن، وارد پوشهٔ پروژه شوید، محیط مجازی (virtualenv) را فعال کنید و بازی را با دستور زیر اجرا نمایید:

Terminal
python flappy.py