Örnek Olay: HTML5 Tuval ile İlgili Sorunları Çözme

Derek Detweiler
Derek Detweiler

Giriş

Geçtiğimiz baharda (2010) HTML5 ve ilgili teknolojilere yönelik hızla artan desteğe ilgi duymaya başladım. O zamanlar bir arkadaşımla birlikte, programlama ve geliştirme becerilerimizi geliştirmek ve sürekli birbirimize attığımız oyun fikirlerini hayata geçirmek için iki haftalık oyun geliştirme yarışmalarında birbirimize meydan okuyorduk. Bu nedenle, işleyiş şeklini daha iyi anlamak ve önceki HTML özelliklerini kullanarak neredeyse imkansız olan şeyleri yapabilmek için yarışma girişlerime HTML5 öğeleri eklemeye başladım.

HTML5'teki birçok yeni özellikten biri olan kanvas etiketi için artan destek, JavaScript'i kullanarak etkileşimli sanat uygulamak için heyecan verici bir fırsat sundu. Bu da şu anda Entanglement olarak adlandırılan bir bulmaca oyunu uygulamayı denememi sağladı. Settlers of Catan taşlarının arka tarafını kullanarak bir prototip oluşturmuştum. Bu prototipi bir tür plan olarak kullanarak, web'de oynamak için HTML5 kanvas üzerinde altıgen taşları oluşturmanın üç temel adımı vardır: altıgeni çizme, yolları çizme ve taşı döndürme. Aşağıda, bu hedeflerin her birini nasıl gerçekleştirdiğim ayrıntılı olarak açıklanmıştır.

Altıgeni çizme

Entanglement'ın orijinal sürümünde, altıgeni çizmek için çeşitli kanvas çizim yöntemleri kullandım ancak oyunun mevcut biçiminde, bir sprite sayfasından kırpılan dokuları çizmek için drawImage() kullanılıyor.

Karo model sayfası
Kart model sayfası

Resimleri tek bir dosyada birleştirdim. Böylece, bu durumda on yerine sunucuya yalnızca bir istek gönderildi. Seçilen bir altıgeni kanvas üzerine çizmek için önce araçlarımızı (kanvas, bağlam ve resim) bir araya getirmemiz gerekir.

Bir kanvas oluşturmak için tek ihtiyacımız olan, HTML dokümanımızdaki canvas etiketidir. Örneğin:

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

Komut dosyamıza çekebilmemiz için bir kimlik veriyorum:

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

İkinci olarak, çizmeye başlayabilmemiz için kanvasın 2D bağlamını almamız gerekir:

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

Son olarak da resme ihtiyacımız var. Web sayfamızla aynı klasörde "tiles.png" adlı bir resim varsa şu adımları uygulayarak bu resmi alabilirsiniz:

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

Üç bileşene sahip olduğumuza göre, model sayfasından istediğimiz tek kenarlı prizmayı tuvale çizmek için ctx.drawImage() işlevini kullanabiliriz:

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

Bu örnekte, üst satırda soldan dördüncü altıgeni kullanıyoruz. Ayrıca, orijinaliyle aynı boyutta olacak şekilde sol üst köşedeki kanvas üzerine çizeceğiz. Altıgenlerin 400 piksel genişliğinde ve 346 piksel yüksekliğinde olduğu varsayıldığında, toplamda aşağıdaki gibi görünür:

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

Resmin bir kısmını tuvale başarıyla kopyaladık. Sonuç olarak şunu elde ettik:

Altıgen karo
Altıgen karo

Çizim yolları

Kanvasımıza altıgen çizdiğimize göre üzerine birkaç çizgi çizmek istiyoruz. Öncelikle, altıgen karoyla ilgili bazı geometrilere bakacağız. Her kenar boyunca uçlardan 1/4 uzaklıkta ve kenarların 1/2'si kadar birbirinden uzak olacak şekilde kenar başına iki çizgi ucu istiyoruz.

Altıgen karodaki çizgi uç noktaları
Altıgen karodaki çizgi uç noktaları

Ayrıca güzel bir eğri de istiyoruz. Bu nedenle, biraz deneme yanılma yaparak, her uç noktadaki kenardan dik bir çizgi oluşturduğumda, altıgenin belirli bir açısının etrafındaki her uç noktası çiftinin kesişiminin, söz konusu uç noktalar için güzel bir Bezier kontrol noktası oluşturduğunu buldum:

Altıgen karodaki kontrol noktaları
Altıgen karodaki kontrol noktaları

Şimdi hem uç noktaları hem de kontrol noktalarını, kanvas resmimize karşılık gelen bir Kartezyen düzlemde eşleştiriyoruz ve koda geri dönmeye hazırız. Basit tutmak için tek bir satırla başlayacağız. Sol üst uçtan sağ alt uca bir yol çizerek başlayacağız. Önceki altıgen resmimiz 400x346 olduğu için üst uç noktamız 150 piksel genişliğinde ve 0 piksel aşağıda olur. Kısaltması (150, 0) olur. Bu çizginin kontrol noktası (150, 86) olur. Alt kenar uç noktası (250, 346) ve kontrol noktası (250, 260) ile gösterilmiştir:

İlk bezier eğrisinin koordinatları
İlk Bezier eğrisi için koordinatlar

Koordinatlarımız hazır olduğu için artık çizmeye başlayabiliriz. ctx.beginPath() ile yeni bir başlangıç yapıp ardından şunları kullanarak ilk uç noktaya gideceğiz:

ctx.moveTo(pointX1,pointY1);

Ardından, ctx.bezierCurveTo() işlevini kullanarak çizgiyi aşağıdaki gibi çizebiliriz:

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

Çizginin güzel bir kenarlığı olmasını istediğimizden, her seferinde farklı bir genişlik ve renk kullanarak bu yolu iki kez çizeceğiz. Renk, ctx.strokeStyle mülkü kullanılarak, genişlik ise ctx.lineWidth kullanılarak ayarlanır. İlk çizginin çizimi şu şekilde görünür:

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

Artık ilk satırın kıvrımlı olduğu altıgen bir karomuz var:

Altıgen karoda tek çizgi
Altıgen karoda tek çizgi

Diğer 10 uç noktanın koordinatlarını ve ilgili Bezier eğrisi kontrol noktalarını girerek yukarıdaki adımları tekrarlayabilir ve aşağıdaki gibi bir karo oluşturabiliriz:

Tamamlanmış altıgen karo.
Tamamlanmış altıgen karo

Tuvali döndürme

Döşememizi oluşturduktan sonra, oyunda farklı yollar alınabilmesi için döşemeyi döndürebilmek isteriz. Bunu tuval kullanarak yapmak için ctx.translate() ve ctx.rotate() değerlerini kullanırız. Karonun kendi merkezi etrafında dönmesini istediğimizden ilk adımımız, kanvas referans noktasını altıgen karonun merkezine taşımaktır. Bunun için şunları kullanırız:

ctx.translate(originX, originY);

Burada originX, altıgen karonun genişliğinin yarısı, originY ise yüksekliğin yarısı olur. Böylece şunu elde ederiz:

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

Artık karoyu yeni merkez noktamızla döndürebiliriz. Altıgen altı kenara sahip olduğundan, onu Math.PI'nin 3'e bölünmüş bir katı kadar döndürmek isteriz. Basit tutarak tek bir saat yönünde dönüş yapacağız. Bunun için şunları kullanacağız:

ctx.rotate(Math.PI / 3);

Ancak, altıgenimiz ve çizgilerimiz başlangıç noktası olarak eski (0,0) koordinatlarını kullandığından, döndürme işlemini tamamladıktan sonra çizmeden önce geri çevirmek isteriz. Böylece, toplamda şunu elde ederiz:

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

Yukarıdaki çeviri ve rotasyonu oluşturma kodumuzdan önce koyduğumuzda, kod artık döndürülmüş karoyu oluşturur:

Döndürülmüş altıgen karo
Döndürülmüş altıgen karo

Özet

Yukarıda, HTML5'in tuval etiketini kullanarak sunduğu özelliklerden birkaçını (ör. resim oluşturma, Bezier eğrileri çizme ve tuvali döndürme) vurguladım. HTML5 kanvas etiketini ve JavaScript çizim araçlarını Entanglement için kullanmak keyifli bir deneyim oldu. Diğerlerinin bu açık ve yeni teknolojiyle oluşturacağı birçok yeni uygulama ve oyunu sabırsızlıkla bekliyorum.

Kod Referansı

Yukarıda verilen tüm kod örnekleri, referans olarak aşağıda birleştirilmiştir:

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