Studi Kasus - Getting Entangled dengan HTML5 Canvas

Derek Detweiler
Derek Detweiler

Pengantar

Musim semi yang lalu (2010) saya tertarik dengan peningkatan dukungan untuk HTML5 dan teknologi terkait yang meningkat pesat. Pada saat itu, saya dan teman saya sedang bertantangan dalam kompetisi pengembangan game selama dua minggu untuk mengasah keterampilan pemrograman dan pengembangan kami serta mewujudkan ide game yang terus kami lemparkan satu sama lain. Jadi, saya secara alami mulai menggabungkan elemen HTML5 ke dalam entri kompetisi untuk mendapatkan pemahaman yang lebih baik tentang cara kerjanya dan dapat melakukan hal-hal yang hampir mustahil menggunakan spesifikasi HTML sebelumnya.

Dari banyak fitur baru di HTML5, peningkatan dukungan untuk tag kanvas memberi saya peluang yang menarik untuk menerapkan seni interaktif menggunakan JavaScript, sehingga saya mencoba menerapkan game teka-teki yang sekarang disebut Entanglement. Saya sudah membuat prototipe menggunakan bagian belakang Ubin Catan, jadi dengan menggunakan ini sebagai cetak biru, ada tiga bagian penting untuk merancang ubin heksagonal pada kanvas HTML5 untuk permainan web: menggambar segi enam, menggambar jalur, dan memutar ubin. Berikut ini detail yang menguraikan cara saya mencapai masing-masing dalam bentuk saat ini.

Menggambar Heksagon

Dalam versi asli Entanglement, saya menggunakan beberapa metode menggambar kanvas untuk menggambar segi enam, tetapi bentuk game saat ini menggunakan drawImage() untuk menggambar tekstur yang dipotong dari sprite sheet.

Sheet sprite kartu
Sprite sheet kartu

Saya menggabungkan gambar menjadi satu file sehingga hanya satu permintaan ke server, bukan, dalam hal ini, sepuluh. Untuk menggambar heksagonal yang dipilih ke kanvas, pertama-tama kita harus mengumpulkan alat-alat tersebut: kanvas, konteks, dan gambar.

Untuk membuat kanvas, yang kita butuhkan hanyalah tag kanvas dalam dokumen html kita seperti ini:

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

Saya memberikan ID sehingga kita dapat menariknya ke dalam skrip:

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

Kedua, kita perlu mengambil konteks 2d untuk kanvas sehingga kita dapat mulai menggambar:

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

Terakhir, kita memerlukan gambar. Jika namanya "tils.png" di folder yang sama dengan halaman web kita, kita bisa mendapatkannya dengan:

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

