Örnek Olay - HTML5 Tuvalinin İç Yüzünü Anlama

Derek Detweiler
Derek Detweiler

Giriş

Geçtiğimiz ilkbaharda (2010) HTML5 ve ilgili teknolojiler için hızla artan destek konusuyla ilgilendim. O dönemde bir arkadaşım ve ben, programlama ve geliştirme becerilerimizi geliştirmenin yanı sıra sürekli olarak birbirimize savurduğumuz oyun fikirlerini hayata geçirmek için iki haftalık oyun geliştirme yarışmalarında birbirimizle mücadele ediyorduk. Bu nedenle, doğal olarak HTML5 öğelerini yarışma girişlerime dahil etmeye başladım. Böylece bunların nasıl çalıştığını daha iyi anladım ve önceki HTML özelliklerini kullanarak neredeyse imkansız olan şeyleri yapabildim.

HTML5'teki pek çok yeni özellik arasında, tuval etiketi için artan destek bana JavaScript kullanarak etkileşimli sanat uygulamam için heyecan verici bir fırsat sundu ve bu da artık Entanglement olarak adlandırılan bir bulmaca oyununu uygulamayı denememe neden oldu. Yerleşim Göçmenleri çinilerinin arkasını kullanarak daha önce bir prototip oluşturmuştuk. Bunu bir tür planı olarak kullanarak HTML5 zeminindeki altıgen döşemeyi oluşturmak için üç temel parça vardır: altıgeni çizme, yolları çizme ve karoyu döndürme. Aşağıda, bunların her birini şu anki haliyle nasıl yaptığımı ayrıntılı bir şekilde özetleyeceğiz.

Altıgen Çizme

Entanglement oyununun orijinal sürümünde, altıgeni çizmek için birkaç tuval çizim yöntemi kullandım, ancak oyunun şu anki biçimi, bir model sayfasından kırpılmış dokuları çizmek için drawImage() özelliğini kullanıyor.

Karolar model sayfası
Karolar model sayfası

Resimleri tek bir dosyada birleştirdim. Böylece bu örnekte on yerine, sunucuya yalnızca bir istek gönderilmiş oluyor. Seçilen bir altıgeni tuvale çizmek için önce araçlarımızı toplamamız gerekir: tuval, bağlam ve resim.

Bir tuval oluşturmak için html dokümanımızda aşağıdaki gibi bir tuval etiketine ihtiyacımız var:

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

Komut dosyamıza alabilmemiz için buna bir kimlik veriyorum:

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

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

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

Son olarak, resme ihtiyacımız var. Web sayfamızla aynı klasörde "tiles.png" olarak adlandırılmışsa bunu şu şekilde bulabiliriz:

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

Artık üç bileşene sahip olduğumuza göre, model sayfasından zemine istediğimiz tek altıgeni çizmek için ctx.drawImage() 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, orijinalle aynı boyutta kalarak resmi sol üst köşedeki kanvasa çizeceğiz. Altıgenlerin 400 piksel genişliğinde ve 346 piksel yüksekliğinde olduğu varsayıldığında, resim 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ı kanvasa başarıyla kopyaladık. Sonuç olarak:

Altıgen karo
Altıgen karo

Yol Çizme

Artık altıgenimizi tuvale çizdiğimize göre, üzerine birkaç çizgi çizmek istiyoruz. İlk olarak, altıgen karoyla ilgili bazı geometriye göz atalım. Her bir kenarda her bir bitişin 1/4'ü olacak şekilde, her bir kenarın 1/4'ü ve kenarın bir/2'sinin birbirinden farklı olduğu iki çizgi sonu olmasını istiyoruz. Örneğin:

Altıgen karo üzerinde çizgi uç noktaları
Altıgen karo üzerinde çizgi uç noktaları

Ayrıca güzel bir eğri de isteriz. Deneme ve yanılma yaparak, her bir uç noktanın kenarından dik bir çizgi çizersem, altıgenin belirli bir açısını çevreleyen her bir uç nokta çiftinin kesişim noktasının, belirtilen uç noktalar için daha iyi bir kontrol noktası sağladığını keşfettim:

