دراسة حالة: التعرّف على لوحة HTML5

مقدمة

في الربيع الماضي (2010)، لفت انتباهي الدعم المتزايد بشكل سريع للغة HTML5 والتقنيات ذات الصلة. في ذلك الوقت، كان صديقي يصعّب عليّ المهام في مسابقات تطوير الألعاب التي تستمر لمدة أسبوعَين لتحسين مهاراتنا في البرمجة والتطوير، بالإضافة إلى تحويل أفكار الألعاب التي نطرحها باستمرار إلى واقع. لذلك، بدأتُ بشكل طبيعي في دمج عناصر HTML5 في مشاركاتي في المسابقة للتعرّف بشكل أفضل على آلية عملها والتمكن من تنفيذ إجراءات كانت تقريبًا مستحيلة باستخدام مواصفات HTML السابقة.

من بين الميزات الجديدة العديدة في HTML5، وفّرت لي الزيادة في التوافق مع علامة canvas فرصة مثيرة لتنفيذ أعمال فنية تفاعلية باستخدام JavaScript، ما دفعني إلى تجربة تنفيذ لعبة أحجية تُعرف الآن باسم Entanglement. لقد أنشأتُ نموذجًا أوليًا باستخدام الجزء الخلفي من مثبّت لعبة Settlers of Catan، لذا باستخدام هذا كنوع من المخطّط، هناك ثلاثة أجزاء أساسية لتصميم المربّع السداسي على لوحة HTML5 للّعب على الويب: رسم المثلث السداسي ورسم المسارات وتدوير المربّع السداسي. يوضّح ما يلي بالتفصيل كيفية تحقيق كلّ من هذه الخطوات بصورتها الحالية.

رسم المضلع السداسي

في النسخة الأصلية من Entanglement، استخدمت عدة طرق لرسم اللوحة لرسم الشكل السداسي، ولكن يستخدم الشكل الحالي من اللعبة drawImage() ل رسم مواد تم اقتصاصها من لوحة صور متحركة.

قائمة صور متحركة للبلاط
جدول أجزاء العناصر المتحرّكة للشرائح

لقد دمجت الصور معًا في ملف واحد حتى يكون الطلب الوحيد هو طلب واحد فقط إلى الخادم بدلاً من عشرة في هذه الحالة. لرسم مسدّس محدّد على اللوحة، علينا أولاً جمع أدواتنا معًا: اللوحة والسياق والصورة.

لإنشاء لوحة، كل ما نحتاجه هو علامة canvas في مستند html على النحو التالي:

<canvas id="myCanvas"></canvas>

أمنح هذا العنصر رقم تعريف لنتمكّن من سحبه إلى النص البرمجي:

var cvs = document.getElementById('myCanvas');

ثانيًا، نحتاج إلى الحصول على السياق ثنائي الأبعاد للوحة حتى نتمكّن من بدء الرسم:

var ctx = cvs.getContext('2d');

أخيرًا، نحتاج إلى الصورة. إذا كان اسمه "tiles.png" في المجلد نفسه الذي يحتوي على صفحة الويب، يمكننا الحصول عليه من خلال:

var img = new Image();
img.src = 'tiles.png';

والآن بعد أن حصلنا على المكوّنات الثلاثة، يمكننا استخدام ctx.drawImage() لرسم المعشر السداسي الوحيد الذي نريده من لوحة الصور المتحركة إلى اللوحة:

ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

في هذه الحالة، سنستخدم السداسي الرابع من اليسار في الصف العلوي. سنرسمها أيضًا على اللوحة في أعلى يمين الشاشة، مع إبقاء حجمها كما هو في الصورة الأصلية. بافتراض أنّ الأشكال السداسية يبلغ عرضها 400 بكسل وبطول 346 بكسل، سيظهر الشكل على النحو التالي:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

لقد نسخنا جزءًا من الصورة إلى اللوحة، وكانت النتيجة على النحو التالي:

مربّع سداسي
الوحدة السداسية

مسارات الرسم

الآن بعد أن رسمنا الشكل السداسي على اللوحة، نريد رسم بعض الخطوط عليه. أولاً، سنلقي نظرة على بعض الأشكال الهندسية المتعلّقة بالبلاط السداسي. نريد أن يكون هناك طرفان للخط على كل جانب، مع وضع كل طرف على مسافة 1/4 من الأطراف على طول كل حافة وبمسافة 1/2 من الحافة عن بعضها البعض، على النحو التالي:

نقاط نهاية الخطوط على مربّع سداسي
نقاط نهاية الخطوط على المربّع السداسي

نريد أيضًا منحنى جميلًا، لذا بعد إجراء بعض التجارب، تبيّن لي أنّه إذا أنشأتُ خطًا عموديًا من الحافة عند كل نقطة نهاية، فإنّ نقطة التقاطع من كل زوج من نقاط النهاية حول زاوية معيّنة من السداسي تشكل نقطة تحكّم ملفتة في منحنى Bezier لنقاط النهاية المحدّدة:

نقاط التحكّم في المربّع السداسي
نقاط التحكّم في المربّع السداسي

