-
المساهمات
189 -
تاريخ الانضمام
-
تاريخ آخر زيارة
آخر الزوار
7405 زيارة للملف الشخصي
إنجازات Ola Abbas

عضو نشيط (3/3)
37
السمعة بالموقع
-
محمد العلي11 بدأ بمتابعة Ola Abbas
-
قد يكون اختيار قاعدة البيانات المناسبة للمشروع أمرًا صعبًا، خاصةً مع وجود العديد من الخيارات. تقدم قاعدتا بيانات MariaDB و MySQL ميزات كثيرة، ولكن يمكن أن تؤثر نقاط القوة والضعف لكل منهما على الأداء وقابلية التوسع والتوافق مع التطبيقات المختلفة مثل WordPress، لذا سنوضح في هذا المقال الاختلافات الأساسية بين قاعدتي البيانات MariaDB و MySQL، وسنتحدث عن ميزاتهما ومدى سرعتهما وأمانهما وكيفية استخدامهما. نظرة عامة على MariaDB و MySQL نشأت كل من MariaDB وMySQL من الجذور نفسها، إذ يعود أصلهما إلى نظام قاعدة بيانات إنجرس Ingres الذي طوّرته جامعة كاليفورنيا في بيركلي. طُوِّرت MySQL لأول مرة في عام 1995 واكتسبت شعبيةً كبيرةً لأنها كانت سهلة الاستخدام وسريعة، ولكن أصبح هناك خلاف بين مالكي MySQL وأوراكل Oracle لاحقًا، لذا جرى تطوير قاعدة بيانات جديدة بالاسم MariaDB. تم تطوير قاعدةَ بيانات MariaDB من طرف نفس مطوري MySQL في عام 2009، وكان الهدف هو الحفاظ على ميزات MySQL نفسها مع إضافة ميزات جديدة أيضًا. تنمو الآن MariaDB و MySQL كل على حدة، ولكل منهما ميزاتها وفوائدها الرئيسية. الميزات والفوائد الرئيسية تتشابه MariaDB و MySQL في الكثير من الأمور، لأنهما نشأتا من قاعدة الشيفرة البرمجية نفسها، ولكنهما تطورتا بطريقة مستقلة، مما أدى إلى بعض الاختلافات في ميزاتهما وفوائدهما. ميزات وفوائد MariaDB تشتهر MariaDB بكونها سريعة وقادرة على التعامل مع عدد كبير من العمليات، ولديها ميزات خاصة تسرّع من عمل الاستعلامات وتجعلها تعمل بطريقة أفضل مع حركة المرور الكبيرة. تحتوي MariaDB أيضًا على طرق جديدة لتخزين البيانات، والتي يمكن أن تساعدها على التصرّف بطريقة أفضل في مواقف معينة. تتضمن بعض ميزات MariaDB الميزة MCS لتحليل البيانات وميزة MaxScale للمحافظة على سير الأمور وموازنة العمل وميزة Galera Cluster لنسخ البيانات بدقة. ميزات وفوائد MySQL تحسّنت قاعدة بيانات MySQL بمرور الوقت، ولا تزال تحظى بشعبية كبيرة لأن العديد من الأشخاص يستخدمونها مع إتاحة المساعدة؛ وتحتوي على طرق مختلفة لتخزين البيانات مثل InnoDB و MyISAM ولكل منها إيجابياتها وسلبياتها. تحتوي MySQL على ميزات مثل البحث عن الكلمات الكاملة والمنبّهات Trigger والإجراءات المُخزَّنة، والتي يمكن أن تكون مفيدةً لإنشاء تطبيقات قواعد بيانات معقدة. مقارنة بين MariaDB و MySQL هناك العديد من الاختلافات الرئيسية بين MySQL و MariaDB بالرغم من وجود بنية ووظائف متشابهة كما هو موضح في الجدول التالي: الميزة قاعدة بيانات MariaDB قاعدة بيانات MySQL محركات التخزين محركات InnoDB و Aria و MyISAM و TokuDB و XtraDB و MariaDB Column Store محرّكات InnoDB و MyISAM و Aria و NDB و TokuDB تحسين السرعة تحسين تنفيذ الاستعلامات وفهرسة أسرع وتحسين أداء مع مجموعات البيانات الكبيرة تحسينات مستمرة في الأداء والتركيز على السرعة اتصالات أكبر وأسرع تدعم المزيد من الاتصالات في وقت واحد وتحسّن الأداء عند الحِمل الكبير تحسين التعامل مع الاتصالات وتحسينات قابلية التوسع تحسين النسخ المتماثل Replication تقنية Galera Cluster للتوافرية العالية والنسخ المتماثل المتزامن ونسخ المجموعات المتماثل النسخ المتماثل غير المتزامن والنسخ المتماثل شبه المتزامن ونسخ المجموعات المتماثل الميزات أو الإضافات الجديدة ميزات وإضافات JSON وأنواع البيانات المكانية Spatial ودوال النافذة و MariaDB Serverless و MariaDB MaxScale ميزات وإضافات JSON وأنواع البيانات المكانية ودوال النافذة و MySQL Shell نموذج قاعدة البيانات الثانوية MariaDB Column Store لأحمال العمل التحليلي MySQL Enterprise Backup للاستعادة في الحالات الكارثية وتخزين البيانات تقنيع البيانات Data Masking إمكانات تقنيع البيانات المُضمَّنة لأمن البيانات والالتزام بها ميزات تقنيع البيانات المتاحة باستخدام إضافات خارجية الأعمدة الديناميكية MariaDB Column Store للتخزين العمودي الديناميكي دعم محدود للأعمدة الديناميكية المراقبة Monitoring أدوات مراقبة متقدمة مدمجة بما في ذلك مخطط الأداء Performance Schema وإضافات المراقبة ميزات المراقبة الأساسية مع الأدوات الإضافية المتوفرة في Enterprise Edition التوجيه Routing إمكانات التوجيه المُضمَّنة مع MaxScale لتحسين معالجة الاستعلامات إمكانات التوجيه المحدودة باستخدام أدوات خارجية أولًا التحليلات Analytics ميزات تحليلية متقدمة مع دعم للاستعلامات وأنواع البيانات المعقدة دعم التحليلات الأساسية مع ميزات أكثر تقدمًا في MySQL Enterprise Edition نجوم GitHub أكثر من 11000 نجمة أكثر من 17000 نجمة عمليات نسخ GitHub أكثر من 2500 عملية نسخ أكثر من 3000 عملية نسخ المقارنة بين معايير قياس الأداء قد نجد صعوبةً في توفير معايير قياس أداء نهائية تنطبق على جميع السيناريوهات، ولكن أظهرت دراسات مختلفة وتجارب واقعية أن كلًا من MariaDB و MySQL يمكنهما تقديم أداء ممتاز في مجموعة متنوعة من حالات الاستخدام. يوضح الرسم البياني التالي عدد العمليات التي يمكن إجراؤها في الثانية OPS لأربعة إصدارات مختلفة من قواعد البيانات (MariaDB 10.0.21 و MariaDB 10.0.18 و MySQL 5.6.27 و MySQL 5.7.9) عند إجراء عمليات قراءة بسيطة مع أعداد مختلفة من المستخدمين من 1 إلى 256. يُظهِر الرسم البياني التالي أن الإصدار MySQL 5.7.9 أفضل من الإصدارات الأخرى دائمًا، مما يعني أن الإصدارات الأحدث أفضل. ترتفع الأرقام مع زيادة عدد المستخدمين، ولكنها تتوقف عن الارتفاع عند حوالي 128 مستخدمًا لمعظم الإصدارات، وهذا يعني أن هناك حدًا لكمية العمليات التي يمكن لقاعدة البيانات التعامل معها بسبب العتاد أو البرمجيات. تُعَد هذه المقارنة مهمةً لفهم الاختلافات في السرعة وكمية العمل التي يمكن لكل إصدار التعامل معها بين MariaDB وMySQL، مما يساعدنا في اتخاذ خيارات مناسبة لترقية قاعدة بياناتنا وإعدادها الصحيح، ولكن يمكن أن تختلف خصائص الأداء المحددة وفقًا لعوامل مثل العتاد وحِمل العمل والضبط. التعامل مع أحمال العمل ذات حركة المرور العالية يمكن ضبط كل من MariaDB و MySQL وتحسينهما للتعامل مع متطلبات التطبيقات ذات الطلب الكبير عند التعامل مع أحمال العمل ذات حركة المرور العالية، حيث تشمل العوامل الرئيسية التي يجب مراعاتها ما يلي: العامل وصفه العتاد Hardware تُعَد الموارد الكافية لوحدة المعالجة المركزية CPU والذاكرة والتخزين ضروريةً للتعامل مع أحمال العمل ذات الحركة العالية الفهرسة Indexing يمكن أن تحسّن الفهرسة المناسبة كثيرًا من أداء الاستعلامات من خلال تقليل كمية البيانات التي يجب مسحها تحسين الاستعلامات Query Optimization يمكن أن يساعد تحسين استعلامات SQL في تقليل استهلاك الموارد وتحسين أوقات الاستجابة التخبئة Caching يمكن أن يقلّل استخدام آليات التخبئة من عدد استعلامات قاعدة البيانات وتحسين الأداء العام النسخ المتماثل Replication والعنقدة Clustering يمكن أن يساعد تطبيق النسخ المتماثل والعنقدة في توزيع حِمل العمل عبر خوادم متعددة، مما يعزز قابلية التوسع والتوافرية المقارنة من حيث محركات التخزين يؤثر اختيار محرّك التخزين تأثيرًا كبيرًا على أداء قاعدة البيانات ووظائفها، حيث تدعم كل من MariaDB و MySQL محرّكات متعددة، ولكن MariaDB تقدم مجموعةً أوسع من الخيارات، بما في ذلك XtraDB و ColumnStore، مما يوسّع إمكاناتها إلى ما هو أبعد من InnoDB و MyISAM وغيرها من محرّكات MySQL. تدعم MariaDB أيضًا كلًا من Blackhole و CSV و Aria و InnoDB و Archive و Connect و Cassandra Storage Engine، والعديد من المحرّكات الأخرى؛ بينما تتضمن محركات التخزين التي تدعمها MySQL أيضًا MyISAM و Merge و Federated و Archive و Memory و CSV و Blackhole و Example. ملاحظة: لا يهم عدد محركات التخزين التي تدعمها قاعدة البيانات، فالمهم هو استخدام قاعدة البيانات التي تدعم المحرّك المناسب للمتطلبات. المقارنة من حيث تحسين الاستعلامات تتضمن كل من MariaDB و MySQL ميزات تحسين الاستعلام لتحسين الأداء وتقليل استهلاك الموارد، إذ يمكن أن تساعد هذه الميزات في ضمان تنفيذ الاستعلامات بكفاءة وتجنب الأعباء غير الضرورية. تتضمن بعض تقنيات تحسين الاستعلامات الشائعة ما يلي: تقنية التحسين وصفها الفهرسة يمكن أن يحسّن إنشاء الفهارس المناسبة كثيرًا من أداء الاستعلامات من خلال تقليل كمية البيانات التي يجب مسحها تخبئة الاستعلامات يمكن أن تقلّل تخبئة الاستعلامات التي يتكرر تنفيذها من الحاجة إلى عمليات بحث متكررة في قاعدة البيانات إعادة كتابة الاستعلام يمكن لقاعدة البيانات في بعض الأحيان إعادة كتابة الاستعلامات لتحسين أدائها أو تجنب الاختناقات المحتملة شرح الخطط يمكن استخدام شرح الخطط لتحليل خطط تنفيذ الاستعلام وتحديد مشكلات الأداء المحتملة المقارنة من حيث البحث عن نص كامل يمكن لكل من MariaDB و MySQL البحث عن كلمات كاملة في النص، ويُعَد ذلك مفيدًا لأشياء مثل محركات البحث وأنظمة إدارة المستندات والمتاجر الإلكترونية. قد تختلف طريقة البحث عن الكلمات الكاملة التي تستخدمها MariaDB و MySQL بعض الشيء في كيفية عملها ومدى سرعتها، لذا يُفضَّل التحقق من احتياجات تطبيقنا ومقارنة كيفية بحث كلتا قاعدتي البيانات عن الكلمات الكاملة. دعم JSON تدعم كل من MariaDB و MySQL تنسيق JSON وتنفذان العديد من الدوال نفسها، ولكن تخزّن MySQL تقارير JSON ككائنات ثنائية، بينما تخزنها MariaDB كسلاسل نصية. تظهر MySQL و MariaDB اختلافات في إمكاناتهما على التعامل مع JSON بالرغم من ارتباطهما الوثيق، حيث تفتخر MariaDB بمجموعة أوسع من دوال JSON بما في ذلك JSON_QUERY و JSON_EXISTS، والتي تفتقر MySQL إليها؛ ولكن MySQL تقدم الدالة JSON_TABLE لهيكلة بيانات JSON ضمن جدول، والتي هي ميزة غير موجودة في MariaDB. الدالة قاعدة بيانات MariaDB قاعدة بيانات MySQL الدالة JSON_ARRAY ✔ ✔ الدالة JSON_EXISTS ✔ ✘ الدالة JSONOBJECTAGG ✔ ✔ الدالة JSON_QUERY ✔ ✘ الدالة JSON_VALUE ✔ ✔ الدالة JSON_TABLE ✘ ✔ الدالة IS_JSON JSON_VALID JSON_VALID التوافق مع Oracle تتمتع MySQL ببعض ميزات أوراكل Oracle الأساسية المتوافقة، ولكن تُعَد MariaDB هي الوحيدة مفتوحة المصدر المتوافقة مع إجراءات وتسلسلات وأنواع البيانات المخزَّنة وغير ذلك لقاعدة بيانات أوراكل. قد تجد المؤسسات ذات الاستثمار الأكبر في نظام أوراكل البيئي أن قاعدة بيانات MySQL بديل جذاب بسبب وضع التوافق مع أوراكل، ولكن يُعَد دعم MySQL للغة PL/SQL الخاصة بأوراكل محدودًا. تُعَد قاعدة بيانات MariaDB خيارًا أفضل، إذ يمكنها العمل مع صيغة أوراكل ولديها دعم كامل للغة PL/SQL، ويكون ذلك مناسبًا للشركات التي تريد التحول من أوراكل مع الاحتفاظ بشيفرتها البرمجية القديمة واستخدام ميزات إجرائية متقدمة. ميزات الأمان تحمي كلٌّ من MySQL و MariaDB البيانات من خلال التحقق من هوية المستخدمين واستخدام التشفير، ولكنهما تطبّقان التشفير بطريقة مختلفة، إذ تتيح MySQL إمكانية اختيار كيفية تشفير سجلات الإعادة والتراجع، ولكنها لا تحمي مساحات الجدول المؤقتة أو السجلات الثنائية. وتحتوي MariaDB على مزيدٍ من الخيارات للتشفير بما في ذلك حماية السجلات الثنائية والجداول المؤقتة، مما يجعل البيانات أكثر أمانًا. تستخدم MySQL و MariaDB أيضًا طرقًا مختلفةً للتحقق من هوية المستخدمين، إذ تحتوي MySQL على validate_password للتأكد من قوة كلمات المرور، وتحتوي MariaDB على مزيد من الخيارات مع إضافات مختلفة للتحقق من صحة البيانات. يُعَد أمان كلمة المرور أفضل في MariaDB باستخدام إضافة الاستيثاق ed25519 في الإصدار 10.4، والتي تُعَد أكثر أمانًا من طريقة SHA-1 القديمة، مما يعني أن MariaDB ملتزمة بأن تكون آمنة جدًا. تجمع الخيوط Thread Pooling تستخدم قواعد البيانات تجمع الخيوط للتعامل مع العديد من الاتصالات في وقت واحد، إذ تستخدم هذه الطريقة الخيوط نفسها للاتصالات الجديدة، مما يوفر من الموارد. تُعَد قاعدة بيانات MariaDB جيدةً جدًا في استخدام تجمّع الخيوط، إذ يحتوي إصدار المجتمع الخاص بها على تجمّع خيوط قوي يمكنه التعامل مع أكثر من 200000 اتصال في وقت واحد؛ وتحتوي MySQL أيضًا على تجمّع خيوط، ولكنها ليست جيدةً بالمقارنة مع تجمع الخيوط الموجود في MariaDB، وهي متوفرة في إصدار المؤسسة المدفوع. التراخيص والقيود تستخدم كل من MariaDB و MySQL ترخيص جنو العمومي General Public License -أو GPL اختصارًا، لكنهما يتبعان خطط ترخيص مختلفة. تُعَد MariaDB مرخصةً بالكامل بموجب ترخيص GPL، مما يعني أنها ستكون مجانيةً ومفتوحة المصدر دائمًا، ويُعَد ذلك مهمًا للمستخدمين الذين يقدّرون أهمية مجانية البرمجيات. تُعَد MySQL مُرخصَّة بترخيص مزدوج، فهي تحتوي على إصدار ترخيص GPL عام وإصدار تجاري خاص؛ إذ يحتوي الإصدار التجاري على مزيد من الميزات والدعم مثل تجمّع الخيوط، كما قد يؤدي ذلك إلى جعلها أسرع، ولكنه يمنع المستخدمين من تغيير الشيفرة البرمجية. قد تختار الشركات استخدام MySQL بسبب دعمها التجاري، ولكنها قد تحدّ من الوصول إلى الشيفرة البرمجية الأساسية نظرًا لاستراتيجيتها المزدوجة. اختيار قاعدة البيانات الصحيحة إذا أردنا قاعدة بيانات أنشأها المجتمع وتتمتع بسرعة وأمان أفضل، فقد يكون استخدام قاعدة بيانات MariaDB مناسبًا. فقد أثبتت بعض الاختبارات أن MariaDB أسرع ويمكنها التعامل مع كمية عمليات أكبر من MySQL، كما أن MariaDB تتمتع ببعض ميزات أمان لا تتوفر في MySQL مثل تشفير البيانات أثناء تخزينها ونقلها، إلى جانب تمتع MariaDB بأمور لا تتوفر في MySQL، مثل الأعمدة الافتراضية ومحرّكات التخزين التسلسلية واستخدام محرّكات تخزين متعددة في جدول واحد. من ناحية أخرى، تم إنشاء قاعدة بيانات MariaDB من طرف المجتمع، لذا تُعَد أكثر انفتاحًا ووضوحًا من MySQL التي تملكها شركة أوراكل، حيث يمكن للمستخدمين المساعدة في جعل البرنامج وتطويره أكثر تركيزًا على احتياجات المجتمع. حالات استخدام MariaDB و MySQL تكون قاعدة MariaDB غالبًا أفضل للتطبيقات الكبيرة والسريعة والاستعلامات المعقدة، فهي تجعلها ميزاتها الخاصة خيارًا جيدًا للعمل الشاق، مثل ميزة Galera Cluster للحفاظ على تشغيل الأشياء وتحسينات محرّك تخزين InnoDB، بينما تكون MySQL غالبًا أفضل للتطبيقات الأصغر واحتياجات قواعد البيانات الأبسط. شركات تستخدم MariaDB شركات تستخدم MySQL سامسونج Samsung بي بي سي BBC شركة Financial Network, Inc. شركة ألعاب Big Fish شركة Virgin Media O2 سبوتيفاي Spotify شركة Campus Cloud Services نتفليكس Netflix شركة Auto Europe ناسا NASA نوكيا Nokia استخدام MariaDB مع WordPress يمكن أن يؤدي استخدام WordPress مع قاعدة بيانات MariaDB إلى تحميل أسرع لموقع الويب، وهذا سيجذب زوّار الموقع أكثر؛ إذ تتميز MariaDB بقدرتها على تحسين الاستعلامات، ويمكنها التعامل مع عدد أكبر من الاتصالات في وقت واحد. من ناحية أخرى، يمكن لقاعدة بيانات MariaDB التعامل مع عدد أكبر من الاتصالات والمعاملات مقارنةً بقاعدة بيانات MySQL. كذلك، تتمتع MariaDB بميزات أمان أفضل مثل تشفير البيانات أثناء تخزينها، وطرق محسّنة للتحقق من هوية المستخدمين، وطرق أفضل لتعقّب ما يحدث. استخدام MariaDB مع موقع Cloudways يوفر موقع Cloudways أحدث إصدارات MariaDB على جميع خوادمه التي أطلقها حديثًا، ويمكن تحديد إصدار MariaDB بناءً على متطلبات المشروع. ملاحظة: لا يمكن تخفيض إصدار MariaDB مرةً أخرى بعد الترقية إلى الإصدار الأعلى. الخلاصة بهذا نكون قد وضحنا ميزات MariaDB و MySQL الرئيسية لإظهار الاختلاف بينهما لتسهيل المفاضلة بين قاعدة بيانات MariaDB أو MySQL. ترجمة -وبتصرّف- للقسم MariaDB vs MySQL: Understanding Key Differences and Choosing the Right Database لصاحبته Hafsa Tahir. اقرأ أيضًا كيفية استيراد وتصدير قواعد بيانات MySQL أو MariaDB كيفية تأمين قواعد البيانات MySQL ,MariaDB على خواديم لينكس كيفية تغيير مجلد تخزين بيانات MariaDB إلى مكانٍ آخر تعلم أساسيات MySQL كيفية ربط قاعدة بيانات MariaDB بتطبيق لارافيل Laravel
-
سنوضّح في هذا المقال من سلسلة دليل جودو كيفية برمجة صاروخ موجه ، والذي هو مقذوف يبحث عن هدف متحرك، ، حيث سنستخدم عقدة Area2D لتنفيذ حركة الصاروخ، مع إضافة التأثيرات البصرية مثل الدخان والانفجارات' كما سنوضح كيفية استخدام التسارع والتوجيه الذكي Steering لتحريك الصاروخ نحو الهدف مع التحكم في قوة التوجيه لتحقيق حركة أكثر واقعية؛ كما سنوضح أيضًا كيفية استدعاء الدوال المناسبة للتحكم في وقت حياة الصاروخ، وكيفية تفاعله مع البيئة المحيطة. ستساعدنا هذه المفاهيم الأساسية على تطوير أنظمة موجهة أخرى في الألعاب التي تتطلب تفاعلًا ديناميكيًا مع الأهداف المتحركة. برمجة صاروخ موجه للكشف عن الهدف المتحرك لبرمجة صاروخ موجه، والذي هو مقذوف يبحث عن هدف متحرك، سنستخدم عقدة Area2D للمقذوف؛ إذ تُعَد عقد المناطق Areas خيارات جيدة للرصاصات أو الأجسام المتحركة التي تُطلق من الصاروخ لأننا بحاجة إلى كشفها عند ملامستها لشيء ما، ولكن إذا كنا بحاجة أيضًا إلى رصاصة ترجع أو ترتد، فقد تكون العقد من نوع PhysicsBody خيارًا أفضل. يتشابه إعداد العقدة وسلوك الصاروخ مع الإعداد الذي نستخدمه مع الرصاصات العادية، لذا في حال كنا قد أنشأنا مسبقًا عدة أنواع من الرصاص، فيمكننا استخدام الوراثة لإعداد جميع مقذوفاتنا استنادًا على الإعداد الأساسي نفسه. فيما يلي العقد التي سنستخدمها: Area2D: Missile Sprite2D CollisionShape2D Timer: Lifetime يمكن استخدام أي صورة نريدها بالنسبة للخامة Texture كما في المثال التالي: يمكن الآن إعداد العقد وضبط خامة الشخصية الرسومية Sprite وشكل التصادم، مع التأكّد من تدوير عقدة Sprite2D بمقدار 90 درجة، بحيث تشير إلى اليمين، مع التأكد من أنها تتطابق مع الاتجاه الأمامي للعقدة الأب. سنضيف سكربتًا ونتصل بالإشارة body_entered الخاصة بالعقدة Area2D والإشارة timeout الخاصة بالعقدة Timer كما يلي: extends Area2D export var speed = 350 var velocity = Vector2.ZERO var acceleration = Vector2.ZERO func start(_transform): global_transform = _transform velocity = transform.x * speed func _physics_process(delta): velocity += acceleration * delta velocity = velocity.clamped(speed) rotation = velocity.angle() position += velocity * delta func _on_Missile_body_entered(body): queue_free() func _on_Lifetime_timeout(): queue_free() سيؤدي هذا إلى إنشاء صاروخ يتحرك في خط مستقيم عند إطلاقه، ويمكن استخدام هذا المقذوف من خلال إنشاء نسخة منه واستدعاء التابع start() الخاص به مع التحويل Transform2D المطلوب لضبط موضعه واتجاهه. سنستخدم التسارع acceleration لتغيير السلوك للبحث عن الهدف، ولكن لا نريد أن يدور الصاروخ بسرعة كبيرة، لذا سنضيف متغيرًا للتحكم في قوة التوجيه Steering، مما يعطي الصاروخ نصفَ قطر دوران يمكن تعديله مع سلوك مختلف. سنحتاج أيضًا إلى متغير الهدف target حتى يعرف الصاروخ ما الذي يطارده، وسنضعه في التابع start() كما يلي: export var steer_force = 50.0 var target = null func start(_transform, _target): target = _target … يمكن تغيير اتجاه الصاروخ للتحرك نحو الهدف باستخدام التسارع في ذلك الاتجاه، فالتسارع هو تغير في السرعة؛ حيث يريد الصاروخ الحالي التحرك نحو الهدف مباشرةً، ولكن تشير سرعته الحالية إلى اتجاه مختلف، ويمكننا إيجاد هذا الفرق باستخدام الرياضيات الشعاعية كما يلي: يمثل السهم الأخضر التغير المطلوب في السرعة، أي التسارع acceleration، ولكن إذا انعطفنا مباشرةً، فسيبدو الأمر غير طبيعي، لذا يجب أن يكون طول متجه التوجيه محدودًا، وهذا سبب استخدام المتغير steer_force. تحسب الدالة التالية هذا التسارع، ويمكننا ملاحظة أنه لن يكون هناك توجيه عند عدم وجود هدف، لذا سيواصل الصاروخ التحرك في خط مستقيم. func seek(): var steer = Vector2.ZERO if target: var desired = (target.position - position).normalized() * speed steer = (desired - velocity).normalized() * steer_force return steer أخيرًا، يجب تطبيق قوة التوجيه الناتجة في الدالة _physics_process() كما يلي: func _physics_process(delta): acceleration += seek() velocity += acceleration * delta velocity = velocity.clamped(speed) rotation = velocity.angle() position += velocity * delta فيما يلي مثال عن النتائج مع بعض التأثيرات البصرية الإضافية مثل دخان الجسيمات والانفجارات: 03_homing_missiles.webm الكود الكامل للصاروخ الموجه مع التأثيرات البصرية فيما يلي السكربت الكامل الذي يضيف سلوك الصاروخ الموجه باستخدام Area2D للكشف عن الهدف، ويشمل أيضًا التأثيرات البصرية، مثل الانفجارات ودخان الجسيمات عند الاصطدام أو انتهاء الوقت. extends Area2D export var speed = 350 export var steer_force = 50.0 var velocity = Vector2.ZERO var acceleration = Vector2.ZERO var target = null func start(_transform, _target): global_transform = _transform rotation += rand_range(-0.09, 0.09) velocity = transform.x * speed target = _target func seek(): var steer = Vector2.ZERO if target: var desired = (target.position - position).normalized() * speed steer = (desired - velocity).normalized() * steer_force return steer func _physics_process(delta): acceleration += seek() velocity += acceleration * delta velocity = velocity.clamped(speed) rotation = velocity.angle() position += velocity * delta func _on_Missile_body_entered(body): explode() func _on_Lifetime_timeout(): explode() func explode(): $Particles2D.emitting = false set_physics_process(false) $AnimationPlayer.play("explode") await $AnimationPlayer.animation_finished queue_free() الخاتمة بهذا نكون قد وصلنا لنهاية مقالنا الذي تعلمنا كيفية برمجة صاروخ موجه في محرك الألعاب جودو Godot مع تطبيق التسارع والتوجيه الذكي لجعل الصاروخ يتبع هدفًا متحركًا' كما استعرضنا كيفية إضافة تأثيرات بصرية مثل الانفجارات والدخان عند الاصطدام أو انتهاء الوقت. يمكن تجربة تطبيق هذه الأساليب لتطوير أي ألعاب فيها أعداء يتبعون اللاعب، إذ يتغير سلوك الأعداء بناءً على موقع اللاعب وحركته. ترجمة -وبتصرّف- للقسم Homing missile من توثيقات Kidscancode. اقرأ أيضًا المقال السابق: برمجة عدو وحيوان أليف في لعبة Godot إعداد كاميرا ديناميكية وإضافة تصادمات مع خط 2D في Godot تعرف على العقد Nodes في محرك ألعاب جودو Godot فهم RayCast2D واستخداماتها في محرك ألعاب جودو
-
سنوضّح في هذا المقال من سلسلة دليل جودو كيفية برمجة عدو لمطاردة اللاعب، وكيفية برمجة كائن في اللعبة مثل حيوان أليف ليتبع شخصية اللاعب. كيفية برمجة عدو لمطاردة اللاعب تتمثّل الخطوة الأولى لجعل العدو يطارد اللاعب في تحديد الاتجاه الذي يجب أن يتحرك به العدو، حيث يمكن الحصول على متجه يؤشّر من A إلى B من خلال عملية الطرح B - A، ثم نوحّد Normalize النتيجة ونحصل على متجه الاتجاه. سنحتاج هنا إلى ضبط سرعة العدو في كل إطار للتأشير إلى اتجاه اللاعب كما يلي: velocity = (player.position - position).normalized() * speed يحتوي كائن Vector2 الخاص بمحرّك الألعاب جودو Godot على دالة مساعدة مُضمَّنة لهذا الغرض وهي: velocity = position.direction_to(player.position) * speed سيؤدي ذلك إلى السماح للعدو بمطاردة اللاعب من أيّ مسافة حتى وإن كانت بعيدة، وهذه مشكلة إلى حد ما ستحتاج إلى حلها من خلال إضافة العقدة Area2D إلى العدو ومطاردة اللاعب عندما يكون ضمن نطاق الكشف Detect Radius فقط. سنربط الآن إشارات body_entered و body_exited من العقدة Area2D حتى يعرف العدو ما إذا كان اللاعب ضمن المجال أم لا: extends CharacterBody2D var run_speed = 25 var player = null func _physics_process(delta): velocity = Vector2.ZERO if player: velocity = position.direction_to(player.position) * run_speed move_and_slide() func _on_DetectRadius_body_entered(body): player = body func _on_DetectRadius_body_exited(body): player = null ملاحظة: تفترض الشيفرة البرمجية السابقة أن اللاعب هو الجسم الوحيد الذي يدخل أو يخرج، والذي يحدث عادةً من خلال ضبط طبقات أو أقنعة التصادم المناسبة. 02_chase_02.webm يمكن توسيع هذا المفهوم ليشمل أنواعًا أخرى من الألعاب، فالفكرة الأساسية هي العثور على متجه الاتجاه من العدو إلى اللاعب، حيث إذا كانت لعبتنا مثلًا هي لعبة ذات عرض جانبي أو لها قيود أخرى في الحركة، فيمكننا استخدام المكوِّن x فقط من المتجه الناتج لتحديد الحركة. القيود يمكن ملاحظة أن هذه الطريقة تعطي حركةً خطيةً مستقيمةً بسيطة، إذ لن يتحرك العدو حول العوائق مثل الجدران، كما لن يتوقف إذا اقترب من اللاعب كثيرًا؛ ويعتمد ما يجب فعله هنا عندما يقترب العدو من اللاعب على طبيعة لعبتنا، إذ يمكن إضافة منطقة ثانية أصغر تتسبب في توقف العدو ومهاجمته، أو يمكن جعل اللاعب يتراجع عند التلامس. توجد مشكلة أخرى مع الأعداء سريعي الحركة، إذ سيغير الأعداء الذين يستخدمون هذه التقنية اتجاههم مباشرةً مع تحرّك اللاعب، ويمكن الحصول على حركة طبيعية أكثر من خلال استخدام سلوك التوجيه Steering. برمجة كائن في اللعبة مثل حيوان أليف ليتبع شخصية اللاعب لنفترض أن لدينا كيان في لعبتنا مثل حيوان أليف أو تابع لمتابعة شخصية اللاعب كما يلي: 03_pet_follow.webm سنبدأ أولًا بإضافة عقدة Marker2D إلى الشخصية، والتي تمثل المكان الذي يريد الحيوان الأليف المشي فيه بالقرب من الشخصية. جعلنا عقدة Marker2D في مثالنا ابنًا للعقدة Sprite2D، لأن الشيفرة البرمجية للشخصية تستخدم الخاصية $Sprite2D.scale.x = -1 لقلب الاتجاه الأفقي عندما تتحرك الشخصية إلى اليسار، وبالتالي ستنقلب عقدة Marker2D أيضًا لأنها ابن للعقدة Sprite2D. سكربت الحيوان الأليف فيما يلي سكربت الحيوان الأليف: extends CharacterBody2D @export var parent : CharacterBody2D var speed = 25 @onready var follow_point = parent.get_node("Sprite2D/FollowPoint") يحتوي المتغير parent على مرجع للشخصية التي يجب أن يتبعها الحيوان الأليف، ونحصل بعد ذلك على عقدة FollowPoint منه لنتمكّن من الحصول على موضعه في الدالة _physics_process(): func _physics_process(delta): var target = follow_point.global_position velocity = Vector2.ZERO if position.distance_to(target) > 5: velocity = position.direction_to(target) * speed if velocity.x != 0: $Sprite2D.scale.x = sign(velocity.x) if velocity.length() > 0: $AnimationPlayer.play("run") else: $AnimationPlayer.play("idle") move_and_slide() إذا كان الموضع قريبًا من نقطة الهدف، فسنوقف حركة الحيوان الأليف. التنقل عبر المعيقات يجب الانتباه إلى أننا قد نجد الحيوان الأليف عالقًا بين المعيقات الموجودة بعالمنا الخاص باللعبة، لذا يمكن استخدام التنقل لتكون متابعة الحيوان الأليف للشخصية أقوى. ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github. ختامًا بهذا نكون قد تعرفنا على كيفية برمجة شخصية عدو قادرة على مطاردة اللاعب، إلى جانب برمجة حيوان أليف قادر تتبع اللاعب داخل اللعبة، عبر محرك جودو Godot. ترجمة -وبتصرّف- للقسمين Chasing the player و Pet Following من توثيقات Kidscancode. اقرأ أيضًا المقال السابق: إعداد كاميرا ديناميكية وإضافة تصادمات مع خط 2D في Godot تعرف على العقد Nodes في محرك ألعاب جودو Godot التفاعل بين الشخصيات والأجسام الصلبة في جودو فهم RayCast2D واستخداماتها في محرك ألعاب جودو
-
سنوضّح في هذا المقال وهو جزء من سلسلة دليل جودو كيفية إعداد كاميرا ديناميكية تتحرّك وتكبّر وتصغّر المشهد لإبقاء عدة عناصر على الشاشة في الوقت نفسه، وسنتعرّف على كيفية إضافة تصادمات مع خط مرسوم ثنائي الأبعاد. طريقة إعداد كاميرا ديناميكية تتبع عدة أهداف في وقت واحد سنوضّح فيما يلي كيفية إعداد كاميرا ديناميكية لإبقاء عدة عناصر على الشاشة في الوقت نفسه. لنفترض أن لدينا لعبة تحتوي على لاعبَين ويجب إبقاء اللاعبين على الشاشة أثناء تحرّكهما، سواءً عند التباعد عن بعضهما البعض أو عند وجودهما قرب بعضهما البعض كما في المثال التالي: قد نكون معتادين على إرفاق الكاميرا باللاعب في لعبة تحتوي على لاعب واحد، بحيث تتبع الكاميرا هذا اللاعب تلقائيًا، ولكننا لا نجد ذلك مطبقًا عند وجود لاعبَين أو أكثر، أو عناصر أخرى في اللعبة نريد إبقاءها على الشاشة طوال الوقت. لحل المشكلة، يمكننا تطبيق ما يلي على الكاميرا: إضافة أو إزالة أيّ عددٍ من الأهداف إبقاء موضع الكاميرا متمركزًا عند نقطة المنتصف للأهداف ضبط تكبير أو تصغير الكاميرا لإبقاء جميع الأهداف على الشاشة سننشئ مشهدًا جديدًا باستخدام عقدة Camera2D ونرفق بها سكربتًا، وسنضيف هذه الكاميرا إلى لعبتنا بعد الانتهاء. سيبدأ السكربت بالتعليمات التالية: extends Camera2D @export var move_speed = 30 # سرعة الاستيفاء الخطي lerp لموضع الكاميرا @export var zoom_speed = 3.0 # سرعة الاستيفاء الخطي lerp لتكبير وتصغير Zoom الكاميرا @export var min_zoom = 5.0 # لن تقترب الكاميرا أكثر من هذه القيمة @export var max_zoom = 0.5 # لن تبتعد الكاميرا أكثر من هذه القيمة @export var margin = Vector2(400, 200) # تضمين بعض المساحة العازلة حول الأهداف var targets = [] # مصفوفة الأهداف التي يجب تعقّبها @onready var screen_size = get_viewport_rect().size تمكّن هذه الإعدادات من ضبط سلوك الكاميرا، إذ سنستخدم الدالة lerp() التي تمثّل الاستيفاء الخطي لجميع تغييرات الكاميرا، ولهذا سيؤدي ضبط سرعات الحركة/التكبير والتصغير على قيم أقل إلى بعض التأخير في تتبّع الكاميرا للتغييرات المفاجئة. تعتمد قيم التكبير أو التصغير العليا والدنيا أيضًا على حجم الكائنات في لعبتنا ومدى القرب أو البعد الذي نريده، لذا سنضبط هذه القيم لتناسب احتياجاتنا. تضيف الخاصية margin مساحةً إضافيةً حول الأهداف، بحيث لا تكون على حافة المنطقة القابلة للعرض بالضبط. ستكون لدينا مصفوفة أهداف، ونحصل على حجم نافذة العرض لنتمكّن من حساب المقياس الصحيح. func add_target(t): if not t in targets: targets.append(t) func remove_target(t): if t in targets: targets.erase(t) توجد دالتان مساعدتان لإضافة وإزالة الأهداف، ويمكنك استخدامهما أثناء اللعب لتغيير الأهداف التي يجب تعقّبها مثل دخول اللاعب 3 إل اللعبة. وكما نلاحظ، لا نريد تعقّب الهدف نفسه مرتين، لذا سنرفضه إذا كان موجودًا مسبقًا. تحدث معظم الوظائف في الدالة _process()، ولكن لنبدأ أولًا بتحريك الكاميرا كما يلي: func _process(delta): if !targets: return # إبقاء الكاميرا متمركزة بين الأهداف var p = Vector2.ZERO for target in targets: p += target.position p /= targets.size() position = lerp(position, p, move_speed * delta) سنكرّر ضمن الحلقة التأكيد على مواضع الأهداف ونعثر على المركز المشترك، ونتأكّد باستخدام الدالة lerp() من التحرّك بسلاسة. بعد ذلك، لا بد لنا من التعامل مع التكبير أو التصغير كما يلي: # العثور على التكبير أو التصغير الذي سيحتوي على جميع الأهداف var r = Rect2(position, Vector2.ONE) for target in targets: r = r.expand(target.position) r = r.grow_individual(margin.x, margin.y, margin.x, margin.y) var z if r.size.x > r.size.y * screen_size.aspect(): z = 1 / clamp(r.size.x / screen_size.x, min_zoom, max_zoom) else: z = 1 / clamp(r.size.y / screen_size.y, min_zoom, max_zoom) zoom = lerp(zoom, Vector2.ONE * z, zoom_speed) تأتي الوظيفة الأساسية من الصنف Rect2، إذ نريد العثور على مستطيل يحيط بكل الأهداف، والذي يمكننا الحصول عليه باستخدام التابع expand()، ثم نوسّع المستطيل باستخدام الخاصية margin. يجب علينا هنا الضغط على زر Tab بالمستطيل المرسوم لتفعيل هذا الرسم في المشروع التجريبي: نجد المقياس ونثبّته في النطاق الأقصى/الأدنى الذي حدّدناه اعتمادًا على ما إذا كان المستطيل أوسع أو أطول، إذ يتعلق ذلك بالنسبة إلى أبعاد الشاشة. السكربت الكامل سيكون السكربت الكامل للعملية على النحو الآتي: extends Camera2D @export var move_speed = 30 # سرعة الاستيفاء الخطي lerp لموضع الكاميرا @export var zoom_speed = 3.0 # سرعة الاستيفاء الخطي lerp لتكبير وتصغير Zoom الكاميرا @export var min_zoom = 5.0 # لن تقترب الكاميرا أكثر من هذه القيمة @export var max_zoom = 0.5 # لن تبتعد الكاميرا أكثر من هذه القيمة @export var margin = Vector2(400, 200) # تضمين بعض المساحة العازلة حول الأهداف var targets = [] @onready var screen_size = get_viewport_rect().size func _process(delta): if !targets: return # إبقاء الكاميرا متمركزة بين الأهداف var p = Vector2.ZERO for target in targets: p += target.position p /= targets.size() position = lerp(position, p, move_speed * delta) # العثور على التكبير أو التصغير الذي سيحتوي على جميع الأهداف var r = Rect2(position, Vector2.ONE) for target in targets: r = r.expand(target.position) r = r.grow_individual(margin.x, margin.y, margin.x, margin.y) var z if r.size.x > r.size.y * screen_size.aspect(): z = 1 / clamp(r.size.x / screen_size.x, max_zoom, min_zoom) else: z = 1 / clamp(r.size.y / screen_size.y, max_zoom, min_zoom) zoom = lerp(zoom, Vector2.ONE * z, zoom_speed * delta) # لتنقيح الأخطاء get_parent().draw_cam_rect(r) func add_target(t): if not t in targets: targets.append(t) func remove_target(t): if t in targets: targets.remove(t) ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github. إضافة تصادمات مع خط مرسوم ثنائي الأبعاد سنوضّح فيما يلي كيفية إضافة تصادمات مع خط مرسوم ثنائي الأبعاد باستخدام عقدة Line2D. إعداد العقد سنحتاج لإضافة العقد التالية إلى مشهدنا، ورسم الخط الذي نريده: Line2D StaticBody2D لا داعي لإضافة شكل تصادم إلى الجسم حاليًا. ملاحظة: يمكن استخدام العقدة Area2D عوضًا عن ذلك إذا أردنا اكتشاف التداخل مع الخط بدلًا من التصادم. يجب بعد ذلك إضافة أشكال تصادم إلى الجسم، حيث يوجد لدينا خياران كما سنوضّح فيما يلي. الخيار الأول: استخدام الشكل SegmentShape2D يمثّل الشكل SegmentShape2D شكل تصادم لخط ومقطع، حيث نريد إنشاء تصادم مقاطع لكل زوج من النقاط على الخط. extends Line2D func _ready(): for i in points.size() - 1: var new_shape = CollisionShape2D.new() $StaticBody2D.add_child(new_shape) var segment = SegmentShape2D.new() segment.a = points[i] segment.b = points[i + 1] new_shape.shape = segment الخيار الثاني: استخدام الشكل RectangleShape2D لا يحتوي الشكل SegmentShape2D على أيّ مكوّن للعرض، لذا إذا أردنا أن يكون لتصادم الخطوط ثخانة، فيمكن استخدام تصادم المستطيل بدلًا من ذلك. extends Line2D func _ready(): for i in points.size() - 1: var new_shape = CollisionShape2D.new() $StaticBody2D.add_child(new_shape) var rect = RectangleShape2D.new() new_shape.position = (points[i] + points[i + 1]) / 2 new_shape.rotation = points[i].direction_to(points[i + 1]).angle() var length = points[i].distance_to(points[i + 1]) rect.extents = Vector2(length / 2, width / 2) new_shape.shape = rect ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github. ختامًا بهذا نكون قد وصلنا إلى نهاية المقال الذي حددنا فيه طريقة إعداد الكاميرا لتكون ديناميكية داخل اللعبة، كما تعرفنا على كيفية إضافة تصادمات مع خط مرسوم ثنائي الأبعاد. ترجمة -وبتصرّف- للقسمين Multitarget Camera و Line2D Collision من توثيقات Kidscancode. اقرأ أيضًا المقال السابق: إطلاق المقذوفات وترتيب عرض الكائنات في الألعاب ثنائية الأبعاد دليلك الشامل إلى بناء كاميرا خاصة بشاشات اللمس في محرّك اﻷلعاب جودو تعرف على العقد Nodes في محرك ألعاب جودو Godot
-
عندما نعمل على تطبيق لارافيل Laravel، قد نحتاج لتكرار مهام معينة مثل إنشاء ملفات أو تنفيذ أوامر خاصة بالتطبيق. وبدلاً من أن نكتب كل هذه الأوامر يدويًا في كل مرة، يمكننا أتمتة المهام المتكررة باستخدام أداة سطر الأوامر سهلة الاستخدام PHP Artisan المدمجة مع إطار عمل لارافيل، فهي تتيح لنا تنفيذ أوامر جاهزة مثل إنشاء جداول قواعد البيانات، أو تشغيل خادم التطوير المحلي، كما تتيح لنا تنفيذ أوامر جديدة خاصة بنا ننشؤها بأنفسنا، مثل إرسال بريد لمستخدم، أو تحديث بيانات معينة. سنوضح في هذا المقال كيفية إنشاء أمر Artisan جديد باستخدام الأمر make:command الذي يؤدي لإنشاء صنف جديد في المجلد app/Console/Commands والمستخدَم لإعداد عمليات التهجير Migrations وقوائم الوِجهات Route وإنشاء الأرتال Queueing والأصناف والمهام الأخرى. نظرة عامة على PHP Artisan يستخدم المطورون أوامر Artisan من لارافيل لإكمال المهام الضوروية مثل إنشاء عمليات التهجير، ونشر موارد الحزم. هناك عدة أوامر مدمجة مع Artisan، ويمكننا عرض قائمة بهذه الأوامر من خلال كتابة أمر php artisan list في الطرفية Terminal، إذ يوفر هذا الأمر قائمةً بجميع الأوامر المتاحة. تساعد هذه الأوامر المطورين على العمل بكفاءة أكبر، فباستخدام أوامر Laravel Artisan المختلفة سنتمكن من إنشاء وظائف الاستيثاق Auth والمتحكم Controller والنموذج والبريد والتهجير والعديد من الوظائف الأخرى بسرعة وبوقت أقل. تشغيل تطبيق لارافيل باستخدام خادم PHP Artisan يتيح الأمر serve إمكانية تشغيل التطبيقات على خادم تطوير PHP بسهولة، ويمكن للمطورين استخدام Laravel Artisan لإنشاء واختبار ميزات التطبيق المختلفة، إذ يمكن بدء تشغيل الخادم المحلي على العنوان http://localhost:8000 بكتابة الأمر php artisan serve، مما يسهل اختبار وتطوير التطبيقات بسرعة؛ كما يمكن تخصيص الخادم لاستخدام مضيف ومنفذ مختلف. يتكامل أمر php artisan serve مع نظام تطوير لارافيل Laravel Ecosystem الذي يتضمن ميزات عديدة مثل تهجير قواعد البيانات Migration، وجدولة المهام، وإدارة الأرتال Queues. إنشاء أوامر مخصصة وتشغيلها يمكن إنشاء أوامر Artisan مخصصة، مع اختيار مكان تخزينها وتحميلها باستخدام مدير الحزم Composer. سنشرح في الفقرات التالية أهم الخطوات المتبعة لإنشاء أوامر مخصصة وتسجيلها وتنفيذها. الخطوة 1: إنشاء أمر جديد يمكننا إنشاء أمر جديد باسم customcommand عبر استخدام الأمر make:command الذي سينشئ صنف أوامر جديد تلقائيًا في المجلد app/Console/Commands، وسيتولّد هذا المجلد إن لم يكن موجودًا مسبقًا. لنكتب الآن الأمر التالي في طرفية Artisan: php artisan make:command customcommand قد يبدو ملف الشيفرة البرمجية CustomCommand.php الخاص بهذا الأمر كما يلي: namespace App\Console\Commands; use Illuminate\Console\Command; class CustomCommand extends Command { protected $signature = 'custom:command'; protected $description = 'Description of the custom command'; public function __construct() { parent::__construct(); } public function handle() { // نضع المنطق البرمجي للأمر هنا } } الخطوة 2: تعريف الأمر عند تنفيذ بعض أوامر Artisan، قد نحتاج أحيانًا لأن يُدخل المستخدم بعض المعلومات، مثل اسمه أو بريده الإلكتروني. يتعامل لارافيل مع ذلك باستخدام خاصية التوقيع signature، ويمكننا استخدام الصيغة القصيرة التالية لضبط الوسطاء Arguments والرايات Flags أو الخيارات المطلوبة والاختيارية، وهذا يسهل علينا تحديد الطريقة التي سيتفاعل بها المستخدمون مع الأمر عند تشغيله. لنلقِ نظرةً على الأمر التالي: protected $signature = 'user:update {id} {--name=} {--email=}'; يتوقّع هذا الأمر لأن يدخل المستخدم وسيطًا للمعرّف ID وخيارات الاسم والبريد الإلكتروني الاختيارية. الخطوة 3: دخل وخرج الأمر يوفّر لارافيل طرقًا سهلةً للحصول على قيم الوسطاء والخيارات عند تشغيل الأمر، لذا من المهم استخدام التابعين $this->argument() و $this->option() ضمن التابع handle الخاص بالأمر. وإن لم يكن الوسيط أو الخيار الذي نبحث عنه موجودًا، فسيعيد هذان التابعان القيمة null. يمكننا الحصول على إدخال من المستخدم أثناء تشغيل الأمر بالإضافة إلى عرض الخرج، حيث يعرض التابع ask سؤالًا للمستخدم ويجمع استجابته ثم يرسلها مرةً أخرى إلى الأمر الخاص بنا. $name = $this->ask('What is your name?', 'Taylor'); والأمر الآتي لطلب التأكيد: if ($this->confirm('Do you wish to continue?')) { // متابعة } يمكن جعل موجّه التأكيد يعيد القيمة true دائمًا من خلال تمرير القيمة true كوسيط ثانٍ للتابع confirm. الخطوة 4: تسجيل الأوامر يسجّل لارافيل جميع الأوامر الموجودة في المجلد app/Console/Commands افتراضيًا، ويمكننا إعداده للبحث في مزيد من المجلدات عن أوامر PHP Artisan، وذلك من خلال استخدام التابع withCommands في ملف bootstrap/app.php الخاص بتطبيقنا. ->withCommands([ __DIR__.'/../app/Domain/Orders/Commands', ]) سيجد حاوي الخدمات جميع الأوامر ويسجلها في تطبيقنا باستخدام Artisan. الخطوة 5: تنفيذ الطلبات قد نرغب في تشغيل أمر Artisan خارج واجهة سطر الأوامر من وجهة Route أو متحكم Controller مثلًا، حيث يمكن ذلك باستخدام التابع call مع الصنف Facade من Artisan؛ ويحتوي هذا التابع على وسيطين هما: اسم الأمر أو صنفه قائمة بمعاملات الأمر ويعيد هذا التابع رمز الخروج. يمكن أيضًا تمرير أمر Artisan بالكامل إلى التابع call كسلسلة نصية كما يلي: Artisan::call('mail:send 1 --queue=default'); يمكن استخدام التابع queue مع الصنف Facade من Artisan لإرسال أوامر Artisan إلى عمّال الرتل، ثم نعالَج هذه الأوامر في الخلفية، ونتأكّد من ضبط الرتل وتشغيل مستمع الرتل قبل استخدام هذا التابع. use Illuminate\Support\Facades\Artisan; Route::post('/user/{user}/mail', function (string $user) { Artisan::queue('mail:send', [ 'user' => $user, '--queue' => 'default' ]); // ... }); الخطوة 6: معالجة الإشارات يمكن لأوامر Artisan من لارافيل معالجة إشارات النظام مثل إشارات SIGINT، حيث يمكن دمج معالجة الإشارات مع الأمر الخاص بنا باستخدام التابع trap كما يلي: $this->trap(SIGINT, function () { $this->info('Command interrupted'); }); الخطوة 7: تخصيص الملفات الجذرية Stub يمكن تخصيص ملفات القوالب التي يستخدمها أمر make:command من Artisan لتسهيل إنشاء الأوامر ذات البنى المتناسقة، لذا نحتاج إلى نشر الملفات الجذرية في مشروعنا كما يلي: php artisan stub:publish الخطوة 8: الأحداث هناك ثلاثة أحداث يرسلها Artisan عند تشغيل الأوامر وهي: Illuminate\Console\Events\ArtisanStarting Illuminate\Console\Events\CommandStarting Illuminate\Console\Events\CommandFinished استخدم التابع event لإطلاق حدث كما يلي: event(new CustomCommandExecuted($this)); إنشاء عمليات التهجير Migrations باستخدام الأمر make:migration يُعَد الأمر php artisan make:migration من أوامر Artisan في لارافيل، ويُستخدَم لإنشاء ملف تهجير جديد. عمليات التهجير هي مخططات أولية لمخطط قاعدة البيانات الخاصة بنا، وتحدّد بنية الجداول والأعمدة والفهارس والعلاقات. في ما يلي خطوات هذه العملية: إنشاء عملية تهجير: يؤدي تشغيل الأمر make:migration إلى إنشاء ملف PHP جديد في المجلد database/migrations تحديد التغييرات: يحتوي ملف التهجير على التابعين up و down، حيث يحدّد التابع up التغييرات التي تطرأ على قاعدة البيانات، مثل إنشاء الجداول أو إضافة الأعمدة، ويلغي التابع down هذه التغييرات تشغيل عملية التهجير: استخدام الأمر php artisan migrate لتطبيق التغييرات على قاعدة البيانات يؤدي الأمر التالي مثلًآ إلى إنشاء ملف جديد بالاسم create_users_table في المجلد database/migrations، ويمكن بعد ذلك تحديد بنية جدول users ضمن ملف التهجير. Bash php artisan make:migration create_users_table فوائد الأمر make:migration تتمثل فوائد الأمر make:migration فيما يلي: التحكم في الإصدارات: من خلال تعقّب تغييرات قاعدة البيانات بمرور الوقت التعاون: مشاركة بنية قاعدة البيانات مع أعضاء الفريق بسهولة بذر أو توليد البيانات Seeding لقاعدة البيانات: ملء قاعدة البيانات بالبيانات التجريبية باستخدام مولّد البيانات Seeder التراجع عن تغييرات قاعدة البيانات: يمكن عكس تغييرات قاعدة البيانات بسهولة إن لزم الأمر تساعد عمليات التهجير على إبقاء مخطط قاعدة البيانات واضحًا ومنظمًا، مما يسهّل عملية إدارة التطبيق وتحديثه. قائمة أوامر Laravel Artisan تُعَد واجهة سطر أوامر Artisan في لارافيل أداةً مفيدةً للعمل مع التطبيقات، فهي تحتوي على أوامر لمهام مختلفة مثل إنشاء الشيفرة البرمجية وإدارة بيئة التطبيق. تكون قائمة أوامر Laravel PHP Artisan الكاملة شاملة، حيث يمكننا استخدام أمر PHP Artisan list لجلبها، ولكن سنفصّل فيما يلي الأوامر الأساسية ضمن فئات في Laravel 11. تتمثل الأوامر الأساسية في الآتي: cache: إدارة ذاكرة التطبيق المخبئية مثل المسح والنسيان وإلخ config: تخزين ملفات الضبط Configuration مؤقتًا أو مسحها أو نشرها down: تعطيل التطبيق مؤقتًا env: إدارة متغيرات البيئة key: توليد مفتاح تطبيق جديد migrate: تشغيل عمليات تهجير قاعدة البيانات optimize: تحسين التطبيق للإنتاج queue: إدارة نظام الرتل route: سرد الوِجهات Routes أو مسحها storage: إدارة مجلد التخزين vendor: إدارة اعتماديات مدير الحزم Composer أما أوامر توليد الشيفرة البرمجية، فتتمثل في ما يلي: make: توليد بنى الشيفرة البرمجية المختلفة للمتحكم والنموذج والتهجير وإلخ model: إنشاء نماذج Eloquent migration: إنشاء ملفات التهجير seed: توليد بيانات وهمية لقاعدة البيانات في حين أن أوامر الاختبار تكون كالآتي: test: تشغيل اختبارات التطبيق dusk: تشغيل اختبارات المتصفح باستخدام Dusk إلى جانب وجود بعض الأوامر الإضافية التي يمكن اعتمادها، وتتمثل في: auth: إدارة العمليات المتعلقة بالاستيثاق Authentication breeze: تثبيت نظام استيثاق Breeze config: إدارة ملفات الضبط horizon: إدارة نظام رتل Laravel Horizon passport: إدارة خادم OAuth2 sanctum: إدارة واجهة برمجة التطبيقات API لاستيثاق الرموز Token telescope: إدارة أداة تنقيح أخطاء Telescope الخلاصة بهذا نكون قد وضّحنا في هذا المقال كيفية إنشاء أوامر مخصصة باستخدام أمر Artisan الذي هو أداة PHP وتتيح تطوير أوامر مختلفة بناءً على احتياجات مشروعنا. ترجمة -وبتصرّف- للقسم How to Create Custom Commands in Laravel 11 with PHP Artisan لصاحبته Hafsa Tahir. اقرأ أيضًا إنشاء أوامر مخصصة في Laravel 11 باستخدام PHP Artisan نصائح لتحسين أداء تطبيقات لارافيل أفضل الحزم البرمجية لتحسين تطبيقات لارافيل
-
يوفّر نظام إدارة قاعدة بيانات مفتوح المصدر MariaDB أداءً وأمانًا ومرونةً مميزة، ويمكن للمطورين استخدام ميزات MariaDB لبناء تطبيقات ويب قوية عند استخدامها مع لارافيل Laravel الذي يُعَد إطار عمل PHP شائع الاستخدام. قد يكون ربط قاعدة بيانات MariaDB بتطبيق لارافيل أمرًا صعبًا بالنسبة لبعض المطورين، وخاصةً المطورين المبتدئين، ولكن يمكن دمج قاعدة بيانات MariaDB بنجاح مع لارافيل باستخدام التوجيه المناسب والموارد الصحيحة. سنوضّح في هذا المقال كيفية ربط قاعدة بيانات MariaDB بتطبيق لارافيل، وسنوضح متطلبات النظام لقاعدة بيانات MariaDB وضبط قاعدة بيانات MariaDB مع إطار عمل لارافيل واختبار الاتصال. نظرة عامة موجزة على MariaDB MariaDB هو نظام إدارة قواعد بيانات عِلاقية مفتوح المصدر يستخدمه مطورو الويب على نطاق واسع، ويُعَد فرعًا من قاعدة بيانات MySQL ويقدم العديد من الميزات المتقدمة مثل تحسين الأداء والأمان ومزيدًا من المرونة من ناحية الضبط Configuration. يستخدم نظام إدارة قواعد البيانات MariaDB لغة SQL لإدارة البيانات ومعالجتها، مما يوفر للمطورين بيئةً مألوفةً ومرنةً للعمل مع البيانات، ويلتزم بالحفاظ على التوافق مع MySQL، مما يسمح بالتهجير Migration أو الانتقال السلس لقواعد بيانات MySQL الموجودة مسبقًا. يمكن لقاعدة بيانات MariaDB التعامل مع عدد كبير من العمليات بكفاءة مع تركيزها على تحسين الأداء وميزاتها المتقدمة، مما يجعلها خيارًا ممتازًا للتطبيقات التي تتراوح من المشاريع الصغيرة إلى الأنظمة التي على مستوى المؤسسات. فوائد استخدام لارافيل Laravel مع MariaDB يمكن أن يوفر استخدام إطار عمل لارافيل مع قاعدة بيانات MariaDB العديد من الفوائد لمطوري الويب مثل: البساطة يسهّل لارافيل من التفاعل مع قواعد البيانات بما في ذلك قاعدة بيانات MariaDB، وبالتالي يمكن للمطورين بسهولة ربط تطبيق لارافيل بقاعدة بيانات MariaDB والاستفادة من ميزاتها القوية. المرونة تدعم قاعدة بيانات MariaDB محركات تخزين أكثر من قاعدة بيانات MySQL، مما يوفر للمطورين مرونةً أكبر عند العمل مع البيانات في تطبيق لارافيل. قابلية التوسع تعمل تقنية عنقود Galera المتقدمة في قاعدة بيانات MariaDB على التخلص من تأخر الخوادم وفقدان المعامَلات وتقليل زمن الاستجابة للعميل، وتحسين قابلية توسيع قراءة العقد، مما يجعلها خيارًا ممتازًا لتطبيقات لارافيل التي تحتاج إلى التعامل مع كميات كبيرة من البيانات. الأمان يركز كل من لارافيل و MariaDB على الأمان، حيث يوفر لارافيل ميزات أمن مُدمَجة معه مثل الاستيثاق Authentication والتصريح Authorization؛ بينما تقدم MariaDB تدابير أمان قوية لحماية البيانات، ويمكن أن يساعد استخدام هاتين التقنيتين مع بعضهما البعض المطورين في بناء تطبيقات ويب آمنة. تشغيل تطبيق لارافيل مع قاعدة بيانات MariaDB يتضمن نشر تطبيق لارافيل باستخدام قاعدة بيانات MariaDB عدة خطوات، سنوضّحها فيما يلي. الخطوة 1: إنشاء مشروع لارافيل جديد لنفتح الطرفية Terminal ونشغل الأوامر التالية لإنشاء مشروع لارافيل جديد: composer global require laravel/installer laravel new YourProjectName cd YourProjectName الخطوة 2: ضبط قاعدة البيانات سنفتح الآن ملف .env في مجلد جذر المشروع ونضبط اتصال قاعدة بيانات MariaDB، وذلك من خلال تحديث الأسطر التالية بمعلومات قاعدة بياناتنا: DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database_name DB_USERNAME=your_database_username DB_PASSWORD=your_database_password الخطوة 3: إنشاء ملفات التهجير Migrations والنماذج Models يستخدم لارافيل ملفات التهجير لإدارة مخطط قاعدة البيانات، حيث يمكننا إنشاء ملف تهجير ونموذج جديد لكيان معين مثل Post من خلال تشغيل الأمر التالي: php artisan make:model Post -m وسيؤدي الأمر السابق إلى إنشاء ملف تهجير في المجلد database/migrations ونموذج في المجلد app. الخطوة 4: تعديل ملف التهجير يمكننا فتح ملف التهجير -مثل الملف xxxx_xx_xx_create_posts_table.php- في المجلد database/migrations وتعريف مخطط الجدول posts كما يلي: public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('content'); $table->timestamps(); }); } الخطوة 5: تشغيل ملف التهجير عند هذه الخطوة سنطبق التهجير لإنشاء جدول قاعدة البيانات، وذلك من خلال تشغيل الأمر التالي: php artisan migrate الخطوة 6: إنشاء الوجهات Routes والمتحكمات Controllers سننشئ الآن وجهات في ملف routes/web.php والمتحكمات المقابلة لتطبيقنا. فعلى سبيل المثال، يمكن إنشاء وِجهة لعرض المنشورات من خلال إضافة السطر التالي إلى الملف routes/web.php: Route::get('/posts', 'PostController@index'); الخطوة 7: إنشاء متحكم لنولّد الآن متحكمًا باستخدام Artisan من خلال تشغيل الأمر التالي: php artisan make:controller PostController سنعرّف بعد ذلك المنطق البرمجي لجلب المنشورات وعرضها في المتحكّم PostController. الخطوة 8: إنشاء العروض Views وتشغيل التطبيق سننشئ هنا عروض Blade في المجلد resources/views لإخراج واجهة المستخدم الخاصة بتطبيقنا؛ بعدها يمكننا تشغيل خادم تطوير لارافيل من خلال تشغيل الأمر التالي: php artisan serve سنحتاج الآن إلى الانتقال إلى العنوان http://localhost:8000/posts في متصفحنا لمشاهدة عمل التطبيق. الخطوة 10: إضافة البيانات إلى قاعدة البيانات يمكن استخدام مولّد البيانات Seeder أو إضافة البيانات يدويًا إلى قاعدة البيانات باستخدام أمر Artisan من لارافيل tinker، لذا لنشغّل الأمر التالي لإضافة البيانات يدويًا: php artisan tinker يمكن إنشاء منشور جديد وحفظه ضمن صدفة Shell الأمر tinker كما يلي: $post = new App\Models\Post; $post->title = 'Sample Title'; $post->content = 'Sample Content'; $post->save(); exit; ضبط قاعدة بيانات MariaDB باستخدام منصة Cloudways لنتبع الخطوات التالية لضبط قاعدة بيانات MariaDB باستخدام منصة Cloudways. الخطوة 1: تسجيل الدخول إلى منصة Cloudways ننقر على عرض جميع الخوادم View all Servers بعد تسجيل الدخول إلى الحساب ونختار الخادم الذي نريده. الخطوة 2: تحديد الإعدادات والحزم نحدد خيار الإعدادات والحزم Settings & Packages من شريط القائمة الأيسر، وننقر على تبويب الحزم Packages. الخطوة 3: اختيار إصدار MariaDB نختار إصدار MariaDB المفضل من الخيارات الموجودة، وننقر على حفظ التغييرات Save Changes. وبذلك نكون قد ضبطنا قاعدة بيانات MariaDB باستخدام منصة Cloudways. الإصدارات المدعومة من MariaDB تدعم منصة Cloudways العديد من إصدارات MariaDB، بما في ذلك 10.0 و 10.1 و 10.2 و 10.3 و 10.4 و 10.5 و 10.6، ولكن يجب ملاحظة أن الخوادم الأحدث والخوادم التي تعمل باستخدام توزيعة Debian 10 تأتي مع MariaDB 10.4 بوصفها قاعدة بيانات افتراضية. إصدار قاعدة البيانات قابلة للترقية إلى MySQL 5.5 الإصدارات MariaDB 10.0 و MariaDB 10.1 و MariaDB 10.2 و MariaDB 10.3. MySQL 5.6 الإصدارات MariaDB 10.1 و MariaDB 10.2 و MariaDB 10.3. MySQL 5.7 الإصدارات MariaDB 10.2 و MariaDB 10.3. MariaDB 10.0 الإصدارات MariaDB 10.1 و MariaDB 10.2 و MariaDB 10.3. MariaDB 10.1 الإصدارات MariaDB 10.2 و MariaDB 10.3. MariaDB 10.2 الإصدار MariaDB 10.3 والإصدارات الأحدث. MariaDB 10.3 الإصدار MariaDB 10.4 والإصدارات الأحدث. MariaDB 10.4 الإصدار MariaDB 10.5 والإصدارات الأحدث. MariaDB 10.5 الإصدار MariaDB 10.6 والإصدارات الأحدث. MariaDB 10.6 سيكون من الممكن ترقيته إلى أيّ إصدار جديد من MariaDB عند توفّره على منصة Cloudways. الخلاصة يمكن ربط قاعدة بيانات MariaDB بتطبيق لارافيل من خلال ضبط اتصال قاعدة البيانات في ملف .env وتشغيل عمليات التهجير لإنشاء جداول قاعدة البيانات الضرورية، وتوفر منصة Cloudways طريقةً سهلةً لضبط قاعدة بيانات MariaDB مع منصتها. هناك العديد من الفوائد التي يوفرها استخدام لارافيل مع قاعدة بيانات MariaDB للمطورين مثل البساطة والمرونة وقابلية التوسع والأمان، وبالتالي يمكن للمطورين إنشاء تطبيقات ويب قوية وموثوقة من خلال الاستفادة من هذه الفوائد. ترجمة -وبتصرّف- للقسم How to Connect MariaDB Database to Laravel Application لصاحبه Inshal Ali. اقرأ أيضًا كيفية تأمين قواعد البيانات MySQL ,MariaDB على خواديم لينكس كيفية استيراد وتصدير قواعد بيانات MySQL أو MariaDB كيفية تغيير مجلد تخزين بيانات MariaDB إلى مكانٍ آخر نصائح لتحسين أداء تطبيقات لارافيل
-
سنوضّح في هذا المقال كيفية إعداد خوارزمية للبحث عن المسار للسماح بالتنقل في بيئة قائمة على الشبكة Grid، حيث يوفّر محرّك الألعاب جودو Godot عددًا من الطرق لتحديد المسار، ولكننا سنستخدم في هذا المقال خوارزمية A*، التي لها استخدام واسع في العثور على أقصر مسار بين نقطتين، ويمكن استخدامها في أيّ هيكل بيانات قائمٍ على الرسم البياني Graph، وليس في بيئة شبكية فقط. يُعَد الصنف AStarGrid2D نسخةً متخصصةً من الصنف AStar2D الأعم في جودو، لذا يُعَد إعداده أسرع وأسهل، نظرًا لأنه متخصص للاستخدام مع الشبكة، إذ لسنا مضطرين لإضافة جميع خلايا الشبكة الفردية واتصالاتها يدويًا. إعداد الشبكة يُعَد ضبط حجم الخلايا والشبكة أهم خطوة، حيث سنستخدم الحجم (64, 64) في مثالنا، وسنستخدم حجم النافذة لتحديد عدد الخلايا الملائمة للشاشة، ولكن كل شيء سيعمل بالطريقة نفسها بغض النظر عن حجم الخلية. سنضيف الآن الشيفرة البرمجية التالية إلى العقدة Node2D: extends Node2D @export var cell_size = Vector2i(64, 64) var astar_grid = AStarGrid2D.new() var grid_size func _ready(): initialize_grid() func initialize_grid(): grid_size = Vector2i(get_viewport_rect().size) / cell_size astar_grid.size = grid_size astar_grid.cell_size = cell_size astar_grid.offset = cell_size / 2 astar_grid.update() نقسم حجمَ الشاشة على حجم الخلية cell_size في هذه الشيفرة البرمجية لحساب حجم الشبكة بالكامل، مما يتيح ضبط الخاصية size الخاصة بالصنف AStarGrid2D. تُستخدَم خاصية الإزاحة offset عندما نطلب مسارًا بين نقطتين، ويمثّل استخدام cell_size / 2 حساب المسار من مركز كل خلية بدلًا من زواياها، ويجب استدعاء الدالة update() بعد ضبط أو تغيير خاصيات الصنف AStarGrid2D. رسم الشبكة سنرسم الشبكة على الشاشة في الشيفرة البرمجية للتوضيح، ولكن قد تكون لدينا عقدة TileMap أو أيّ تمثيل مرئي آخر لعالمنا في تطبيق اللعبة. يمكن رسم الشبكة باستخدام الشيفرة البرمجية التالية: func _draw(): draw_grid() func draw_grid(): for x in grid_size.x + 1: draw_line(Vector2(x * cell_size.x, 0), Vector2(x * cell_size.x, grid_size.y * cell_size.y), Color.DARK_GRAY, 2.0) for y in grid_size.y + 1: draw_line(Vector2(0, y * cell_size.y), Vector2(grid_size.x * cell_size.x, y * cell_size.y), Color.DARK_GRAY, 2.0) وسنحصل على الشبكة التالية: رسم المسار نحتاج إلى نقطة بداية ونقطة نهاية للعثور على مسار، لذا علينا إضافة المتغيرات التالية في بداية السكربت: var start = Vector2i.ZERO var end = Vector2i(5, 5) بعد ذلك نضيف الأسطر التالية في الدالة _draw() لإظهار هذه النقاط: draw_rect(Rect2(start * cell_size, cell_size), Color.GREEN_YELLOW) draw_rect(Rect2(end * cell_size, cell_size), Color.ORANGE_RED) يمكننا الآن العثور على المسار بين النقطتين باستخدام التابع get_point_path()، ولكننا نحتاج أيضًا إلى إظهاره، لذا من المهم استخدام العقدة Line2D وإضافة إلى المشهد، حيث يمكن الحصول على المسار وإضافة النقاط الناتجة إلى العقدة Line2D كما يلي: func update_path(): $Line2D.points = PackedVector2Array(astar_grid.get_point_path(start, end)) وسنحصل على النتيجة التالية: وكما نلاحظ، لدينا خطًا قطريًا بين النقطتين لأن المسار يستخدم الخطوط القطرية افتراضيًا، ويمكن تعديل ذلك من خلال تغيير الخاصية diagonal_mode باستخدام القيم التالية: DIAGONAL_MODE_ALWAYS: القيمة الافتراضية، وتستخدم الخطوط القطرية DIAGONAL_MODE_NEVER: تكون الحركة عمودية DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE: تسمح هذه القيمة بالخطوط القطرية، ولكنها تمنع المسار من المرور بين العوائق الموضوعة قطريًا DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE: تسمح بالخطوط القطرية في المناطق المفتوحة فقط، وليس بالقرب من العوائق قد يؤدي تعديل هذه الخاصية إلى إعطاء نتائج مختلفة جدًا، لذا تأكد من التجربة بناءً على الإعداد الذي تستخدمه، لذا يجب إضافة ما يلي في الدالة initialize_grid(): astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER وأصبح لدينا الآن الحركات المتعامدة فقط كما يلي: إضافة العوائق يمكننا أيضًا إضافة العوائق إلى الشبكة، حيث لن يتضمن المسار خليةً ما من خلال وضع علامة عليها بوصفها صلبة Solid، ويمكن التبديل بين القيمتين صلبة وغير صلبة للخلية باستخدام الدالة set_point_solid(). سنضيف الشيفرة البرمجية التالية لرسم الجدران (إن وُجدت) من خلال العثور على الخلايا الصلبة وتلوينها: func fill_walls(): for x in grid_size.x: for y in grid_size.y: if astar_grid.is_point_solid(Vector2i(x, y)): draw_rect(Rect2(x * cell_size.x, y * cell_size.y, cell_size.x, cell_size.y), Color.DARK_GRAY) سنستدعي الآن هذه الدالة في الدالة _draw()، ويمكن بعد ذلك استخدام الفأرة للنقر على الخلايا وتبديل حالتها كما يلي: func _input(event): if event is InputEventMouseButton: # إضافة أو إزالة حائط if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: var pos = Vector2i(event.position) / cell_size if astar_grid.is_in_boundsv(pos): astar_grid.set_point_solid(pos, not astar_grid.is_point_solid(pos)) update_path() queue_redraw() يمكن ملاحظة أننا نستخدم التابع is_in_boundsv() أولًا، مما يمنع حدوث أخطاء إذا نقرنا خارج حدود الشبكة. يمكننا الآن رؤية تأثير العوائق على المسار كما يلي: الاختيار الاستكشافي أو التجريبي Heuristic يُعَد الاختيار الاستكشافي الذي نستخدمه عاملًا مهمًا يؤثر على المسار الناتج، حيث يشير مصطلح Heuristic إلى أفضل تخمين، ويمثل ببساطة الاتجاه الذي يجب أن نجرّبه أولًا عند التحرك نحو الهدف في سياق العثور على المسار. تستخدم المسافة الإقليدية مثلًا نظرية فيثاغورس لتقدير المسار الذي يجب تجربته كما يلي: بينما تأخذ مسافة مانهاتن في حساباتها المسافة في اتجاهات الشمال/الجنوب أو الشرق/الغرب فقط كما يلي: وتعطي طريقة التجزيء الاستكشافية Octile Heuristic المسار التالي: يمكن استخدام الاختيار الاستكشافي باستخدام الخاصية التالية: astar_grid.default_estimate_heuristic = AStarGrid2D.HEURISTIC_OCTILE يعتمد تحديد الخيار الأفضل الذي يعطي المسارات المناسب على طبيعة بيئتنا مثل احتوائها على مساحات مفتوحة واسعة مع القليل من العوائق المنتشرة حولها أم أنها متاهة من الممرات المتعرجة، لذا يجب التأكّد من تجربة مشروعنا أولًا. ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github لتجربة الإعداد، ويمكن استخدام أزرار الفأرة الأيمن/الأوسط لتحريك مواقع النهاية/البداية بالإضافة إلى وضع الجدران. ترجمة -وبتصرّف- للقسم Pathfinding on a 2D Grid من توثيقات Kidscancode. اقرأ أيضًا المقال السابق: مفهوم Coyote Time وكيفية إعداد منصة متحركة في مشهد اللعبة إطلاق المقذوفات وترتيب عرض الكائنات في الألعاب ثنائية الأبعاد إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد
-
سنوضح في هذا المقال تقنية Coyote Time ونضيفها إلى شخصية منصة موجودة مسبقًا، وسنتعرّف على كيفية تحريك المنصات في لعبة المنصات ثنائية الأبعاد. شرح مفهوم Coyote Time قد يكون القفز غير موجود في ألعاب المنصات، حيث لا يتمتع اللاعب بقدرٍ جيد من التحكم، وقد يفشل أحيانًا في القفز من حافة المنصات. يتمثّل الحل لهذه المشكلة في استخدام تقنية Coyote Time التي تمنح اللاعب شعورًا أكبر بالتحكم ومساحة ضئيلة للحركة حول عملية القفز من حواف المنصات، حيث تعمل هذه التقنية بالطريقة التالية: إذا مشى اللاعب بعد حافة منصة، فسنسمح له بالقفز كما لو كان لا يزال على الأرض بعد بضعة إطارات. ملاحظة: أتى اسم هذه التقنية من شخصية الذئب Coyote الكرتونية الشهيرة التي لا تسقط حتى تنظر إلى الأسفل: سنضيف هذه التقنية إلى شخصية منصة موجودة مسبقًا، لذا يمكن الاطلاع على مقال إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد لمعرفة كيفية إعدادها. سنضيف عقدة Timer بالاسم CoyoteTimer ونضبطها على لقطة واحدة One Shot للتعامل مع ضبط الوقت، وتوجد بعض المتغيرات الجديدة التي نحتاجها لتعقّب وقت coyote كما يلي: var coyote_frames = 6 # عدد الإطارات في الهواء المسموح بها للقفز var coyote = false # تتبّع ما إذا كنا في وقت coyote أم لا var last_floor = false # حالة الإطار الأخير على الأرض سنستخدم الإطارات لضبط المدة، وبالتالي يمكننا نقل ذلك إلى وقت ضبط طول العقدة Timer في التابع _ready() كما يلي: $CoyoteTimer.wait_time = coyote_frames / 60.0 سنخزّن القيمة الحالية للتابع is_on_floor() في كل إطار لاستخدامها في الإطار التالي، لذا يجب وضع ما يلي في الدالة _physics_process() بعد الدالة move_and_slide(): last_floor = is_on_floor() يجب التحقق مما إذا كانت الشخصية على الأرض أو في وقت Coyote عندما نكتشف إدخال القفز كما يلي: if Input.is_action_just_pressed("jump") and (is_on_floor() or coyote): velocity.y = jump_speed jumping = true يبدأ وقت Coyote إذا مشى اللاعب بعد حافة المنصة، وبالتالي لم يَعُد على الأرض وكان على الأرض في الإطار السابق. يمكننا التحقق من ذلك وبدء تشغيل المؤقت إذا انتقلنا من الأرض إلى فوقها وفق التالي: if !is_on_floor() and last_floor and !jumping: coyote = true $CoyoteTimer.start() تخبرنا العقدة CoyoteTimer متى تنتهي حالة coyote كما يلي: func _on_coyote_timer_timeout(): coyote = false يمكن تطبيق العملية نفسها على الشخصيات ثلاثية الأبعاد. ملاحظة: نُفِّذت شخصية القسم التالي باستخدام تقنية Coyote Time. كيفية إعداد منصة متحركة في مشهد اللعبة سنوضّح فيما يلي كيفية تحريك المنصات في لعبة المنصات ثنائية الأبعاد، حيث توجد طرق متعددة للتعامل مع ذلك؛ إذ سنستخدم عقد AnimatableBody2D للمنصة ونحرّكها باستخدام عقدة الانتقال التدريجي Tween، مما يسمح بمجموعة متنوعة من أنماط الحركة مع تقليل الشيفرة البرمجية التي يجب كتابتها. ملاحظة: يمكن أيضًا تطبيق هذه التقنية لتحريك المنصات باستخدام عقدة AnimationPlayer بدلًا من عقدة Tween، حيث سيبقى معظم الإعداد نفسه في كلتا الطريقتين، ولكن ستحرّك خاصية موضع position الجسم بدلًا من شيفرة Tween البرمجية. الإعداد سنبدأ بإعداد لعبة منصات بسيطة باستخدام الطريقة المتبعة في مقال إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد، حيث ستعمل هذه الحركة الأساسية بنجاح مع المنصات، وإذا عدّلناها أو استخدمنا طريقتنا الخاصة، فيجب أن يعمل كل شيء بالطريقة نفسها. إنشاء المنصة يحتوي مشهد المنصة على العقد التالية: Node2D المنصة المتحركة MovingPlatform: تكون العقدة الأب Node2D موجودةً لتعمل بوصفها نقطة ارتكاز أو نقطة بداية للمنصة، حيث سنحرّك موضع position المنصة بالتناسب مع هذه العقدة الأب AnimatableBody2D: تمثل هذه العقدة المنصة نفسها، وهي العقدة التي ستتحرّك Sprite2D: يمكن استخدام ورقة الشخصية الرسومية Sprite Sheet هنا أو صور فردية أو حتى عقدة TileMap CollisionShape2D: لا يجب أن نجعل مربع الاصطدام كبيرًا جدًا، وإلّا فسيبدو اللاعب وكأنه يحوم فوق حافة المنصة ستُعِدّ خامة Texture العقدة Sprite2D وشكل الاصطدام بطريقة مناسبة، ونضبط خاصية التزامن مع الفيزياء Sync to Physics على القيمة On في العقدة AnimatableBody2D، مما يضمن تحريك الجسم أثناء خطوة الفيزياء بما أننا نحرّكه في الشيفرة البرمجية، وبالتالي يكون متزامنًا مع اللاعب والأجسام الفيزيائية الأخرى. سنضيف الآن السكربت التالي إلى العقدة الجذر Node2D: extends Node2D @export var offset = Vector2(0, -320) @export var duration = 5.0 func _ready(): start_tween() func start_tween(): var tween = get_tree().create_tween().set_process_mode(Tween.TWEEN_PROCESS_PHYSICS) tween.set_loops().set_parallel(false) tween.tween_property($AnimatableBody2d, "position", offset, duration / 2) tween.tween_property($AnimatableBody2d, "position", Vector2.ZERO, duration / 2) حتى الآن قد استخدمنا بعض خيارات عقد Tween لجعل كل شيء يعمل بسلاسة مثل: set_process_mode(): يضمن حدوث الحركة أثناء خطوة المعالجة الفيزيائية set_loops(): يعمل على تكرار الانتقال التدريجي Tween set_parallel(false): تحدث جميع تغييرات tween_property() في الوقت نفسه افتراضيًا، ويؤدي هذا التابع إلى حدوث هذين الأمرين الواحد تلوَ الآخر، وهما التحرك إلى أحد طرفي الإزاحة ثم العودة إلى البداية يمكن ضبط حركة المنصة باستخدام هاتين الخاصيتين المُصدَّرتين، وهما الإزاحة offset التي يجب أن تضبطها لتحديد المكان الذي يتحرك فيه الانتقال التدريجي Tween بالنسبة لنقطة بدايته، و المدة duration التي يجب أن تضبطها لتحديد المدة التي تستغرقها لإكمال الدورة. سنضيف الان بعض المنصات في المستوى أو العالم الخاص بنا ونجرّبها كما في المثال التالي: 02_moving_platform4.webm ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github. ترجمة -وبتصرّف- للقسمين Coyote Time و Moving Platforms من توثيقات Kidscancode. اقرأ أيضًا المقال السابق: إطلاق المقذوفات وترتيب عرض الكائنات في الألعاب ثنائية الأبعاد إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد مدخل إلى محرك الألعاب جودو Godot تعرف على واجهة محرك الألعاب جودو
-
شاع استخدام مصطلح البيانات الضخمة Big Data في الآونة الأخيرة، فبعد أن صارت البيانات تُجمع بمعدل غير مسبوق من كل شيء حولنا، سواءً من أجهزة الاستشعار الخاصة بأجهزة إنترنت الأشياء IoT، أو إشارات نظام تحديد المواقع العالمي GPS من الهواتف المحمولة، أو منشورات وسائل التواصل الاجتماعي، أو المعاملات التجارية التي نجريها عبر الإنترنت، وغيرها من المصادر المتنوعة، مما زاد من حجم البيانات كثيرًا؛ دفع هذا الأمر الشركات للاستفادة من هذه البيانات وتحليلها واتخاذ القرارات بناءً عليها، وعليه صارت الكثير من القطاعات اليوم تسعى للاستفادة من هذا الكم الهائل من هذه البيانات لفهم توقعات عملائها وتعزيز عملياتها التجارية وزيادة قدرتها التنافسية في السوق. وفي ظل هذا التوجه والسعي الكبير لجمع البيانات وتحليلها، قد يخطر ببالنا سؤال مهم وهو هل البيانات الضخمة ضرورية بالفعل لعملنا؟ وهل تمتلك الشركات التي تستفيد من البيانات الضخمة قدرةً أكبر على تطوير الحلول المبتكرة مقارنةً بتلك التي نكتفي بالتعامل مع البيانات الصغيرة والمتوسطة الحجم أو التي تعرف باسم البيانات التقليدية؟ سنحاول في هذا المقال الإجابة على هذه التساؤلات، ونعقد مقارنةً بين البيانات الضخمة والبيانات التقليدية وحالات استخدام كل منهما. ما هي البيانات الضخمة Big Data البيانات الضخمة هي بيانات تستخدم لاستخراج وتحليل وإدارة معالجة أحجام هائلة ومعقدة من البيانات التي تنمو بتسارع. ووفقًا لمعدل أُسِّي، فهي تجمع باستمرار من مختلف المصادر وقد يصل حجمها إلى أرقام مهولة وضخمة جدًا. تتسم البيانات الضخمة بتنوع أشكالها، فهي لا تقتصر على البيانات المنظمة المخزنة في قواعد البيانات، بل تشمل أيضًا بيانات غير منظمة مثل النصوص والصور ومقاطع الفيديو. تتدفق هذه البيانات بسرعة كبيرة، لذا يصعب تسجيلها أو تخزينها أو تحليلها باستخدام أنظمة إدارة قواعد البيانات التقليدية، وهذا يستدعي منا استخدام تقنيات متقدمة للتعامل معها. تتميز البيانات الضخمة بخمس سمات رئيسية يشار لها اختصارًا بـ 5Vs، وهي: الحجم Volume: الذي يشير لكمية البيانات الإجمالية، والذي قد يصل إلى عشرات التيرابايت TeraByte السرعة Velocity: وتشير لسرعة توليد البيانات ومعالجتها وتحليلها التنوع Variety: ويشمل بيانات بأشكال مختلفة الدقة Veracity: وتحدد مدى موثوقية البيانات وصحتها وخلوها من الأخطاء القيمة Value: فتراكم البيانات وحده لا يخلق قيمة للبيانات بل يجب أن توفر هذه البيانات فائدة فعلية للعمل وإلا فلا داعي لتكديسها تُستخدم البيانات الضخمة في العديد من المجالات لتحسين العمليات واتخاذ القرارات الذكية؛ ففي الرعاية الصحية، تساعد في تقديم رعاية مخصصة واكتشاف الأمراض مبكرًا؛ أما في تجارة التجزئة، فتُستخدم لتحليل سلوك العملاء وتقديم توصيات مخصصة وتحسين إدارة المخزون؛ وفي القطاع المالي، تساهم في كشف الاحتيال وتقييم المخاطر الائتمانية؛ أما في قطاع الطاقة، فهي تُستخدم للتنبؤ بالطلب وتحسين كفاءة التوزيع والاستهلاك؛ وفي المدن الذكية، تعزز كفاءة الخدمات عبر تحليل البيانات المتعلقة بالمرور وإدارة الموارد وصيانة البنية التحتية. مصادر البيانات الضخمة قد تكون البيانات الضخمة بأشكال متنوعة ومن مصادر عديدة تشمل: البيانات المنظمة Structured Data على شكل جداول يسهل البحث فيها وتحليلها باستخدام استعلامات محددة تشبه جداول إكسل وقواعد البيانات العلاقية Relational Database، ومن الأمثلة عليها البيانات التي نجمعها من التطبيقات والمواقع الإلكترونية كسجلات المستخدمين أو سجلات المعاملات البنكية. البيانات غير المنظمة Unstructured Data من الصعب هيكلتها ضمن جداول، مثل مقاطع الفيديو والتغريدات ومنشورات وسائل التواصل الاجتماعي، أو بيانات أجهزة إنترنت الأشياء IoT، وهي تحتوي على قيمة تحليلية عالية، لكنها أصعب في التحليل وتحتاج لأساليب متقدمة لتخزين وتحليل البيانات. البيانات شبه المنظمة Semi-Structured Data وهي نوع هجين يحتوي على بعض التنظيم، ولكنه لا يتناسب تمامًا مع قواعد البيانات التقليدية كالبيانات التي تجمعها أجهزة الاستشعار ورسائل البريد الإلكتروني وملفات XML وJSON. أدوات التعامل مع البيانات الضخمة عند اعتماد البيانات الضخمة في بيئة العمل، سنحتاج لاستخدام العديد من الأدوات الاحترافية والمتخصصة للتعامل مع حجمها الهائل وتعقيدها. يمكن تقسيم هذه الأدوات إلى أربع أنواع رئيسية نوضحها فيما يلي: أدوات التخزين يجب أن تتمتع أنظمة تخزين البيانات الضخمة بالقدرة على استيعاب كميات ضخمة من البيانات مع إمكانية التوسع مع مرور الوقت وضمان سرعة نقل البيانات إلى أنظمة المعالجة والتحليل. ومن أبرز التقنيات المستخدمة في هذا المجال منصات تخزين البيانات الموزعة مثل Hadoop HDFS وAmazon S3. أدوات التنقيب في البيانات تهدف إلى استخراج أنماط ورؤى ذات قيمة من كم هائل من البيانات، مما يمكن المؤسسات من التنبؤ بالاتجاهات المستقبلية واتخاذ قرارات أدق. من بين الأدوات المستخدمة في التنقيب في البيانات Apache Spark MLlib وGoogle BigQuery، والتي تعتمد على تقنيات الذكاء الاصطناعي وتعلم الآلة. أدوات تنظيف البيانات قبل تحليل البيانات، يجب تنظيفها والتأكد من خلوها من الأخطاء وتوحيدها قبل معالجتها. تستخدم في هذه المرحلة أدوات مثل Trifacta و OpenRefine لضمان دقة البيانات وتحسين جودتها. أدوات تحليل البيانات بعد تنظيف البيانات، سنحتاج لمعالجتها وتحليلها لاستخلاص رؤى واضحة حول الأنماط والاتجاهات المستقبلية. سنحتاج لأدوات مختلفة لإجراء التحليل مثل Apache Kafka وTableau وPower BI لعرض البيانات بطريقة مرئية تدعم اتخاذ القرارات وتساعدنا على الإجابة على أسئلة معينة حول هذه البيانات وكما نلاحظ، فالعملية ليست بالسهولة التي قد تبدو عليها، إذ يتطلب التعامل مع البيانات الضخمة استخدام عدة أدوات وتقنيات، كما يحتاج لبنية تحتية قوية، ووجود خبرات بشرية متخصصة في هذا المجال، وسنواجه كذلك عدة تحديات أخرى سنناقشها في فقرة لاحقة يجب أخذها بالحسبان قبل أن نقرر جمعها والاعتماد عليها في أعمالنا. حالات استخدام البيانات الضخمة تتيح البيانات الضخمة للشركات والمؤسسات إمكانية جمع ومعالجة أحجام هائلة من البيانات في الوقت الفعلي، لكن البيانات الضخمة التي تجمعها الشركات بحد ذاتها ليس الهدف، فإن لم تكن لدى الشركة أهداف فعلية للاستفادة من هذه البيانات، فلا جدوى من جمعها وتكديسها لديها، وفيما يلي بعض حالات استخدام البيانات الضخمة المفيدة: كشف الاتجاهات والأنماط في سلوك العملاء من أجل توقع احتياجاتهم المستقبلية وتلبيتها تحليل تفضيلات العملاء وسلوكهم الشرائي لتخصيص المنتجات وفقًا لاحتياجاتهم مما يعزز رضاهم ويساهم في زيادة المبيعات المرونة والتكيف مع تغيرات السوق بسرعة والعمل على تحسين المنتجات الحالية أو تطوير منتجات جديدة اتخاذ قرارات استراتيجية في العمل وإيجاد فرص نمو جديدة تحديد نقاط الضعف في العمل واكتشاف الجوانب التي يمكن تحسينها، مثل تقليل التكاليف أو تحسين العمليات التشغيلية تقييم المخاطر والتعرف على التهديدات المحتملة في العمل بوقت مبكر وقبل أن تفع فعلًا، ووضع استراتيجيات فعالة لإدارتها والتخفيف من آثارها التنبؤ في الوقت الفعلي كأن نتوقع متى وأين سيزيد الطلب على منتج معين لتلبية احتياجات السوق وضمان وصوله للعملاء في الوقت المناسب فإذا لم تكن أولويات العمل تحتاج لتحليل سلوك العملاء، واتخاذ قرارات استراتيجية بناءً عليها، فلا داعي لتخزين البيانات بكميات ضخمة وتكبد وقت وجهد وتكلفة في تخزينها ومعالجتها. تحديات التعامل مع البيانات الضخمة ينشأ عن الاعتماد على البيانات الضخمة في العمل عدة تحديات وعوائق يجب الانتباه لها ومن أهمها: الخصوصية والأمان يمكن أن تصبح الكمية الضخمة من البيانات في المؤسسات هدفًا سهلًا للتهديدات الأمنية لاحتوائها على معلومات حساسة؛ فمع تزايد حجم البيانات، سيزداد خطر اختراقها وتسريبها، لذا يتوجب على الشركات الحفاظ على أمن بياناتها من خلال المصادقة المناسبة، والتشفير القوي والامتثال لمعايير صارمة لحمايتها. نمو البيانات بسرعة إن نمو البيانات بمعدل سريع يجعل من الصعب استخلاص الرؤى منها؛ فهناك المزيد والمزيد من البيانات التي يتم إنتاجها كل ثانية. ومن بين هذه البيانات، يجب انتقاء البيانات ذات الصلة والمفيدة فعليًا لتحليلها، كما يصعب على المؤسسات تخزين وإدارة كمية كبيرة من البيانات بدون الأدوات والتقنيات المناسبة. سرعة المعالجة بعض التطبيقات مثل أنظمة كشف الاحتيال تتطلب منا تحليل البيانات في الوقت الفعلي، وبالتالي سنحتاج لاستخدام تقنيات مخصصة، مثل Apache Flink و Spark Streaming لضمان الاستجابة الفورية بالوقت المناسب دون أي تأخير مشكلات جودة البيانات لا يمكن أن تكون البيانات الضخمة دقيقةً بنسبة 100٪، كما قد تحتوي على بيانات مكررة أو غير مكتملة؛ إضافةً إلى وجود ضوضاء وأخطاء عديدة فيها، لذا إن لم ننظف هذه البيانات ونجهز جيدًا فستؤدي إلى تحليلات غير دقيقة واستنتاجات خاطئة في العمل صعوبة دمج وتكامل البيانات تستورد فلبيانات من مصادر مختلفة وبصيغ متعددة، وقد لا تكون البيانات من مصدر معين محدثة مقارنةً مع البيانات من مصدر آخر. نقص الكفاءات المتخصصة يتطلب تحليل البيانات الضخمة مهارات تقنية متقدمة، مثل علم البيانات وتحليل البيانات، وعدد المتخصصين في هذا المجال لا يزال محدودًا لا سيما عربيًا. تحديات إدارية وتنظيمية قد تظهر بعض التحديات الإدارية والتنظيمية خلال التعامل مع الكم الهائل للبيانات الضخمة، مثل ضعف الحوكمة وعدم وجود سياسات وآليات واضحة لضبط جمع بيانات الأشخاص، وعدم وجود توضيحات كافية لطريقة استخدامها تضمن التعامل معها بأمان؛ لذا وقبل أن نقرر التعامل مع البيانات الضخمة، يجب أن تطرح على نفسنا السؤال التالي: هل نحن بحاجة فعلًا لكل هذا الكم الكبير من البيانات وكل هذه التحليلات المعقدة لنجاح عملنا، أم أن البيانات التقليدية ـأي البيانات الصغيرة والمتوسطة الحجم- وحدها تكفينا لتسيير أمور العمل بنجاح واستقرار. ما هي البيانات التقليدية؟ تشير البيانات التقليدية إلى البيانات المهيكلة التي يمكن تخزينها على هيئة جداول مكونة من أسطر وأعمدة بهيكلية واضحة ومنظمة، مثل معلومات العملاء وقوائم المخزون والسجلات المالية. وتعتمد معالجة البيانات في هذه الأنظمة على الأساليب الإحصائية التقليدية والأدوات، مثل لغة الاستعلامات الهيكلية SQL للبحث عن المعلومات واسترجاعها؛ فباستخدام هذه الأدوات، يمكن للشركات اتخاذ قرارات مفيدة وتحسين أدائها؛ وعلى الرغم من أن هذه البيانات المنظمة سهلة في التعامل، إلا أنها تكون قد محدودةً في حال احتجنا لتقديم رؤى متطورة ومعقدة تخص بعض الأعمال، مثل الحاجة لنظام مراقبة ذكي يراقب الأسواق المالية ويكشف الاحتيال في المعاملات البنكية فورًا. أدوات التعامل مع البيانات التقليدية البيانات التقليدية أسهل في التعامل من البيانات الضخمة لأنها غالبًا ما تكون صغيرة الحجم ومنظمةً جيدًا ومن أبرز أدوات البيانات التقليدية نذكر: قواعد البيانات العلائقية RDBM مثل MySQL و Oracle و SQL Server، والتي تستخدم لتخزين البيانات في جداول مترابطة، وهي مثالية للبيانات المنظمة والمهيكلة جداول البيانات Spreadsheets مثل إكسل Excel أو جداول جوجل Google Sheets لتحليل البيانات الصغيرة والمتوسطة خاصة عندما تكون البيانات محدودة ومنظمة برامج إدارة البيانات مثل SQLite التي تستخدم لتخزين وإدارة البيانات الصغيرة وتوفر لنا القدرة على إجراء بعض العمليات البسيطة على البيانات حالات استخدام البيانات التقليدية إذا كانت البيانات التي نجمعها في العمل هي بيانات منظمة مثل قواعد البيانات الجاهزة، فإن الحلول التقليدية مثل قواعد البيانات العلائقية غالبا ستكون كافيةً لإدارتها وتحليلها ولا داعي لأن نرهق أنفسنا بتعقيدات البيانات الضخمة. وفيما يلي بعض الحالات التي تعد فيها البيانات التقليدية هي الأنسب للاستخدام: إدارة السجلات المالية ضمن نظام يتعامل مع كمية محدودة من بيانات الفواتير والمدفوعات ويصدر تقارير مالية منظمة إدارة المخزون في متاجر صغيرة أو متوسطة تتضمن بيانات عن المنتجات والمبيعات ومستويات المخزون وتسعى لتحسين استراتيجيات المبيعات باستخدام قواعد بيانات تقليدية الرعاية الصحية ضمن مستشفيات تنظم وتدير معلومات المرضى، مع تتبع تاريخ المرضى ونتائج فحوصاتهم وتتابع خطط علاجهم وترقب التقدم في حالتهم الصحية إدارة الموظفين في الشركات الصغيرة والمتوسط باستخدام أنظمة تقليدية لتخزين المعلومات الشخصية للموظفين وجداول حضورهم وأدائهم إدارة المنصات التعليمية كالمدارس والمعاهد التعليمية التي تحتاج لإدارة بيانات الطلاب وتخزين درجاتهم وتتبع حضورهم ومستوياتهم الدراسية في كل هذه الحالات والحالات المشابهة، سيكون حجم البيانات محدودًا نسبيًا، أو ستكون البيانات منظمةً جيدًا، مما يسهّل معالجتها باستخدام الأنظمة التقليدية دون الحاجة إلى تقنيات معقدة كتلك التي تتطلبها البيانات الضخمة؛ أما في حال زاد تعقيد البيانات سواءً من حيث الحجم أو السرعة أو تنوع المصادر وكانت هناك حاجة لتحليلها بدقة واتخاذ قرارات بناءً عليها بسرعة، فعندها سيكون اللجوء إلى تقنيات البيانات الضخمة أمرًا ضروريًا. مميزات التعامل مع البيانات التقليدية يتسم التعامل مع البيانات التقليدية بما يلي: السهولة يمكن التعامل مع البيانات المعالجة باستخدام الأساليب التقليدية بسهولة باستخدام الأدوات القياسية، مما يجعلها أكثر سهولةً في التعامل ولا تتطلب معرفةً تقنيةً متقدمة. الوصول السريع للبيانات تقدم قواعد البيانات التقليدية وصولًا سريعًا وموثوقًا إلى البيانات من خلال العمل المستقل على خادم أو حاسوب محلي لا على بيئات سحابية أو خارجية، وتتجاوز مشكلات تأخير الشبكة أو انقطاع الخدمة أو اختراقات الأمان. سهولة حماية البيانات تُعَد البيانات الصغيرة والمتوسطة أسهل في تأمينها وحمايتها نظرًا لصغر حجمها وعدم اعتمادها على الهيكلية الموزعة، خاصةً وأنها لا تعتمد في أغلب الأحيان على التخزين الخارجي، مما يجعلها مناسبةً لإدارة المعلومات الحساسة أو السرية. سهولة إدارة البيانات تقدم قواعد البيانات التقليدية للمستخدمين تحكمًا كبيرًا في إدارة البيانات، حيث يمكن للمستخدمين تعريف أنواع البيانات، وتحديد القواعد بينها، وإنشاء العلاقات المخصصة وفقًا لاحتياجاتهم. تتطلب تكاليف وموارد أقل تتطلب الأساليب التقليدية تكاليف أقل وموارد أقل مقارنة بأنظمة معالجة البيانات الضخمة التي تتطلب تكاليف وموارد ضخمة. الخلاصة أصبحت البيانات الضخمة بلا شك جزءًا أساسيًا من العمليات التجارية والخدمات الحكومية، نظرًا للنمو السريع في حجم البيانات الرقمية. ومع تطور تقنيات الذكاء الاصطناعي، أتاح ذلك للمؤسسات القدرة على استخراج رؤى أكثر دقة واتخاذ قرارات أكثر ذكاءً وتحسينات كبيرة في مختلف القطاعات؛ لكن مع ذلك، ينبغي أن نتذكر أن البيانات الضخمة ليست مجرد جمع كميات هائلة من المعلومات، بل هي عملية تتطلب استراتيجيات مدروسة، وأدوات متخصصة، وتخطيطًا دقيقًا لتحقيق أقصى استفادة منها وتحويلها إلى قرارات تدعم النجاح والنمو. مع ذلك، وبالرغم من هذا التطور الكبير في مجال البيانات الضخمة، لازالت البيانات التقليدية الصغيرة والمتوسطة كافية للعديد من الحالات إن لم نقل أغلبها نظرًا لتوفير هذا النوع من البيانات حلولًا مناسبةً سهلة التعامل وبتكاليف أقل. لذا، متى لم يكن ما نعمل عليه يتطلب بيانات ضخمة للحصول على تحليلات وفهم كاف، فلا حاجة لأن نلجأ في أعمالنا لحلول البيانات الضخمة، لاسيما أنها تحتاج لخبرة وتكاليف أكبر وتعقيدات تقنية للتعامل مع البيانات وتحليلها وإدارتها، خاصةً وأننا إذا لم نحسن التعامل مع هذه البيانات ونستخدمها بطريقة صحيحة، فلن نتمكن من فهمها أو اتخاذ قرارات صائبة بناءً عليها. المصادر ?What is Big Data When to Use Big Data — and When Not To 7 Pros and Cons of Big Data Difference Between Traditional Data and Big Data Big Data Analytics Versus Traditional Data Analytics: A Comprehensive Overview اقرأ أيضًا مقدمة إلى مفهوم البيانات الضخمة Big Data المفاهيم الأساسية لتعلم الآلة أساسيات الذكاء الاصطناعي: دليل المبتدئين تعلم الذكاء الاصطناعي
- 1 تعليق
-
- 1
-
-
سنوضّح في هذا المقال من سلسلة دليل جودو كيفية إطلاق المقذوفات وترتيب عرض الكائنات بناءً على موقعها على محور Y في الألعاب ثنائية الأبعاد في محرك الألعاب الشهير جودو Godot. إطلاق المقذوفات من أجل إطلاق مقذوفات، سنحتاج بطبيعة الحال إلى القيام بعدة خطوات تسمح لنا بتنفيذ العملية بنجاح. سنوضّح فيما يلي كيفية إطلاق المقذوفات من اللاعب أو الشخصية المتوحشة وغير ذلك في الألعاب ثنائية الأبعاد. إعداد الرصاصة سنضبط أولًا كائن الرصاصة Bullet الذي يمكننا إنشاء نسخة منه، وفيما يلي العقد التي سنستخدمها: Area2D: Bullet Sprite2D CollisionShape2D يمكن استخدام أي صورة نريدها بالنسبة لخامة العقدة Sprite2D كما هو الحال في المثال التالي: يمكن الآن إعداد العقد وضبط الشخصية الرسومية Sprite وشكل التصادم. إذا كانت الخامة الخاصة بنا موجهةً نحو الأعلى كما هو الحال في المثال السابق، فسنتأكد من تدوير العقدة Sprite بمقدار 90 درجة بحيث تشير إلى اليمين. سنتأكد أيضًا من أنها تتطابق مع الاتجاه الأمامي للعقدة الأب. سنضيف الآن سكربت اتصل من خلاله بالإشارة body_entered الخاصة بالعقدة Area2D كما يلي: extends Area2D var speed = 750 func _physics_process(delta): position += transform.x * speed * delta func _on_Bullet_body_entered(body): if body.is_in_group("mobs"): body.queue_free() queue_free() سنزيل الرصاصة في مثالنا إذا أصابت شيئًا ما، وسنحذف أيّ شيء نشير إليه في مجموعة المتوحشين "mobs"، والذي تصيبه الرصاصة. إطلاق النار يجب إعداد موقع ظهور الرصاصات، لذا من المهم إضافة العقدة Marker2D ووضعها في المكان الذي نريد ظهور الرصاصات فيه، حيث وضعناه عند فوهة البندقية مثلًا، وسميناه بالفوهة "Muzzle": نلاحظ بقاء التحويل transform الخاص بالفوهة موجَّهًا مع اتجاه البندقية نفسه عند دوران اللاعب، وسيكون ذلك مناسبًا عند ظهور الرصاصات، حيث يمكن استخدام هذا التحويل للحصول على الموضع والاتجاه المناسبين. سلنضبط الآن التحويل transform الجديد الخاص بالرصاصة ليكون مساويًا لتحويل الفوهة. ملاحظة: ستنجح هذه الطريقة مع أيّ نوع من الشخصيات، وليس فقط مع أسلوب الدوران والتحريك الموضّح في هذا المقال، فما علينا سوى إرفاق العقدة Marker2D حيث نريد ظهور الرصاصات. سنضيف الآن متغيرًا في سكربت الشخصية للاحتفاظ بمشهد الرصاصة لإنشاء نسخة منه كما يلي: @export var Bullet : PackedScene وتحقق أيضًا من إجراء الإدخال المُعرَّف كما يلي: if Input.is_action_just_pressed("shoot"): shoot() يمكننا الآن إنشاء نسخة من الرصاصة وإضافتها إلى الشجرة في الدالة shoot()، ولكن تُعَد إضافة الرصاصة كابن للاعب من الأخطاء الشائعة: func shoot(): var b = Bullet.instantiate() add_child(b) b.transform = $Muzzle.transform تكمن المشكلة في تأثّر الرصاصات عندما يتحرك اللاعب أو يدور لأنها أبناء للاعب. يمكن إصلاح هذه المشكلة من خلال التأكد من إضافة الرصاصات إلى المستوى العالمي بدلًا من ذلك، حيث سنستخدم owner في هذه الحالة، والذي يشير إلى العقدة الجذر للمشهد الذي يتواجد فيه اللاعب. وكما نلاحظ، سنحتاج أيضًا إلى استخدام التحويل العام الخاص بالفوهة، وإلّا لن تكون الرصاصة موجودةً في المكان الذي نتوقعه. ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github. ترتيب عرض الكائنات بناء على موقعها على محور Y تستخدم العديد من الألعاب ثنائية الأبعاد منظور 3/4، مما يعطي الانطباع بأن الكاميرا تنظر إلى العالم من زاوية معينة، لذا يجب عرض الكائنات الأبعد خلف الكائنات الأقرب، ويعني ذلك عمليًا أننا نريد ترتيب عرضها بناءً على موقعها على محور Y، مما يجعل ترتيب الرسوم مرتبطًا بإحداثيات الكائن على المحور Y، وكلما كان الكائن أعلى على الشاشة، سيكون أبعد؛ وبالتالي سيكون أخفض وفق ترتيب التصيير Render؛ وفيما يلي مثال لهذه المشكلة: تُرسَم هذه الكائنات وفقًا لترتيب التصيير الافتراضي، والذي هو ترتيب الشجرة، حيث يكون ترتيبها في شجرة المشهد كما يلي: يحتوي جودو Godot على خيارٍ مُضمَّن لتغيير ترتيب التصيير، حيث يمكننا تفعيل الخاصية Y Sort Enabled مع أيّ عقدة CanvasItem مثل Node2D أو Control، ثم يمكن فرز جميع العقد الأبناء بناءً على موقعها على محور Y. يمكن تفعيل هذه الخاصية مع عقدة TileMap في المثال السابق، ولكن ستبقى المشكلة موجودةً كما يلي: يعتمد ترتيب الرسم على إحداثيات المحور Y لكل كائن، حيث يكون مركز الكائن افتراضيًا كما هو موضح في الشكل التالي: نريد الآن إعطاء انطباع بأن الكائنات موجودة على الأرض، لذا يمكن حل هذه المشكلة من خلال إزاحة الشخصية الرسومية Sprite لكل كائن بحيث يحاذي موضعُ position الكائن أسفلَ الشخصية الرسومية كما يلي: وبهذا تكون الأمور قد أصبحت أفضل بكثير كما يلي: ملاحظة: يمكن تنزيل شيفرة المشروع البرمجية من Github. ترجمة -وبتصرّف- للقسمين Shooting projectiles و Using Y-Sort من توثيقات Kidscancode. اقرأ أيضًا المقال السابق: إنشاء وحدة تحكم واقعية للسيارات في ألعاب الفيديو ثنائية الأبعاد آلية الالتفاف حول الشاشة وتحريك الشخصيات في الألعاب ثنائية الأبعاد إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد
-
سنوضّح في هذا المقال كيفية إنشاء وحدة تحكم للسيارات في الألعاب ثنائية الأبعاد من الأعلى إلى الأسفل. قد لا يتمكّن المبتدئون من إنشاء شيءٍ يتعامل مع لعبةٍ مشابهة لسيارة حقيقية، لذا سنذكر بعض الأخطاء الشائعة التي قد تظهر في ألعاب السيارات للهواة: لا تدور السيارة حول مركزها، إذ لا تنزلق Slide عجلات السيارة الخلفية من جانب إلى آخر إلّا في حالة الانجراف Drifting، وسنتحدث عن ذلك لاحقًا لا يمكن للسيارة أن تستدير إلا عندما تتحرك، إذ لا يمكنها الدوران في مكانها السيارة ليست قطارًا، فهي ليست على قضبان سكة حديدية، لذا يجب أن تتضمن الاستدارة بسرعات عالية بعض الانزلاق أو الانجراف توجد العديد من الأساليب لفيزياء السيارات ثنائية الأبعاد، وتعتمد بصفة أساسية على مدى الواقعية التي نريد أن تكون عليها، لذا نريد الوصول في هذا المقال إلى مستوى معين من الواقعية. ملاحظة: تعتمد الطريقة التي سنوضّحها فيما يلي على الخوارزمية الموجودة في مقال فيزياء توجيه السيارات في الألعاب ثنائية الأبعاد البسيطة. تُقسَم الطريقة المتبعة فيما يلي إلى 5 أجزاء، حيث يضيف كل جزء منها ميزة مختلفة لحركة السيارة، لذا يمكن المزج بينها وفقًا لاحتياجاتنا. إعداد المشهد فيما يلي إعداد لمشهد السيارة: CharacterBody2D Sprite2D CollisionShape2D Camera2D سنضيف أي خامة شخصية رسومية Sprite Texture نريدها، حيث سنستخدم في مثالنا حزمة السباق من موقع Kenney. تُعَد العقدة CapsuleShape2D خيارًا جيدًا للتصادم، بحيث لا تكون للسيارة زوايا حادة قد تتسبب في تعطلها بسبب العوائق. سنستخدم أيضًا أربعة إجراءات إدخال هي: steer_right و steer_left و accelerate و brake، لذا يمكننا ضبطها على أيّ مفاتيح إدخال نفضلها. الجزء الأول: الحركة تتمثل الخطوة الأولى في برمجة الحركة بناءً على الخوارزمية السابقة، لذا سنبدأ ببعض المتغيرات كما يلي: extends CharacterBody2D var wheel_base = 70 # المسافة من العجلة الأمامية إلى العجلة الخلفية var steering_angle = 15 # مقدار دوران العجلة الأمامية بالدرجات var steer_direction نضبط المتغير wheelbase على قيمة تتوافق مع شخصيتنا الرسومية، ويمثّل المتغير steer_direction مقدار دوران العجلات. ملاحظة: نستخدم في مثالنا عناصر تحكم لوحة المفاتيح، لذا سيحدث الدوران أو لن يحدث على الإطلاق، ولكن إذا كنا تستخدم عصا تحكم للعب، فيمكننا بدلًا من ذلك تغيير هذه القيمة بناءً على المسافة التي تتحركها العصا. func _physics_process(delta): get_input() calculate_steering(delta) move_and_slide() يجب التحقق من الإدخال وحساب التوجيه Steering في كل إطار، ثم نمرّر السرعة velocity الناتجة إلى الدالة move_and_slide()، ونعرّف الدالتين التاليتين كما يلي: func get_input(): var turn = Input.get_axis("steer_left", "steer_right") steer_direction = turn * deg_to_rad(steering_angle) velocity = Vector2.ZERO if Input.is_action_pressed("accelerate"): velocity = transform.x * 500 سنتحقق من إدخال المستخدم ونضبط السرعة. وكما نلاحظ فقيمة السرعة 500 مؤقتة حتى نتمكّن من اختبار الحركة، وسنعالجها في الجزء التالي. سننفّذ بعد ذلك الخوارزمية السابقة كما يلي: func calculate_steering(delta): # 1. العثور على مواضع العجلات var rear_wheel = position - transform.x * wheel_base / 2.0 var front_wheel = position + transform.x * wheel_base / 2.0 # 2. تحريك العجلات للأمام rear_wheel += velocity * delta front_wheel += velocity.rotated(steer_direction) * delta # 3. العثور على متجه الاتجاه الجديد var new_heading = rear_wheel.direction_to(front_wheel) # 4. ضبط السرعة والدوران على الاتجاه الجديد velocity = new_heading * velocity.length() rotation = new_heading.angle() سنشغّل الآن المشروع ويجب أن تتحرك وتدور السيارة، ولكن لا تزال الحركة غير طبيعية؛ إذ ستبدأ السيارة بالحركة وتتوقف مباشرةً، ويمكن إصلاح ذلك من خلال إضافة التسارع إلى العملية الحسابية. الجزء الثاني: التسارع Acceleration سنحتاج إلى متغير إعدادٍ آخر ومتغيرًا لتتبّع التسارع الكلي للسيارة كما يلي: var engine_power = 900 # قوة التسارع للأمام var acceleration = Vector2.ZERO علينا الآن التعديل شيفرة الإدخال البرمجية لتطبيق التسارع بدلًا من تغيير سرعة velocity السيارة مباشرةً كما يلي: func get_input(): var turn = Input.get_axis("steer_left", "steer_right") steer_direction = turn * deg_to_rad(steering_angle) if Input.is_action_pressed("accelerate"): acceleration = transform.x * engine_power يمكن تطبيق التسارع على السرعة بعد الحصول عليه كما يلي: func _physics_process(delta): acceleration = Vector2.ZERO get_input() calculate_steering(delta) velocity += acceleration * delta move_and_slide() يجب الآن أن تزيد السيارة من سرعتها تدريجيًا عند تشغليها، ولكن ليس لدينا أيّ طريقة لإبطاء السرعة بعد. الجزء الثالث: الاحتكاك Friction/السحب Drag تتعرض السيارة لقوتين مختلفتين لإبطاء السرعة هما: الاحتكاك والسحب. الاحتكاك هو القوة التي تطبقها الأرض، وتكون مرتفعةً جدًا عند القيادة على الرمال، ومنخفضةً جدًا عند القيادة على الجليد، ويتناسب الاحتكاك مع السرعة، فكلما زادت السرعة، زادت هذه القوة؛ أما السحب، فهو القوة الناتجة عن مقاومة الرياح، ويعتمد على المقطع العرضي للسيارة، إذ تتعرض الشاحنة الكبيرة أو الشاحنة الصغيرة لسحب أكبر من سيارة السباق، ويتناسب السحب مع مربع السرعة. يكون الاحتكاك ملحوظًا عند التحرك ببطء، ولكن يغلب السحب عند السرعات العالية. سنضيف كلتا هاتين القوتين إلى العمليات الحسابية الخاصة بنا. ستمنح قيم هاتين القوتين سيارتنا أقصى سرعة، وهي النقطة التي لا تستطيع فيها قوة المحرّك التغلب على قوة السحب. سنوضح فيما يلي القيم الابتدائية لهاتين الكميتين: var friction = -55 var drag = -0.06 تعني هذه القيم أن قوة السحب تتغلب على قوة الاحتكاك عند السرعة 600 كما نرى في هذا الرسم البياني: ملاحظة: يمكن تعديل هذه القيم لمعرفة كيفية تغيرها. سنستدعي في الدالة _physics_process() دالةً لحساب الاحتكاك الحالي ونطبقّه على قوة التسارع كما يلي: func _physics_process(delta): acceleration = Vector2.ZERO get_input() apply_friction(delta) calculate_steering(delta) velocity += acceleration * delta velocity = move_and_slide(velocity) func apply_friction(delta): if acceleration == Vector2.ZERO and velocity.length() < 50: velocity = Vector2.ZERO var friction_force = velocity * friction * delta var drag_force = velocity * velocity.length() * drag * delta acceleration += drag_force + friction_force سنحدد أولًا السرعة الدّنيا، مما يضمن عدم استمرار السيارة في التحرك للأمام بسرعات منخفضة للغاية، لأن الاحتكاك لا يؤدي إلى خفض السرعة إلى الصفر أبدًا؛ ونحسب بعد ذلك القوتين ونضيفهما إلى التسارع الكلي، وستؤثران على السيارة في الاتجاه المعاكس لأن قيمتهما سالبة. 02_car_friction.webm الجزء الرابع: الرجوع للخلف/الفرامل Brake نحتاج إلى متغيرين آخرين للإعدادات: var braking = -450 var max_speed_reverse = 250 سنضيف الآن الدخل إلى الدالة get_input() كما يلي: if Input.is_action_pressed("brake"): acceleration = transform.x * braking يُعَد ذلك جيدًا للتوقف، ولكننا نريد أيضًا أن نتمكن من وضع السيارة في وضع الرجوع للخلف، ولكن ذلك لن ينجح حاليًا، لأن التسارع يُطبَّق في اتجاه التوجّه دائمًا الذي هو إلى الأمام، لذا نحتاج إلى تسارع عكسي عند الرجوع للخلف. func calculate_steering(delta): var rear_wheel = position - transform.x * wheel_base / 2.0 var front_wheel = position + transform.x * wheel_base / 2.0 rear_wheel += velocity * delta front_wheel += velocity.rotated(steer_angle) * delta var new_heading = (front_wheel - rear_wheel).normalized() var d = new_heading.dot(velocity.normalized()) if d > 0: velocity = new_heading * velocity.length() if d < 0: velocity = -new_heading * min(velocity.length(), max_speed_reverse) rotation = new_heading.angle() يمكننا معرفة ما إذا كنا نتسارع للأمام أم للخلف باستخدام حاصل الضرب النقطي Dot Product، حيث إذا كان المتجهان متحاذيين، فستكون النتيجة أكبر من 0، وإذا كانت الحركة في الاتجاه المعاكس لاتجاه السيارة، فسيكون حاصل الضرب النقطي أقل من 0 ويجب أن نتحرك للخلف. 03_car_reverse.webm الجزء الخامس: الانجراف Drift والانزلاق Slide يمكن التوقف عند هذه النقطة وسنحظى بتجربة قيادة جيدة، ولكن ستبقى السيارة أشبه بأنها تسير على قضبان سكة حديدية، إذ تكون المنعطفات مثالية حتى عند السرعة القصوى، وكأنّ لإطارات السيارة تحكّم مثالي. لحل المشكلة، يجب أن تتسبب قوة الانعطاف عند السرعات العالية أو حتى المنخفضة إذا رغبنا في ذلك، في انزلاق الإطارات مما يؤدي إلى حركة انزلاقية، وسنفعل ذلك الآتي: var slip_speed = 400 # السرعة عند تقليل الشدّ Traction var traction_fast = 2.5 # الشد عالي السرعة var traction_slow = 10 # الشد منخفض السرعة سنطبّق هذه القيم عند حساب التوجيه، إذ تُضبَط السرعة مباشرةً على الاتجاه الجديد حاليًا، ولكننا سنستخدم بدلًا من ذلك الاستيفاء باستخدام الدالة lerp() لجعلها تدور جزئيًا نحو الاتجاه الجديد فقط، وستحدّد قيم الشدّ Traction مدى تماسك الإطارات. func calculate_steering(delta): var rear_wheel = position - transform.x * wheel_base / 2.0 var front_wheel = position + transform.x * wheel_base / 2.0 rear_wheel += velocity * delta front_wheel += velocity.rotated(steer_angle) * delta var new_heading = (front_wheel - rear_wheel).normalized() # اختر قيمة الشد التي تريد استخدامها، ويجب أن يكون الانزلاق منخفضًا عند السرعات المنخفضة var traction = traction_slow if velocity.length() > slip_speed: traction = traction_fast var d = new_heading.dot(velocity.normalized()) if d > 0: velocity = lerp(velocity, new_heading * velocity.length(), traction * delta) if d < 0: velocity = -new_heading * min(velocity.length(), max_speed_reverse) rotation = new_heading.angle() نختار قيمة الشد التي نريد استخدامها ونطبق الدالة lerp() على السرعة velocity. 04_car_drift.webm التعديلات Adjustments لدينا في هذه المرحلة عدد كبير من الإعدادات التي تتحكّم في سلوك السيارة، ويمكن أن يؤدي تعديلها إلى تغيير جذري في كيفية قيادة السيارة. سننزّل المشروع الخاص بهذا المقال لتسهيل تجربة قيم مختلفة. سنرى عند تشغيل اللعبة مجموعةً من أشرطة التمرير التي يمكن استخدامها لتغيير سلوك السيارة أثناء القيادة، ويمكن الضغط على <Tab> لإظهار أو إخفاء لوحة أشرطة التمرير. الخاتمة بهذا نكون قد وصلنا لنهاية مقالنا الذي استعرضنا فيه خطوات إنشاء وحدة تحكم لسيارات الألعاب ثنائية الأبعاد بأسلوب واقعي بدءًا من الحركة الأساسية، مرورًا بالتسارع والفرملة، وصولًا إلى الانجراف والانزلاق. يمكن اعتبار هذا المقال بمثابة أساس لتطوير تجربة قيادة أكثر ديناميكية وواقعية في الألعاب. وللتذكير، يمكن تنزيل شيفرة المشروع البرمجية كاملة من Github. ترجمة -وبتصرّف- للقسم Car steering من توثيقات Kidscancode. اقرأ أيضًا المقال السابق: آلية الالتفاف حول الشاشة وتحريك الشخصيات في الألعاب ثنائية الأبعاد إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد دليلك الشامل إلى برمجة الألعاب ألعاب الفيديو: تطورها وأهميتها وخطوات برمجتها
-
سنوضّح في هذا المقال كيفية الالتفاف حول الشاشة وبرمجة الحركة من الأعلى إلى الأسفل وتحريك الشخصيات بالاعتماد على الشبكة 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. اقرأ أيضًا المقال السابق: إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد فهم RayCast2D واستخداماتها في محرك ألعاب جودو سحب وإفلات جسم صلب RigidBody2D في جودو إنشاء شخصيات ثلاثية الأبعاد في جودو Godot برمجة لعبة متاهة باستخدام محرك يونيتي Unity
-
سنكتشف في هذا المقال متى يدخل أو يخرج كائن من الشاشة، وسنتعرّف على كيفية إنشاء وتحريك شخصية في ألعاب المنصات ثنائية الأبعاد. إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد سننشئ الأن شخصيةً في ألعاب المنصات ثنائية الأبعاد. يُفاجَأ المطورون المبتدئون في أغلب الأحيان بمدى تعقيد برمجة شخصيات ألعاب المنصات، لذا يوفر محرّك ألعاب جودو Godot بعض الأدوات المُضمَّنة للمساعدة، ولكن يمكن القول بأن هناك عددًا كبيرًا من الحلول التي تضاهي عدد الألعاب الموجودة. لن نتعمق في شرح الميزات مثل ميزات القفزات المزدوجة أو الانحناء أو القفز على الحائط أو الرسوم المتحركة، بل سنناقش أساسيات الحركة في ألعاب المنصات. ملاحظة: يمكن استخدام العقدة RigidBody2D لإنشاء شخصية لألعاب المنصات، ولكننا سنركز على استخدام العقدة CharacterBody2D، إذ تُعَد الأجسام الحركية Kinematic مناسبةً لألعاب المنصات، حيث لن تكون مهتمًا كثيرًا بالفيزياء الواقعية بقدر الاهتمام بالشعور المتجاوب في اللعبة. سنبدأ بعقدة CharacterBody2D، ونضيف إليها عقدتي Sprite2D و CollisionShape2D، ثم نرفق السكربت التالي بالعقدة الجذر للشخصية: extends CharacterBody2D @export var speed = 1200 @export var jump_speed = -1800 @export var gravity = 4000 func _physics_process(delta): # إضافة الجاذبية في كل إطار velocity.y += gravity * delta # يؤثر الدخل على المحور x فقط velocity.x = Input.get_axis("walk_left", "walk_right") * speed move_and_slide() # السماح بالقفز عند وجود الشخصية على الأرض فقط if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y = jump_speed يمكننا ملاحظة أننا نستخدم إجراءات الإدخال التي عرّفناها في خريطة الإدخال InputMap وهي: "walk_right" و "walk_left" و "jump"، لذا يمكن الاطلاع على إجراءات الإدخال InputActions لمزيد من التفاصيل. تعتمد القيم المُستخدَمة للسرعة speed والجاذبية gravity وسرعة القفز jump_speed اعتمادًا كبيرًا على حجم الشخصية الرسومية Sprite الخاصة باللاعب، وتكون خامة Texture اللاعب 108x208 بكسل في هذا المثال، وإذا كانت الصورة أصغر حجمًا، فستستخدم قيمًا أصغر. نريد أيضًا قيمًا عالية للشعور بالسرعة والاستجابة، إذ تؤدي الجاذبية المنخفضة إلى لعبة نشعر فيها بعدم التوازن؛ بينما تمثل القيمة العالية العودة إلى الأرض بسرعة والجاهزية للقفز مرةً أخرى. سنتحقق من التابع is_on_floor() بعد استخدام الدالة move_and_slide() التي تضبط قيمة هذا التابع، لذا يجب عدم التحقق منه قبل ذلك، وإلّا سنحصل على القيمة من الإطار السابق. الاحتكاك Friction والتسارع Acceleration تُعَد الشيفرة البرمجية السابقة بداية جيدة، إذ يمكننا استخدامها كأساس لمجموعة متنوعة من متحكمات المنصات، ولكنها تواجه مشكلة الحركة اللحظية Instantaneous Movement، ولكن يمكننا الحصول على شعور طبيعي أكثر من خلال تسارع الشخصية مثلًا حتى تصل إلى سرعتها القصوى، ثم تتوقف عند عدم وجود دخل. إحدى الطرق لإضافة هذا السلوك هي استخدام الاستيفاء الخطي Linear Interpolation أو "lerp"، بحيث ننتقل باستخدام الاستيفاء الخطي من السرعة الحالية إلى السرعة القصوى عند التحرك، وننتقل باستخدام الاستيفاء الخطي من السرعة الحالية إلى الصفر أثناء التوقف، وبالتالي سيوفر تعديل مقدار الاستيفاء الخطي مجموعة متنوعة من أنماط الحركة. ملاحظة: يمكن الاطلاع على مقال الاستيفاء للحصول على مزيد من المعلومات عن الاستيفاء الخطي. extends CharacterBody2D @export var speed = 1200 @export var jump_speed = -1800 @export var gravity = 4000 @export_range(0.0, 1.0) var friction = 0.1 @export_range(0.0 , 1.0) var acceleration = 0.25 func _physics_process(delta): velocity.y += gravity * delta var dir = Input.get_axis("walk_left", "walk_right") if dir != 0: velocity.x = lerp(velocity.x, dir * speed, acceleration) else: velocity.x = lerp(velocity.x, 0.0, friction) move_and_slide() if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y = jump_speed سنجرب هنا تغيير قيم الاحتكاك friction والتسارع acceleration لمعرفة مدى تأثيرها على شعورنا أثناء اللعب. فقد يحتاج مستوى الجليد إلى قيم منخفضة جدًا مثلًا، مما يجعل الحركة أصعب. تمنحنا هذه الشيفرة البرمجية نقطة بداية لبناء متحكم منصات خاص بك، لذا يمكن الاطلاع على المقالات اللاحقة لمزيد من ميزات المنصات المتقدمة مثل القفز على الحائط. تحديد متى يدخل كائن إلى الشاشة أو يغادرها يوفر محرّك الألعاب العقدة VisibleOnScreenNotifier2D، حيث إذا أرفقنا هذه العقدة بالكائن، فسنتمكّن من استخدام إشارات screen_entered و screen_exited الخاصة بها. تطبيق عملي 1 ليكن لدينا مثلًا مقذوف يتحرك في خط مستقيم بعد إطلاقه، وإذا استمرينا في الإطلاق، فسنحصل في النهاية على عدد كبير من الكائنات التي يتعقّبها المحرّك، حتى وإن كانت هذه الكائنات خارج الشاشة، مما قد يتسبب في حدوث تأخير. توضح الشيفرة البرمجية التالية حركة المقذوف: extends Area2D var velocity = Vector2(500, 0) func _process(delta): position += velocity * delta يمكن حذف المقذوف تلقائيًا عند تحرّكه خارج الشاشة من خلال إضافة العقدة VisibleOnScreenNotifier2D والاتصال بإشارة screen_exited الخاصة بها. func _on_VisibleOnScreenNotifier2D_screen_exited(): queue_free() تطبيق عملي 2 ليكن لدينا مثلًا عدو ينفّذ بعض الإجراءات مثل التحرك على طول مسار أو تشغيل رسوم متحركة، ولن يظهر على الشاشة سوى عدد قليل من الأعداء في الوقت نفسه في خريطة كبيرة تحتوي على العديد من الأعداء، إذ يمكننا تعطيل إجراءات العدو أثناء وجوده خارج الشاشة باستخدام العقدة VisibleOnScreenNotifier2D كما هو الحال في جزء الشيفرة البرمجية التالي: var active = false func _process(delta): if active: play_animation() move() func _on_VisibleOnScreenNotifier2D_screen_entered(): active = true func _on_VisibleOnScreenNotifier2D_screen_exited(): active = false الخاتمة استعرضنا في هذا المقال كيفية استخدام العقدة VisibleOnScreenNotifier2D لتحديد متى يدخل كائن إلى الشاشة أو يغادرها، مما يساعد في تحسين أداء اللعبة من خلال إدارة الكائنات غير المرئية بكفاءة؛ كما تناولنا أساسيات إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد باستخدام العقدة CharacterBody2D، مع التركيز على مفاهيم مثل الجاذبية، القفز، والتسارع للحصول على حركة سلسة واستجابة طبيعية. يمكن تنزيل شيفرة المشروع البرمجية من Github للاطلاع عليه وفهمه أكثر. ترجمة -وبتصرّف- للقسمين Entering/Exiting the screen و Platform character من توثيقات Kidscancode. اقرأ أيضًا فهم RayCast2D واستخداماتها في محرك ألعاب جودو سحب وإفلات جسم صلب RigidBody2D في جودو إنشاء شخصيات ثلاثية الأبعاد في جودو Godot دليلك الشامل إلى برمجة الألعاب ألعاب الفيديو: تطورها وأهميتها وخطوات برمجتها برمجة لعبة متاهة باستخدام محرك يونيتي Unity
-
عندما نعمل على تطبيق لارافيل Laravel، قد نحتاج لتكرار مهام معينة خاصة بالتطبيق. وبدلاً من أن نكتب كل هذه الأوامر يدويًا في كل مرة يمكننا أتمتتها باستخدام أداة Artisan المدمجة مع إطار عمل لارافيل، فهذه الأداة تتيح لنا تنفيذ أوامر جاهزة مثل إنشاء جداول قواعد البيانات، أو تشغيل خادم التطوير المحلي، كما تمكننا من تنفيذ أوامر جديدة خاصة بنا كإرسال بريد لمستخدم، أو تحديث بيانات معينة سنوضح في هذا المقال كيفية إنشاء أمر Artisan جديد في إصدار لارافيل 11، سنبدأ بالتعرف على أساسيات Artisan والأوامر التي توفرها، ثم ننتقل بعد ذلك لشرح طريقة إنشاء وتشغيل أوامرنا المخصصة. نظرة عامة على Artisan Artisan هو أداة سطر الأوامر مدمجة مع إطار العمل لارافيل توفر مجموعة من الأوامر المفيدة التي يمكن أن تساعدنا أثناء بناء التطبيقات، مثل إنشاء عمليات التهجير Migrations، ونشر موارد الحزم. هنالك عدة أوامر مدمجة مع Artisan ويمكن عرض قائمة بهذه الأوامر من خلال كتابة أمر php artisan list في طرفية Artisan كما في الصورة التالية: تساعدنا هذه الأوامر على العمل بكفاءة أكبر، حيث سنتمكن على سبيل المثال من إنشاء وظائف الاستيثاق Authentication، والمتحكمات Controllers، والنماذج Models، وإنشاء عمليات التهجير Migrations وغيرها من الوظائف المتقدمة بسهولة وخلال وقت أقل. أساسيات أوامر Laravel Artisan يُعَد الصنف Illuminate\Console\Application أساسًا في Artisan فهو الذي يحدد كيفية التعامل مع الأوامر، كما أنه يوسّع الصنف Symfony\Component\Console\Application، وهذا يسهّل على مطوري إطار عمل سيمفوني Symfony التعامل مع Artisan لأنهم سيجدون بيئة عمل مألوفة. فيما يلي مثال على تعريف أمر بسيط: namespace App\Console\Commands; use Illuminate\Console\Command; class ExampleCommand extends Command { protected $signature = 'example:run {name}'; protected $description = 'Runs an example command'; public function handle() { $name = $this->argument('name'); $this->info("Hello, {$name}!"); } } دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن تشغيل تطبيق لارافيل باستخدام خادم PHP Artisan يتيح الأمر serve تشغيل التطبيقات على خادم تطوير PHP بسهولة، ويمكن للمطورين استخدام Artisan لإنشاء واختبار ميزات التطبيق المختلفة، يمكن بدء تشغيل الخادم المحلي على العنوان http://localhost:8000 بكتابة الأمر التالي: php artisan serve وهذا يسهل اختبار وتطوير التطبيقات بسرعة، كما يمكننا تخصيص الخادم لاستخدام مضيف ومنفذ مختلف. إنشاء أوامر مخصصة وتشغيلها يمكن إنشاء أوامر Artisan مخصصة واختيار مكان تخزينها وتحميلها باستخدام مدير الحزم Composer. سنشرح في الفقرات التالية أهم الخطوات المتبعة لإنشاء أوامر مخصصة وتسجيلها وتنفيذها. الخطوة 1: إنشاء أمر جديد لإنشاء أمر جديد باسم customcommand نستخدام الأمر make:command الذي ينشئ تلقائيًا صنف أوامر جديد في المجلد app/Console/Commands، ويولّد هذا المجلد إن لم يكن موجودًا مسبقًا. لنكتب الآن الأمر التالي في طرفية Artisan: php artisan make:command customcommand يبدو ملف الشيفرة البرمجية الخاص بهذا الأمر كما يلي: namespace App\Console\Commands; use Illuminate\Console\Command; class CustomCommand extends Command { protected $signature = 'custom:command'; protected $description = 'Description of the custom command'; public function __construct() { parent::__construct(); } public function handle() { // نضع المنطق البرمجي للأمر هنا } } الخطوة 2: تعريف الأمر المخصص عند تنفيذ بعض أوامر Artisan، قد يحتاج المستخدم لإدخال بعض المعلومات كاسمه أو بريده الإلكتروني، ويتعامل لارافيل مع ذلك باستخدام خاصية التوقيع signature. يمكننا استخدام الصيغة القصيرة التالية لضبط الوسطاء arguments والرايات flags أو الخيارات الإجبارية والاختيارية، وهذا يسهّل علينا تحديد الطريقة التي سيتفاعل بها المستخدمون مع الأمر. لنلقِ نظرة على الأمر التالي: protected $signature = 'user:update {id} {--name=} {--email=}'; يتوقّع هذا الأمر أن يدخل المستخدم وسيطًا هو المعرف id، ويدخل الاسم والبريد الإلكتروني اختياريًا. الخطوة 3: دخل وخرج الأمر يوفّر لارافيل طرقًا سهلة للحصول على قيم الوسطاء عند تشغيل الأمر، حيث يمكننا استخدام التابعين $this->argument() و $this->option() ضمن التابع handle الخاص بالأمر. إن لم يكن الوسيط أو الخيار الذي نبحث عنه موجودًا، فسيعيد هذان التابعان القيمة null. المطالبة بالإدخال يمكننا الحصول على إدخال من المستخدم أثناء تشغيل الأمر بالإضافة إلى عرض الخرج، حيث يعرض التابع ask سؤالًا للمستخدم ويحصل على استجابته ثم يرسلها مرة أخرى إلى الأمر الخاص بك. $name = $this->ask('What is your name?', 'Taylor'); طلب التأكيد if ($this->confirm('Do you wish to continue?')) { // متابعة } يمكن جعل موجّه التأكيد يعيد القيمة true دائمًا من خلال تمرير القيمة true كوسيط ثانٍ للتابع confirm. الخطوة 4: تسجيل الأوامر يسجّل لارافيل جميع الأوامر الموجودة في المجلد app/Console/Commands افتراضيًا، ويمكننا إعداد لارافيل للبحث في مزيد من المجلدات عن أوامر PHP Artisan من خلال استخدام التابع withCommands في ملف bootstrap/app.php الخاص بالتطبيق. ->withCommands([ __DIR__.'/../app/Domain/Orders/Commands', ]) سيجد حاوي الخدمات جميع الأوامر ويسجلها في التطبيق باستخدام Artisan. الخطوة 5: تنفيذ الطلبات قد نرغب بتشغيل أمر Artisan خارج واجهة سطر الأوامر من وجهة Route أو متحكم Controller مثلًا، ويمكن تحقيق ذلك باستخدام التابع call مع الصنف Facade من Artisan، يحتوي هذا التابع على وسيطين الأول هو اسم الأمر أو اسم الصنف المرتبط بالأمر، والوسيط الثاني هو قائمة بمعاملات الأمر، ويعيد هذا التابع رمز الخروج ليشير إلى ما إذا كان الأمر قد نجح أم لا. يمكن أيضًا تمرير أمر Artisan بالكامل للتابع call كسلسلة نصية كما يلي: Artisan::call('mail:send 1 --queue=default'); ترتيب أوامر Artisan ضمن رتل يمكننا استخدام التابع queue مع الصنف Facade من Artisan لإرسال أوامر Artisan إلى عمّال الرتل Queue Workers، ثم تُعالَج هذه الأوامر في الخلفية، وعلينا التأكّد من ضبط الرتل وتشغيل مستمع الرتل قبل استخدام هذا التابع. use Illuminate\Support\Facades\Artisan; Route::post('/user/{user}/mail', function (string $user) { Artisan::queue('mail:send', [ 'user' => $user, '--queue' => 'default' ]); // ... }); الخطوة 6: معالجة الإشارات يمكن لأوامر Artisan من لارافيل معالجة إشارات النظام مثل إشارات SIGINT، حيث يمكننا دمج معالجة الإشارات مع الأمر الخاص بنا باستخدام التابع trap كما يلي: $this->trap(SIGINT, function () { $this->info('Command interrupted'); }); الخطوة 7: تخصيص الملفات الجذرية Stub يمكن تخصيص ملفات القوالب التي يستخدمها أمر make:command من Artisan لتسهيل إنشاء الأوامر ذات البنى المتناسقة، لذا نحتاج إلى نشر الملفات الجذرية في مشروعنا كما يلي: php artisan stub:publish الخطوة 8: الأحداث هناك ثلاثة أحداث مهمة ترسلها أداة Artisan عند تشغيل الأوامر وهي: الحدث Illuminate\Console\Events\ArtisanStarting الحدث Illuminate\Console\Events\CommandStarting الحدث Illuminate\Console\Events\CommandFinished نستخدم التابع event لإطلاق حدث كما يلي: event(new CustomCommandExecuted($this)); إنشاء عمليات التهجير Migrations يُعَد الأمر php artisan make:migration من أوامر Artisan المفيدة في لارافيل، حيث يستخدم لإنشاء ملف تهجير جديد. وعمليات التهجير هي مخططات أولية لمخطط قاعدة البيانات وتحدّد لنا بنية الجداول والأعمدة والفهارس والعلاقات. فيما يلي خطوات هذه العملية: إنشاء عملية تهجير: يؤدي الأمر make:migration لإنشاء ملف جديد في المجلد database/migrations تحديد التغييرات: يحتوي ملف التهجير على التابعين up و down يحدّد up التغييرات التي تطرأ على قاعدة البيانات مثل إنشاء الجداول، ويلغي down هذه التغييرات تشغيل عملية التهجير: يفيد الأمر php artisan migrate في تطبيق التغييرات على قاعدة البيانات على سبيل المثال سيؤدي الأمر التالي لإنشاء ملف جديد بالاسم create_users_table في المجلد database/migrations، ويمكننا بعد ذلك تحديد بنية جدول users ضمن ملف التهجير. php artisan make:migration create_users_table تتمثل فوائد الأمر make:migration فيما يلي: التحكم في الإصدارات من خلال تعقّب تغييرات قاعدة البيانات بمرور الوقت التعاون ومشاركة بنية قاعدة البيانات مع أعضاء الفريق بسهولة بذر أو توليد البيانات Seeding لقاعدة البيانات لملئها بالبيانات التجريبية باستخدام Seeder التراجع عن تغييرات قاعدة البيانات بسهولة عند الحاجة إبقاء مخطط قاعدة البيانات واضحًا ومنظمًا، مما يسهّل إدارة تطبيقنا وتحديثه قائمة بأهم أوامر Laravel Artisan تحتوي واجهة سطر أوامر Artisan في لارافيل على أوامر لمهام مختلفة مثل إنشاء الشيفرة البرمجية وإدارة بيئة التطبيق ويمكن مطالعة القائمة الشاملة لكافة الأوامر بكتابة الأمر PHP Artisan list كما وضحنا سابقًا، ولكن سنفصّل فيما يلي الأوامر الأساسية الموجودة ضمن لارافيل 11 ضمن فئات. الأوامر الأساسية cache: إدارة ذاكرة التطبيق المخبئية config: تخزين ملفات الضبط Configuration مؤقتًا أو مسحها أو نشرها down: تعطيل التطبيق مؤقتًا env: إدارة متغيرات البيئة key: لتوليد مفتاح تطبيق جديد migrate: تشغيل عمليات تهجير قاعدة البيانات optimize: تحسين التطبيق للإنتاج queue: إدارة نظام الرتل route: سرد الوجهات Routes أو مسحها storage: إدارة مجلد التخزين vendor: إدارة اعتماديات مدير الحزم Composer أوامر توليد الشيفرة البرمجية make: توليد بنى الشيفرة البرمجية المختلفة للمتحكم والنموذج والتهجير ...إلخ. model: إنشاء نماذج Eloquent migration: إنشاء ملفات التهجير seed: توليد بيانات وهمية لقاعدة البيانات أوامر الاختبار test: تشغيل اختبارات التطبيق dusk: تشغيل اختبارات المتصفح باستخدام Dusk أوامر أخرى auth: إدارة العمليات المتعلقة بالاستيثاق Authentication breeze: تثبيت نظام استيثاق Breeze config: إدارة ملفات الضبط horizon: إدارة نظام رتل Laravel Horizon passport: إدارة خادم OAuth2 sanctum: إدارة واجهة برمجة التطبيقات API لاستيثاق الرموز Token telescope: إدارة أداة تنقيح أخطاء Telescope الخلاصة وضّحنا في هذا المقال كيفية إنشاء أوامر مخصصة باستخدام أمر Artisan الذي يوفر إمكانية تطوير أوامر مختلفة واستدعاءها بناءً على احتياجات مشروعنا. بعض الأسئلة الشائعة لنتعرف على أبرز الأسئلة الشائعة حول أوامر Artisan 1. ما الغرض من أوامر PHP Artisan في لارافيل أوامر Artisan هي أداة سطر أوامر في لارافيل تساعدنا على أتمتة المهام وإدارة عمليات التهجير وتوليد الشيفرة البرمجية المساعدة، ويمكننا استخدامها أيضًا لتشغيل الاختبارات وإنجاز مهام التطوير والإدارة الأخرى في تطبيقات لارافيل. 2. لماذا نستخدم أمر PHP Artisan Serve يشغّل أمر PHP Artisan serve خادم تطوير محلي لتطبيق لارافيل الخاص بنا بسرعة، ويُعَد مثاليًا لإنشاء النماذج الأولية السريعة والاختبار أثناء التطوير. 3. كيف نشغّل أوامر PHP Artisan في cpanel نفتح الطرفية Terminal من cPanel وننتقل إلى مجلد مشروع لارافيل الخاص بنا، ثم نشغّل الأمر باستخدام PHP كما يلي: /usr/local/bin/php artisan {command} تحتاج للتأكد من وضع مسار PHP الصحيح للخادم مكان /usr/local/bin/php، والذي يمكننا العثور عليه في MultiPHP Manager ضمن cPanel. 4. هل يمكن تعديل أو توسيع أوامر Artisan الموجودة مسبقًا في لارافيل نعم، لدينا القدرة على تعديل أو توسيع أوامر Artisan الموجودة مسبقًا في لارافيل من خلال إنشاء أمر مخصص يرث صنف الأمر الأساسي، ثم تنفيذ المنطق البرمجي أو الوظيفة المحددة التي نريدها ضمن هذا الأمر المخصص. 5. هل يمكن استخدام أوامر Artisan المخصصة لأتمتة المهام الشائعة في لارافيل نعم، يمكننا استخدام أوامر Artisan المخصصة في لارافيل لأتمتة المهام الشائعة مثل تشغيل عمليات التهجير، وتوليد بيانات لقاعدة البيانات وتوليد الشيفرة البرمجية وتطبيق المهام المجدولة وتنفيذ أي منطق برمجي مخصص. ترجمة -وبتصرّف- للمقال How to Create Custom Commands in Laravel 11 with PHP Artisan للكاتبة Hafsa Tahir. اقرأ أيضًا نصائح لتحسين أداء تطبيقات لارافيل أفضل الحزم البرمجية لتحسين تطبيقات لارافيل كيف تنشئ نموذجا Model في Laravel أساسيات التخبئة Cache في Laravel
-
سنتعرف في مقال اليوم على العقدة RayCast2D في محرك جودو وكيفية استخدامها بكفاءة في تطوير الألعاب ثنائية الأبعاد، من أجل كشف تصادم الأشعة Raycasting الذي يفيدنا في العديد من حالات الاستخدام. أهمية العقدة RayCast2D في تطوير الألعاب تمثل العقدة RayCast2D في محرك ألعاب جودو شعاعًا ينطلق من نقطة الأصل الممثلة بمركز العقدة إلى نقطة نهاية، والشعاع ray هو خط افتراضي ينطلق من نقطة باتجاه زاوية معينة ويمتد في الفضاء، ويمكننا التحقق فيما إذا كان هذا الشعاع قد اصطدم بشيء ما في المشهد أو وصل إلى نهايته دون أي تصادم. تعد هذه التقنية أساسية في العديد من أنواع الألعاب، مثل ألعاب التصويب أو ألعاب المنصات، حيث يمكن استخدامها للكشف الاصطدامات collisions بين الكائنات أو الأسطح، كأن نحتاج لمعرفة إذا كان اللاعب يرى العدو أو إذا كان يلمس الأرضية أم لا، كما يمكن أن نتحقق من خلالها فيما إذا كانت القذيفة التي أطلقناها قد أصابت هدفًا معينًا. أهم خصائص العقدة RayCast2D تتضمن العقدة RayCast2D في جودو مجموعة من الخصائص المهمة التي تساعدنا على ضبط سلوك الشعاع بشكل دقيق. دعونا ننشئ مشروع جديد للعبة ثنائية الأبعاد، ونضيف للمشهد عقدة RayCast2D ونتحقق من خصائصها الظاهرة في الفاحص Inspector كما يلي: فيما يلي الخاصيات الرئيسية التي ستحتاج إلى فهمها للتعامل مع هذه العقدة: الخاصية Enabled تستخدم للتحكم في تفعيل أو تعطيل شعاع RayCast، فعند تفعيل هذه الخاصية، سيبدأ الشعاع في الكشف عن التصادمات مع الأجسام في كل إطار فيزيائي. وإذا ألغينا تفعيلها فسيتعطل عمل كشف تصادم الأشعة. الخاصية Exclude Parent عند تفعيل هذه الخاصية، سيتجاهل الشعاع التصادم مع العقدة الأب المباشرة. وتكون هذه الخاصية مفعَّلة افتراضيًا لتجنب الكشف عن تصادمات غير مرغوب بها مع الكائن الذي يحتوي على الشعاع. الخاصية Target Position تحدد هذه الخاصية نقطة نهاية الشعاع بالنسبة لموضع العقدة نفسه، أي باستخدام الإحداثيات المحلية، على سبيل المثال، إذا كانت قيمتها (250,0) فسيمتد الشعاع أفقيًا لليمين لمسافة 250 وحدة وهي نقطة الوجهة للشعاع في الإحداثيات المحلية. ملاحظة: تمثل الإحداثيات المحلية Local Coordinates موقع العقدة بالنسبة لنقطة الأصل الخاصة بالعقدة الأم، بينما تمثل الإحداثيات العامة Global Coordinates الموقع المطلق للعقدة داخل المشهد بأكمله. لنلاحظ أيضًا القسم بعنوان Collide With ضمن الفاحص، ففي هذا القسم يمكننا تحديد أنواع الكائنات التي يجب أن يتفاعل معها الشعاع، حيث سيكتشف الشعاع افتراضيًا الأجسام الفيزيائية Bodies فقط مثل KinematicBody2D أو RigidBody2D، ولو أردنا منه اكتشاف المناطق أيضًا مثل Area2D فعلينا تفعيل الخيار Area. دوال مفيدة للعقدة RayCast2D يمكن الاطلاع على القائمة الكاملة لدوال العقدة RayCast2D في توثيق واجهة برمجة التطبيقات API، ولكن سنوضح تاليًا بعض الدوال المفيدة للتعامل مع التصادمات: is_colliding(): دالة منطقية تتيح معرفة فيما إذا كان الشعاع يصطدم بشيء ما get_collision_point(): إذا اصطدم الشعاع بشيء ما ستعيد هذه الدالة موضع التصادم في الإحداثيات العامة get_collider(): إذا اصطدم الشعاع بشيء ما ستعيد هذه الدالة مرجعًا إلى الكائن المتصادم get_collision_normal(): تعيد هذه الدالة الشعاع الناظم Normal على الكائن المتصادم عند نقطة الاصطدام أمثلة عملية توجد العديد من الاستخدامات العملية المفيدة لتقنية كشف تصادم الأشعة مثل الرؤية Visibility أي هل يمكن للكائن A أن يرى الكائن B أم أن هناك عائقًا بينهما يحول دون ذلك، والقرب Proximity أي هل اللاعب قريب من جدار أو أرض أو عائق وغير ذلك من الاستخدامات المختلفة. سنوضح فيما يلي بعض الأمثلة العملية المفيدة. المثال الأول: إطلاق النار تواجه المقذوفات سريعة الحركة مشكلة تسمى Tunneling، فهي تتحرك بسرعة كبيرة بحيث لا يمكنها اكتشاف الاصطدام في إطار واحد وبالتالي سيؤدي هذا لمرورها عبر العوائق أو الأسطح بدل أن تصطدم بها، في هذا الحالة يمكننا استخدام العقدة Raycast2D لتمثيل حركة المقذوف على شكل مسار أو شعاع مستمر مثل شعاع الليزر وبهذا نضمن اكتشاف تصادماته بدقة حتى عند السرعات العالية. يمثّل الشكل التالي شخصية اللاعب حيث أضفنا عقدة Raycast2D عند نهاية السلاح، وضبطنا موضع الهدف target_position على القيمة (250,0)لضبط اتجاه الشعاع الذي يُطلق من السلاح. إذا أطلق اللاعب قذيفة، فيجب التحقق فيما إذا كان الشعاع يصطدم بشيء ما كما يلي: func _input(event): if event.is_action_pressed("shoot"): if $RayCast2D.is_colliding(): print($RayCast2D.get_collider().name) المثال الثاني: اكتشاف حافة منصة لنفترض وجود عدو يمشي على منصة ضمن لعبة ما، لكننا لا نريده أن يسقط من حافة هذه المنصة بل نريده أن يرتد ويتحرك في الاتجاه المعاكس، لذا سنضيف إلى العدو عقدتين من النوع Raycast2D كما يلي: نتحقق متى سيتوقف الشعاع عن الاصطدام بأي شيء في سكربت العدو من خلال الدالة ()is_colliding، ففي حال أعادت الدالة false، فهذا يعني أننا وجدنا الحافة ويجب علينا الالتفاف: func _physics_process(delta): velocity.y += gravity * delta if not $RayRight.is_colliding(): dir = -1 if not $RayLeft.is_colliding(): dir = 1 velocity.x = dir * speed $AnimatedSprite.flip_h = velocity.x > 0 velocity = move_and_slide(velocity, Vector2.UP) يعمل الكود أعلاه على تحريك العدو على المنصة مستعينًا بشعاعي Raycast2D للكشف عن الوصول لحافة المنصة، فإذا لم يصطدم الشعاع الأيمن بشيء سيتحرك العدو لليسار، وإذا لم يصطدم الشعاع الأيسر بشيء سيتحرك العدو لليمين. وسيبدو الأمر أثناء العمل كما يلي: الخاتمة نحتاج إلى تنفيذ تقنيات كشف التصادم في معظم أنواع الألعاب، لذا من الضروي أن نفهم جيدًا كيفية إعداد واستخدام العقدة RayCast2D في جودو ونوظفها بشكل صحيح لحل مشكلات شائعة في تطوير الألعاب مثل التصويب واكتشاف حواف المنصات وتجنب العوائق وغيرها من الحالات. ترجمة -وبتصرّف- للقسم RayCast2D من توثيقات Kidscancode. اقرأ أيضًا التفاعل بين الشخصيات والأجسام الصلبة في جودو استخدام RigidBody2D في جودو للتوجه نحو هدف والتحرك نحوه ترتيب معالجة العقد والتنقل في شجرة المشاهد في Godot تحريك سفينة فضاء باستخدام RigidBody2D في جودو