راهنمای پای گیم

خانه ‹ وبلاگ ‹ برنامه‌نویسی ‹ راهنمای Pygame Zero؛ ساخت بازی و خطاهای رایج

راهنمای کامل Pygame Zero؛ از نصب pgzero تا ساخت بازی و حل خطاهای رایج

Pygame Zero یک چارچوب آموزشی برای ساخت بازی‌های دوبعدی با Python است که روی Pygame ساخته شده و بسیاری از جزئیات پیچیده حلقه بازی را برای هنرجوی تازه‌کار ساده می‌کند. در مسیر آموزشی ترم اول می‌توان از ساخت یک پنجره ساده شروع کرد و قدم‌به‌قدم به حرکت بازیکن، جمع‌آوری سکه، امتیاز، دشمن، جان، زمان، برد و باخت، منوی شروع و Restart رسید.

این راهنما بر اساس همان مسیر آموزشی پروژه جمع‌آوری سکه تنظیم شده است و بخش خطاها را از همان مرحله نصب در VS Code تا نوشتن توابع draw() و update()، کار با keyboard، برخوردها، random و math، لیست سکه‌ها، دشمن، game_state، clock.schedule_interval() و Reset بازی پوشش می‌دهد.

راهنمای آموزشی سیگما • مناسب هنرجویان Pygame Zero و بازی‌سازی با Python

نکته مهم درباره نصب: نام پکیجی که باید با pip نصب شود pgzero است، یعنی: pip install pgzero. عبارت pgzrun نام Runner/ماژولی است که بعد از نصب Pygame Zero در اختیار شما قرار می‌گیرد. در محیط‌هایی مثل VS Code می‌توان در ابتدای فایل import pgzrun و در انتهای فایل pgzrun.go() نوشت. اگر کسی pip install pgzrun بزند، از همان ابتدا مسیر نصب را اشتباه رفته است.
🎮

۱. Pygame Zero چیست؟

Pygame Zero برای آموزش بازی‌سازی طراحی شده است. در Pygame معمولی هنرجو باید خودش حلقه بازی، Event Loop و بخش‌های بیشتری از راه‌اندازی را مدیریت کند؛ اما در Pygame Zero کافی است توابع شناخته‌شده‌ای مثل draw() و update() را تعریف کند و چارچوب آن‌ها را در زمان مناسب اجرا می‌کند.

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

مفاهیم اصلی ترم اول

WIDTH / HEIGHT / TITLE: اندازه پنجره و عنوان بازی را مشخص می‌کنند.
draw(): هر چیزی که باید دیده شود؛ پس‌زمینه، بازیکن، سکه، دشمن و متن.
update(): منطق بازی؛ حرکت، برخورد، وضعیت‌ها و تغییر متغیرها.
keyboard: گرفتن کلیدهای جهت‌دار برای حرکت بازیکن.
math / random: محاسبه فاصله و ساخت موقعیت‌های تصادفی.
game_state / clock: کنترل شروع، بازی، برد، باخت و شمارش زمان.

مسیر پروژه ترم اول

مسیر پیشنهادی پروژه این است: اول پنجره را می‌سازیم، بعد بازیکن را رسم می‌کنیم، حرکت چهارجهته و محدودیت خروج از صفحه را اضافه می‌کنیم، سپس سکه و امتیاز، چند سکه، دشمن و جان، زمان، وضعیت برد و باخت و در آخر منوی شروع و امکان Restart را اضافه می‌کنیم. این ترتیب باعث می‌شود هر جلسه فقط یک مفهوم جدید روی پروژه قبلی سوار شود.

🧰

۲. نصب Pygame Zero و آماده‌سازی VS Code

قبل از کدنویسی بهتر است محیط را درست آماده کنیم. Python، Visual Studio Code و افزونه Python در VS Code باید نصب باشند و اینترنت برای نصب کتابخانه در دسترس باشد. سپس Terminal خود VS Code را باز می‌کنیم و نصب را از همان محیط انجام می‌دهیم تا احتمال اشتباه شدن Interpreter کمتر شود.

  • دستور اصلی نصب: pip install pgzero
  • اگر pip به Python دیگری وصل است: python -m pip install pgzero یا در بعضی سیستم‌ها py -m pip install pgzero
  • برای بررسی نصب: pip show pgzero
  • برای اجرای مستقیم از Terminal: pgzrun game.py
  • برای اجرای داخل IDE: import pgzrun در بالا و pgzrun.go() در انتهای فایل.

