اذهب إلى المحتوى

دليلك الشامل إلى بناء كاميرا خاصة بشاشات اللمس في محرّك اﻷلعاب جودو


ابراهيم الخضور

سنعمل في هذا المقال على بناء كاميرا لشاشة لمس في محرك الألعاب جودو كي يتمكن اللاعب من تحريك الكاميرا حول محورها وتكبير وتصغير وتدوير الكاميرا عن طريق اللمس.

تهيئة الكاميرا ثنائية البعد

افتح محرك الألعاب جودو وأنشئ مشروعك الأول، وابدأ بعقدة من النوع CameraD2، ولجعلها حساسة للمس، سنضيف سكريبت يدير العملية، وذلك بالنقر عليها بالزر الأيمن للفأرة ثم اختيار "إلحاق نص برمجي attach script" ولنسمّه "TouchCameraController.gd".

ملاحظة أضفنا أيقونة إلى المشهد لكي نرى الكائن الذي يتحرك.

01 adding camer

إعداد الكاميرا

نحتاج بداية إلى مجموعة من المتغيرات للتحكم بالكاميرا التي تمثلها في جودو العقدة Camera2D. تمثل هذه المتغيرات سرعة الحركة المحورية وسرعة التكبير والتصغير، وسرعة الدوران، وأخرى للتحكم في إمكانية التحريك أو التكبير أو التدوير. وإضافة إلى ذلك، ننشئ متغيرات لتخزين حالة الكاميرا ومدخلات اللمس. إليك طريقة تعريف المتغيرات:

extends Camera2D

@export var zoom_speed: float = 0.1
@export var pan_speed: float = 1.0
@export var rotation_speed: float = 1.0
@export var can_pan: bool
@export var can_zoom: bool
@export var can_rotate: bool

var start_zoom: Vector2
var start_dist: float
var touch_points: Dictionary = {}
var start_angle: float
var current_angle: float

قمنا في البداية بتوسيع الصنف Camera2D في Godot الذي يمثل كاميرا ثنائية الأبعاد 2D في جودو ثم حددنا مجموعة المتغيرات التي يمكن تعديلها وهي:

  • zoom_speed: قيمة عددية تحدد سرعة عملية التكبير والتصغير للكاميرا.
  • pan_speed: قيمة عددية تحدد سرعة عملية التحريك الجانبي للكاميرا.
  • rotation_speed: قيمة عددية تحدد سرعة عملية تدوير الكاميرا.
  • can_pan: قيمة بوليانية تحدد إمكانية التحريك الجانبي للكاميرا.
  • can_zoom: قيمة بوليانية تحدد إمكانية التكبير أو التصغير للكاميرا.
  • can_rotate: قيمة بوليانية تحدد إمكانية تدوير الكاميرا.

ثم عرفنا المتغيرات التي ستخزن معلومات عن اللمس وهي:

  • start_zoom لتخزين قيمة بداية عملية التكبير أو التصغير الحالي للكاميرا.
  • start_dist لتخزين المسافة بين نقاط اللمس عند بداية التكبير أو التصغير.
  • touch_points لتخزين مواقع نقاط اللمس على الشاشة لاستخدامها في حسابات التكبير والتدوير.
  • start_angle لتخزين زاوية بداية التدوير لحساب الزاوية المطلوبة للتدوير.
  • current_angle لتخزين الزاوية الحالية لعملية التدوير لتحديث تدوير العنصر المرئي بشكل مستمر.

تحريك الكاميرا حول محورها

