जावास्क्रिप्ट और कैनवास के साथ खुद का खेल

छवि इतनी देर पहले, मैं उत्सुक हो गया कि आधुनिक ब्राउज़र HTML5 का समर्थन कैसे करते हैं और मुझे इससे बेहतर नहीं मिला।
सबसे सरल 2 डी platformer लिखने की तुलना में। एक खिलौना विकसित करने और जावास्क्रिप्ट का उपयोग करने में कौशल को सुधारने की खुशी के अलावा, श्रमसाध्य काम के दौरान एक निश्चित अनुभव प्राप्त हुआ और मुख्य रेक अनुभवजन्य रूप से पाया गया, जिनमें से कई को मुझे आगे बढ़ना था। इस लेख में मैं संक्षेप में और उदाहरणों के साथ यह बताने की कोशिश करूँगा कि मैंने जो काम किया था, उसके लिए मैंने क्या सीखा। यदि आप अपना स्वयं का उच्च-प्रदर्शन जावास्क्रिप्ट एप्लिकेशन बनाना चाहते हैं जो ग्राफिक्स के साथ कुशलता से काम करता है, तो कृपया, बिल्ली का उपयोग करें।

सामान्य टिप्पणी


प्लेटफ़ॉर्म संसाधनों के लिए जावास्क्रिप्ट कोड बहुत महत्वपूर्ण है। इस तथ्य के बावजूद कि लगभग सभी आधुनिक इंजनों ने जेएस कोड की व्याख्या करना बंद कर दिया है, इसके निष्पादन की गति अभी भी "देशी" कोड की गति से बहुत अधिक हीन है। इस बीच, यहां तक ​​कि सबसे सरल गेम बहुत सारे कोड हैं जिनके पास एनीमेशन के दो आसन्न फ्रेम प्रदान करने के बीच चलने का समय होना चाहिए। इसके अलावा, JS एक बहुत ही विशिष्ट भाषा है और इस पर कई कोड लिखना मुश्किलों से भरा है। सभी मिलकर जेएस पर आवेदन को उम्मीदों पर खरा उतरने और जल्दी निराश होने का कारण बन सकते हैं। मैं थोड़ा सा निष्कर्ष निकालने की कोशिश करूंगा कि मैं प्रयोगों के माध्यम से आया हूं।

1. अनुकूलता
यदि हमने विशेष रूप से HTML5 और कैनवस का उपयोग करने का फैसला किया है, तो आइए अब हम पुराने ब्राउज़रों के साथ संगतता के मुद्दों से परेशान नहीं होंगे - फिर भी उनके तहत कुछ भी काम नहीं करेगा। इस प्रकार, आप सुरक्षित रूप से ECMAScript 5 के मुख्य नवाचारों का उपयोग कर सकते हैं। दूसरी तरफ, अवमानना ​​के साथ IE6 जैसे अच्छे पुराने सॉफ़्टवेयर के उपयोगकर्ताओं को अपमानित न करें। यह सलाह दी जाती है कि वे हमारे अद्भुत एनीमेशन के बजाय एक अंजीर को एक ग्रे वर्ग क्यों देखें, इसके कारणों की सूचना दें। इस तत्व को करने के लिए, कैनवस और भाषा के निर्माण के लिए समर्थन का निदान करना पर्याप्त है

<canvas id="gameArea"> <span style="color:red">Your browser doesn't support HTML5 Canvas.</span> </canvas> <script type="text/javascript"> (function(){ if(typeof ({}.__defineGetter__) != "function" && typeof (Object.defineProperty) != "function") alert("Your browser doesn't support latest JavaScript version.");})() </script> 

सब कुछ ठीक हो जाएगा, लेकिन परेशानी यह है, क्रॉस-ब्राउज़र संगतता की समस्याओं का पूरी तरह से हल नहीं किया गया है। आधुनिक ब्राउज़रों में, मानक कार्यों के नाम पर कोई सहमति नहीं है। जिस तरह से बाहर या तो पूरी तरह से उनके उपयोग को छोड़ देना है, या महंगा एडेप्टर बनाना है। उदाहरण के लिए, मैं खुद को संपत्ति के विवरणकों के उपयोग से इनकार नहीं कर सकता था और इसके नकारात्मक परिणाम थे। उनका उपयोग कैसे करें क्रॉस-ब्राउज़र को यहां और यहां अच्छी तरह से वर्णित किया गया है । लेकिन उन्हें जल्दी से कैसे काम किया जाए यह एक रहस्य बना हुआ है।

