سنوضّح في هذا المقال كيفية الالتفاف حول الشاشة وبرمجة الحركة من الأعلى إلى الأسفل وتحريك الشخصيات بالاعتماد على الشبكة Grid وفي ثمانية اتجاهات مختلفة في الألعاب ثنائية الأبعاد.
آلية الالتفاف حول الشاشة
يُعَد السماح للاعب بالالتفاف حول الشاشة والانتقال الفوري من أحد جانبي الشاشة إلى الجانب الآخر ميزةً شائعة، وخاصة في الألعاب ثنائية الأبعاد القديمة مثل لعبة باك مان Pac-man، إذ يمكن السماح للاعب بالالتفاف حول الشاشة من خلال اتباع الخطوات.
الخطوة الأولى هي بالحصول على حجم الشاشة أو نافذة العرض Viewport كما يلي:
@onready var screen_size = get_viewport_rect().size
حيث تتوفر الدالة get_viewport_rect()
لأي عقدة مشتقة من CanvasItem
.
أما ثاني خطوة، فتتمثل في مقارنة موضع اللاعب كما يلي:
if position.x > screen_size.x: position.x = 0 if position.x < 0: position.x = screen_size.x if position.y > screen_size.y: position.y = 0 if position.y < 0: position.y = screen_size.y
وكما نلاحظ، تم استخدام موضع position
العقدة الذي يكون عادةً مركزًا للشخصية الرسومية Sprite و/أو مركز الجسم.
ثالث خطوة هي عبر تبسيط ما سبق باستخدام الدالة wrapf()
، إذ يمكن تبسيط الشيفرة البرمجية السابقة باستخدام الدالة wrapf()
في لغة GDScript، والتي تكرّر القيمة بين الحدود المحدّدة.
position.x = wrapf(position.x, 0, screen_size.x) position.y = wrapf(position.y, 0, screen_size.y)
برمجة الحركة من الأعلى إلى الأسفل
إذا أردنا إنشاء لعبة ثنائية الأبعاد من الأعلى إلى الأسفل، فيجب أن تتحكم في حركة الشخصية، لذا لنفترض تحديد إجراءات الإدخال التالية:
اسم الإجراء | المفتاح أو مجموعة المفاتيح |
---|---|
"up"
|
المفتاح W أو ↑ |
"down"
|
المفتاح S أو ↓ |
"right"
|
المفتاح D أو → |
"left"
|
المفتاح A أو ← |
"click"
|
زر الفأرة 1 |
سنفترض أيضًا أننا نستخدم عقدة CharacterBody2D
. هنا سيكون بإمكاننا أيضًا التحكم في حركة الشخصية باستخدام طرق متعددة اعتمادًا على نوع السلوك الذي تبحث عنه كما سنوضح فيما يلي.
الخيار الأول: الحركة في 8 اتجاهات
يستخدم اللاعب في هذه الحالة مفاتيح الاتجاهات الأربعة للتحرك (بما في ذلك الاتجاهات القطرية) كما يلي:
extends CharacterBody2D var speed = 400 # السرعة بالبكسلات في الثانية func _physics_process(delta): var direction = Input.get_vector("left", "right", "up", "down") velocity = direction * speed move_and_slide()
الخيار الثاني: التدوير والتحريك
تدوّر الإجراءات لليسار/لليمين "left/right" في هذه الحالة الشخصية وتحرّك الإجراءات للأعلى/للأسفل الشخصية للأمام وللخلف في أيّ اتجاه تواجهه، ويُشار إلى ذلك أحيانًا باسم "الحركة التي تحاكي حركة الكويكبات".
extends CharacterBody2D var speed = 400 # سرعة الحركة بالبكسل/ثانية var rotation_speed = 1.5 # سرعة الدوران بالراديان/ثانية func _physics_process(delta): var move_input = Input.get_axis("down", "up") var rotation_direction = Input.get_axis("left", "right") velocity = transform.x * move_input * speed rotation += rotation_direction * rotation_speed * delta move_and_slide()
ملاحظة: يَعُد محرّك ألعاب جودو Godot أن الزاوية 0 درجة تؤشّر على طول المحور x، مما يعني أن اتجاه العقدة للأمام (transform.x
) يتجه إلى اليمين، لذا يجب التأكد من رسم الشخصية الرسومية لتشير إلى اليمين.
الخيار الثالث: التصويب بالفأرة
يُعَد هذا الخيار مشابهًا للخيار الثاني، ولكننا نتحكم في دوران الشخصية باستخدام الفأرة هذه المرة، أي أنّ الشخصية تشير دائمًا إلى الفأرة، وتُطبَّق الحركة للأمام/للخلف باستخدام المفاتيح كما في السابق.
extends CharacterBody2D var speed = 400 # سرعة الحركة بالبكسل/ثانية func _physics_process(delta): look_at(get_global_mouse_position()) var move_input = Input.get_axis("down", "up") velocity = transform.x * move_input * speed move_and_slide()
الخيار الرابع: النقر والتحريك
تنتقل الشخصية في هذا الخيار إلى الموقع الذي نقرنا عليه.
extends CharacterBody2D var speed = 400 # سرعة الحركة بالبكسل/ثانية var target = null func _input(event): if event.is_action_pressed("click"): target = get_global_mouse_position() func _physics_process(delta): if target: # look_at(target) velocity = position.direction_to(target) * speed if position.distance_to(target) < 10: velocity = Vector2.ZERO move_and_slide()
وكما نلاحظ، سنتوقف عن الحركة إذا اقتربنا من موضع الهدف، فإن لم نفعل ذلك، فستهتز الشخصية ذهابًا وإيابًا بحيث تتحرك قليلًا بعد الهدف وتعود، ثم تعاود الكرّة وهكذا. يمكننا اختياريًا استخدام الدالة look_at()
لتكون مواجهةً لاتجاه الحركة.
ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github.
تحريك الشخصيات بالاعتماد على الشبكة Grid
سنوضّح فيما يلي كيفية تحريك شخصية ثنائية الأبعاد في نمط شبكي؛ إذ تعني الحركة المعتمدة على الشبكة Grid أو المربع Tile أن موضع الشخصية مقيد، ولا يمكنها الوقوف إلا على مربع معين، كما لا يمكنها أن تكون بين مربعين أبدًا.
إعداد الشخصية
سنوضح فيما يلي العقد التي سنستخدمها للاعب:
-
Area2D
(اللاعب "Player"): يمثّل استخدام العقدةArea2D
أنه يمكننا اكتشاف التداخل لالتقاط الأشياء أو الاصطدام بالأعداء -
Sprite2D
: يمكن استخدام ورقة الشخصية الرسومية Sprite Sheet هنا، وسنوضّح إعداد الرسوم المتحركة Animations لاحقًا -
CollisionShape2D
: يجب أن لا نجعل مربع الاصطدام كبيرًا جدًا، حيث ستكون التداخلات من عند المركز لأن اللاعب سيقف في منتصف المربع - RayCast2D: للتحقق مما إذا كانت الحركة ممكنة في اتجاه محدّد
- AnimationPlayer: لتشغيل الرسوم المتحركة الخاصة بمشي الشخصية
سنضيف هنا بعض إجراءات الإدخال إلى خريطة الإدخال Input Map، ويمكننا لأجل ذلك استخدام إجراءات up وdown وleft، و right في هذا المثال.
الحركة الأساسية
سنبدأ بإعداد الحركة على المربعات الواحد تلو الآخر دون أي رسوم متحركة أو استيفاء Interpolation.
extends Area2D var tile_size = 64 var inputs = {"right": Vector2.RIGHT, "left": Vector2.LEFT, "up": Vector2.UP, "down": Vector2.DOWN}
يجب ضبط حجم المربعات tile_size
ليتطابق مع حجم المربعات الخاصة بنا، ويمكن ضبطه في مشروع أكبر باستخدام المشهد الرئيسي عند إنشاء نسخة من اللاعب، ولكن سنستخدم مربعات بحجم 64x64 في مثالنا. يربط القاموس inputs
أسماء إجراءات الإدخال مع متجهات الاتجاه، لذا يجب التأكّد من كتابة الأسماء نفسها هنا وفي خريطة الإدخال مع مراعاة استخدام الحروف الكبيرة.
func _ready(): position = position.snapped(Vector2.ONE * tile_size) position += Vector2.ONE * tile_size/2
تتيح الدالة snapped()
تقريب الموضع إلى أقرب زيادة في المربع، وتضمَن إضافة كمية بمقدار نصف مربع أن يكون اللاعب في مركز المربع.
func _unhandled_input(event): for dir in inputs.keys(): if event.is_action_pressed(dir): move(dir) func move(dir): position += inputs[dir] * tile_size
تمثل الشيفرة البرمجية السابقة الحركة الفعلية. حيث إذا ظهر حدث إدخال، فسنتحقق من الاتجاهات الأربعة لمعرفة أيّ منها يتطابق مع الحدث، ثم نمرّره إلى الدالة move()
لتغيير الموضع.
الاصطدام Collision
يمكننا الآن إضافة بعض العوائق، حيث يمكن استخدام عقد StaticBody2D
لإضافة بعض العوائق يدويًا، مع تفعيل الالتقاط للتأكد من محاذاتها مع الشبكة، أو استخدام TileMap مع تحديد الاصطدامات كما هو الحال في المثال التالي، وسنستخدم عقدة RayCast2D لتحديد السماح بالانتقال إلى المربع التالي.
onready var ray = $RayCast2D func move(dir): ray.target_position = inputs[dir] * tile_size ray.force_raycast_update() if !ray.is_colliding(): position += inputs[dir] * tile_size
إذا غيّرنا الخاصية target_position
الخاصة بعقدة RayCast2D
، فلن يعيد محرّك الفيزياء حساب تصادماته حتى الإطار الفيزيائي التالي. تتيح الدالة force_raycast_update()
إمكانية تحديث حالة الشعاع مباشرةً، حيث إن لم يكن هناك تصادم، فسنسمح بالتحرك.
ملاحظة: تتمثل إحدى الطرق الشائعة الأخرى في استخدام أربع عقد RayCast2D
من خلال استخدام عقدة لكل اتجاه.
تنشيط الحركة
وأخيرًا، يمكننا استيفاء الموضع بين المربعات، مما يعطي إحساسًا سلسًا بالحركة، حيث سنستخدم عقدة Tween
لتحريك خاصية الموضع position
.
var animation_speed = 3 var moving = false
سنضيف الآن مرجعًا إلى عقدة Tween
ومتغيرًا لضبط سرعة الحركة كما يلي:
func _unhandled_input(event): if moving: return for dir in inputs.keys(): if event.is_action_pressed(dir): move(dir)
سنتجاهل أي إدخال أثناء تشغيل الانتقال التدريجي Tween ونزيل تغيير الموضع position
المباشر حتى يتمكّن الانتقال التدريجي من التعامل معه.
func move(dir): ray.target_position = inputs[dir] * tile_size ray.force_raycast_update() if !ray.is_colliding(): #position += inputs[dir] * tile_size var tween = create_tween() tween.tween_property(self, "position", position + inputs[dir] * tile_size, 1.0/animation_speed).set_trans(Tween.TRANS_SINE) moving = true await tween.finished moving = false
سنجرِّب الآن انتقالات تدريجية مختلفة للحصول على تأثيرات حركية مختلفة.
ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github.
تحريك الشخصيات في 8 اتجاهات مختلفة
سنوضّح فيما يلي كيفية تحريك شخصية ثنائية الأبعاد في 8 اتجاهات مختلفة، حيث سنستخدم في مثالنا شخصية محارب التي تحتوي على رسوم متحركة في 8 اتجاهات لحالات عدم الحركة والجري والهجوم والعديد من الحالات الأخرى.
تُنظَّم الرسوم المتحركة ضمن مجلدات، مع وجود صورة منفصلة لكل إطار. سنستخدم العقدة AnimatedSprite2D
وسنسمّي الرسوم المتحركة بناءً على اتجاهها.
على سبيل المثال، يشير الرسم المتحرك idle0
إلى اليمين، ثم ننتقل باتجاه عقارب الساعة حتى الوصول إلى الرسم المتحرك idle7
، وتختار الشخصية عند تحرّكها رسومًا متحركة بناءً على اتجاه الحركة:
سنستخدم الفأرة للتحرك، حيث ستواجه الشخصية الفأرة دائمًا وتتحرك في هذا الاتجاه عندما نضغط على زر الفأرة. يمكن اختيار الرسوم المتحركة التي ستعمل من خلال الحصول على اتجاه الفأرة وربطه مع هذا المجال نفسه من 0 إلى 7؛ إذ تعطي الدالة get_local_mouse_position()
موضع الفأرة بالنسبة للشخصية، ويمكننا بعد ذلك استخدام الدالة snappedf()
لضبط زاوية متجه الفأرة إلى أقرب مضاعف للزاوية 45 درجة (أو PI/4
راديان) مما يعطي النتيجة التالية:
سنقسّم كل قيمة على 45 درجة ( أو PI/4
راديان) ونحصل على النتيجة التالية:
في الأخير، يجب ربط المجال الناتج مع المجال 0-7 باستخدام الدالة wrapi()
، وسنحصل على القيم الصحيحة، حيث تعطي إضافة هذه القيمة إلى نهاية اسم الرسوم المتحركة ("idle" و "run" وغيرها) الرسم المتحرك الصحيح كما يلي:
func _physics_process(delta): current_animation = "idle" var mouse = get_local_mouse_position() angle = snappedf(mouse.angle(), PI/4) / (PI/4) angle = wrapi(int(angle), 0, 8) if Input.is_action_pressed("left_mouse") and mouse.length() > 10: current_animation = "run" velocity = mouse.normalized() * speed move_and_slide() $AnimatedSprite2D.animation = current_animation + str(a)
وسنشاهد ما يلي عند اختبار الحركة:
الإدخال من لوحة المفاتيح
إذا كنا نستخدم عناصر التحكم من لوحة المفاتيح بدلًا من الفأرة، فيمكننا الحصول على زاوية الحركة بناءً على المفاتيح التي نضغط عليها، وتسير بقية العملية باستخدام الطريقة نفسها كما يلي:
func _process(delta): current_animation = "idle" var input_dir = Input.get_vector("left", "right", "up", "down") if input_dir.length() != 0: angle = input_dir.angle() / (PI/4) angle = wrapi(int(a), 0, 8) current_animation = "run" velocity = input_dir * speed move_and_slide() $AnimatedSprite2D.play(current_animation + str(angle))
الخاتمة
استعرضنا في هذا المقال أساليب مختلفة لتحريك الشخصيات في الألعاب ثنائية الأبعاد باستخدام محرك ألعاب جودو، بما يشمل الالتفاف حول الشاشة، والحركة من الأعلى إلى الأسفل، والحركة الشبكية، والحركة في ثمانية اتجاهات.
تساعد هذه الأساليب على تحسين تجربة اللعب وتوفير تحكم دقيق ومتناسق مع نوع اللعبة. يُنصح باختيار الطريقة الأفضل حسب تصميم اللعبة وأسلوب اللعب المطلوب. ويمكن تنزيل شيفرة المشروع البرمجية من Github لمزيد من الفهم.
ترجمة -وبتصرّف- للأقسام Screen wrap و Top-down movement و Grid-based movement و 8-Directional Movement/Animation من توثيقات Kidscancode.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.