لنبدأ اﻵن بتحريك الكاميرا حول محورها، وهي الحركة اﻷبسط التي تنفذها الكاميرا في المشهد. سنحتاج إلى وسيلة لترصّد بعض أحداث الدخل، لهذا سنتجاوز override الدالة (input(event_ للتحقق من أحداث اللمس touch والسحب drag على الشاشة والاستجابة لها بالشكل المناسب:

func _input(event):
    if event is InputEventScreenTouch:
        _handle_touch(event)
    elif event is InputEventScreenDrag:
        _handle_drag(event)

تخزّن الدالة (handle_touch(event_ موقع كل نقطة لُمست على الشاشة ضمن القاموس touch_points وتستخدم دليل اللمس touch index كمفتاح، وهذا أساسي لتتبع المواقع التي تُلمس على الشاشة. كما سنضبط قيمة المتغير start_dist على 0 إن كان عدد النقاط التي لُمست أقل من 2:

func _handle_touch(event: InputEventScreenTouch):
    if event.pressed:
        touch_points[event.index] = event.position
    else:
        touch_points.erase(event.index)

    if touch_points.size() < 2:
        start_dist = 0

بإمكاننا تحريك الكاميرا حول محورها بالاستفادة من نقطة لمس واحدة، وذلك باستخدام الدالة (handle_drag(event_ المعرفة كالتالي:

func _handle_drag(event: InputEventScreenDrag):
    touch_points[event.index] = event.position

    if touch_points.size() == 1 and can_pan:
        offset -= event.relative * pan_speed

إن شغّلت اللعبة على محاكي أندرويد سترى كيف تتحرك الكاميرا:

02 pan camera

إضافة ميزة التكبير والتصغير

سنضيف اﻵن إمكانية التكبير والتصغير (تقريب وإبعاد)، لهذا سنعدّل الدالة (handle_touch(event_ كي نعالج حالة وجود نقطتي لمس والتي سنحسب فيها المسافة اﻷولية بينهما ونخزّنها:

func _handle_touch(event: InputEventScreenTouch):
        if event.pressed:
        touch_points[event.index] = event.position
    else:
        touch_points.erase(event.index)
    if touch_points.size() == 2:
        var touch_point_positions = touch_points.values()
        start_dist = touch_point_positions[0].distance_to(touch_point_positions[1])
        start_zoom = zoom
        start_dist = 0

نضيف بعد ذلك إمكانية التحكم بالتكبير والتصغير إلى الدالة handle_drag(event)_، فعند وجود نقطتي لمس، نحسب المسافة الحالية بينهما وبناء عليها نضبط التكبير والتصغير:

func _handle_drag(event: InputEventScreenDrag):
    touch_points[event.index] = event.position
    # Handle 1 touch point
    if touch_points.size() == 1 and can_pan:
        offset -= event.relative * pan_speed   # Handle 2 touch points    elif touch_points.size() == 2 and can_zoom:
        var touch_point_positions = touch_points.values()
        var current_dist = touch_point_positions[0].distance_to(touch_point_positions[1])

        var zoom_factor = start_dist / current_dist
        zoom = start_zoom / zoom_factor

        limit_zoom(zoom) # This is about to be created!

نستخدم الدالة (limit_zoom(zoom لمنع التكبير أو التصغير من تجاوز الحدود وسيقف التكبير أو التصغير عند حد معين:

func limit_zoom(new_zoom: Vector2):
    if new_zoom.x < 0.1:
        zoom.x = 0.1
    if new_zoom.y < 0.1:
        zoom.y = 0.1
    if new_zoom.x > 10:
        zoom.x = 10
    if new_zoom.y > 10:
        zoom.y = 10

انقر على زر التشغيل لترى النتيجة:

03 zoom camera

إضافة الدوران

لنضف أخيرًا إمكانية تدوير الكاميرا، لهذا سنقيس الزاوية الأولية بين نقطتي اللمس من خلال الدالة handle_touch(event)_:

func _handle_touch(event: InputEventScreenTouch):
    if event.pressed:
        touch_points[event.index] = event.position
    else:
        touch_points.erase(event.index)
    if touch_points.size() == 2:

    if touch_points.size() == 2:
        var touch_point_positions = touch_points.values()
        start_dist = touch_point_positions[0].distance_to(touch_point_positions[1])     start_angle = get_angle(touch_point_positions[0], touch_point_positions[1])
        start_zoom = zoom
    elif touch_points.size() < 2:
        start_dist = 0

نضيف بعد ذلك إمكانية التحكم بالدوران ضمن الدالة handle_drag(event)_:

func _handle_drag(event: InputEventScreenDrag):
    touch_points[event.index] = event.position

    if touch_points.size() == 1:
        if can_pan:
            offset -= event.relative * pan_speed
    elif touch_points.size() == 2:
        var touch_point_positions = touch_points.values()
        var current_dist = touch_point_positions[0].distance_to(touch_point_positions[1])
        var current_angle = get_angle(touch_point_positions[0], touch_point_positions[1]) #this will be created below

        var zoom_factor = start_dist / current_dist
        if can_zoom:
            zoom = start_zoom / zoom_factor
        if can_rotate:
            rotation -= (current_angle - start_angle) * rotation_speed
            start_angle = current_angle # حدث الزاوية اﻷولية إلى الزاوية الحالية

        limit_zoom(zoom)

تٌستخدم الدالة (get_angle(p1, p2 لحساب الزاوية:

func get_angle(p1: Vector2, p2: Vector2) -> float:
    var delta = p2 - p1
    return fmod((atan2(delta.y, delta.x) + PI), (2 * PI))

جرّب تدوير اﻷيقونة اﻵن!

04 rotate camera

السكريبت المكتمل

إليك الشيفرة الكاملة:

extends Camera2D

@export var zoom_speed: float = 0.1
@export var pan_speed: float = 1.0
@export var rotation_speed: float = 1.0
@export var can_pan: bool
@export var can_zoom: bool
@export var can_rotate: bool

var start_zoom: Vector2
var start_dist: float
var touch_points: Dictionary = {}
var start_angle: float
var current_angle: float

func _ready():
    start_zoom = zoom

func _input(event):
    if event is InputEventScreenTouch:
            _handle_touch(event)
    elif event is InputEventScreenDrag:
            _handle_drag(event)

func _handle_touch(event: InputEventScreenTouch):
    if event.pressed:
        touch_points[event.index] = event.position
    else:
        touch_points.erase(event.index)

    if touch_points.size() == 2:
        var touch_point_positions = touch_points.values()
        start_dist = touch_point_positions[0].distance_to(touch_point_positions[1])
        start_angle = get_angle(touch_point_positions[0], touch_point_positions[1])
        start_zoom = zoom
    elif touch_points.size() < 2:
        start_dist = 0

func _handle_drag(event: InputEventScreenDrag):
    touch_points[event.index] = event.position

    if touch_points.size() == 1:
        if can_pan:
            offset -= event.relative * pan_speed
    elif touch_points.size() == 2:
        var touch_point_positions = touch_points.values()
        var current_dist = touch_point_positions[0].distance_to(touch_point_positions[1])
        var current_angle = get_angle(touch_point_positions[0], touch_point_positions[1])

        var zoom_factor = start_dist / current_dist
        if can_zoom:
            zoom = start_zoom / zoom_factor
        if can_rotate:
            rotation -= (current_angle - start_angle) * rotation_speed
            start_angle = current_angle # Update the start_angle to the current_angle for the next drag event

        limit_zoom(zoom)

func limit_zoom(new_zoom: Vector2):
    if new_zoom.x < 0.1:
        zoom.x = 0.1
    if new_zoom.y < 0.1:
        zoom.y = 0.1
    if new_zoom.x > 10:
        zoom.x = 10
    if new_zoom.y > 10:
        zoom.y = 10

func get_angle(p1: Vector2, p2: Vector2) -> float:
    var delta = p2 - p1
    return fmod((atan2(delta.y, delta.x) + PI), (2 * PI))

الخلاصة

صممنا في هذا المقال سكريبت كاميرا تتجاوب مع أفعال اللمس على الشاشة في محرك الألعاب جودو 4، ويمكنها التحرك حول محورها أو التكبير والتصغير أو الدوران. انتبه فقط إلى ضرورة ربط السكريبت بعقدة من النوع Camera2D واستمتع بعملك!

ترجمة -وبتصرف- لمقال Building a touchscreen camera in Godot 4: A comprehensive guide

اقرأ أيضًا

 


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...