إنشاء لعبة "Like Coins" على محرك Godot. الجزء 2

آمل أن تكون قد سئمت من انتظار الجزء الثاني من المقالة ، الذي يتناول جوانب تطوير اللعبة باستخدام محرك Godot ، باستخدام مثال لعبة Like Coins؟ تم إعداد الكثير من كل شيء "لذيذ" و "صحي" على جدول الأعمال. سأحجز على الفور أننا في هذه المقالة سننهي اللعبة التي بدأتها سابقًا ، والتي يمكنك قراءتها في البداية - إنشاء لعبة "Like Coins" على محرك Godot. الجزء الأول ، لكن سلسلة المقالات ستستمر ، لأن كان هناك الكثير من المواد التي جعلتني أترك جزءًا منها ، ولكن بالتأكيد سنعود إليها لاحقًا. فلتبدأ "جاميدف"!


المشهد "الرئيسي"


في الجزء السابق من المقال ، توقفنا عند المرحلة Main ( Main ) ، وربما نستمر منه. نحذف كل شيء تمت إضافته سابقًا (إذا أضفت شيئًا بالطبع للتحقق من كيفية عمل كل شيء) ، إذا لم يتم تحميل أي شيء في المشهد ، فيجب أن نضيف Node ، والتي ستكون الأصل للعقد المدرجة أدناه ، والتي بدورها يجب أن تضاف إلى المشهد:


ColorRect ("الخلفية") - املأ لون الخلفية ؛
Player - كائن "Player" (آمل ألا تكون مرتبكًا لأنني أسمي مشهد Player كائنًا؟) ؛
Node ("الحاوية") - "الحاوية" للتخزين المؤقت للعملات المعدنية ؛
Position2D ("PlayerStart") - في بداية اللعبة تحدد الموضع الأولي لكائن "Player" ؛
Timer ("GameTimer") - عداد الحد الزمني ؛


حدد ColorRect وعلى شريط الأدوات حدد: Layout -> Full Rect إلى كامل مساحة الشاشة (في المستقبل غالبًا ما نلجأ إلى هذه الوظيفة ، لذا أنصحك بدراسة العمليات الأخرى المحددة في قائمة Layout ) ، لهذه العقدة ، في خاصية "اللون" حدد لون التعبئة المطلوب. يمكنك أن تفعل الشيء نفسه مع TextureRect ، فقط بدلاً من التعبئة ستحتاج إلى تحميل الصورة من خلال خاصية "Texture". بالنسبة إلى Position2D ، في خاصية "position" ، حدد قيم "x" و "y" - سيكون هذا بمثابة الموضع الأولي Player . بالطبع ، بمساعدة البرنامج النصي ، يمكنك تعيين قيم تحديد المواقع مباشرة في Player نفسه ، ولكننا لا نتعلم فقط تطوير الألعاب ، ولكن أيضًا دراسة "Godot" ، لذا فإن التفكير في خيارات مختلفة لحل مشكلة واحدة لن يكون غير ضروري.


نص "رئيسي"


قم بإضافة برنامج نصي Node وطباعة ما يلي:


 extends Node #PackedScene        export (PackedScene) var Coin export (int) var playtime var level #  var score # var left_time #     var window_size #   var playing = false #    

سيتم عرض الخصائص "عملة" و "وقت اللعب" في Inspector . اسحب مشهد "Coin.tscn" إلى خاصية "Coin" واضبط "وقت اللعب" على "40" (مدة اللعبة بالثواني).


عندما تبدأ اللعبة ، في كل مرة يجب أن يكون هناك تهيئة - التحضير للعمل ، وتحديد المعايير المطلوبة للتشغيل عالي الجودة وخالي من الأخطاء للتطبيق. هذه خطوة إلزامية ، لذا يجب عليك الاهتمام بها أولاً.


 func _ready(): randomize() #        window_size = get_viewport().get_visible_rect().size #    $Player.window_size = window_size #    "" $Player.hide() #   

لاحظ أنه عند تحديد اسم كائن Player ، يتم استخدام الرمز "$" - هذا هو "السكر النحوي" الذي يسمح لك بالوصول مباشرة إلى العقدة في المشهد الحالي ، وهو بديل جيد get_node("Node1") (على الرغم من أن استخدام الأخير غير محظور). إذا كان لدى "Node1" سليل "Node2" ، يمكنك أيضًا استخدام هذه الطريقة - $Node1/Node2 . لاحظ أن الملء التلقائي يعمل بشكل جيد في Godot ، لذلك لا تهمله. استخدام مسافة في أسماء العقد أمر غير مرغوب فيه ، ولكنه لا يزال صالحًا ، في هذه الحالة استخدم علامات الاقتباس - $"My best Node1" .


