دراسة حالة - التشابك مع HTML5 Canvas

مقدمة

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

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

رسم الشكل السداسي

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

ورقة الرموز المتحركة للمربّعات
ورقة الرموز المتحركة للمربّعات

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

لإنشاء لوحة رسم، كل ما نحتاجه هو علامة لوحة الرسم في مستند html الخاص بنا كما يلي:

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

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

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

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

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

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

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

الآن بعد أن أصبح لدينا المكونات الثلاثة، يمكننا استخدام بِنَاءُ c.plotImage() لرسم السداسية السداسية المفردة التي نريدها من ورقة الرموز المتحركة إلى لوحة الرسم:

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 من الحافة مفصولاً عن بعضها البعض، على النحو التالي:

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

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

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

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

إحداثيات منحنى بيزيه الأول
إحداثيات لمنحنى "بيزيه" الأول

مع وجود إحداثياتنا في متناول اليد، نحن الآن مستعدون لبدء الرسم. سنبدأ من جديد بـ بِت ا ل& بدء خ ُ ص , ثم ننتقل إلى نقطة النهاية الأولى باستخدام:

ctx.moveTo(pointX1,pointY1);

يمكننا بعد ذلك رسم الخط نفسه باستخدام() ، ، ، باستخدام ،؟

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

نظرًا لأننا نريد أن يكون للخط حدودًا جميلة، فسنعمل على الحد من هذا المسار مرتين باستخدام عرض ولون مختلفين في كل مرة. سيتم ضبط اللون باستخدام خاصية بِـ js.matchStyle، وسيتم ضبط العرض باستخدام الآخرون الآخرون الآخرون الآخرون Displayed ؟ إجمالًا، سيبدو رسم الخط الأول على النحو التالي:

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();

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

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

عند إدخال إحداثيات لنقاط النهاية العشر الأخرى بالإضافة إلى نقاط تحكم منحنى "بيزيه" المقابلة، يمكننا تكرار الخطوات المذكورة أعلاه وقد ننشئ مربعًا على النحو التالي:

مربع سداسي مكتمل.
مربّع سداسي سداسي مكتمل

تدوير اللوحة

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

ctx.translate(originX, originY);

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

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 باستخدام علامة لوحة الرسم، بما في ذلك عرض الصور ورسم منحنيات بيزيه وتدوير اللوحة. أثبت استخدام علامة لوحة 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();