اگر ترمینال پیام نصب موفق نشان داد ولی داخل VS Code همچنان ModuleNotFoundError می‌بینید، معمولاً مشکل از این است که VS Code از یک Python دیگر استفاده می‌کند. Interpreter فعال را از نوار پایین VS Code بررسی کنید و نصب را با همان Python تکرار کنید.

🚀

۳. ساخت اولین بازی؛ از import pgzrun تا pgzrun.go()

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

۱
۱) کتابخانه را وارد کنید.
import pgzrun را در ابتدای فایل قرار دهید.
۲
۲) پنجره را تعریف کنید.
WIDTH، HEIGHT و TITLE را با حروف بزرگ بنویسید.
۳
۳) draw() را بسازید.
ابتدا صفحه را با screen.clear() یا screen.fill(...) پاک و رنگ کنید، سپس شکل‌ها و متن را رسم کنید.
۴
۴) بازی را اجرا کنید.
pgzrun.go() باید در انتهای فایل قرار بگیرد تا اجرای IDE آغاز شود.

اسکلت ساده و قابل تست

در نمونه زیر هنوز حرکت یا برخورد نداریم. فقط می‌خواهیم مطمئن شویم پنجره باز می‌شود، عنوان درست است و draw() می‌تواند پس‌زمینه، متن و بازیکن را رسم کند.

import pgzrun

WIDTH = 700
HEIGHT = 450
TITLE = "Coin Collector"

player_x = 350
player_y = 220
player_radius = 22

def draw():
    screen.clear()
    screen.fill((235, 245, 255))
    screen.draw.text("Coin Collector", center=(WIDTH / 2, 60),
                     fontsize=32, color=(40, 60, 100))
    screen.draw.filled_circle((player_x, player_y),
                              player_radius, (40, 148, 230))

pgzrun.go()

۴. حرکت بازیکن، update() و جلوگیری از خروج از صفحه

در مرحله حرکت، متغیرهای player_x، player_y، player_speed و player_radius اهمیت پیدا می‌کنند. تابع update() مرتب اجرا می‌شود و در هر بار اجرا وضعیت کلیدهای جهت‌دار را می‌خواند. چون مختصات بازیکن داخل تابع تغییر می‌کند، در این مدل آموزشی از global player_x, player_y استفاده می‌شود.

منطق حرکت چهارجهته

اگر keyboard.left فعال باشد، x کم می‌شود؛ راست x را زیاد می‌کند؛ بالا y را کم می‌کند و پایین y را زیاد می‌کند. برای جلوگیری از خروج بازیکن از صفحه، باید شعاع بازیکن را هم در مرزها حساب کنیم: x نباید از player_radius کمتر یا از WIDTH - player_radius بیشتر شود و همین منطق برای y هم برقرار است.

اگر جهت‌ها برعکس شده‌اند، به محور صفحه دقت کنید: در بیشتر سیستم‌های گرافیکی، x به سمت راست زیاد می‌شود اما y به سمت پایین زیاد می‌شود. بنابراین حرکت «بالا» یعنی کم کردن y.

۵. سکه، امتیاز، برخورد، دشمن، جان، زمان و Game State

بعد از حرکت بازیکن، منطق اصلی بازی مرحله‌به‌مرحله اضافه می‌شود. برای سکه از مختصات و شعاع استفاده می‌کنیم، با math.sqrt() فاصله بازیکن و سکه را حساب می‌کنیم و اگر فاصله از مجموع شعاع‌ها کمتر باشد، برخورد اتفاق افتاده است. بعد سکه به کمک random.randint() به جای جدید می‌رود و امتیاز زیاد می‌شود.

از یک سکه تا بازی کامل

در مرحله بعد چند سکه را داخل یک List از Dictionaryها نگه می‌داریم و برای هر سکه وضعیت taken داریم. سپس دشمن و متغیر lives اضافه می‌شود. در جلسات پایانی نیز time_left، متغیر game_state با حالت‌های start، play، win و lose، تابع reset_game() و رویدادهای صفحه‌کلید برای Start و Restart ساخته می‌شوند.

برای تایمر از clock.schedule_interval(count_down, 1.0) استفاده می‌شود. تابعی که به Clock می‌دهید باید بدون پرانتز نوشته شود؛ یعنی count_down نه count_down(). اگر پرانتز بگذارید، تابع همان لحظه اجرا می‌شود و زمان‌بندی درست انجام نمی‌شود.