لعبة جديدة


لبدء لعبة جديدة ، سنحدد الوظيفة المقابلة لذلك ، والتي يمكننا عندئذٍ استدعاؤها ، على سبيل المثال ، بالضغط على زر.


 func new_game(): playing = true #   level = 1 score = 0 time_left = playtime $Player.start($PlayerStart.position) $Player.show() $GameTimer.start() #    spawn_coins() #  

ستعمل الوظيفة "start ()" التي تكون $PlayerStart.position هي $PlayerStart.position على نقل المشغل إلى موقع البداية ، وتكون الوظيفة "spawn_coins ()" مسؤولة ، كما قد تخمن ، عن عملات معدنية في الملعب.


 func spawn_coins(): for i in range(4 + level): var c = Coin.instance() $CoinContainer.add_child(c) c.window_size = window_size c.position = Vector2(rand_range(0, window_size.x), rand_range(0, window_size.y)) 

سترجع وظيفة range(4 + level) صفيفًا بنطاق معين قيمته تساوي مجموع عدد العملات وقيمة المستوى الحالي. يمكن أن يحتوي النطاق على وسيطة واحدة ، كما في حالتنا ، إما وسيطتين أو ثلاث وسيطات (ستكون الوسيطة الثالثة خطوة صفيف). في هذه الوظيفة ، ننشئ عدة حالات لكائن "Coin" ونضيف CoinContainer كعناصر CoinContainer (آمل أنك لم تنس أننا بالفعل لدينا حق الوصول إلى الكائن ، بفضل PackedScene ). تذكر أنه في كل مرة تقوم فيها بإنشاء مثيل للعقدة الجديدة (طريقة instance() ) ، يجب إضافتها إلى الشجرة باستخدام add_child() . بعد ذلك ، قمنا بتعيين المنطقة لفرز محتمل للعملات المعدنية بحيث لا تظهر عن طريق الخطأ خلف الشاشة ، ثم نقوم بتعيين موضع عشوائي. لا يبدو السطر الأخير ممتعًا من الناحية الجمالية قليلاً ، لذلك أقترح تبسيطه باللجوء إلى Singleton.


سينجلتون


الاسم الأوسط ل Singleton هو "بدء التشغيل". موحية بالفعل ، أليس كذلك؟ أقول لك ، يعمل المفرد على النحو التالي: برنامج نصي يمكننا من خلاله كتابة أي شيء نريده (بدءًا من الإعلان عن المتغيرات وانتهاءً بـ "تبديل" المشاهد ، بما في ذلك تحميلها وتفريغها) يتم تحميله أولاً ، مع تشغيل التطبيق ، ويمكن الوصول إلى جميع محتوياته من أي نقاط المشروع. بطريقة ما ، هذا هو نوع من المستودعات العالمية المخصصة "لأي شيء" متاح في أي وقت.


لاحظ أن المشروع يحتوي على مستودع عالمي خاص به ، يمكننا أيضًا استخدام محتوياته ، ويمكنك الوصول إليه باستخدام ProjectSettings.get_setting(name) ، حيث يكون name هو اسم المعلمة المطلوبة.

الآن ، لاستخدام شيء ما من المستودع "_G" ، يكفي تسميته بالاسم ، ثم تحديد الطريقة التي سيتم استدعاؤها ، أو أي شيء لدينا. لذا ، أنشئ نصًا فارغًا واكتب فيه الوظيفة الموضحة أدناه:


 extends Node func rand(): var rrand = Vector2(rand_range(40, 760), rand_range(40, 540)) return rrand #   


بعد ذلك ، احفظه Project -> Project Settings -> AutoLoad إلى إعدادات المشروع: Project -> Project Settings -> AutoLoad . نختار البرنامج النصي الذي تم إنشاؤه حديثًا ، ونعين اسمًا له ، على سبيل المثال ، "_G" ، ونعود إلى وظيفة "spawn_coins ()" لتعديل الموعد النهائي قليلاً ، واستبداله بالرمز التالي:


  ... c.position = _G.rand() 

