केस स्टडी - HTML5 कैनवस के साथ उलझना

शुरुआती जानकारी

पिछले वसंत (2010) में मैंने HTML5 और इससे जुड़ी टेक्नोलॉजी को तेज़ी से बढ़ रही सहायता में दिलचस्पी दिखाई. उस समय, मेरे दोस्त और मेरे दोस्त ने प्रोग्रामिंग और डेवलपमेंट का अपना हुनर निखारने के लिए दो हफ़्तों की गेम डेवलपमेंट प्रतियोगिताओं में एक-दूसरे को चुनौती दी थी. साथ ही, गेम के आइडिया को पूरा करने के साथ-साथ गेम के आइडिया को हकीकत में बदलने की कोशिश कर रही थी. इसलिए, मैंने स्वाभाविक रूप से अपनी प्रतिस्पर्धी एंट्री में HTML5 एलिमेंट को शामिल करना शुरू कर दिया, ताकि इस बात को बेहतर तरीके से समझा जा सके कि वे कैसे काम करते हैं और ऐसे काम कर पा रहे हैं जो एचटीएमएल की शुरुआती जानकारी का इस्तेमाल करना मुमकिन नहीं था.

HTML5 की कई नई सुविधाओं में से, कैनवस टैग के बढ़ते इस्तेमाल ने मुझे JavaScript का इस्तेमाल करके इंटरैक्टिव आर्ट को लागू करने का शानदार मौका दिया. इसकी वजह से मैंने एक पहेली गेम बनाया, जिसे अब Entangment भी कहते हैं. मैंने कैटन टाइल के सेटलर के पीछे का इस्तेमाल पहले ही एक प्रोटोटाइप बना लिया था, इसलिए इसे एक ब्लूप्रिंट के तौर पर इस्तेमाल करके, वेब प्ले के लिए HTML5 कैनवस पर हेक्सागॉन टाइल तैयार करने के तीन ज़रूरी हिस्से हैं: हेक्सागॉन बनाना, पाथ बनाना, और टाइल को घुमाना. नीचे इस बारे में विस्तार से बताया गया है कि मैंने इन सभी को अपने मौजूदा फ़ॉर्म में कैसे हासिल किया.

षट्भुज बनाना

Entangment के मूल वर्शन में, हेक्सागॉन बनाने के लिए मैंने कैनवस ड्रॉ के कई तरीकों का इस्तेमाल किया था. हालांकि, गेम के मौजूदा फ़ॉर्मैट में, स्प्राइट शीट से क्लिप की गई टेक्सचर को बनाने के लिए drawImage() का इस्तेमाल किया गया है.

टाइल स्प्राइट शीट
टाइल स्प्राइट शीट

मैंने इमेज को एक साथ मिलाकर एक फ़ाइल बना दी, ताकि सर्वर से सिर्फ़ एक अनुरोध हो, इस मामले में दस न हो. कैनवस पर चुने गए हेक्सगॉन बनाने के लिए, हमें सबसे पहले अपने टूल को इकट्ठा करना होगा: कैनवस, कॉन्टेक्स्ट, और इमेज.

कैनवस बनाने के लिए, हमें अपने एचटीएमएल दस्तावेज़ में सिर्फ़ कैनवस टैग की ज़रूरत होती है. जैसे:

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

मैं इसे आईडी देता/देती हूं, ताकि हम इसे अपनी स्क्रिप्ट में शामिल कर सकें:

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

दूसरा, हमें कैनवस के लिए 2D संदर्भ पाना होगा, ताकि हम ड्रॉइंग बनाना शुरू कर सकें:

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

आखिर में, हमें इमेज की ज़रूरत है. अगर हमारे वेब पेज वाले फ़ोल्डर में इसका नाम "tills.png" है, तो हम इसे यहां से मिल सकते हैं:

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

अब हमारे पास तीन कॉम्पोनेंट हैं. इसलिए, हम स्प्राइट शीट से कैनवस तक, किसी एक हेक्सगॉन को ड्रॉ करने के लिए ctx.drawImage() का इस्तेमाल कर सकते हैं:

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 हो और वे एक-दूसरे से अलग हों, जैसे:

हेक्सागोनल टाइल पर लाइन एंडपॉइंट
हेक्सागोनल टाइल पर लाइन एंडपॉइंट

