ساخت بازی با پایتون و Pygame؛ اولین بازی کامل قدمبهقدم

🗓 آخرین بهروزرسانی:
ساختن بازی یکی از سرگرمکنندهترین راهها برای تمرین برنامهنویسی است: هر خطی که مینویسید، فوراً نتیجهاش را روی صفحه میبینید. در این آموزش با کتابخانه Pygame یک بازی کامل به نام «گرفتن ستارهها» میسازیم: ستارهها از بالا میافتند، شما با کلیدهای جهتنما سکو را حرکت میدهید، هر ستاره امتیاز دارد و هر ستارهای که از دست برود یک جان کم میکند. در مسیر، مفاهیم پایهای همه بازیها را یاد میگیرید: حلقه بازی، رویدادها، حرکت، برخورد و نمایش امتیاز.
کدها با پایتون 3.13.5 و Pygame 2.6.1 اجرا شدهاند. برای آزمودن بدون مانیتور، بازی را با یک بازیکن خودکار اجرا کردهایم و خروجی واقعی آن را آوردهایم.
Pygame چیست؟
Pygame کتابخانهای متنباز برای ساخت بازیهای دوبعدی و برنامههای چندرسانهای با پایتون است. پنجره میسازد، تصویر و شکل رسم میکند، صدا پخش میکند و ورودی صفحهکلید و موس را میخواند. برای بازیهای سهبعدی یا تجاری بزرگ ابزارهای دیگری مناسبترند، اما برای یادگیری منطق بازیسازی Pygame ساده و عالی است.
نصب Pygame
در ترمینال (ترجیحاً داخل یک محیط مجازی) دستور زیر را اجرا کنید. اگر با pip آشنا نیستید، آموزش نصب کتابخانه با pip را ببینید:
python -m pip install pygameبرای اطمینان از نصب درست، نسخه را بررسی کنید:
import pygame
print("pygame", pygame.version.ver)
print("SDL", ".".join(map(str, pygame.get_sdl_version())))خروجی:
pygame 2.6.1
SDL 2.28.4ساختار هر بازی: حلقه بازی
همه بازیها، از ساده تا پیچیده، در هسته یک حلقه دارند که دهها بار در ثانیه تکرار میشود. هر دور این حلقه سه کار انجام میدهد:
- دریافت رویدادها: آیا کاربر کلیدی زده یا پنجره را بسته است؟
- بهروزرسانی وضعیت: جابهجا کردن اشیا، بررسی برخورد، محاسبه امتیاز.
- رسم: پاک کردن صفحه و کشیدن همه چیز در جای جدید.
این کوچکترین برنامه Pygame است که یک پنجره سرمهای باز میکند. برای تست، حلقه را بعد از ۳ فریم متوقف کردهایم؛ در برنامه واقعی تا بستن پنجره ادامه مییابد:
import pygame
pygame.init()
screen = pygame.display.set_mode((480, 640))
pygame.display.set_caption("My first window")
clock = pygame.time.Clock()
frames = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((11, 29, 63))
pygame.display.flip()
clock.tick(60)
frames += 1
if frames == 3: # only for this test
running = False
print("window size:", screen.get_size(), "frames:", frames)
pygame.quit()خروجی:
window size: (480, 640) frames: 3clock.tick(60) سرعت حلقه را به حداکثر ۶۰ دور در ثانیه محدود میکند تا بازی روی کامپیوترهای سریع و کند یکسان رفتار کند. display.flip() هم تصویر رسمشده را روی صفحه نمایش میدهد.
مختصات و Rect
در Pygame نقطه (0, 0) گوشه بالا چپ پنجره است؛ x به سمت راست و y به سمت پایین زیاد میشود. برای نگه داشتن موقعیت و اندازه اشیا از کلاس pygame.Rect استفاده میکنیم که متدهای آمادهای برای جابهجایی و تشخیص برخورد دارد:
import pygame
player = pygame.Rect(200, 600, 60, 14)
star = pygame.Rect(210, 590, 18, 18)
print("player center:", player.center)
print("collide?", player.colliderect(star))
star.y -= 100
print("after moving star up, collide?", player.colliderect(star))خروجی:
player center: (230, 607)
collide? True
after moving star up, collide? Falseکد کامل بازی گرفتن ستارهها
حالا همه را کنار هم میگذاریم. کد را در فایلی به نام game.py ذخیره کنید و با python game.py اجرا کنید. پارامترهای max_frames و autoplay فقط برای تست خودکار اضافه شدهاند و در بازی عادی کاری نمیکنند:
import random
import pygame
WIDTH, HEIGHT = 480, 640
PLAYER_W, PLAYER_H = 60, 14
STAR_SIZE = 18
def new_star():
return pygame.Rect(random.randint(0, WIDTH - STAR_SIZE), -STAR_SIZE, STAR_SIZE, STAR_SIZE)
def main(max_frames=None, autoplay=False, seed=None):
if seed is not None:
random.seed(seed)
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Catch the Stars")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 36)
player = pygame.Rect(WIDTH // 2 - PLAYER_W // 2, HEIGHT - 40, PLAYER_W, PLAYER_H)
stars = [new_star()]
speed, score, lives, frame = 4, 0, 3, 0
running = True
while running and lives > 0:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# --- input ---
keys = pygame.key.get_pressed()
move = 0
if keys[pygame.K_LEFT]:
move = -7
if keys[pygame.K_RIGHT]:
move = 7
if autoplay: # simple AI for testing: follow the lowest star
target = max(stars, key=lambda s: s.y)
move = 7 if target.centerx > player.centerx else -7
player.x = max(0, min(WIDTH - PLAYER_W, player.x + move))
# --- update ---
for star in stars[:]:
star.y += speed
if star.colliderect(player):
stars.remove(star)
score += 1
if score % 5 == 0:
speed += 1
elif star.top > HEIGHT:
stars.remove(star)
lives -= 1
if len(stars) < 1 + score // 10:
stars.append(new_star())
# --- draw ---
screen.fill((11, 29, 63))
pygame.draw.rect(screen, (255, 255, 255), player, border_radius=6)
for star in stars:
pygame.draw.rect(screen, (255, 201, 40), star, border_radius=4)
text = font.render(f"Score: {score} Lives: {lives}", True, (255, 255, 255))
screen.blit(text, (12, 10))
pygame.display.flip()
clock.tick(60)
frame += 1
if max_frames and frame >= max_frames:
break
pygame.quit()
return score, lives, frame
if __name__ == "__main__":
main()
توضیح بخشهای مهم کد
- new_star: یک ستاره در موقعیت افقی تصادفی، کمی بالاتر از لبه صفحه میسازد. از ماژول
randomاستفاده میکند؛ مثل شبیهساز تاس. - key.get_pressed: وضعیت همه کلیدها را برمیگرداند؛ برای حرکت پیوسته مناسبتر از رویداد KEYDOWN است که فقط یک بار هنگام فشردن کلید رخ میدهد.
- محدود کردن حرکت: عبارت
max(0, min(...))جلوی خروج سکو از صفحه را میگیرد. - stars[:]: روی یک کپی از لیست حلقه میزنیم چون داخل حلقه عضو حذف میکنیم؛ حذف از خود لیست هنگام پیمایش باعث رد شدن از بعضی عضوها میشود.
- سختی تدریجی: هر ۵ امتیاز سرعت سقوط یک واحد بیشتر و هر ۱۰ امتیاز یک ستاره بهطور همزمان اضافه میشود.
- font.render: متن امتیاز را به تصویر تبدیل میکند و
blitآن را روی صفحه میچسباند. برای ساختن متن از f-string استفاده شده است.
اجرای آزمایشی بدون پنجره
برای اینکه مطمئن شویم منطق بازی درست است، آن را ۶۰۰ فریم (۱۰ ثانیه بازی) با بازیکن خودکار و بذر تصادفی ثابت اجرا کردیم. این خروجی واقعی آن اجراست:
import game
score, lives, frames = game.main(max_frames=600, autoplay=True, seed=42)
print(f"frames={frames} score={score} lives={lives}")خروجی:
frames=600 score=3 lives=3بازیکن خودکار همیشه به سمت پایینترین ستاره میرود. اجرای این تست کمک میکند بعد از هر تغییر بفهمید چیزی خراب نشده است؛ مثلاً اگر امتیاز همیشه صفر شد، احتمالاً شرط برخورد را اشتباه تغییر دادهاید.
رویدادهای صفحهکلید: KEYDOWN در برابر get_pressed
Pygame دو روش برای خواندن صفحهکلید دارد و انتخاب درست بین آنها رفتار بازی را تعیین میکند. رویداد KEYDOWN فقط یک بار، در لحظه فشرده شدن کلید، ساخته میشود؛ مناسب کارهایی مثل پریدن، شلیک یک گلوله یا مکث بازی. در مقابل key.get_pressed() در هر فریم میگوید کدام کلیدها همین حالا پایین نگه داشته شدهاند؛ مناسب حرکت پیوسته. در کد زیر چند رویداد را بهصورت مصنوعی در صف رویدادها قرار دادهایم تا بدون صفحهکلید واقعی، طرز پردازش آنها را ببینیم:
import pygame
pygame.init()
pygame.display.set_mode((100, 100))
for key in (pygame.K_SPACE, pygame.K_p, pygame.K_LEFT):
pygame.event.post(pygame.event.Event(pygame.KEYDOWN, key=key))
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
print("jump!")
elif event.key == pygame.K_p:
print("pause")
else:
print("other key:", pygame.key.name(event.key))
pygame.quit()خروجی:
jump!
pause
other key: leftقاعده سرانگشتی: هر کاری که باید «یک بار به ازای هر فشار» رخ دهد را در حلقه رویدادها و هر کاری که «تا وقتی کلید پایین است» ادامه دارد را با get_pressed بنویسید. اشتباه گرفتن این دو باعث میشود مثلاً شخصیت بازی با یک فشار کلید ده بار بپرد.
افزودن صفحه پایان بازی و بهترین امتیاز
بازی کامل به صفحه پایان نیاز دارد. سادهترین روش این است که منطق «تمام شدن» را از حلقه اصلی جدا کنیم. تابع زیر امتیاز نهایی را با رکورد ذخیرهشده در یک فایل مقایسه میکند و در صورت لزوم رکورد جدید را مینویسد. این بخش به Pygame وابسته نیست، پس میتوانید آن را جداگانه تست کنید:
import json
from pathlib import Path
RECORD = Path("best_score.json")
def save_best(score):
best = json.loads(RECORD.read_text())["best"] if RECORD.exists() else 0
if score > best:
RECORD.write_text(json.dumps({"best": score}))
return f"New record: {score}!"
return f"Score: {score} (best: {best})"
RECORD.unlink(missing_ok=True)
for s in (12, 7, 20):
print(save_best(s))خروجی:
New record: 12!
Score: 7 (best: 12)
New record: 20!در بازی، بعد از خروج از حلقه اصلی (وقتی lives صفر شد)، این تابع را صدا بزنید و پیام برگشتی را با font.render وسط صفحه نشان دهید. سپس یک حلقه کوچک بنویسید که منتظر کلید R برای شروع دوباره یا Escape برای خروج بماند. جدا کردن منطق از نمایش، همان اصلی است که در پروژههای بزرگتر کد را قابلآزمایش و قابلنگهداری نگه میدارد.
چرا بازیسازی تمرین خوبی برای یادگیری است؟
در یک بازی کوچک تقریباً همه مفاهیم پایه برنامهنویسی کنار هم به کار میروند: متغیرها وضعیت را نگه میدارند، شرطها قوانین را پیاده میکنند، حلقه زمان را جلو میبرد، لیستها اشیای متحرک را نگه میدارند و تابعها کد را مرتب میکنند. خطاها هم فوراً دیده میشوند؛ اگر شرط برخورد اشتباه باشد، ستاره از وسط سکو رد میشود. همین بازخورد سریع باعث میشود اشکالزدایی را سریعتر یاد بگیرید. پیشنهاد ما این است که بعد از تمام کردن این بازی، یک ویژگی کوچک را بدون نگاه کردن به راهنما اضافه کنید؛ مثلاً حالت سخت که از اول دو ستاره همزمان بیفتد.
اشتراکگذاری بازی با دوستان
دوستانتان برای اجرای game.py به پایتون و Pygame نیاز دارند. اگر میخواهید بازی را به شکل یک فایل اجرایی برای ویندوز بفرستید، ابزار PyInstaller را با pip نصب کنید و دستور pyinstaller --onefile --windowed game.py را اجرا کنید. فایل نهایی در پوشه dist ساخته میشود. دقت کنید فایل اجرایی ویندوز باید روی خود ویندوز ساخته شود و اگر از تصویر یا صدا استفاده کردهاید، آن فایلها را هم کنار فایل اجرایی قرار دهید یا با گزینههای PyInstaller ضمیمه کنید. حجم این فایلها به دلیل همراه داشتن مفسر پایتون معمولاً چند ده مگابایت است و طبیعی است. گاهی هم آنتیویروسها به فایلهای ساختهشده با PyInstaller بیدلیل حساس میشوند؛ ارسال کد منبع همراه با فایل requirements.txt راه مطمئنتری برای دوستان برنامهنویس است.
اگر این بازی را تمام کردید، قدم بعدی خوب ساخت بازی مار با پایتون و Pygame است؛ در آن حرکت روی شبکه، رشد مار و تشخیص برخورد را با منطقی جدا از نمایش و قابل تست پیاده میکنیم. پروژههای دیگر را هم در فهرست ۳۲ پروژه پایتون از مبتدی تا پیشرفته ببینید.
توسعه بازی: ایدههای بعدی
- صفحه پایان بازی: وقتی جانها تمام شد، پیام Game Over و امتیاز نهایی را نشان دهید و با کلید R بازی را از نو شروع کنید.
- بهترین امتیاز: رکورد را در یک فایل ذخیره کنید؛ آموزش کار با فایل JSON کمک میکند.
- تصویر و صدا: بهجای مستطیل از
pygame.image.loadو برای صدا ازpygame.mixer.Soundاستفاده کنید. - ستارههای ویژه: ستاره قرمز که با گرفتنش جان کم شود یا ستاره سبز که جان اضافه دهد.
- کلاسها: بازیکن و ستاره را به کلاس تبدیل کنید یا از
pygame.sprite.Spriteبهره ببرید؛ برای شروع آموزش شیءگرایی در پایتون را بخوانید.
اشتباههای رایج در Pygame
- پنجره هنگ میکند یا Not Responding میشود: حلقه رویدادها را فراموش کردهاید. حتی اگر رویدادی لازم ندارید،
pygame.event.get()را در هر دور صدا بزنید. - اثر کشیده شدن اشیا روی صفحه: در ابتدای مرحله رسم،
screen.fillرا صدا نزدهاید. - چیزی نمایش داده نمیشود:
pygame.display.flip()را در انتهای حلقه فراموش کردهاید. - سرعت بازی روی هر کامپیوتر فرق دارد:
clock.tickرا اضافه کنید. - خطای ModuleNotFoundError: pygame: کتابخانه در مفسر دیگری نصب شده است؛ راهحل در این آموزش است.
📌 همچنین بخوانید: تمرین پایتون با جواب، ساخت ربات با پایتون و تشخیص عدد اول و زوج یا فرد در پایتون
سوالات متداول
آیا با پایتون میشود بازی ساخت؟
بله. با کتابخانه Pygame میتوانید بازیهای دوبعدی مثل بازیهای آرکید، پازل و پلتفرمر بسازید. برای بازیهای سهبعدی بزرگ معمولاً موتورهای بازیسازی دیگری استفاده میشوند.
Pygame را چطور نصب کنم؟
در ترمینال دستور python -m pip install pygame را اجرا کنید و با import pygame و چاپ pygame.version.ver نصب را بررسی کنید.
حلقه بازی چیست؟
حلقهای که چندین بار در ثانیه تکرار میشود و در هر دور ورودیها را میخواند، وضعیت بازی را بهروز میکند و صفحه را دوباره رسم میکند.
برای ساخت بازی با پایتون چه چیزهایی باید بلد باشم؟
متغیر، شرط، حلقه، لیست و تابع کافی است. برای بازیهای بزرگتر، شیءگرایی و کار با فایل هم لازم میشود.