الآن من الجدير التحقق مما حدث بوضع "spawn_coins ()" في كتلة "_ready ()" وتشغيل التطبيق على F5. ولا تنس اختيار Main.tscn رئيسي ، إذا كنت قد أخطأت لسبب ما في الاختيار ، فيمكنك تغيير المشهد الرئيسي يدويًا ، لذلك تحتاج إلى الانتقال إلى إعدادات المشروع: General -> Run -> MainScene . هل تعمل؟ ثم انتقل.


كم عدد العملات المتبقية؟


دعنا نواصل. بعد ذلك ، تحتاج إلى التحقق من عدد القطع النقدية المتبقية لنقل اللاعب إلى المستوى التالي ، ومنحه "مكافأة" صغيرة في شكل زيادة في الوقت بمقدار 5 ثوانٍ ، ثم إعادة إنتاج القطع النقدية.


 func _process(delta): #   ?     ? if playing and $CoinContainer.get_child_count() == 0: #    level += 1 # ""   time_left += 5 #  spawn_coins() 

واجهة المستخدم


ستتكون واجهتنا بالكامل من العناصر التالية: مؤشر النتيجة والمستوى الحالي والوقت واسم اللعبة وزرًا يؤدي إلى إطلاق اللعبة. قم بإنشاء مشهد ( HUD.tscn ) مع أحد الوالدين CanvasLayer (يسمح لك برسم واجهة مستخدم أعلى مجال اللعب). بالنظر إلى المستقبل ، سأقول أنه ليس من الملائم جدًا إدارة عناصر واجهة المستخدم ، على الأقل بالنسبة لي هذا صحيح ، ولكن قائمة واسعة إلى حد ما من العناصر والتطوير النشط تلهم مزاجًا إيجابيًا في المستقبل المشرق لتطوير هذا الجانب من المحرك.



في Godot هناك ما يسمى "عقد التحكم" التي تسمح لك بتنسيق العناصر الفرعية تلقائيًا فيما يتعلق بالمعلمات المحددة للوالد. لكل نوع من "عقد التحكم" خصائص خاصة تتحكم في كيفية التحكم في موقع أحفادهم. الممثل الحيوي من هذا النوع هو MarginContainer ، الذي يجب إضافته إلى المشهد. بمساعدة Layout -> Top Wide بتمديده في أعلى النافذة ، وفي خصائص هذا الكائن ، في قسم Margin ، حدد المسافات البادئة من الحواف: يسار ، وأعلى ، ويمين. يجب أن MarginContainer على ثلاثة Label ScoreLabel بالأسماء التالية: ScoreLabel و LevelLabel و TimeLabel . أضفهم إلى المشهد. باستخدام خاصية Align ، اجعلها محاذاة إلى اليسار والوسط واليمين. يبقى إضافة Label أخرى ( Messagelabel ) ، ووضعها في المنتصف ، وكل ذلك أيضًا بمساعدة Layout ، ومكان أقل قليلاً ( StartButton ).



دعنا الآن نجعل الواجهة متجاوبة ، نحتاج إلى تحديث الوقت وعدد العملات التي تم جمعها وإبراز المستوى الحالي. إضافة برنامج نصي للعقدة HUD .


 extends CanvasLayer signal start_game func update_score(value): $MarginContainer/ScoreLabel.text = str(value) func update_level(value): if len(str(value)) == 1: $MarginContainer/TimeLabel.text = "0: 0" + str(value) else: $MarginContainer/TimeLabel.text = "0: " + str(value) func update_timer(value): $MarginContainer/TimeLabel.txt = str(value) 

بالنسبة إلى MessageLabel نحتاج إلى موقّت من أجل تغيير نص الرسالة لفترة قصيرة. أضف عقدة Timer واستبدل اسمه بـ MessageTimer . في المفتش ، قم بتعيين وقت الانتظار على ثانيتين وحدد خانة الاختيار في حقل One Shot . يضمن ذلك تشغيل المؤقت مرة واحدة فقط عند بدء التشغيل.


 func show_message(text): $MessageLabel.text = text $MessageLabel.show() $MessageTimer.start() 

قم بتوصيل إشارة timeout() بـ "MessageTimer" وإضافة ما يلي:


 func _on_MessageTimer_timeout(): $MessageLabel.hide() 