हमें एक अच्छा कर्व भी चाहिए, इसलिए थोड़ा-बहुत ट्रायल और गड़बड़ी इस्तेमाल करके, मैंने यह पाया कि अगर मैं हर एंडपॉइंट पर किनारे से वर्टिकल लाइन बनाता हूं, तो दिए गए एंडपॉइंट के लिए किसी दिए गए ऐंगल के आस-पास मौजूद एंडपॉइंट के हर जोड़े से इंटरसेक्शन, दिए गए एंडपॉइंट के लिए एक अच्छा बेज़र कंट्रोल पॉइंट बनाता है:

हेक्सागोनल टाइल पर कंट्रोल पॉइंट
हेक्सागोनल टाइल पर कंट्रोल पॉइंट

अब हम एंडपॉइंट और कंट्रोल पॉइंट, दोनों को हमारी कैनवस इमेज से मेल खाने वाले कार्टीज़न प्लेन से मैप करते हैं और कोड पर वापस जाने के लिए तैयार हैं. इसे आसान रखने के लिए, हम एक लाइन से शुरुआत करेंगे. हम सबसे ऊपर बाएं एंडपॉइंट से सबसे नीचे दाएं एंडपॉइंट में एक पाथ बनाकर शुरुआत करेंगे. हमारे पिछले हेक्सागॉन इमेज का साइज़ 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. {2/}Style प्रॉपर्टी का इस्तेमाल करके सेट किया जाएगा और चौड़ाई को 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 एंडपॉइंट के लिए निर्देशांक और उनसे जुड़े बेज़ियर कर्व कंट्रोल पॉइंट डालकर, हम ऊपर दिए गए चरणों को दोहरा सकते हैं और कुछ इस तरह की टाइल बना सकते हैं:

हेक्सागोनल टाइल
पूरे हो चुके हेक्सागॉनल टाइल

कैनवस को घुमाना

टाइल मिलने के बाद, हम इसे इस तरह से मोड़ सकते हैं जब गेम में अलग-अलग रास्ते इस्तेमाल किए जा सकें. कैनवस का इस्तेमाल करके ऐसा करने के लिए, हम ctx.translate() और ctx.rotate() का इस्तेमाल करते हैं. हम चाहते हैं कि टाइल अपने बीच में घूम जाए, इसलिए हमारा पहला कदम है कैनवस रेफ़रंस पॉइंट को हेक्सागॉन टाइल के बीच में ले जाना. ऐसा करने के लिए, हम इन चीज़ों का इस्तेमाल करते हैं:

ctx.translate(originX, originY);

जहां OriginX, हेक्सागोनल टाइल की चौड़ाई का आधा होगा और उसकी शुरुआत की ऊंचाई का आधा हिस्सा होगा. इसका मतलब है कि:

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

अब हम अपने नए सेंटर पॉइंट से टाइल को घुमा सकते हैं. हेक्सागॉन के छह हिस्से होते हैं. इसलिए, हम इसे Math.PI के कुछ मल्टीपल से भाग देकर 3 से भाग देना चाहेंगे. हम इसे आसान रखेंगे और इनका इस्तेमाल करते हुए आपको घड़ी की दिशा में एक मोड़ देंगे:

ctx.rotate(Math.PI / 3);

हालांकि, हमारा हेक्सागॉन और लाइनें मूल के तौर पर पुराने (0,0) निर्देशांकों का इस्तेमाल कर रही हैं, इसलिए एक बार रोटेट करने के बाद, हम ड्रॉ करने से पहले पुराने (0,0) निर्देशांकों का इस्तेमाल करना चाहेंगे. कुल मिलाकर, अब हमारे पास है:

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

हमारे रेंडरिंग कोड से पहले ऊपर दिए गए अनुवाद और रोटेशन को लागू करने से, अब रोटेट की गई टाइल रेंडर हो जाएगी:

घुमाई गई हेक्सागोनल टाइल
घुमावदार हेक्सागोनल टाइल

खास जानकारी

ऊपर मैंने कैनवस टैग का इस्तेमाल करके HTML5 में मिलने वाली कुछ सुविधाओं को हाइलाइट किया है. इनमें इमेज रेंडर करना, बेज़ियर कर्व बनाना, और कैनवस को घुमाना शामिल है. Entangment के लिए HTML5 कैनवस टैग और इसके JavaScript ड्रॉइंग टूल का इस्तेमाल करना एक आनंददायक अनुभव था. साथ ही, मुझे उम्मीद है कि इस खुली और उभरती टेक्नोलॉजी की मदद से दूसरे लोग कई नए ऐप्लिकेशन और गेम बनाएंगे.

कोड रेफ़रंस

ऊपर दिए गए सभी कोड उदाहरण नीचे एक रेफ़रंस के तौर पर दिए गए हैं:

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