2. कोड अनुकूलन को तोड़ना आसान है
इतना समय पहले नहीं क्रोमियम के लिए V8 इंजन के बारे में एक बहुत ही उपयोगी लेख Habré पर फिसल गया। सबसे महत्वपूर्ण बात जो मैंने खुद के लिए सीखने में कामयाब रही, उनके साथ काम करने के लिए छिपी हुई कक्षाएं और कोड अनुकूलन है। दरअसल, जेएस अपने निर्माण के बाद अक्सर किसी वस्तु की संरचना को बदलने के लिए उकसाता है। यदि लक्ष्य तेजी से और आसानी से बनाए रखा कोड बनाना है तो ऐसा न करें। जैसे ही मुझे यह एहसास हुआ, खेल पर काम और अधिक मजेदार हो गया, और कोड क्लीनर और तेज हो गया।

 function myObject() { }; var mo = new myObject(); mo.id = 12; //    //     . var v; v = 12; //,  var v = 12; v = “12”; //  ,        var v = 15; //  ,      

आपको चर के दायरे को कम से कम करने के लिए प्रयास करने की आवश्यकता है - इससे कोड अनुकूलन की संभावना बढ़ जाती है।

3. जेएस में कोई वर्ग, वंशानुक्रम या अन्य कक्षा उन्मुख प्रोग्रामिंग नहीं हैं।
आपको प्रोटोटाइप का उपयोग करके कक्षाएं लागू करने से इंजन को तनाव नहीं देना चाहिए - लाभ संदिग्ध है, और कोड काफी धीमा हो जाता है (ओपेरा)! परिष्कृत प्रोटोटाइप वंशानुक्रम और वारिस के लिए बुनियादी कार्यक्षमता का एक ईमानदारी से व्यवस्थित हस्तांतरण पहले से ही सबसे अच्छा अनुकूलन नहीं लाता है।

4. हम जो उपयोग करते हैं, उसके लिए भुगतान करते हैं
एक गेम या किसी अन्य संसाधन-गहन अनुप्रयोग के विकास की प्रक्रिया में, आपको अनिवार्य रूप से "महंगी" संसाधनों को कैश करना होगा, उदाहरण के लिए, गणना की गई एनिमेशन या गतिशील रूप से भरी हुई स्क्रिप्ट। किसी भी संसाधन में एक जीवनकाल होता है, जिसके बाद इसकी आवश्यकता नहीं होती है। और यहाँ यह सही ढंग से छुटकारा पाने के लिए महत्वपूर्ण है।

 var resCache = { res1 : new getCostlyResource() }//      resCache.res1 = null; 

सबसे अधिक संभावना है, मेमोरी को कचरा कलेक्टर (जीसी) द्वारा मुक्त नहीं किया जाएगा। इसे किसी भी समय एकत्र किया जाएगा, और यह सबसे अनुचित होगा, क्योंकि जीसी इस बिंदु पर जमा होने वाले सभी कचरे को हटाने की कोशिश करेगा। यह पहले से ही बेहतर है:

 delete resCache.res1; resCache.res1 = null; //   

पहली नज़र में, कुछ भी जटिल नहीं है, लेकिन अधिक जटिल मामलों में, बारीकियां दिखाई देती हैं, और हटाने का व्यवहार हमेशा स्पष्ट नहीं होता है