Sekarang setelah kita memiliki tiga komponen, kita dapat menggunakan ()`.drawImage() untuk menggambar heksagon tunggal yang kita inginkan dari lembar sprite ke kanvas:

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

Dalam hal ini, kita menggunakan heksagonal keempat dari kiri pada baris atas. Selain itu, kita akan menggambarnya ke kanvas di sudut kiri atas, dengan mempertahankannya dengan ukuran yang sama seperti aslinya. Dengan asumsi bahwa segi enam memiliki lebar 400 piksel dan tinggi 346 piksel, secara keseluruhan akan terlihat seperti ini:

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

Kita telah berhasil menyalin sebagian gambar ke kanvas, dengan hasil sebagai berikut:

Ubin heksagonal
Ubin heksagonal

Menggambar Jalur

Sekarang setelah segi enam kita digambar ke kanvas, kita ingin menggambar beberapa garis di atasnya. Pertama-tama, kita akan melihat beberapa geometri terkait ubin segi enam. Kita ingin dua ujung garis per sisi dengan setiap ujung 1/4 dari ujung di sepanjang setiap tepi dan 1/2 tepi terpisah satu sama lain, seperti ini:

Ujung garis pada ubin heksagonal
Titik akhir garis pada ubin heksagonal

Kita juga menginginkan kurva yang bagus, jadi, dengan sedikit coba-coba, saya temukan bahwa, jika saya membuat garis tegak lurus dari tepi di setiap titik akhir, perpotongan dari setiap pasangan titik akhir di seputar sudut tertentu segi enam membuat titik kontrol bezier yang bagus untuk titik akhir yang diberikan:

Titik kontrol pada ubin heksagonal
Mengontrol titik pada ubin heksagonal

Sekarang, kita akan memetakan endpoint dan titik kontrol ke bidang Kartesius yang sesuai dengan gambar kanvas, dan kita siap untuk kembali ke kodenya. Sederhananya, kita akan mulai dengan satu baris. Kita akan mulai dengan menggambar jalur dari endpoint kiri atas ke endpoint kanan bawah. Dengan gambar segi enam sebelumnya menjadi 400x346, itu akan membuat endpoint teratas kita berukuran 150 piksel dan 0 piksel ke bawah, singkatan (150, 0). Titik kontrolnya adalah (150, 86). Endpoint tepi bawah adalah (250, 346) dengan titik kontrol (250, 260):

Koordinat untuk kurva bezier pertama
Koordinat untuk kurva bezier pertama

Dengan koordinat yang ada, kami sekarang siap untuk mulai menggambar. Kita akan mulai dari yang baru dengan ctx.beginPath() dan kemudian berpindah ke endpoint pertama menggunakan:

ctx.moveTo(pointX1,pointY1);

Kemudian kita dapat menggambar garis itu sendiri menggunakan ctx.bezierCurveTo() sebagai berikut:

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

Karena kita ingin garis memiliki batas yang bagus, kita akan mengecat jalur ini dua kali dengan menggunakan lebar dan warna yang berbeda setiap kalinya. Warna akan disetel menggunakan properti ctx.strokeStyle dan lebar akan disetel menggunakan ctx.lineWidth. Secara keseluruhan, menggambar garis pertama akan terlihat seperti ini:

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

Kita sekarang memiliki ubin heksagonal dengan garis pertama berkelok-kelok melintasi:

Garis soliter pada ubin heksagonal
Garis tunggal pada ubin heksagonal

Dengan memasukkan koordinat untuk 10 endpoint lainnya serta titik kontrol kurva bezier yang sesuai, kita dapat mengulangi langkah-langkah di atas dan dapat membuat kartu seperti ini:

Ubin heksagonal selesai.
Ubin heksagonal lengkap

Memutar Kanvas

Setelah memiliki kartu, kita ingin dapat mengubahnya sehingga jalur yang berbeda dapat diambil dalam game. Untuk melakukannya menggunakan kanvas, kami menggunakan ctx.translate() dan ctx.rotate(). Kita ingin ubin berputar di tengahnya. Jadi, langkah pertama kita adalah memindahkan titik referensi kanvas ke pusat ubin heksagonal. Untuk melakukannya, kita menggunakan:

ctx.translate(originX, originY);

Dengan originX akan menjadi setengah dari lebar ubin heksagonal dan originY akan setengah dari tinggi, sehingga memberi kita:

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

Sekarang kita dapat memutar ubin dengan titik tengah baru kita. Karena segi enam memiliki enam sisi, kita akan memutarnya dengan kelipatan Math.PI dibagi 3. Kita akan membuatnya sederhana dan melakukannya dengan satu putaran searah jarum jam menggunakan:

ctx.rotate(Math.PI / 3);

Namun, karena segi enam dan garis menggunakan koordinat lama (0,0) sebagai asal, setelah kita selesai memutarnya, kita perlu menerjemahkan kembali sebelum menggambar. Jadi, secara keseluruhan kita sekarang memiliki:

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

Menempatkan terjemahan dan rotasi di atas sebelum kode rendering akan menyebabkannya merender kartu yang diputar:

Ubin heksagonal diputar
Ubin heksagonal diputar

Ringkasan

Di atas, saya telah menyoroti beberapa kemampuan yang ditawarkan HTML5 menggunakan tag kanvas, termasuk merender gambar, menggambar kurva bezier, dan memutar kanvas. Menggunakan tag kanvas HTML5 dan alat gambar JavaScript-nya untuk Entanglement terbukti menjadi pengalaman yang menyenangkan, dan saya menantikan banyak aplikasi dan game baru yang dibuat orang lain dengan teknologi yang terbuka dan baru ini.

Referensi Kode

Semua contoh kode yang diberikan di atas digabungkan di bawah ini sebagai referensi:

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