الآن، سنربط كلّ من نقاط النهاية ونقاط التحكّم بمستوى إحداثيات ديكارتية متوافق مع صورة اللوحة، وسنعود إلى الرمز البرمجي. لنبسّط الأمر، سنبدأ بخط واحد. سنبدأ برسم مسار من نقطة النهاية في أعلى يمين الصفحة إلى نقطة النهاية في أسفل يمين الصفحة. بما أنّ صورة الهرم السداسي التي سبق أن عرضناها كانت أبعادها 400×346، ستبلغ نقطة النهاية العلوية 150 بكسل عرضًا و0 بكسل أسفلًا، أي الاختصار (150, 0). ستكون نقطة التحكّم (150, 86). نقطة نهاية الحافة السفلية هي (250، 346) مع نقطة تحكّم هي (250، 260):

إحداثيات منحنى Beziers الأول
إحداثيات منحنى Beziers الأول

بعد الحصول على الإحداثيات، أصبحنا جاهزين الآن لبدء الرسم. سنبدأ من جديد باستخدام ctx.beginPath() ثم ننتقل إلى نقطة النهاية الأولى باستخدام:

ctx.moveTo(pointX1,pointY1);

يمكننا بعد ذلك رسم الخط نفسه باستخدام ctx.bezierCurveTo() على النحو التالي:

ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);

بما أنّنا نريد أن يكون للخط حدودًا جميلة، سنرسم هذا المسار مرتين باستخدام عرض ولون مختلفَين في كل مرة. سيتم ضبط اللون باستخدام سمة ctx.strokeStyle وسيتم ضبط العرض باستخدام ctx.lineWidth. إجمالاً، سيبدو رسم السطر الأول على النحو التالي:

var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.beginPath();
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

لدينا الآن مربّع سداسي الشكل مع السطر الأول المتعرّج:

خط منفرد على مربّع سداسي
خط منفرد على مربّع سداسي

عند إدخال إحداثيات النقاط العشر الأخرى بالإضافة إلى نقاط التحكّم في منحنى بيريز المناظرة، يمكننا تكرار الخطوات أعلاه وإنشاء ملف ملف ‎bathmat على النحو التالي:

قطعة سداسية مكتملة
وحدة سداسية مكتملة

تدوير اللوحة

بعد الحصول على العنصر، نريد أن نتمكّن من تحويله لكي يتمكّن اللاعبون من اجتياز مسارات مختلفة في اللعبة. لتنفيذ ذلك باستخدام اللوحة، نستخدم ctx.translate() وctx.rotate(). نريد أن تدور الشاشة حول مركزها، لذا فإنّ أول خطوة هي نقل نقطة مرجعية للوحة إلى مركز الشاشة السداسية. ولإجراء ذلك، نستخدم ما يلي:

ctx.translate(originX, originY);

حيث سيكون originX نصف عرض المربّع السداسي وoriginY هو نصف الارتفاع، ما يمنحنا:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);

يمكننا الآن تدوير المربّع باستخدام نقطة المركز الجديدة. بما أنّ الرمز السداسي العميق يتألّف من ستة جوانب، سنحتاج إلى تدويره بمقدار بعض مضاعفات Math.PI مقسومة على 3. سنبسّط الأمر ونختار إجراء دورة واحدة باتجاه عقارب الساعة باستخدام:

ctx.rotate(Math.PI / 3);

ومع ذلك، بما أنّ الشكل السداسي والخطوط يستخدمان الإحداثيات القديمة (0,0) كنقطة بدء، سنحتاج إلى إعادة الترجمة بعد الانتهاء من التدوير قبل البدء بالرسم. وبالتالي، لدينا الآن ما يلي:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

يؤدي وضع الترجمة والدوران أعلاه قبل رمز العرض إلى عرض المربّع الذي تم تدويره الآن:

مربّع سداسي مُدار
شريحة سداسية مُدارة

ملخّص

لقد أبرزنا أعلاه بعض الإمكانات التي يوفّرها HTML5 باستخدام علامة canvas، بما في ذلك عرض الصور ورسم منحنيات bezier وقلب اللوحة. لقد أثبت استخدام علامة لوحة HTML5 وأدوات الرسم المستندة إلى JavaScript في Entanglement أنّه تجربة ممتعة، وأتطلّع إلى استخدام التطبيقات والألعاب الجديدة العديدة التي ينشئها الآخرون باستخدام هذه التكنولوجيا المفتوحة والجديدة.

مرجع الرموز

في ما يلي جميع أمثلة الرموز البرمجية المقدَّمة أعلاه كمرجع:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

ctx.beginPath();
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 250;
pointY1 = 0;
controlX1 = 250;
controlY1 = 86;
controlX2 = 150;
controlY2 = 86;
pointX2 = 75;
pointY2 = 43;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 150;
pointY1 = 346;
controlX1 = 150;
controlY1 = 260;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 43;
controlX1 = 250;
controlY1 = 86;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 130;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 25;
pointY1 = 130;
controlX1 = 100;
controlY1 = 173;
controlX2 = 100;
controlY2 = 173;
pointX2 = 25;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 303;
controlX1 = 250;
controlY1 = 260;
controlX2 = 150;
controlY2 = 260;
pointX2 = 75;
pointY2 = 303;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();