5. निकटता और गुण - गति के दुश्मन
क्लोज़र एक कार्यात्मक भाषा की एक बुनियादी विशेषता है। ऐसा लगता है कि इस विशेष स्थान को जेएस इंजन द्वारा जितना संभव हो उतना अनुकूलित किया जाना चाहिए। लेकिन, अभ्यास से पता चलता है कि ऐसा नहीं है। यहां एक छोटा परीक्षण है जो ऑब्जेक्ट डेटा ( परीक्षण कोड ) तक पहुंचने के विभिन्न तरीकों के प्रदर्शन की तुलना करता है।
विभिन्न ब्राउज़रों और प्लेटफार्मों के लिए परिणाम (एमएस):
विंडोज एक्सपी (x86), कोर 2 डुओ, 3 गीगाहर्ट्ज़ओपेरा 12फ़ायरफ़ॉक्स 17क्रोम 23
कोई शॉर्ट सर्किट, वस्तु के क्षेत्रों तक सीधी पहुंच9617
कोई बंद नहीं, विधियों के माध्यम से डेटा तक पहुंच161128
क्लोजर, विधियों के माध्यम से पहुंच341223
क्लोजर, गुणों के माध्यम से पहुंच387899489
विंडोज 7 (x64), कोर i3-2100, 3.1 गीगाहर्ट्ज़ओपेरा 12क्रोम 23IE 10
कोई शॉर्ट सर्किट, वस्तु के क्षेत्रों तक सीधी पहुंच7515
कोई बंद नहीं, विधियों के माध्यम से डेटा तक पहुंच131113
क्लोजर, विधियों के माध्यम से पहुंच27914
क्लोजर, गुणों के माध्यम से पहुंच22231599
आश्चर्यजनक रूप से, ओपेरा दूसरों की तुलना में परीक्षण में बेहतर दिखता है। दुर्भाग्य से, समग्र निष्कर्ष निराशाजनक है, क्लोजर केवल क्रोम में अनुकूलित हैं, और संपत्तियों के माध्यम से पहुंच एक महान लक्जरी है जो किसी एप्लिकेशन के प्रदर्शन को परिमाण के क्रम से नीचा दिखा सकती है।

ग्राफिक्स इंजन नोट्स


जावास्क्रिप्ट में प्रोग्रामिंग ग्राफिक्स एक अलग बड़ा विषय है, जिसे बाद में मांग होने पर मैं छू सकता हूं। यहाँ मैंने कुछ सरल ट्रिक्स को उजागर करने की कोशिश की, जिनका उपयोग करना आसान था और उन्होंने एक अच्छा परिणाम दिया।

1. फ्रेम-बाय-फ्रेम एनीमेशन।
एनिमेशन बनाने के लिए दो दृष्टिकोण हैं: घटना-संचालित और समय-चूक। पहला मुख्य रूप से सरल कार्यों के लिए उपयुक्त है, जैसे बटन पर रोशनी जब आप माउस को घुमाते हैं और इसी हैंडलर में प्रदर्शन किया जा सकता है। दूसरा जटिल एनीमेशन कार्यों को करने के लिए उपयुक्त है, जो उपयोगकर्ता के कार्यों की परवाह किए बिना "अपना स्वयं का जीवन" जीना चाहिए, उदाहरण के लिए, गतिशील गेम।
गेम बनाते समय, फ्रेम दर (फ्रेम) की स्थिरता पर भरोसा करते हुए, गेम प्रक्रिया की गणना करना सबसे आसान (और कंप्यूटिंग संसाधनों के मामले में सस्ता) है। आप उन ब्राउज़र से एनीमेशन फ्रेम अनुरोध का उपयोग करने का प्रयास कर सकते हैं जो इसका समर्थन करते हैं। यह करना इतना आसान नहीं है, क्योंकि यह विधि वास्तविक मानक बन गई है और किसी कारणवश ECMAScript 5 में नहीं आई है।

 var raf = window.requestAnimationFrame || window.msRequestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame; //     : var myRedrawFunc = function () { /*     */ raf(myRedrawFunc) } raf(myRedrawFunc); 

RequestAnimationFrame का लाभ मुख्य रूप से एक अनलोड किए गए एनीमेशन के साथ है (जब ड्राइंग प्रक्रिया आधे फ्रेम से कम समय लेती है), यह एक अधिक चिकनी एनीमेशन के लिए अनुमति देता है और मोबाइल प्लेटफार्मों पर संसाधन की खपत को कम करता है। लेकिन वास्तव में ऐसा नहीं हो सकता है। इसके उपयोग के नुकसान में एक निश्चित फ्रेम दर (60 एफपीएस) और अगले फ्रेम की अवधि के लिए मुआवजे की कमी शामिल है अगर पिछले एक के प्रतिपादन में देरी हुई थी।
हालांकि, क्या होगा अगर raf === null? यह तब हो सकता है जब आपका एप्लिकेशन ओपेरा के हाथों में गिर गया, जो परंपरागत रूप से अपना रास्ता खुद करता है। फिर अच्छे पुराने सेटटाइमआउट हमारी मदद करेंगे। नतीजतन, कोड कुछ इस तरह दिखाई देगा:

 var fps = 60; var frameTimeCorrection = 0; var lastFrameTime = new Date(); var to = 1000/fps; var myRedrawFunc = function() { /*      */ var now_time = new Date(); frameTimeCorrection += now_time - lastFrameTime - to; if(frameTimeCorrection >= to) frameTimeCorrection = to - 1; //   lastFrameTime = now_time; if(raf) raf(redrawFunc); else setTimeout(myRedrawFunc, to - frameTimeCorrection); }; myRedrawFunc (); 

इस दृष्टिकोण का दोष स्पष्ट है - यदि फ्रेम गणना आपके द्वारा समायोजित किए जाने से अधिक धीमा हो जाती है, तो खेल प्रक्रिया वास्तविक समय में गणना करने के लिए बंद हो जाएगी। लेकिन आप एक चाल के लिए जा सकते हैं। एक नियम के रूप में, ब्रेक का कारण अगले फ्रेम के तत्वों को प्रदान कर रहा है, क्योंकि ब्राउज़र इंजन के लिए, यह एक ओवरहेड ऑपरेशन है। इसलिए, आप कोड को लिख सकते हैं ताकि मामले में जब समय सुधार विफल हो जाता है (स्थिति अगर (फ्रेमवर्क को सुधारने के लिए = = से शुरू हो रहा है), तो अगले फ्रेम में आप केवल गेम की दुनिया को फिर से परिभाषित किए बिना गणना कर सकते हैं। तथाकथित खेल में " अंतराल " धीमी गति की तुलना में कम कष्टप्रद लगता है।

2. हम केवल वही दिखाते हैं जो कैनवास पर दिखाई देता है।
वर्षों में सबसे सरल और सबसे सिद्ध एनीमेशन पद्धति स्प्राइट है । इस पद्धति की ख़ासियत यह है कि आंदोलन का भ्रम पैदा करने के लिए, उनके प्रतिपादन के निर्देशांकों को बदलकर खेल के स्थान पर कदम बढ़ाते हैं। एक नियम के रूप में, खेल स्थान फ्रेम रेंडरिंग क्षेत्र के आकार से काफी अधिक है, और यदि खेल स्थान बड़ा है, और बहुत सारे स्प्राइट उन्हें मूर्त समय का एक बहुत ले जाएगा। कैनवास संदर्भ विधियाँ DOM के तत्व हैं, और उन्हें एक्सेस करना बहुत महंगा है। अनुकूलन में से एक केवल वही दिखाई देना है जो फ्रेम में दिखाई देता है। दिए गए परीक्षण में , पहले 9000 स्मार्ट स्प्राइट्स कैनवास पर बनाए गए और प्रदर्शित किए गए हैं, जो कि रिड्रास्टिंग करते समय, उनके निर्देशांक का पालन करते हैं और यदि वे फ्रेम के बाहर हैं तो कैनवास के तरीकों का उपयोग न करें। फिर 9000 "मूर्खतापूर्ण" स्प्राइट्स बनाए जाते हैं जो गुंजाइश ( टेस्ट कोड ) की निगरानी नहीं करते हैं।
परीक्षा परिणाम (एफपीएस):
विंडोज एक्सपी (x86), कोर 2 डुओ, 3 गीगाहर्ट्ज़ओपेरा 12फ़ायरफ़ॉक्स 17क्रोम 23
स्मार्ट स्प्राइट473525
सिल्ली स्प्राइट्स151412
विंडोज 7 (x64), कोर i3-2100, 3.1 गीगाहर्ट्ज़ओपेरा 12क्रोम 23IE 10
स्मार्ट स्प्राइट563261
सिल्ली स्प्राइट्स191551
अंतर स्पष्ट है (और क्रोम ओह हमें फिर से निराश करते हैं - मिथक को मिटा दिया गया है)।

3. कैशे रैस्टराइजेशन
ग्राफिक्स इंजन का उपयोग करके रेखांकन करना एक अधिक संसाधन-गहन कार्य है। इसलिए, एक महत्वपूर्ण अनुकूलन स्मृति में रेखांकन के परिणामों को कैश करना है। सबसे आसान तरीका एक अलग कैनवास बनाना और इसमें वेक्टर ग्राफिक्स को तेज करना है। संचित परिणाम को प्रदर्शित करने के लिए पॉइंटर को कैश में और मुख्य ड्रॉइंग एरिया में रखें। वर्णन करने के लिए, 1000 पाठ स्प्राइट्स का रेखांकन करें। एक प्रदर्शन परीक्षण में , 800 पाठ स्प्राइट्स को 20 सेकंड के अंतराल पर वैकल्पिक रूप से खींचा जाता है। सबसे पहले, रैस्टराइजेशन के परिणाम को कैशिंग के साथ, फिर बिना कैशिंग ( टेस्ट कोड ) के।
परीक्षा परिणाम (एफपीएस):
विंडोज एक्सपी (x86), कोर 2 डुओ, 3 गीगाहर्ट्ज़ओपेरा 12फ़ायरफ़ॉक्स 17क्रोम 23
रेखांकन कैशिंग233260
कोई कैशिंग नहीं51247
विंडोज 7 (x64), कोर i3-2100, 3.1 गीगाहर्ट्ज़ओपेरा 12क्रोम 23IE 10
रेखांकन कैशिंग336161
कोई कैशिंग नहीं55623
इस दृष्टिकोण के साथ, मेमोरी, कैश फ्लशिंग आवृत्ति और रेखांकन गति के बीच संतुलन बनाना महत्वपूर्ण है। इसलिए, यदि पाठ गतिशील रूप से और काफी तीव्रता से बदलता है (जैसे, एनीमेशन के प्रत्येक 10 फ्रेम), तो कैशिंग यह केवल समग्र प्रदर्शन को खराब कर सकता है, क्योंकि कैशिंग ऑपरेशन अपने आप में रैस्टराइजेशन से अधिक ओवरहेड जोड़ देगा।

4. गतिशील संसाधन लोड हो रहा है
यदि एनीमेशन बिटमैप्स से स्प्राइट्स पर बनाया गया है, तो इससे पहले कि वे कैनवास पर खींचे जा सकें, आपको इन्हीं नक्शों को ब्राउजर इमेज कैश में लोड करना चाहिए। ऐसा करने के लिए, बस एक छवि तत्व बनाएं और स्रोत के रूप में छवि संसाधन यूआरएल पास करें। कठिनाई उस क्षण की प्रतीक्षा करना है जब ब्राउज़र छवि को अपने कैश में लोड करता है। ऐसा करने के लिए, आप ऑनलोड घटना का उपयोग कर सकते हैं, जिसमें पहले से डाउनलोड की गई तस्वीरों के काउंटर को बढ़ाएं। जैसे ही इस काउंटर का मूल्य डाउनलोड में जोड़े गए चित्रों की संख्या से मेल खाता है, संसाधन निरंतर हो जाएगा, और हम मुख्य गेम कोड को निष्पादित कर सकते हैं।

 function Cache() { var _imgs = {}; var _addedImageCount = 0; var _loadedImgsCount = 0; this.addSpriteSource = function(src) { var img = new Image(); img.onload = function() { _loadedImgsCount++; }; img.src = src; _imgs[src] = img; _addedImageCount++; } this.getLoadedImagePc() { return _loadedImgsCount * 100 / _addedImageCount; } this.getImage = function(src) { return _imgs[src]; } } //  Cache.addSpriteSource("img1.jpg"); Cache.addSpriteSource("img2.jpg"); //,    function waitImagesLoading() { var pc = Cache. getLoadedImagePc(); if(pc < 100) setTimeout(waitImagesLoading, 200); /*        */ } waitImagesLoading(); 

मेरे खिलौने में, मैंने प्रत्येक स्तर को एक अलग स्क्रिप्ट फ़ाइल के रूप में वर्णित करने का निर्णय लिया। यह स्पष्ट है कि ऐसी लिपियों का स्थैतिक लोडिंग हानिकारक है क्योंकि समय के प्रत्येक क्षण में उनमें से केवल एक की जरूरत होती है। समस्या का समाधान छवियों को डाउनलोड करने के मामले में उसी दृष्टिकोण से मदद की गई थी।
केवल एक चेतावनी है - स्क्रिप्ट ऑब्जेक्ट में कोई ईवेंट नहीं है, लेकिन यह कोई समस्या नहीं है, क्योंकि गतिशील रूप से भरी हुई स्क्रिप्ट के कोड में, आप कैश में वैश्विक स्क्रिप्ट पंजीकरण फ़ंक्शन सम्मिलित कर सकते हैं। फिर हम चित्रों को लोड करने के लिए समान रूप से आगे बढ़ते हैं - हम अतुल्यकालिक रूप से प्रतीक्षा करते हैं जब तक कि स्क्रिप्ट इसमें वर्णित प्रकारों को पंजीकृत नहीं करती है, और फिर हमारे लिए आवश्यक प्रकार के पंजीकृत प्रकार बनाएं।
उपयोगकर्ता को ऊब नहीं होने के लिए, आप सभी आवश्यक संसाधनों के लोडिंग का प्रतिशत दिखा सकते हैं।

5. आंशिक निर्देशांक
कैनवास पर रेखापुंज या वेक्टर प्राइमेटिव्स बनाते समय, आप आंशिक निर्देशांक और आकार निर्दिष्ट कर सकते हैं। नतीजतन, ब्राउज़र ग्राफिक इंजन स्क्रीन पर प्रदर्शित छवि को चिकना करने के लिए मजबूर किया जाता है। सरल बनाने के लिए, यह होता है क्योंकि स्क्रीन पर पिक्सेल के साथ रेखापुंजित छवि का आभासी पिक्सेल संयोग नहीं करेगा। नतीजतन, छवि चौरसाई एल्गोरिदम चालू हो जाएगी, जो प्रदर्शन को काफी प्रभावित कर सकती है।
प्रदर्शन परीक्षण में , वैकल्पिक रूप से 20 सेकंड के अंतराल के साथ, पूर्णांक और आंशिक निर्देशांक ( परीक्षण कोड ) के साथ छिड़का जाता है।
परीक्षा परिणाम (एफपीएस):
विंडोज एक्सपी (x86), कोर 2 डुओ, 3 गीगाहर्ट्ज़ओपेरा 12फ़ायरफ़ॉक्स 17क्रोम 23
पूर्णांक निर्देशांक और आयाम576060
आंशिक निर्देशांक और आयाम505260
विंडोज 7 (x64), कोर i3-2100, 3.1 गीगाहर्ट्ज़ओपेरा 12क्रोम 23IE 10
पूर्णांक निर्देशांक और आयाम606161
आंशिक निर्देशांक और आयाम606161
यहां यह समझाया जाना चाहिए कि 64-बिट प्लेटफॉर्म के मामले में, स्थिति को एक बेहतर ग्राफिक्स एडेप्टर द्वारा बचाया जाता है, जो स्पष्ट रूप से एंटी-एलियासिंग और एंटी-अलियासिंग का काम करता है। अपेक्षाकृत तेज़ी से बढ़ने वाले स्प्राइट्स (दसियों पिक्सेल प्रति सेकंड) के मामले में, आप पूरे निर्देशांक और आकारों के साथ कर सकते हैं। हालांकि, निर्देशांक और आयाम स्वयं को आंशिक मात्रा में माना जाना चाहिए ताकि मापदंडों के सुचारू परिवर्तन के साथ सटीकता न खोएं। यह दृष्टिकोण पूरी तरह से उचित है जब निर्देशांक और आकार के सभी मानों की गणना और गोलाई के बिना संग्रहीत की जाती है, और कैनवास पर सीधे आउटपुट से पहले, उन्हें Math.floor की मदद से गोल किया जाता है।

एक निष्कर्ष के बजाय।


जावास्क्रिप्ट के आधुनिक विकास, एचटीएमएल 5 और विभिन्न ब्राउज़रों द्वारा इन सुविधाओं का समर्थन पहले से ही उत्पादक इंटरैक्टिव ग्राफिक अनुप्रयोगों को लिखना संभव बनाता है, जो कि अधिकांश कार्यों में पारंपरिक फ्लैश प्रोग्रामिंग के लिए बाधाओं को देगा।

Source: https://habr.com/ru/post/In163373/


All Articles