۶. خطاهای رایج Pygame Zero در ترم اول و راه‌حل آن‌ها

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

نام پکیج نصب‌شدنی pgzero است، نه pgzrun. دستور درست pip install pgzero است. pgzrun Runner/ماژولی است که همراه Pygame Zero نصب می‌شود.

از python -m pip install pgzero یا در ویندوز از py -m pip install pgzero استفاده کنید. همچنین مطمئن شوید Python به PATH اضافه شده است.

احتمالاً VS Code از Python دیگری استفاده می‌کند. Interpreter فعال را بررسی کنید و pgzero را با همان Interpreter نصب کنید.

ممکن است Scripts مربوط به Python در PATH نباشد. برای محیط آموزشی می‌توانید فایل را با import pgzrun و pgzrun.go() از داخل IDE اجرا کنید.

Interpreter فعال VS Code و افزونه Python را بررسی کنید. اگر از روش IDE استفاده می‌کنید، import pgzrun باید در ابتدای فایل و pgzrun.go() در انتهای فایل باشد.

نام فایل پروژه را با نام کتابخانه‌ها یکسان نگذارید. نام‌هایی مثل game.py یا coin_collector.py مناسب‌تر هستند.

ابتدا مطمئن شوید pgzrun.go() در انتهای فایل قرار دارد و قبل از رسیدن به آن SyntaxError یا خطای دیگری رخ نداده است.

متغیرهای WIDTH و HEIGHT را با حروف بزرگ و قبل از اجرای بازی تعریف کنید. مقدارها باید عدد باشند.

متغیر TITLE را قبل از اجرای بازی تعریف کنید و مطمئن شوید رشته متنی داخل کوتیشن قرار گرفته است.

نام تابع باید دقیقاً draw و بدون آرگومان باشد. تورفتگی کد داخل تابع را هم بررسی کنید.

کد screen.fill((R, G, B)) را داخل draw قرار دهید. هر مقدار RGB باید معمولاً بین 0 تا 255 باشد.

مختصات و شعاع را بررسی کنید. اگر x یا y خارج صفحه باشد یا radius صفر/منفی باشد، شکل دیده نمی‌شود.

مختصات اجزای صورت باید نسبت به player_x و player_y محاسبه شوند؛ اگر عدد ثابت بگذارید با حرکت بازیکن همراه نمی‌شوند.

جای متن، رنگ و fontsize را بررسی کنید. اگر متن بیرون صفحه یا همرنگ پس‌زمینه باشد دیده نمی‌شود.

کد حرکت باید داخل update() باشد و از keyboard.left/right/up/down استفاده کند. نام تابع update را اشتباه ننویسید.

چون داخل تابع update مقدار متغیرهای سراسری را تغییر می‌دهید، در الگوی این دوره از global player_x, player_y در ابتدای تابع استفاده کنید.

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

بعد از حرکت، مرزها را با شعاع بازیکن کنترل کنید. مثلاً x نباید از player_radius کمتر شود و نباید از WIDTH – player_radius بیشتر شود.

اگر فقط x را با صفر و WIDTH مقایسه کنید، مرکز بازیکن داخل صفحه می‌ماند اما بخشی از دایره بیرون می‌رود. شعاع را در محدودیت‌ها لحاظ کنید.

مقدار player_speed را بررسی کنید. در ترم اول یک مقدار کوچک ثابت مناسب است؛ بعداً می‌توان حرکت زمان‌محور را آموزش داد.

اگر برای فاصله از math.sqrt() استفاده می‌کنید باید در ابتدای فایل import math بنویسید.

برای جابه‌جایی تصادفی سکه باید import random انجام شده باشد.

بررسی کنید تابع برخورد واقعاً داخل update() فراخوانی می‌شود و فرمول فاصله و شرط مقایسه با مجموع شعاع‌ها درست نوشته شده‌اند.

اگر score سراسری است و داخل تابع آن را تغییر می‌دهید، global score را در همان تابع اعلام کنید.

بعد از ثبت برخورد باید coin_x و coin_y مقدار جدید بگیرند. اگر این متغیرها سراسری هستند، global آن‌ها هم لازم است.

