مطالعه موردی - درگیر شدن با بوم HTML5

معرفی

در بهار گذشته (2010) من به افزایش سریع پشتیبانی از HTML5 و فناوری های مرتبط علاقه مند شدم. در آن زمان، من و یکی از دوستانم در مسابقات دو هفته ای بازی سازی یکدیگر را به چالش می کشیدیم تا مهارت های برنامه نویسی و توسعه خود را تقویت کنیم و همچنین ایده های بازی را که دائماً به سمت یکدیگر می پرتاب می کردیم، زنده کنیم. بنابراین، من به طور طبیعی شروع به ترکیب عناصر HTML5 در ورودی های مسابقه خود کردم تا درک بهتری از نحوه کار آنها به دست بیاورم و بتوانم کارهایی را انجام دهم که با استفاده از مشخصات HTML قبلی تقریبا غیرممکن بود.

از میان بسیاری از ویژگی‌های جدید در HTML5، پشتیبانی فزاینده از برچسب بوم فرصتی هیجان‌انگیز برای پیاده‌سازی هنر تعاملی با استفاده از جاوا اسکریپت به من داد، که باعث شد من یک بازی پازلی را که اکنون Entanglement نامیده می‌شود، امتحان کنم. من قبلاً یک نمونه اولیه با استفاده از پشت کاشی‌های Settlers of Catan ایجاد کرده بودم، بنابراین با استفاده از این به عنوان یک طرح اولیه، سه بخش اساسی برای شکل دادن به کاشی شش ضلعی روی بوم HTML5 برای بازی در وب وجود دارد: ترسیم شش ضلعی، ترسیم مسیرها، و چرخاندن کاشی در ادامه به تفصیل می‌پردازیم که نشان می‌دهد چگونه هر یک از اینها را در شکل فعلی‌شان به انجام رساندم.

رسم شش ضلعی

در نسخه اصلی Entanglement، من از چندین روش ترسیم بوم برای ترسیم شش ضلعی استفاده کردم، اما شکل فعلی بازی drawImage() برای ترسیم بافت های بریده شده از یک صفحه sprite استفاده می کند.

ورق اسپرایت کاشی
ورق اسپرایت کاشی

من تصاویر را با هم در یک فایل ترکیب کردم، بنابراین به جای ده درخواست، تنها یک درخواست به سرور است. برای کشیدن یک شش ضلعی انتخاب شده روی بوم، ابتدا باید ابزارهای خود را با هم جمع کنیم: بوم، زمینه و تصویر.

برای ایجاد یک بوم، تنها چیزی که نیاز داریم تگ canvas در سند html ما است مانند این:

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

من به آن شناسه می دهم تا بتوانیم آن را در اسکریپت خود بکشیم:

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

در مرحله دوم، ما باید زمینه 2 بعدی را برای بوم بگیریم تا بتوانیم طراحی را شروع کنیم:

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

در نهایت، ما به تصویر نیاز داریم. اگر نام "tiles.png" در همان پوشه صفحه وب ما باشد، می‌توانیم آن را از طریق:

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

اکنون که سه مولفه را داریم، می‌توانیم از ctx.drawImage() برای ترسیم شش ضلعی که می‌خواهیم از صفحه sprite به بوم استفاده کنیم:

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 برای نقاط پایانی داده شده:

نقاط کنترل روی کاشی شش ضلعی
نقاط کنترل روی کاشی شش ضلعی

اکنون، هر دو نقطه پایانی و نقاط کنترل را به یک صفحه دکارتی مطابق با تصویر بوم ما نگاشت می کنیم و آماده بازگشت به کد هستیم. برای ساده نگه داشتن آن، با یک خط شروع می کنیم. ما با ترسیم مسیری از نقطه پایانی بالا سمت چپ به نقطه پایانی پایین سمت راست شروع خواهیم کرد. از آنجایی که تصویر شش ضلعی قبلی ما 400x346 است، نقطه پایانی بالای ما 150 پیکسل در عرض و 0 پیکسل پایین، خلاصه شده (150، 0) می شود. نقطه کنترل آن (150، 86) خواهد بود. نقطه پایانی لبه پایینی (250، 346) با نقطه کنترل (250، 260) است:

مختصات اولین منحنی بزیه
مختصات اولین منحنی بزیه

با در دست داشتن مختصات، اکنون آماده شروع طراحی هستیم. ابتدا با 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();

ما اکنون یک کاشی شش ضلعی داریم که خط اول آن در سراسر پیچ خورده است:

خط انفرادی روی کاشی شش ضلعی
خط انفرادی روی کاشی شش ضلعی

با وارد کردن مختصات برای 10 نقطه پایانی دیگر و همچنین نقاط کنترل منحنی bezier مربوطه، می‌توانیم مراحل بالا را تکرار کنیم و ممکن است یک کاشی چیزی شبیه به این ایجاد کنیم:

کاشی شش ضلعی تکمیل شده.
کاشی شش ضلعی تکمیل شده

چرخاندن بوم

وقتی کاشی خود را داشتیم، می‌خواهیم بتوانیم آن را بچرخانیم تا مسیرهای مختلفی در بازی طی شود. برای انجام این کار با استفاده از canvas، از ctx.translate() و ctx.rotate() استفاده می کنیم. ما می خواهیم کاشی به دور مرکز خود بچرخد، بنابراین اولین قدم ما این است که نقطه مرجع بوم را به مرکز کاشی شش ضلعی منتقل کنیم. برای انجام این کار از:

ctx.translate(originX, originY);

جایی که originX نیمی از عرض کاشی شش ضلعی و originalY نصف ارتفاع خواهد بود، به ما می دهد:

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 با استفاده از برچسب بوم ارائه می‌دهد، از جمله رندر کردن تصاویر، ترسیم منحنی‌های bezier و چرخش بوم برجسته کرده‌ام. استفاده از برچسب بوم HTML5 و ابزارهای ترسیم جاوا اسکریپت آن برای 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();