Altıgen karoda denetim noktaları
Altıgen karo üzerinde kontrol noktaları

Şimdi, hem uç noktaları hem de kontrol noktalarını tuval resmimize karşılık gelen bir Kartezyen düzlemde eşliyoruz ve koda geri dönmek için hazırız. Basit tutmak için tek satırla başlayacağız. Sol üstteki uç noktadan sağ alttaki uç noktaya doğru bir yol çizerek başlayacağız. Önceki altıgen resmimizin 400x346 olması, üst uç noktamızın 150 piksel ve 0 piksel aşağı (kısayol) olmasını sağlar. Denetim noktası (150, 86) olacaktır. Alt kenar uç noktası (250, 346) ve denetim noktası (250, 260):

İlk bezier eğrisinin koordinatları
İlk bezier eğrisinin koordinatları

Koordinatlarımız elimizdeyken çizime başlamak için hazırız. ctx.beginPath() ile en baştan başlayacak ve daha sonra, aşağıdakileri kullanarak ilk uç noktaya geçeceğiz:

ctx.moveTo(pointX1,pointY1);

Daha sonra, ctx.bezierCurveTo() işlevini aşağıdaki gibi kullanarak çizginin kendisini çizebiliriz:

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

Çizginin iyi bir kenarlığa sahip olmasını istediğimiz için, her defasında farklı bir genişlik ve renk kullanarak bu yolu iki kez geçireceğiz. Renk, ctx.stroStyle özelliği kullanılarak, genişlik ise ctx.lineWidth kullanılarak ayarlanır. Bütün olarak, ilk çizgi çizim ş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();

Şimdi, ilk çizgisi kıvrımlı olan altıgen bir karomuz var:

Altıgen karo üzerinde tek çizgi
Altıgen karo üzerinde tek çizgi

Diğer 10 uç noktanın koordinatlarının yanı sıra ilgili bezier eğrisi kontrol noktalarının koordinatlarını girerek yukarıdaki adımları tekrar edebilir ve aşağıdakine benzer bir karo oluşturabiliriz:

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

Tuvali Döndürme

Kutumuzu edindikten sonra, oyunda farklı yollar kullanabilmek için kutuyu döndürmek isteriz. Bunu tuval kullanarak yapmak için ctx.translate() ve ctx.rotate() değerlerini kullanıyoruz. Karonun merkezi etrafında dönmesini istediğimizden ilk adımımız tuval referans noktasını altıgen karonun merkezine taşımaktır. Bunun için aşağıdakileri kullanırız:

ctx.translate(originX, originY);

originX, altıgen karon genişliğinin yarısı, originY ise yüksekliğin yarısı kadar olur. Bu durumda bize şu değeri verir:

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

Artık karoyu yeni merkez noktamızla döndürebiliriz. Bir altıgenin altı kenarı olduğundan, Matematik.PI değerinin 3'e bölünmesinin birkaç katı kadar döndürmek isteriz. Bunu basit bir şekilde yapıp saat yönünde tek bir dönüşle devam edeceğiz:

ctx.rotate(Math.PI / 3);

Bununla birlikte, altıgen ve çizgilerimiz başlangıç noktası olarak eski (0,0) koordinatları kullandığından, döndürmeyi bitirdikten sonra çizimden önce geri çevirmek isteriz. Bu doğrultuda, artık elimizde şu var:

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

Yukarıdaki çeviriyi ve döndürmeyi oluşturma kodumuzdan önce getirirseniz, döndürülen kutucuk artık oluşturulur:

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 bazılarını (resim oluşturma, bezier eğrileri çizme ve kanvası döndürme gibi) vurguladım. HTML5 tuval etiketini ve Entanglement için JavaScript çizim araçlarını kullanmak keyifli bir deneyim oldu. Başkalarının bu açık ve yeni çıkan teknolojiyle oluşturduğu birçok yeni uygulama ve oyunu sabırsızlıkla bekliyorum.

Kod Referansı

Yukarıda verilen tüm kod örnekleri aşağıda referans olarak bir araya getirilmiş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();