در random.randint شعاع سکه را در بازه لحاظ کنید؛ مثلاً از coin_radius تا WIDTH – coin_radius.

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

پرانتزها، توان 2 و جمع دو محور را دقیق بررسی کنید. یک اشتباه کوچک در (player_x-coin_x)**2 یا بخش y نتیجه برخورد را خراب می‌کند.

براکت‌ها، ویرگول بین Dictionaryها و کوتیشن کلیدهای x، y و taken را بررسی کنید.

یکی از Dictionaryهای سکه کلید taken ندارد یا نام کلید را متفاوت نوشته‌اید. ساختار همه آیتم‌های لیست باید یکسان باشد.

در draw فقط سکه‌هایی را رسم کنید که not coin['taken'] هستند.

تابع برخورد باید روی همه سکه‌ها حلقه بزند. اگر return یا break را زود گذاشته باشید، حلقه بعد از اولین آیتم تمام می‌شود.

count را قبل از حلقه صفر کنید، فقط برای سکه‌های گرفته‌نشده افزایش دهید و return count را بعد از تمام شدن حلقه قرار دهید.

بعد از هر برخورد یا در update بررسی کنید که coins_left() به صفر رسیده است. همچنین taken باید هنگام گرفتن سکه True شود.

تابع move_enemy() باید در update اجرا شود و enemy_y یا مختصات موردنظر در آن تغییر کند.

وقتی دشمن از یک مرز عبور می‌کند، جهت را عوض کنید یا موقعیتش را Reset کنید. متغیر enemy_direction باید درست تغییر کند.

اگر داخل تابع جهت دشمن را تغییر می‌دهید و متغیر سراسری است، global enemy_direction را اضافه کنید.

اگر بازیکن چند فریم روی دشمن بماند، برخورد چند بار ثبت می‌شود. بعد از برخورد، دشمن یا بازیکن را جابه‌جا کنید یا یک زمان مصونیت کوتاه در نظر بگیرید.

پس از کاهش lives شرط صفر شدن را بررسی کنید و game_state = 'lose' یا فلگ مناسب را تنظیم کنید.

ترتیب رسم در draw مهم است. پیام نهایی را بعد از رسم عناصر اصلی یا در شاخه جداگانه مربوط به وضعیت lose رسم کنید.

ممکن است clock.schedule_interval را چند بار اجرا کرده باشید. آن را معمولاً یک بار و بیرون توابع اصلی زمان‌بندی کنید.

تابع callback باید بدون آرگومان تعریف شده باشد و در schedule به شکل clock.schedule_interval(count_down, 1.0) ارسال شود.

پرانتز را حذف کنید. باید خود تابع را بدهید، نه نتیجه اجرای آن: count_down.

در count_down قبل یا بعد از کم کردن زمان شرط صفر را بررسی کنید و در صفر game_state را روی lose قرار دهید.

در update فقط وقتی game_state == 'play' است توابع حرکت و برخورد را اجرا کنید.

مثل منوی Start، منطق update را به وضعیت play محدود کنید.

در draw بر اساس game_state شاخه‌های واضح برای start، play، win و lose داشته باشید و رشته‌های وضعیت را در همه جا یکسان بنویسید.

تابع رویداد را درست نام‌گذاری کنید و کلید را با keys.SPACE بررسی کنید. همچنین شرط game_state باید start باشد.

در رویداد صفحه‌کلید بررسی کنید که اگر state برابر win یا lose بود و کلید R زده شد، reset_game() اجرا شود.

داخل reset_game همه متغیرهای لازم مثل player_x، player_y، score، lives، time_left، دشمن، وضعیت taken سکه‌ها و game_state را به مقدار اولیه برگردانید.

متغیرهای سراسری که داخل تابع مقدارشان تغییر می‌کند باید با global معرفی شوند. برای ساختارهای mutable مثل لیست هم مراقب جایگزینی کامل لیست باشید.

در reset_game روی لیست سکه‌ها حلقه بزنید و taken همه را False کنید یا لیست را از نو بسازید.

در reset_game دوباره schedule_interval را ثبت نکنید اگر قبلاً یک بار ثبت شده است. همان callback زمان‌بندی‌شده باید بر اساس game_state کار کند.

دو نقطه انتهای خط را بررسی کنید و مطمئن شوید بلوک بعدی تورفتگی دارد.

فاصله ابتدای خط‌ها باید منظم باشد. Tab و Space را با هم مخلوط نکنید؛ VS Code را روی Space ثابت تنظیم کنید.

املای نام، حروف کوچک و بزرگ و ترتیب اجرای کد را بررسی کنید. ممکن است متغیر داخل تابع دیگری تعریف شده باشد.

این خطای منطقی است. مقدار متغیرها را موقتاً با screen.draw.text یا print نمایش دهید و هر تابع را جداگانه تست کنید.

چک‌لیست سریع عیب‌یابی Pygame Zero

دستور نصب را به شکل pip install pgzero زده‌ام؟
VS Code از همان Pythonای استفاده می‌کند که pgzero روی آن نصب شده؟
import pgzrun بالا و pgzrun.go() پایین فایل قرار دارند؟
WIDTH و HEIGHT و TITLE درست تعریف شده‌اند؟
draw() بدون آرگومان و update() درست نام‌گذاری شده‌اند؟
globalهای لازم برای متغیرهای سراسری نوشته شده‌اند؟
توابع حرکت، برخورد و وضعیت‌ها واقعاً داخل update فراخوانی می‌شوند؟
game_state، timer و reset_game همه متغیرهای لازم را کنترل می‌کنند؟

۷. پرسش‌های متداول Pygame Zero

Pygame کتابخانه اصلی بازی‌سازی است؛ Pygame Zero روی Pygame ساخته شده و برای آموزش، بخشی از راه‌اندازی و حلقه بازی را ساده می‌کند.

دستور اصلی pip install pgzero است. pgzrun نام Runner/ماژول اجرای Pygame Zero است.

این روش برای اجرای Pygame Zero داخل IDEهایی مثل VS Code یا IDLE مناسب است؛ در این حالت pgzrun.go() هم در انتهای فایل قرار می‌گیرد.

نصب pgzero معمولاً وابستگی Pygame را هم نصب می‌کند. اگر محیط خاصی دارید و خطای Pygame می‌بینید، نصب و نسخه آن را جدا بررسی کنید.

draw مسئول نمایش است؛ update مسئول تغییر منطق و وضعیت بازی. بهتر است تغییر متغیرهای بازی در update انجام شود و رسم در draw.

در این سبک آموزشی player_x بیرون تابع تعریف شده و داخل update دوباره مقداردهی می‌شود؛ برای همین Python باید بداند منظور همان متغیر سراسری است.

چون بازیکن و سکه به صورت دایره‌ای رسم شده‌اند؛ مقایسه فاصله مراکز با مجموع شعاع‌ها یک روش ساده و مناسب برای برخورد دایره‌هاست.

چون هر سکه چند ویژگی مثل x، y و taken دارد و Dictionary این ویژگی‌ها را کنار هم نگه می‌دارد. چند Dictionary هم داخل یک List قرار می‌گیرند.

با یک متغیر می‌توان مشخص کرد بازی در منوی شروع، حالت اجرا، برد یا باخت است و draw/update را بر اساس همان وضعیت کنترل کرد.

یک تابع بدون آرگومان را در فاصله‌های زمانی مشخص اجرا می‌کند؛ مثلاً count_down هر 1 ثانیه برای کم کردن time_left.

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

نه. آخرین بخشی را که اضافه کرده‌اید جدا کنید، توابع را تک‌تک تست کنید و با نمایش مقدار متغیرها علت خطا را پیدا کنید.

🎯

جمع‌بندی

ترم اول Pygame Zero می‌تواند یک مسیر بسیار منظم از «اولین پنجره» تا یک بازی کامل داشته باشد: نصب pgzero، راه‌اندازی با pgzrun، رسم با draw()، حرکت با update() و keyboard، سکه و امتیاز، برخورد، چند سکه، دشمن و جان، زمان، Game State، Start Menu و Restart.

مهم‌ترین مهارت در این مسیر فقط ساخت بازی نیست؛ بلکه یاد گرفتن Debugging است. هنرجو باید بتواند تشخیص دهد مشکل از نصب است، از Scope و global است، از فرمول برخورد است، از حلقه روی لیست سکه‌هاست یا از وضعیت بازی و تایمر. وقتی این روند عیب‌یابی به عادت تبدیل شود، پروژه‌های بازی‌سازی بعدی بسیار ساده‌تر و حرفه‌ای‌تر پیش می‌روند.

Pygame Zero

pgzero

pgzrun

Game State

Collision

Python Game