في علامة التبويب "العقدة" ل StartButton ، قم بتوصيل إشارة pressed() . عند النقر فوق الزر StartButton يجب أن يختفي مع MessageLabel ، ثم يرسل إشارة إلى المشهد الرئيسي ، حيث سنقوم لاحقًا باعتراضها في نفس الوقت عن طريق تمرير الوظيفة لتنفيذ - "new_game ()". ننفذ هذا باستخدام الرمز أدناه. لا تنس أن الزر الموجود في خاصية Text بتعيين أي استدعاء نصي لبدء اللعبة.


 func _on_StartButton_pressed(): $StartButton.hide() $MessageLabel.hide() emit_signal("start_game") 

لإنهاء الواجهة في النهاية ، سنكتب الوظيفة الأخيرة والأخيرة - وظيفة عرض رسالة حول نهاية اللعبة. في هذه الوظيفة ، نحتاج إلى عرض نقش "Game Over" ليتم عرضه لمدة لا تزيد عن ثانيتين ، ثم يختفي ، وهو أمر ممكن بفضل الوظيفة "show_message ()". ومع ذلك ، يجب عليك مرة أخرى إظهار زر البدء للعبة جديدة ، بمجرد أن تختفي رسالة تفيد بأن اللعبة انتهت. yield() بإيقاف تنفيذ الوظيفة مؤقتًا حتى يتم تلقي إشارة من MessageTimer ، وتلقي إشارة من MessageTimer حول تنفيذها ، ستستمر الوظيفة في التنفيذ ، وتعيدنا إلى حالتها الأصلية حتى نتمكن من بدء لعبة جديدة مرة أخرى.


 func show_game_over(): show_message("Game Over") yield($MessageTimer, "timeout") $StartButton.show() $MessageLabel.text = "LIKE COINS!" $MessageLabel.show() 

تنتهي؟


لنقم بإعداد التعليقات بين HUD و Main . أضف مشهد HUD إلى المشهد الرئيسي وقم بتوصيل إشارة GameTimer خلال timeout() على المشهد الرئيسي بإضافة ما يلي:


 func _on_GameTimer_timeout(): time_left -= 1 #  $HUD.update_timer(time_left) #   if time_left <= 0: game_over() #     

ثم قم بتوصيل إشارات pickup() die() للاعب.


 func _on_Player_pickup(): score += 1 $HUD.update_score(score) func _on_Player_die(): game_over() 

في نهاية اللعبة ، يجب أن تحدث العديد من الأشياء التي لا يجب تجاهلها. اكتب الكود التالي ، وسأشرح لك.


 func game_over(): playing = false $GameTimer.stop() for coin in $CoinContainer.get_children(): coin.queue_free() $HUD.show_game_over() $Player.die() 

ستوقف هذه الوظيفة اللعبة ، ومن ثم سيتم إعادة show_game_over() العملات المتبقية show_game_over() العملات المتبقية ، ثم سيتم استدعاء show_game_over() لـ HUD . الخطوة التالية هي بدء الرسم المتحرك وإيقاف عملية تنفيذ عقدة Player .


أخيرًا ، يجب تنشيط StartButton ، الذي يجب أن يكون متصلاً new_game() . انقر على عقدة HUD وفي مربع حوار الاتصال ، تحتاج إلى النقر فوق Make Function to Off (سيؤدي ذلك إلى منع إنشاء وظيفة جديدة) Method In Node حقل Method In Node ، حدد اسم الوظيفة المتصلة - new_game . سيؤدي ذلك إلى توصيل الإشارة بوظيفة موجودة ، بدلاً من إنشاء وظيفة جديدة.


اللمسة الأخيرة هي إزالة new_game() من وظيفة _ready() وإضافة السطرين التاليين إلى وظيفة new_game() :


 ... $HUD.update_score(score) $HUD.update_timer(time_left) 

الآن يمكننا أن نقول بثقة أن اللعبة جاهزة ، والآن أصبحت "قابلة للعب" تمامًا ، ولكن بدون تأثيرات. سننظر في هذا الأخير في المقالة التالية ، مع تكريس اهتمام كبير لمختلف "الزخارف" من أجل تنويع اللعب واستكشاف المزيد من إمكانيات "Godot". لذلك ، لا تنسَ متابعة نشر المقالات. حظا سعيدا!

Source: https://habr.com/ru/post/ar430800/


All Articles