🎮 جزوه گودوت | جلسه اول
🎮 جلسه اول: قدم به دنیای ساخت بازی
به اولین جلسه از دورهی جامع موتور بازیسازی گودوت (Godot 4) خوش آمدید! این جزوه طوری طراحی شده که بدون پیشزمینه برنامهنویسی یا بازیسازی، بتوانید اولین قدمهای محکم را بردارید. در طول دو ساعت، یاد میگیرید که گودوت چیست، چطور یک صحنه بسازید، یک کاراکتر ساده حرکت دهید و با مفاهیم گرهها، سیگنالها و اسکریپت نویسی آشنا شوید. بیایید شروع کنیم!
🔰 بخش اول: گودوت را بهتر بشناسیم
۱-۱ گودوت چیست و چرا انقلابی در ساخت بازی ایجاد کرده؟
گودوت یک موتور بازی متنباز، رایگان و فوقالعاده قدرتمند است که بدون نیاز به پرداخت حق امتیاز، به شما اجازه میدهد بازیهای دو بعدی و سه بعدی بسازید. حجم نصب آن کمتر از ۱۰۰ مگابایت است اما قابلیتهای حرفهای مانند انیمیشن، فیزیک، نورپردازی و اسکریپتنویسی را دارد. زبان اصلی آن GDScript نام دارد که شباهت زیادی به پایتون داشته و یادگیری آن برای تازهکاران بسیار شیرین است. اگر به دنبال یک ابزار سبک، سریع و بدون هزینه هستید، گودوت بهترین انتخاب است.
بازیهای موفقی مانند Brotato، Cassette Beasts و Cruelty Squad با گودوت ساخته شدهاند و جامعه کاربری آن روزبهروز در حال رشد است. در این جلسه روی شاخه پایدار Godot 4.2 یا بالاتر تمرکز میکنیم. پس بیایید نصب را شروع کنیم.
به سایت رسمی godotengine.org بروید و نسخه استاندارد (Standard) را برای سیستمعامل خود دانلود کنید. نیازی به نصب .NET ندارید مگر اینکه قصد برنامهنویسی سیشارپ داشته باشید. فایل اجرایی را باز کنید، در پنجره Project Manager گزینه «New Project» را بزنید. نام پروژه را MyFirstGame بگذارید و یک پوشه خالی برای آن انتخاب کنید. سپس دکمه «Create & Edit» را بزنید. محیط اصلی گودوت را همین الان میبینید: پنج بخش اصلی شامل صحنه (Scene)، نمایش (Viewport)، فایل سیستم، بازرس (Inspector) و خروجی (Output). نگران نباشید، قدم به قدم با همه آشنا میشوید.
۱-۲ مفهوم گره (Node) و صحنه (Scene) – الفبای گودوت
در گودوت هر چیزی که در بازی میبینید (کاراکتر، دشمن، سکه، دوربین، موسیقی) یک «گره» است. گرهها مانند جعبههای کوچکی هستند که میتوانند ویژگیهایی مثل موقعیت، شکل، رفتار فیزیکی یا اسکریپت داشته باشند. یک صحنه در حقیقت درختی از گرههاست که با هم یک موجودیت بازی را میسازند. برای مثال یک بازیکن شامل گرههای Sprite2D (نمایش ظاهر)، CollisionShape2D (برخورد) و اسکریپت حرکت میشود. وقتی این درخت گره را ذخیره میکنید، یک «صحنه» با پسوند .tscn دارید.
برای ساخت اولین صحنه، روی دکمه «+» (یا Scene → New Scene) کلیک کنید و گره CharacterBody2D را انتخاب کنید. سپس نام آن را Player بگذارید. حالا یاد میگیرید که به این بازیکن ظاهر و برخورد بدهید.
🏃♂️ بخش دوم: تولد اولین کاراکتر متحرک
۲-۱ اضافه کردن Sprite و Collision (شکل و فیزیک)
روی گره Player کلیک راست کرده و Add Child Node را بزنید. در لیست جستجو عبارت Sprite2D را وارد کنید. اکنون یک گره Sprite2D زیرمجموعه Player دارید. حالا در بخش بازرس (Inspector) سمت راست، برای ویژگی Texture یک تصویر انتخاب کنید. اگر تصویری ندارید، نگران نباشید! میتوانید به جای آن از ColorRect استفاده کنید یا به سادگی روی دکمه پایین کادر Texture کلیک کرده و New RectangleShape را بزنید؟ نه بهتر است یک شکل رنگی ساده بسازید. در پنل پایین «FileSystem» کلیک راست کرده → New → Resource → New Resource و نوع RectangleShape2D را انتخاب کنید. اما روش آسانتر: برای ظاهر از Sprite2D و یک مربع رنگی میتوانید از گره ColorRect استفاده نکنید، پیشنهاد من این است: به جای Sprite2D میتوان از Sprite2D با Texture خودکار استفاده کرد؟ بیایید ساده بگوییم: به کمک بازرس، در قسمت Texture، گزینه «New Image» را انتخاب کرده و یک تصویر ۳۲×۳۲ رنگی (مثلاً آبی) بسازید. اما برای سرعت تمرین، یک تصویر ساده از فایل سیستم بکشید. اگر هیچ فایلی ندارید، گودوت به شما اجازه میدهد یک NoiseTexture ساده بسازید. در کل فرض کنید یک شکل شبیه جعبه قرمز اضافه کردهاید. حالا اندازه آن را با مقیاس (Scale) در بازرس تنظیم کنید.
سپس دوباره روی Player کلیک راست کرده و گره CollisionShape2D را اضافه کنید. در بازرس، کنار گزینه Shape، روی [empty] کلیک کرده و New RectangleShape2D را بزنید. ابعاد مستطیل برخورد را حدوداً ۴۰ در ۴۰ پیکسل تنظیم کنید تا با ظاهر بازیکن هماهنگ باشد. حتماً ذخیره کنید (Ctrl+S) و نام صحنه را player.tscn بگذارید.
۲-۲ نوشتن اولین اسکریپت (کد حرکت با کیبورد)
روی گره Player کلیک راست کرده و Attach Script را انتخاب کنید. نام پیشفرض player.gd را قبول کرده و دکمه ایجاد را بزنید. حالا ویرایشگر کد باز میشود. کد زیر را خط به خط تایپ کنید (ما هر خط را توضیح میدهیم):
extends CharacterBody2D
# سرعت حرکت بر حسب پیکسل در ثانیه
@export var speed = 300
# تابع physics_process هر فریم فیزیک (۶۰ بار در ثانیه) اجرا میشود
func _physics_process(delta):
# بردار جهت حرکت (صفر اولیه)
var input_dir = Vector2.ZERO
# گرفتن ورودی از صفحه کلید (کلیدهای پیشفرض جهتها)
input_dir.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
input_dir.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
# نرمال کردن برای جلوگیری از حرکت مورب سریعتر
if input_dir.length() > 1:
input_dir = input_dir.normalized()
# اعمال سرعت به بردار velocity
velocity = input_dir * speed
# حرکت و مدیریت برخورد
move_and_slide()
✍️ تحلیل خط به خط
extends CharacterBody2D : یعنی این اسکریپت به گره CharacterBody2D متصل میشود و تمام قابلیتهای آن را به ارث میبرد. (دسترسی به velocity , move_and_slide)
@export var speed = 300 : یک متغیر به نام سرعت تعریف میکند. @export باعث میشود در بخش Inspector (پنل سمت راست) بتوانید مقدار سرعت را بدون نیاز به ویرایش کد تغییر دهید. واحد: پیکسل بر ثانیه. سرعت ۳۰۰ یعنی در هر ثانیه ۳۰۰ پیکسل جابجا شود.
func _physics_process(delta): : این تابع هر فریم فیزیک (به طور پیشفرض ۶۰ بار در ثانیه) اجرا میشود. ورودی delta یعنی زمان سپری شده از فریم قبلی، ولی اینجا مستقیماً استفاده نمیکنیم چون move_and_slide آن را درونی مدیریت میکند.
var input_dir = Vector2.ZERO : یک بردار دو بعدی میسازد با مقدار (۰,۰). این بردار جهت حرکت را ذخیره میکند.
input_dir.x = Input.get_action_strength(“ui_right”) – Input.get_action_strength(“ui_left”) : input_dir.x مربوط به حرکت افقی است. Input.get_action_strength برای کلیدهای راست و چپ مقدار ۰ یا ۱ برمیگرداند. اگر راست فشرده شود، مقدار ۱ منهای ۰ = ۱ (یعنی حرکت به راست). اگر چپ فشرده شود، ۰ منهای ۱ = -۱ (یعنی حرکت به چپ). هر دو را همزمان صفر میشود.
input_dir.y = Input.get_action_strength(“ui_down”) – Input.get_action_strength(“ui_up”) : مشابه برای محور Y: پایین مثبت و بالا منفی (زیرا در گودوت Y+ پایین است).
if input_dir.length() > 1: input_dir = input_dir.normalized() : وقتی دو کلید مورب (مثلاً راست+پایین) فشرده شود، طول بردار به ۱.۴ میرسد که باعث میشود حرکت مورب سریعتر شود. نرمال کردن یعنی طول بردار را برابر ۱ میکند تا سرعت در همه جهات یکسان بماند.
velocity = input_dir * speed : متغیر velocity (که از CharacterBody2D به ارث رسیده) را برابر حاصلضرب جهت در سرعت قرار میدهد. مثلاً اگر جهت (۱,۰) و سرعت ۳۰۰ باشد، velocity.x=300 و velocity.y=0.
move_and_slide() : این تابع جادویی باعث میشود گره براساس velocity حرکت کند و با collisionها برخورد داشته باشد (دیوارها، سقف و …). حتماً باید بعد از مقداردهی velocity فراخوانی شود.
۲-۳ تست بازی و اجرا
حالا باید صحنه Player را به عنوان صحنه اصلی معرفی کنید. در پنل FileSystem روی فایل player.tscn راست کلیک کرده و گزینه Set as Main Scene را بزنید. سپس کلید F5 را فشار دهید یا روی دکمه پلی (▶) در گوشه بالا سمت راست کلیک کنید. پنجره بازی باز میشود و با فشردن کلیدهای جهتها (بالا، پایین، چپ، راست) خواهید دید که جعبه آبی/قرمز شما حرکت میکند. اگر حرکت نکرد، مطمئن شوید CollisionShape2D فعال است (چشمک کنار آن خاموش نباشد) و گره اصلی همان CharacterBody2D باشد. تبریک میگویم! شما اولین بازی متحرک خود را ساختید.
📡 بخش سوم: سیگنالها – قلب ارتباط در گودوت
۳-۱ سیگنال چیست؟ واکنش به رویدادها
در بازیسازی، خیلی وقتها نیاز داریم وقتی دو شیء به هم برخورد کردند یا دکمهای فشرده شد، اتفاقی بیفتد. سیگنالها مانند پیامکهایی هستند که گرهها برای یکدیگر میفرستند. برای مثال گره Area2D میتواند سیگنال body_entered بفرستد تا بفهمیم بازیکن وارد منطقه شده است. بیایید یک «خط پایان» بسازیم:
- به صحنه اصلی (که میتواند یک صحنه خالی جدید به نام Game باشد) برگردید. یک گره Area2D اضافه کنید و نامش را FinishFlag بگذارید.
- زیر Area2D یک گره CollisionShape2D با شکل مستطیل و اندازه حدود ۶۰×۶۰ اضافه کنید.
- همچنین یک Sprite2D به عنوان ظاهر (یک ستاره ساده یا مستطیل سبز) به آن بدهید.
- روی گره Area2D کلیک کنید، به تب Node در کنار Inspector بروید و دنبال سیگنال body_entered بگردید. روی آن دوبار کلیک کنید. یک پنجره باز میشود که از شما میخواهد تابع را به کدام گره متصل کنید. همان Area2D را انتخاب کنید و نام تابع را تأیید کنید.
- کد زیر در اسکریپت FinishFlag نوشته میشود:
extends Area2D
func _on_body_entered(body):
if body.name == "Player": # اگر جسم وارد شده بازیکن باشد
print("🏆 برنده شدی! به خط پایان رسیدی.")
# در آینده میتوانید صحنه بعدی را بارگذاری کنید
get_tree().reload_current_scene() # برای تمرین بازی رو ریست میکند
حالا اگر بازیکن به منطقه برخورد کند، پیام در خروجی چاپ میشود. این قدرت سیگنالهاست: بدون وابستگی زیاد، گرهها با هم ارتباط برقرار میکنند.
۳-۲ تفاوت گرههای اصلی (بدنها)
برای اینکه در آینده گیج نشوید، بهتر است تفاوت این گرههای پایه را بدانید:
- CharacterBody2D: مناسب برای کاراکترهایی که شما حرکت آنها را با کد کنترل میکنید (بازیکن، دشمن ساده).
- RigidBody2D: تحت تأثیر فیزیک موتور است (مانند توپها، اشیای قابل پرتاب).
- Area2D: تشخیص ورود، منطقههای آسیب یا جمعآوری آیتم. خودش گرانش و برخورد فیزیکی ندارد.
- StaticBody2D: برای موانع ثابت مثل دیوارها و زمین.
✍️ بخش چهارم: تمرین کلاسی – سکه و امتیاز
زمان آن رسیده که دست به کار شوید. تمرین زیر را انجام دهید تا مفاهیم کاملاً در ذهنتان تثبیت شود:
- یک صحنه جدید به نام Coin بسازید. گره ریشه از نوع Area2D باشد.
- زیر آن یک Sprite2D (ظاهر سکه، مثلاً یک دایره زرد) و CollisionShape2D قرار دهید.
- اسکریپت به Coin بچسبانید و سیگنال body_entered را متصل کنید.
- در تابع سیگنال، بررسی کنید اگر body.name == “Player” باشد، با دستور queue_free() سکه حذف شود و print(“+1 امتیاز”) را چاپ کنید.
- چند نسخه از سکه را در صحنه اصلی بازی (که Player در آن قرار دارد) کپی کنید و بچینید.
- بازی را اجرا کنید. هر بار به سکه برخورد کنید، ناپدید شده و پیام امتیاز میآید.
این کار شما را با منطق جمعآوری آیتم آشنا میکند. در جلسات بعدی یاد میگیریم که امتیاز را روی صفحه نمایش دهیم و افکتهای جذاب اضافه کنیم.
📌 جمعبندی و گامهای بعدی
در این جلسه دو ساعته، یاد گرفتید که گودوت چیست، یک صحنه CharacterBody2D ساختید، اسکریپت حرکت نوشتید، با سیگنالها آشنا شدید و یک آیتم جمعآوریکننده طراحی کردید. این پایههای اصلی برای ساخت هر بازی ۲D هستند. به خودتان افتخار کنید!
تکلیف جلسه اول: یک «دشمن ساده» طراحی کنید که با برخورد به بازیکن، پیام «Game Over» در خروجی چاپ کند. میتوانید از Area2D یا StaticBody2D استفاده کنید و سیگنال body_entered را مشابه سکه پیادهسازی کنید، با این تفاوت که بازیکن حذف نمیشود، فقط هشدار بدهید. برای چالش بیشتر، میتوانید با دستور get_tree().reload_current_scene() بازی را پس از برخورد مجدداً راهاندازی کنید.
📌 برای مشاهده جلسات بعدی، کانال ما را دنبال کنید.
