مع تزايد شهرة صناعة ألعاب الفيديو وازدياد الاهتمام بتطوير الألعاب الخاصة، أصبح من الأمور الأساسية لأي مطور مبتدئ أو محترف في هذا المجال أن يكون على إلمام قوي ببرمجة الألعاب وفهم منطق عملها الرياضي والفيزيائي.
هناك تقنيات كثيرة لبرمجة الألعاب ولعل المفضل من بينها بالنسبة لي محرك ألعاب جودوت أو جودو Godot Engine الذي يوفر أداة قوية ومجانية ومفتوحة المصدر تسهل عليك إنشاء ألعاب رائعة ومثيرة.
في هذه المدونة، سنقوم بتوضيح مفاهيم أساسية في برمجة الألعاب باستخدام Godot Engine. سنتناول مجموعة متنوعة من الموضوعات، من بينها لغة GDScript المستخدمة في Godot وكيفية إنشاء مشاهد ونسخها والتحكم في الحركة والفيزياء، إلى جانب التفاعلات عبر الشبكة والعديد من المفاهيم المثيرة الأخرى.
سواء كنت مبتدئًا يبحث عن بداية قوية في تطوير الألعاب أو مطورًا ذو خبرة يرغب في استكشاف ميزات جديدة، هذه المدونة ستقدم لك تفسيرات مبسطة وأمثلة توضيحية للمفاهيم المختلفة ونستكشف سويًا كيفية بناء ألعاب افتراضية تفاعلية مميزة.
هل أنت مستعد لخوض رحلة ممتعة في عالم تطوير الألعاب باستخدام Godot Engine؟ دعنا نبدأ رحلة التعلم؟
نشرح في مقال اليوم أساسيات لغة GDScript وهي لغة برمجة مصممة خصيصًا للاستخدام مع محرك ألعاب Godot.
ما هي لغة GDScript؟
هي لغة برمجة مرنة وسهلة التعلم تشبه في تركيبتها اللغوية لغة بايثون وتحاكيها في البساطة ويعتمدها محرك ألعاب جودو لتبسيط عملية برمجة الألعاب وتجعلها أكثر سلاسة.
مميزات لغة GDScript
1. ديناميكية : فأنت لا تحتاج للتصريح عن أنواع المتغيرات بشكل صريح. يتم تحديد الأنواع في وقت التشغيل بناءً على القيم المسندة للمتغير. على سبيل المثال:
var player_name = "wael"
var player_score = 100
2. كائنية التوجه: فكل شيء في جودو Godot هو كائن أو عقدة Node وكل عقدة هي مكون من مكونات اللعبة ويمكنك معالجتها لبناء منطق اللعبة ووظائفها.
3. توفر ميزة المشاهد التي تضم عقد مختلفة للعبة مثل الشخصيات وعناصر البيئة المحيطة بالشخصيات وتكون العقد مرتبة ضمن تسلسل هرمي فالمشهد هو العقدة الأب وباقي العقد أبناء.
4.تستخدم GDScript إشارات للاتصال بين العقد. يمكن أن ترسل العقدة إشارة للإشارة إلى وقوع حدث، ويمكن للعقد الأخرى الاتصال بتلك الإشارة للاستجابة وفقًا لذلك.
5. تضم العديد من الدوال البرمجية لتجميع التعليمات البرمجية التي يمكن تنفيذها. ويمكنك تعريف الدوال في النطاق العام أو ضمن عقدة محددة.
6. تضم مفهوم المتغيرات والثوابت وتعرف المتغير باستخدام الكلمة المفتاحية var والثابت بالكلمة المفتاحية const.
7. تدعم بنى التحكم مثل الحلقات التكرارية والتعليمات الشرطية.
8. تدعم مبدأ الوراثة مما يسمح لك بإنشاء أصناف جديدة ترث الخصائص والطرق من الأصناف الآباء.
9. تسمح بتحميل الموارد الخارجية مثل الأصوات والصور وغيرها ضمن اللعبة.
10.توفر أنماط بيانات مضمنة مثل الأعداد الصحيحة والعوامات والسلاسل والمصفوفات والقواميس لبناء ألعاب متقدمة.
11. تقوم بإدارة الذاكرة تلقائيًا باستخدام وتمنع حدوث نفاذ أو تسرب في الذاكرة.
مثال على كود مكتوب بلغة GDScript
لاحظ معي المثال البسيط التالي لدالة باسم add_numbers تحسب مجموع رقمين ممررين لها:
func add_numbers(a, b):
return a + b
var result = add_numbers(5, 7)
print("Result:", result)
مفاهيم متقدمة في لغة GDScript
دعنا نتعمق قليلاً في بعض المفاهيم الإضافية في GDScript
دمج المتغيرات في لغة GDScript
ذكرنا أن لغة GDScript ديناميكة ومرنة في تحديد أنماط المتغيرات وهي تمكننا كذلك من دمج قيم المتغيرات باستخدام المعامل + كما في المثال التالي:
var name = "Wael"
var score = 100
var message = "Player: " + name + ", Score: " + score
يستخدم الكود أعلاه لإنشاء رسالة تتضمن اسم لاعب ونقاطه في لعبة. يعرف السطر الأول متغير باسم name ويمنحه قيمة Wael يستخدم هذا المتغير لتخزين اسم اللاعب أما السطر الثاني فيعريف متغير باسم score ويمنحه قيمة 100. يستخدم هذا المتغير لتخزين نقاط اللاعب في اللعبة. نأتي للسطر الأخير لاحظ هنا أننا عرفنا متغير باسم message وحددنا له قيمة تتألف من سلسلة نصية تجمع بين النص الثابت Player وقيمة المتغير name ثم نص آخر يحتوي على النص Score وقيمة المتغير score.
في النهاية، المتغير message سيحمل القيمة "Player: Wael, Score: 100"، وهذه القيمة يمكن استخدامها لعرض رسالة تظهر اسم اللاعب ونقاطه في اللعبة.
هياكل البيانات المركبة في لغة GDScript
من ميزات لغة GDScript أيضًا دعم بنى معطيات مركبة مثل المصفوفات والقواميس لإدارة مجموعات البيانات بمنتهى السهولة.
يمكن أن تحتوي المصفوفات على قيم متعددة من نفس النوع، بينما تخزن القواميس أزواج مكونة من قيمة ومفتاح.
لاحظ المثال التالي:
var colors = ["red", "green", "blue"]
var person = {
"name": "Ali",
"age": 25
}
يعرف السطر الأول في الكود أعلاه متغير باسم colors ويمنحه قيمة عبارة عن مصفوفة تحتوي على ثلاث عناصر: "red" و "green" و "blue". هذه المصفوفة تستخدم لتخزين مجموعة من الألوان. والسطر الثاني يعرف متغير باسم person ويمنحه قيمة عبارة عن قاموس (أو كائن JSON). القاموس هو مجموعة من الأزواج الرئيسية-القيمة، حيث يكون لكل رئيسية (معروفة باسم "مفتاح") قيمة مرتبطة بها. يحتوي في هذا المثال، القاموس على اثنين من الأزواج: "name" و "age". المفتاح "name" يرتبط بالقيمة "Ali"، والمفتاح "age" يرتبط بالقيمة 25. يمكن استخدام القاموس لتخزين معلومات حول شخص، مثل اسمه وعمره.
بهذه الطريقة، تتمثل مكونات البيانات المعقدة في الكود في مصفوفة من الألوان وقاموس يحمل معلومات شخص. يمكنك استخدام هذه المتغيرات للوصول إلى البيانات والمعلومات في البرنامج وعرضها أو استخدامها حسب الحاجة.
الحلقات التكرارية في GDScript
تدعم لغة GDScript أنواعًا مختلفة من الحلقات مثل "for" و "while" و "foreach" للتكرار عبر المصفوفات أو القواميس أو غيرها من البني متعددة القيم. انظر المثال التالي:
for color in colors:
print(color)
for key in person:
print(key, ":", person[key])
for i in range(1, 6):
print(i)
يحتوي الكود أعلاه على ثلاث حلقات for مختلفة. لأشرحها بإيجاز:
الحلقة الأولى :for color in colors
هذه الحلقة تتكرر لكل عنصر في المصفوفة colors. وفي كل تكرار يأخذ المتغير color قيمة عنصر من المصفوفة colors وتُطبع هذه القيمة باستخدام الدالة print
الحلقة الثانية :for key in person
هذه الحلقة تتكرر لكل مفتاح في القاموس person وفي كل تكرار يأخذ المتغير key قيمة مفتاح من القاموس person وتُطبع هذه القيمة إلى جانب قيمة المفتاح المقابلة باستخدام الدالة print.
الحلقة الثالثة :for i in range(1, 6)
هذه الحلقة تتكرر خمس مرات (من 1 إلى 5) وفي كل تكرار يأخذ المتغير i قيمة من 1 إلى 5 وتُطبع هذه القيمة باستخدام الدالة print.
تجاوز الطرق
لغة GDScript هي لغة كائنية التوجه كما شرحت مسبقًا وبالتالي يمكنك تجاوز الطرق في الأصناف الأبناء الفرعية لتوفير تطبيقات مخصصة. هذا مفيد بشكل خاص عند العمل مع عقد Godot. لاحظ هذا الكود:
extends Node2D
func _ready():
print("This method is overridden")
يمثل الكود السابق جزءًا من كود GDScript للعبة داخل محرك Godot. دعوني أشرح كل سطر بإيجاز:
يشير السطر الأول إلى أن الصنف المحتوي على هذا الكود يوسع صنف اسمه Node2D في Godot وNode2D هو نوع من العقد أو الكائنات ثنائية الأبعاد.
يبدأ السطر الثاني بتعريف دالة باسم _ready وهي دالة جاهزة في Godot تُستدعى تلقائيًا عندما يتم إنشاء وعرض الكائن وتُستخدم عادة لإجراءات التهيئة الأولية.
أخيرًا يطبع السطر الأخير النص "This method is overridden" على وحدة الإخراج عند تنفيذ الدالة _ready
النمط التعدادي Enums في GDScript
Enum أو نمط التعداد هي بنية تمثل مجموعة من الأسماء المفهرسة التي تساهم في تحسن قابلية قراءة الكود. باستخدام الـ Enum، يمكنك تجميع مجموعة من القيم ذات الصلة تحت اسم معين، مما يسهل فهم الكود ويجعله أكثر تنظيمًا.
لاحظ هذا المثال الذي يعرف مجموعة من القيم المميزة (تمثل الاتجاهات هنا) واستخدامها لزيادة قراءة وفهم الكود. هذا يجعل الكود أكثر وضوحًا ويساعد في تجنب أخطاء الكتابة والتعامل مع القيم المميزة بشكل مرتب ومنظم.
enum Direction { UP, DOWN, LEFT, RIGHT }
var current_direction = Direction.RIGHT
يعرف السطر الأول متغير باسم Direction من نوع من البيانات enum الذي يسمح لك بإنشاء مجموعة محددة من القيم المميزة. وفي هذا الحالة، Direction تحتوي على أربع قيم مميزة: UP ،DOWN ،LEFT، و RIGHT، والتي تمثل اتجاهات يمكن التحرك لها في اللعبة.
يعرف السطر التالي متغير باسم current_direction ويمنحه قيمة من الـ Enum Direction باسم RIGHT. هذا يعني أن current_direction سيحمل قيمة "RIGHT"، والتي هي واحدة من القيم الممكنة في Enum Direction.
الأنواع الاختيارية
تستخدم الأنواع الاختيارية في لغة GDScript للإشارة إلى أن المتغير يمكن أن يحتوي على قيمة أو قيمة فارغة لتجنب الأخطاء الناتجه عن قيم مرجعية لفارغة.
var optional_var: int = null
الإشارات المخصصة Custom Signals
يمكن إنشاء إشارات مخصصة في GDScript لتوفير اتصال أكثر تحديدًا بين العقد.
signal health_decreased(damage)
تصدير المتغيرات
يمكنك استخدام الكلمة المفتاحية export لجعل المتغيرات قابلة للتعديل في لوحة Inspector panel بمحرر Godot. هذا مفيد لتعديل القيم دون تعديل الكود.
export var player_speed: float = 200.0
تحميل الموارد وإنشاء مثيل لها
يتيح لك نظام GDScript تحميل الموارد وإنشاء مثيل لها مثل المشاهد والأصوات بشكل ديناميكي أثناء وقت التشغيل.
var scene_instance = load("res://scenes/character.tscn").instance()
هذا الكود يستخدم لتحميل مشهد (Scene) من الملفات وإنشاء مثيل (Instance) منه في Godot ففي السطر السابق حملت ملف المشهد باستخدام الدالة load وحددنا مسار إلى "res://scenes/character.tscn" حيث تشير "res://" إلى المجلد المصدري لمشروع اللعبة والمسار "scenes/character.tscn" يمثل المسار النسبي لملف المشهد.
بعد تحميل المشهد، أنشانا منه مثيل باستخدام الدالة ()instance وذلك لإنشاء نسخة جديدة من المشهد تمامًا. يتم تخزين هذا المثيل في المتغير scene_instance.
مفاهيم تحتاجها للعمل مع GDScript في سياق تطوير الألعاب
أخيرًا سنناقش مجموعة مفاهيم متقدمة تحتاج لاستخدامها في سياق برمجة ألعاب متقدمة في جودو.
الدوال القابلة للإيقاف Coroutine
تدعم لغة GDScript مفهوم الكوروتين coroutines لتحقيق تأثيرات توقيتية ورسوم متحركة وهي دوال برمجية خاصة يمكن إيقافها مؤقتًا واستئنافها. هذا مفيد للتعامل مع المهام غير المتزامنة والرسوم المتحركة.
مثال على استخدام هذا النوع من الدوال:
func _ready():
start_coroutine(animation_sequence())
func animation_sequence():
$Sprite.modulate = Color(1, 1, 0)
yield(get_tree().create_timer(1.0), "timeout")
$Sprite.modulate = Color(1, 1, 1)
يبدأ الكود بتعريف دالة باسم _ready. في Godot، تُستدعى هذه الدالة تلقائيًا كما شرحت سابقاً عندما يتم إنشاء الكائن ويكون جاهز للعرض والتفاعل وتفيد في تهيئة الكائن عند بدء تشغيل اللعبة. والسطر التالي يستدعي دالة start_coroutine ليدل على أن الدالة التالية ستكون من نوع coroutine أي الدالة animation_sequence هي دالة يمكن أن توقف وتستأنف التنفيذ خلال إطارات متعددة.
والكود ضمن الدالة animation_sequence يغير لون الخاصية modulate اللكائن sprite إلى الأصفر ثم استخدمنا get_tree().create_timer(1.0) لإنشاء مؤقت (Timer) ينتهي بعد ثانية واحدة. أي الدالة ستوقف حتى ينتهي المؤقت وبعد انتهاء فترة الانتظار (المؤقت) تستأنف وتتابع تغيير اللون إلى اللون الأبيض مرة أخرى ليعود إلى اللون الأصلي.
باختصار، هذا الكود يقوم بتغيير لون نوع كائن ما sprite إلى الأصفر، ثم ينتظر لثانية، وبعد ذلك يعيد اللون إلى الأبيض.
مفهوم Singletons
يمكنك إنشاء نصوص فردية لتخزين البيانات والوظائف العامة التي يمكن الوصول إليها من أي مكان في اللعبة.
extends Node
static func get_singleton():
return get_node("/root/Singleton")
func global_function():
print("This is a global function.")
عرفنا كود يوسع الصنف Nodes وهو صنف يعبر عن مكون اللعبة الأساسي وعرفنا ضمنه دالة باسم get_singleton. هذه الدالة هي دالة ثابتة (static)، وهذا يعني أنها يمكن استدعاؤها مباشرةً من الصنف نفسه دون الحاجة لإنشاء مثيل أو غرض من الضنف. هذه الدالة تستخدم لاسترجاع مثيل من الـ Singleton الذي يعود إلى العقدة الجذر/root/Singleton ويسمح بالوصول إلى الـ Singleton يمكن استخدامه لتخزين بيانات ومتغيرات عامة تستخدم عبر كل أجزاء اللعبة. وعرفنا كذلك دالة عامة global_function تقوم بطباعة رسالة.
التعامل مع الملفات
توفر لغة GDScript العديد من الدوال البرمجية لقراءة الملفات وكتابتها. يمكن أن يكون هذا مفيدًا لحفظ وتحميل بيانات اللعبة والإعدادات ومستوى اللعب.
var file = File.new()
if file.open("user_data.sav", File.WRITE) == OK:
file.store_string("Player name: John\nScore: 100")
file.close()
هذا الكود يستخدم متغير من نوع File للتحكم في العمليات المتعلقة بالملفات. يقوم الكود بفتح ملف بإسم "user_data.sav" بوضع الكتابة، ثم يخزن معلومات اللاعب (اسمه ونقاطه) في الملف. بعد الانتهاء، يتم إغلاق الملف. كما تلاحظ تفيدك الملفات عندما تحتاج إلى حفظ بيانات معينة في اللعبة ضمن ملفات بصورة دائمة.
تكامل الفيزياء Physics Integration
يحتوي Godot على محرك فيزيائي مدمج. يمكنك استخدامه لإنشاء تفاعلات واقعية بين كائنات اللعبة من خلال تطبيق القوى واكتشاف الاصطدامات ومحاكاة السلوك الفيزيائي.
على سبيل المثال تخيل أنك تحتاج إلى تحديث سرعة كائن باستخدام الجاذبية، ثم تقوم بتحريك الكائن وفقًا للسرعة المحدثة لتحقيق حركة واقعية تعتمد على الفيزياء في هذه الحالة ستكتب كود كالتالي:
func _physics_process(delta):
velocity += gravity * delta
move_and_slide(velocity)
يقوم الكود بتحديث السرعة باستخدام قوة الجاذبية ويقوم بتحريك الكائن باستخدام السرعة المحدثة باستخدام الدالة move_and_slide، وذلك في كل إطار خلال محرك الفيزياء.
حيث عرفت في البداية دالة _physics_process تُستدعى في كل إطار خلال محرك الفيزياء في Godot. هذا يتيح التحكم في الحركة والتفاعلات التي تعتمد على الفيزياء في اللعبة. في السطر التالي حدثت متغير السرعة velocity باستخدام الجاذبية gravity. وضربت قيمة قوة الجاذبية في delta وأضفت النتيجة إلى velocity.
ثم عرفت دالة باسم move_and_slide لنقل الكائن باستخدام السرعة المحدثة velocity. هذه الدالة تأخذ السرعة وتحاول تحريك الكائن وتضمن عدم اختراقه بالجدران والأسطح. تعتبر هذه الخطوة أساسية في تنفيذ الحركة بناءً على الفيزياء في اللعبة.
الأصناف المخصصة
تسمح لك لغة GDScript بتعريف أصناف لتغليف الوظائف وتحسين تنظيم الكود وإنشاء مكونات قابلة لإعادة الاستخدام. على سبيل المثال إذا احتجنا لتعريف كائن سلاح، حيث يمكن استخدام السلاح لإطلاق النيران على الأهداف. ويحمل السلاح قيمة تعبر عن الضرر أو الأذية التي يمكن أن يسببها للهدف لتعبر عن قوته وجدواه وعدد الذخيرة التي يملكها. عند إطلاق النار، يتم تقليل سلامة الهدف وإنقاض الذخيرة بواحد. للقيام بذلك نكتب الكود التالي:
class Weapon:
var damage: int
var ammo: int = 0
func fire(target):
if ammo > 0:
target.health -= damage
ammo -= 1
استدعاء الإجراءات عن بُعد RPCs
يتيح لك GDScript استخدام استدعاءات الإجراءات عن بُعد (RPCs) للتواصل بين حالات الألعاب متعددة اللاعبين المتصلة بالشبكة. كل ما عليك كتابة الكلمة remote قبل اسم الدالة كما يلي:
remote func _on_remote_request(data):
# Handle remote request from other players
يتعامل الكود أعلاه مع دالة تسمى _on_remote_request يمكن أن تستدعى عن بُعد من لاعبين آخرين في اللعبة أي لاعبين عبر الشبكة في لعبة متعددة اللاعبين.
هذه كانت أبرز النقاط التي عليك تعلمها في برمجة الألعاب بلغة GDScript المضمنة في محرك جودو، بالطبع يوفر محرك ألعاب جودو Godot مجموعة ميزات عديدة أخرى مثل أدوات تصحيح الأخطاء وإدارة الذاكرة وتجميع الكائنات وتقليل الحسابات غير الضرورية وإدارة الموارد بكفاءة سنتعرف عليها بمزيد من التفصيل عندما نبدأ ببرمجة لعبة متكاملة خاصة بنا.
تابع معي الدروس القادمة لتعلم المزيد عن لغة GDScript ومحرك Godot.
وأخيرًا تذكر أن أفضل طريقة لتعلم برمجة الألعاب هي تطبيق هذه المفاهيم في مشاريع تطوير الألعاب الخاصة بك لاكتساب الخبرة العملية وتعميق فهمك لـ GDScript ومحرك Godot. تابع التدوينات القادمة لتعلم المزيد، وإذا كان لديك أي سؤال عن الشرح لا تتردد في طرحه في قسم التعليقات أسفل التدوينة.