جزوه کامل گودوت – جلسه اول | آموزش گام‌به‌گام با دیاگرام و توضیح کد


🎮 جزوه حرفه‌ای گودوت | جلسه اول

قدم‌به‌قدم از نصب تا حرکت بازیکن + تحلیل خط به خط کد و آشنایی با مختصات (X , Y)

🔰 پیش‌نیازها: گودوت، گره‌ها و مفهوم مختصات

گودوت یک موتور بازی‌سازی مدرن و متن‌باز است. در این جزوه فرض می‌کنیم تازه‌کار هستید، پس همه مفاهیم را از صفر توضیح می‌دهیم. اولین چیزی که باید درک کنید دستگاه مختصات در صحنه ۲ بعدی است. در گودوت مانند یک صفحه شطرنج، هر نقطه دارای یک موقعیت (x,y) می‌باشد.

📐 محور X (افقی) :

محور X از چپ به راست حرکت می‌کند. مقادیر مثبت → سمت راست صفحه، مقادیر منفی → سمت چپ صفحه. فرض کنید بازیکن شما در مختصات (۰,۰) وسط صحنه است، با افزایش X به سمت راست می‌رود.

📐 محور Y (عمودی) :

محور Y از بالا به پایین افزایش می‌یابد. مقادیر مثبت → پایین صفحه، مقادیر منفی → بالای صفحه. برخلاف ریاضیات معمول، در موتورهای بازی، Y مثبت به سمت پایین است.

⬅️ X-    | (0,0) |    X+
(Y-) بالا ↑
↓ پایین (Y+)


محور X و Y در گودوت

📌 تصویر ۱: پنجرهٔ نمایش گودوت – نقطه (۰,۰) مرکز Viewport است. با جابجایی گره می‌توانید مختصات را در Inspector ببینید.

توصیه عملی: در گودوت یک گره Sprite2D اضافه کنید و در Inspector مقدار Position را تغییر دهید. هرچه X بیشتر شود به راست، Y بیشتر شود به پایین حرکت می‌کند. این پایهٔ حرکت بازیکن است.

🛠️ گام اول: ساخت پروژه و اولین صحنه (CharacterBody2D)

بعد از نصب گودوت، پروژه جدید با نام MyFirstGame بسازید. در پنجره اصلی، روی + کلیک کرده و گره CharacterBody2D را انتخاب کنید – این گره پایه‌ی کاراکتری است که قرار است حرکت کند. نام آن را Player بگذارید. حالا دو فرزند به Player اضافه کنید: Sprite2D برای ظاهر و CollisionShape2D برای تشخیص برخورد. مراحل تصویری:

درخت گره ها

تصویر ۲: پنل Scene – ریشه Player, فرزندان Sprite2D , CollisionShape2D

💡 چرا CharacterBody2D؟ این گره توکار move_and_slide() را دارد و برای حرکت بازیکن با کدنویسی عالی است. برخلاف RigidBody2D که تحت تأثیر جاذبه و نیروهای خارجی است، CharacterBody کاملاً تحت کنترل شماست.

📎 اتصال اسکریپت – نوشتن اولین کد حرکت

روی گره Player راست کلیک → Attach Script. نام فایل player.gd خواهد بود. کد زیر را تایپ کنید. نگران نباشید، هر خط را با جزئیات کامل توضیح می‌دهیم.

extends CharacterBody2D

@export var speed = 300

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 = 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 فراخوانی شود.

نمایش سرعت در Inspector

تصویر ۳: پس از اتصال اسکریپت، در Inspector می‌توانید فیلد speed را مثلاً به ۵۰۰ تغییر دهید، بدون دست زدن به کد.

🏃 بخش دوم: تست حرکت و اضافه کردن دوربین

صحنه Player را با Ctrl+S ذخیره کنید. حالا آن را به عنوان صحنه اصلی (Main Scene) تنظیم کنید (کلیک راست روی player.tscn → Set as Main Scene). کلید F5 را بزنید. پنجره بازی باز می‌شود و با کلیدهای جهت‌ها (↑ ↓ ← →) بازیکن حرکت می‌کند. اگر حرکت نکرد، چک کنید که CollisionShape2D فعال باشد و بازیکن از لبه های صفحه خارج نشود. برای اینکه دوربین به دنبال بازیکن برود، یک گره Camera2D به Player اضافه کنید (Add Child Node → Camera2D). سپس در Inspector گزینه Current را فعال کنید. حالا دوربین همیشه بازیکن را دنبال می‌کند. تصویر زیر:

دوربین دنبال‌کننده

تصویر ۴: افزودن Camera2D به Player و تیک Current = On

📡 سیگنال‌ها: ارتباط بین گره‌ها (بدون کدنویسی پیچیده)

سیگنال‌ها مانند یک زنگ اخبار هستند: هرگاه اتفاق خاصی بیفتد (مثل ورود بازیکن به یک منطقه)، سیگنال به بقیه اطلاع می‌دهد. بیایید یک “خط پایان” بسازیم که با ورود بازیکن پیام «برنده شدی» چاپ کند.

ساخت منطقه پایان (Area2D)

  • در Scene اصلی (مثلاً صحنه Game) یک گره Area2D بسازید و نام FinishZone بگذارید.
  • دو فرزند: CollisionShape2D (یک مستطیل سبز) و Sprite2D (یک پرچم ساده).
  • روی گره Area2D کلیک کنید، سپس در تب بالای Inspector، گزینه Node را انتخاب کنید (کنار Inspector). لیست سیگنال‌ها را ببینید، روی body_entered دوبار کلیک کنید. در پنجره باز شده، گره خود Area2D را انتخاب و تأیید کنید. کد زیر در اسکریپت FinishZone ایجاد می‌شود:
extends Area2D

func _on_body_entered(body):
    if body.name == "Player":
        print("🎉 برنده شدی! به خط پایان رسیدی.")
        # می‌توانید بازی را ریست کنید یا صحنه بعد را بار کنید
        # get_tree().reload_current_scene()

توضیح سیگنال: تابع _on_body_entered زمانی اجرا می‌شود که یک جسم (مثل Player) وارد منطقه Area2D شود. متغیر body همان گره‌ای است که وارد شده. شرط body.name == “Player” مطمئن می‌شود فقط ورود بازیکن واکنش نشان دهد.
تمرین سریع: برای درک بیشتر، یک Area2D دیگر به عنوان سکه بسازید و در سیگنال body_entered دستور queue_free() را اضافه کنید تا سکه ناپدید شود. این روش پایه جمع‌آوری آیتم‌هاست.

🧪 تمرین عملی اجباری (برای تثبیت یادگیری)

🥇 تمرین ۱ – جمع‌آوری سکه

یک صحنه Coin بسازید (ریشه Area2D). سیگنال body_entered را متصل کنید، به ازای برخورد با Player، سکه حذف شود و در خروجی +۱ امتیاز چاپ شود. چند نسخه از Coin در صحنه کپی کنید.

⚠️ تمرین ۲ – دشمن ساده

یک Area2D به عنوان Enemy طراحی کنید. با ورود بازیکن، پیام Game Over چاپ کند و بازی را ریست کند (get_tree().reload_current_scene()).

نمایش برخوردها

تصویر ۵: مسیر حرکت بازیکن (دایره قرمز) با سکه‌ها و خط پایان – تعامل از طریق سیگنال body_entered

💡 خلاصه و گام بعدی

در این جزوه یاد گرفتید که محورهای X و Y در گودوت چگونه کار می‌کنند (X+ راست، Y+ پایین). یک کاراکتر متحرک با کد خط به خط ساختی و معنی هر بخش از کد را فهمیدی. همچنین سیگنال‌ها را عملاً پیاده‌سازی کردی. این جلسه پایه و اساس تمام بازی‌های دو بعدی شما خواهد بود.

📘 تکلیف جلسه آینده: یک متغیر سراسری (Autoload) برای امتیاز تعریف کنید، امتیاز را روی صفحه نمایش دهید (با Label) و بعد از جمع‌آوری ۳ سکه پیام موفقیت بدهید. همچنین انیمیشن ساده برای بازیکن (Idle/Run) با AnimatedSprite2D.

🎯 گودوت را گام به گام یاد بگیر – این جزوه به صورت رایگان برای وبسایت آموزشی طراحی شده است. برای مشاهده تصاویر واقعی، از محیط گودوت اسکرین‌شات بگیرید و در img placeholder قرار دهید.


دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *