কেস স্টাডি - বাউন্সি মাউস

ভূমিকা

বাউন্সি মাউস

গত বছরের শেষে আইওএস এবং অ্যান্ড্রয়েডে বাউন্সি মাউস প্রকাশ করার পরে, আমি কয়েকটি অত্যন্ত গুরুত্বপূর্ণ পাঠ শিখেছি। তাদের মধ্যে মূল ছিল যে একটি প্রতিষ্ঠিত বাজারে ভাঙা কঠিন। পুঙ্খানুপুঙ্খভাবে স্যাচুরেটেড আইফোন বাজারে, ট্র্যাকশন অর্জন করা খুব কঠিন ছিল; কম স্যাচুরেটেড অ্যান্ড্রয়েড মার্কেটপ্লেসে, অগ্রগতি সহজ ছিল, কিন্তু এখনও সহজ নয়। এই অভিজ্ঞতার প্রেক্ষিতে, আমি Chrome ওয়েব স্টোরে একটি আকর্ষণীয় সুযোগ দেখেছি। যদিও ওয়েব স্টোরটি কোনওভাবেই খালি নয়, এর উচ্চ-মানের HTML5-ভিত্তিক গেমগুলির ক্যাটালগ সবেমাত্র পরিপক্কতায় বৃদ্ধি পেতে শুরু করেছে। একজন নতুন অ্যাপ ডেভেলপারের জন্য, এর মানে হল র‌্যাঙ্কিং চার্ট তৈরি করা এবং দৃশ্যমানতা অর্জন করা অনেক সহজ। এই সুযোগের কথা মাথায় রেখে, আমি বাউন্সি মাউসকে HTML5-এ পোর্ট করার কথা স্থির করেছি এই আশায় যে আমি একটি উত্তেজনাপূর্ণ নতুন ব্যবহারকারীর কাছে আমার সাম্প্রতিক গেমপ্লে অভিজ্ঞতা প্রদান করতে পারব। এই কেস স্টাডিতে, আমি বাউন্সি মাউসকে HTML5-এ পোর্ট করার সাধারণ প্রক্রিয়া সম্পর্কে কিছু কথা বলব, তারপর আমি তিনটি ক্ষেত্রে আরও গভীরভাবে খনন করব যা আকর্ষণীয় প্রমাণিত হয়েছে: অডিও, পারফরম্যান্স এবং নগদীকরণ।

HTML5 এ একটি C++ গেম পোর্ট করা

বাউন্সি মাউস বর্তমানে Android(C++), iOS (C++), Windows Phone 7 (C#), এবং Chrome (Javascript) এ উপলব্ধ। এটি মাঝে মাঝে প্রশ্নটি প্ররোচিত করে: আপনি কীভাবে একটি গেম লিখবেন যা সহজেই একাধিক প্ল্যাটফর্মে পোর্ট করা যেতে পারে?। আমি অনুভব করি যে লোকেরা কিছু ম্যাজিক বুলেটের জন্য আশা করে যা তারা হ্যান্ড-পোর্টের আশ্রয় না নিয়ে এই স্তরের বহনযোগ্যতা অর্জন করতে ব্যবহার করতে পারে। দুঃখের বিষয়, আমি নিশ্চিত নই যে এই ধরনের একটি সমাধান এখনও বিদ্যমান রয়েছে (সবচেয়ে কাছের জিনিসটি সম্ভবত Google-এর প্লেএন ফ্রেমওয়ার্ক বা ইউনিটি ইঞ্জিন , কিন্তু এইগুলির কোনটিই আমার আগ্রহের সমস্ত লক্ষ্যগুলিকে আঘাত করে না)৷ আমার পদ্ধতি ছিল, আসলে, একটি হাত বন্দর. আমি প্রথমে C++ এ iOS/Android সংস্করণ লিখেছি, তারপর প্রতিটি নতুন প্ল্যাটফর্মে এই কোডটি পোর্ট করেছি। যদিও এটি অনেক কাজের মত শোনাতে পারে, WP7 এবং Chrome সংস্করণ প্রতিটি সম্পূর্ণ হতে 2 সপ্তাহের বেশি সময় নেয় না। তাহলে এখন প্রশ্ন হল, কোডবেসকে সহজে হ্যান্ড-পোর্টেবল করার জন্য কিছু করা যেতে পারে? আমি কিছু জিনিস করেছি যা এতে সাহায্য করেছে:

কোডবেস ছোট রাখুন

যদিও এটি সুস্পষ্ট মনে হতে পারে, এটি সত্যিই প্রধান কারণ আমি এত দ্রুত গেমটি পোর্ট করতে সক্ষম হয়েছি। বাউন্সি মাউসের ক্লায়েন্ট কোড C++ এর প্রায় 7,000 লাইন। কোডের 7,000 লাইন কিছুই নয়, তবে এটি পরিচালনাযোগ্য হওয়ার জন্য যথেষ্ট ছোট। ক্লায়েন্ট কোডের C# এবং জাভাস্ক্রিপ্ট উভয় সংস্করণই মোটামুটি একই আকারে শেষ হয়েছে। আমার কোডবেস ছোট রাখা মূলত দুটি মূল অনুশীলনের পরিমাণ: কোনো অতিরিক্ত কোড লিখবেন না এবং প্রাক-প্রসেসিং (নন-রানটাইম) কোডে যতটা সম্ভব করুন। কোনও অতিরিক্ত কোড না লেখা স্পষ্ট মনে হতে পারে, তবে এটি এমন একটি জিনিস যা আমি সর্বদা নিজের সাথে লড়াই করি। আমি প্রায়শই এমন কিছুর জন্য একটি হেল্পার ক্লাস/ফাংশন লেখার তাগিদ পাই যা একজন হেল্পারে পরিণত হতে পারে। যাইহোক, যতক্ষণ না আপনি আসলে একাধিকবার সাহায্যকারী ব্যবহার করার পরিকল্পনা করেন, এটি সাধারণত আপনার কোড ফুলিয়ে দেয়। বাউন্সি মাউসের সাহায্যে, আমি সতর্ক ছিলাম যে আমি অন্তত তিনবার এটি ব্যবহার না করা পর্যন্ত সাহায্যকারী লিখতে চাই না। যখন আমি একটি হেল্পার ক্লাস লিখেছিলাম, তখন আমি আমার ভবিষ্যতের প্রকল্পগুলির জন্য এটি পরিষ্কার, বহনযোগ্য এবং পুনরায় ব্যবহারযোগ্য করার চেষ্টা করেছি। অন্যদিকে, শুধুমাত্র বাউন্সি মাউসের জন্য কোড লেখার সময়, পুনঃব্যবহারের কম সম্ভাবনা সহ, আমার ফোকাস ছিল কোডিং কাজটি যতটা সহজ এবং দ্রুত সম্ভব সম্পন্ন করা, এমনকি এটি লেখার "সুন্দরতম" উপায় না হলেও কোড কোডবেস ছোট রাখার দ্বিতীয়, এবং আরও গুরুত্বপূর্ণ অংশটি ছিল যতটা সম্ভব প্রিপ্রসেসিং ধাপে ধাক্কা দেওয়া। আপনি যদি একটি রানটাইম টাস্ক নিতে পারেন এবং এটি একটি প্রিপ্রসেসিং টাস্কে নিয়ে যেতে পারেন, তবে আপনার গেমটি দ্রুত চলবে না, তবে আপনাকে প্রতিটি নতুন প্ল্যাটফর্মে কোডটি পোর্ট করতে হবে না। একটি উদাহরণ দেওয়ার জন্য, আমি মূলত আমার স্তরের জ্যামিতি ডেটা একটি মোটামুটি অপ্রসেসড ফর্ম্যাট হিসাবে সংরক্ষণ করেছি, রান টাইমে প্রকৃত OpenGL/WebGL ভার্টেক্স বাফারগুলিকে একত্রিত করে। এটি কিছুটা সেটআপ এবং রানটাইম কোডের কয়েকশ লাইন নিয়েছে। পরে, আমি এই কোডটিকে একটি প্রিপ্রসেসিং ধাপে নিয়ে গিয়েছিলাম, কম্পাইলের সময় সম্পূর্ণরূপে প্যাক করা OpenGL/WebGL ভার্টেক্স বাফারগুলি লিখেছিলাম। কোডের প্রকৃত পরিমাণ প্রায় একই ছিল, কিন্তু সেই কয়েকশ লাইনগুলি একটি প্রিপ্রসেসিং ধাপে স্থানান্তরিত হয়েছিল, যার অর্থ আমাকে কখনই সেগুলিকে কোনও নতুন প্ল্যাটফর্মে পোর্ট করতে হয়নি। বাউন্সি মাউসে এর প্রচুর উদাহরণ রয়েছে এবং যা সম্ভব তা গেম থেকে গেমে পরিবর্তিত হবে, তবে রানটাইমে ঘটতে হবে না এমন কিছুর জন্য কেবল নজর রাখুন।

আপনার প্রয়োজন নেই এমন নির্ভরতা গ্রহণ করবেন না

বাউন্সি মাউস সহজে পোর্ট করার আরেকটি কারণ হল এর প্রায় কোনো নির্ভরতা নেই। নিম্নলিখিত চার্টটি প্রতি প্ল্যাটফর্মে বাউন্সি মাউসের প্রধান লাইব্রেরি নির্ভরতাগুলির সংক্ষিপ্ত বিবরণ দেয়:

অ্যান্ড্রয়েড iOS HTML5 WP7
গ্রাফিক্স OpenGL ES OpenGL ES ওয়েবজিএল এক্সএনএ
শব্দ OpenSL ES OpenAL ওয়েব অডিও এক্সএনএ
পদার্থবিদ্যা বক্স2ডি বক্স2ডি Box2D.js Box2D.xna

যে প্রায় কাছাকাছি এটা. Box2D ছাড়া কোনো বড় 3য় পক্ষের লাইব্রেরি ব্যবহার করা হয়নি, যা সমস্ত প্ল্যাটফর্ম জুড়ে বহনযোগ্য। গ্রাফিক্সের জন্য, WebGL এবং XNA উভয়ই OpenGL এর সাথে প্রায় 1:1 মানচিত্র, তাই এটি একটি বড় সমস্যা ছিল না। শুধুমাত্র শব্দের ক্ষেত্রে প্রকৃত লাইব্রেরিগুলো ভিন্ন ছিল। যাইহোক, বাউন্সি মাউসে সাউন্ড কোড ছোট (প্ল্যাটফর্ম-নির্দিষ্ট কোডের প্রায় একশ লাইন), তাই এটি একটি বিশাল সমস্যা ছিল না। বাউন্সি মাউসকে বড় নন-পোর্টেবল লাইব্রেরি থেকে মুক্ত রাখার অর্থ হল রানটাইম কোডের যুক্তি সংস্করণগুলির মধ্যে প্রায় একই হতে পারে (ভাষা পরিবর্তন সত্ত্বেও)। অতিরিক্তভাবে এটি আমাদেরকে একটি নন-পোর্টেবল টুল চেইনে লক হওয়া থেকে বাঁচায়। আমাকে জিজ্ঞাসা করা হয়েছে যে ওপেনজিএল/ওয়েবজিএল-এর বিরুদ্ধে কোডিং সরাসরি Cocos2D বা ইউনিটির মতো লাইব্রেরি ব্যবহার করার তুলনায় জটিলতা বাড়ায় (সেখানে কিছু ওয়েবজিএল সহায়তাকারীও রয়েছে)। আসলে, আমি ঠিক বিপরীত বিশ্বাস করি। বেশিরভাগ মোবাইল ফোন / HTML5 গেম (অন্তত বাউন্সি মাউসের মতো) খুব সহজ। বেশিরভাগ ক্ষেত্রে, গেমটি শুধুমাত্র কয়েকটি স্প্রিট এবং সম্ভবত কিছু টেক্সচার্ড জ্যামিতি আঁকে। বাউন্সি মাউসে OpenGL-নির্দিষ্ট কোডের মোট যোগফল সম্ভবত 1000 লাইনের কম। আমি আশ্চর্য হব যদি একটি সহায়ক লাইব্রেরি ব্যবহার করে আসলে এই সংখ্যাটি হ্রাস করে। এমনকি যদি এটি এই সংখ্যাটিকে অর্ধেক করে ফেলে, তবে 500 লাইন কোড সংরক্ষণ করতে আমাকে নতুন লাইব্রেরি/সরঞ্জাম শেখার জন্য উল্লেখযোগ্য সময় ব্যয় করতে হবে। এর উপরে, আমি আগ্রহী এমন সমস্ত প্ল্যাটফর্ম জুড়ে পোর্টেবল পোর্টেবল সহকারী লাইব্রেরি খুঁজে পাইনি, তাই এই ধরনের নির্ভরতা গ্রহণ করা উল্লেখযোগ্যভাবে বহনযোগ্যতাকে ক্ষতিগ্রস্ত করবে। আমি যদি একটি 3d গেম লিখতাম যার জন্য লাইটম্যাপ, ডাইনামিক LOD, স্কিনড অ্যানিমেশন এবং আরও অনেক কিছুর প্রয়োজন হয়, তাহলে আমার উত্তর অবশ্যই পরিবর্তিত হবে। এই ক্ষেত্রে আমি ওপেনজিএল-এর বিরুদ্ধে আমার সম্পূর্ণ ইঞ্জিনকে হ্যান্ড-কোড করার চেষ্টা করার জন্য চাকাটি পুনরায় উদ্ভাবন করব। এখানে আমার বক্তব্য হল যে বেশিরভাগ মোবাইল/HTML5 গেমগুলি (এখনও) এই বিভাগে নেই, তাই প্রয়োজনীয় হওয়ার আগে জিনিসগুলিকে জটিল করার দরকার নেই।

ভাষার মধ্যে মিলকে অবমূল্যায়ন করবেন না

একটি শেষ কৌশল যা আমার C++ কোডবেসকে একটি নতুন ভাষায় পোর্ট করতে অনেক সময় বাঁচিয়েছিল তা হল উপলব্ধি যে বেশিরভাগ কোড প্রতিটি ভাষার মধ্যে প্রায় অভিন্ন। যদিও কিছু মূল উপাদান পরিবর্তিত হতে পারে, এইগুলি পরিবর্তন হয় না এমন জিনিসগুলির তুলনায় অনেক কম। আসলে, অনেক ফাংশনের জন্য, C++ থেকে জাভাস্ক্রিপ্টে যাওয়া কেবল আমার C++ কোডবেসে কয়েকটি নিয়মিত এক্সপ্রেশন প্রতিস্থাপন চালানো জড়িত।

পোর্টিং উপসংহার

এটি পোর্টিং প্রক্রিয়ার জন্য এটি মোটামুটি। আমি পরবর্তী কয়েকটি বিভাগে কয়েকটি HTML5 নির্দিষ্ট চ্যালেঞ্জের উপর স্পর্শ করব, তবে মূল বার্তাটি হল যে, আপনি যদি আপনার কোড সহজ রাখেন, তাহলে পোর্টিং একটি ছোট মাথাব্যথা হবে, দুঃস্বপ্ন নয়।

শ্রুতি

একটি এলাকা যা আমাকে (এবং আপাতদৃষ্টিতে অন্য সবাই) কিছু সমস্যা সৃষ্টি করেছিল তা হল অডিও। আইওএস এবং অ্যান্ড্রয়েডে, বেশ কয়েকটি কঠিন অডিও পছন্দ উপলব্ধ (ওপেনএসএল, ওপেনএএল), কিন্তু HTML5 এর বিশ্বে, জিনিসগুলি আরও খারাপ লাগছিল৷ HTML5 অডিও উপলব্ধ থাকাকালীন, আমি দেখেছি যে গেমগুলিতে ব্যবহার করার সময় এতে কিছু চুক্তি-ব্রেকিং সমস্যা রয়েছে। এমনকি নতুন ব্রাউজারগুলিতেও, আমি প্রায়শই অদ্ভুত আচরণের মধ্যে পড়ি। ক্রোম, উদাহরণস্বরূপ, আপনি তৈরি করতে পারেন এমন একযোগে অডিও উপাদানের সংখ্যার একটি সীমা আছে বলে মনে হচ্ছে। উপরন্তু, এমনকি যখন শব্দ বাজবে, এটি কখনও কখনও ব্যাখ্যাতীতভাবে বিকৃত হয়ে যাবে। সব মিলিয়ে আমি একটু চিন্তিত ছিলাম। অনলাইনে অনুসন্ধান করে জানা গেছে যে প্রায় প্রত্যেকেরই একই সমস্যা রয়েছে। আমি প্রাথমিকভাবে যে সমাধানটি অবতরণ করেছি তা ছিল সাউন্ড ম্যানেজার 2 নামক একটি API। এই APIটি উপলব্ধ হলে HTML5 অডিও ব্যবহার করে, জটিল পরিস্থিতিতে ফ্ল্যাশে ফিরে আসে। এই সমাধানটি কাজ করার সময়, এটি এখনও বগি এবং অপ্রত্যাশিত ছিল (শুধু HTML5 অডিওর চেয়ে কম)। লঞ্চ করার এক সপ্তাহ পরে, আমি গুগলের কিছু সহায়ক লোকের সাথে কথা বলেছিলাম, যারা আমাকে ওয়েবকিটের ওয়েব অডিও এপিআই-তে নির্দেশ করেছিল। আমি মূলত এই API ব্যবহার করার কথা বিবেচনা করেছিলাম, কিন্তু API-এর অপ্রয়োজনীয় (আমার জন্য) জটিলতার পরিমাণের কারণে এটি থেকে দূরে সরে গিয়েছিলাম। আমি শুধু কয়েকটি শব্দ বাজাতে চেয়েছিলাম: HTML5 অডিওর সাথে এটি জাভাস্ক্রিপ্টের কয়েকটি লাইনের সমান। যাইহোক, ওয়েব অডিওতে আমার সংক্ষিপ্ত চেহারায়, আমি এর বিশাল (70 পৃষ্ঠার) স্পেসিফিকেশন, ওয়েবে অল্প পরিমাণ নমুনা (একটি নতুন API-এর জন্য সাধারণ), এবং একটি "প্লে", "পজ" বাদ দিয়ে মুগ্ধ হয়েছিলাম। , বা স্পেকের যে কোন জায়গায় "স্টপ" ফাংশন। Google এর আশ্বাসের সাথে যে আমার উদ্বেগগুলি ভালভাবে প্রতিষ্ঠিত হয়নি, আমি আবার API এ খনন করেছি। আরও কয়েকটি উদাহরণ দেখার পরে এবং আরও কিছু গবেষণা করার পরে, আমি দেখতে পেলাম যে Google সঠিক ছিল-এপিআই অবশ্যই আমার চাহিদা মেটাতে পারে, এবং এটি অন্যান্য এপিআইগুলিকে আঘাত করে এমন বাগগুলি ছাড়াই এটি করতে পারে। ওয়েব অডিও API এর সাথে শুরু করা নিবন্ধটি বিশেষত সহজ, আপনি যদি এপিআই সম্পর্কে গভীরভাবে উপলব্ধি করতে চান তবে এটি যাওয়ার জন্য একটি দুর্দান্ত জায়গা। আমার আসল সমস্যা হল যে API বোঝার এবং ব্যবহার করার পরেও, এটি এখনও আমার কাছে একটি API এর মত মনে হচ্ছে যা "শুধু কয়েকটি শব্দ বাজাতে" ডিজাইন করা হয়নি। এই দুশ্চিন্তা দূর করার জন্য, আমি একটি ছোট সহায়ক শ্রেণী লিখেছিলাম যা আমাকে API ব্যবহার করতে দেয় যেভাবে আমি চেয়েছিলাম-বাজানো, বিরতি দেওয়া, থামানো এবং একটি শব্দের অবস্থা জানতে। আমি এই সাহায্যকারী ক্লাস AudioClip কল. Apache 2.0 লাইসেন্সের অধীনে GitHub-এ সম্পূর্ণ উৎস পাওয়া যায় এবং আমি নীচের ক্লাসের বিশদ বিবরণ নিয়ে আলোচনা করব। কিন্তু প্রথমে, ওয়েব অডিও API-তে কিছু পটভূমি:

ওয়েব অডিও গ্রাফ

HTML5 অডিও এলিমেন্টের তুলনায় ওয়েব অডিও এপিআইকে আরও জটিল (এবং আরও শক্তিশালী) করে তোলে প্রথম জিনিসটি হল ব্যবহারকারীর কাছে আউটপুট করার আগে অডিও প্রক্রিয়া/মিশ্রিত করার ক্ষমতা। শক্তিশালী হলেও, যে কোনো অডিও প্লেব্যাকে একটি গ্রাফ জড়িত থাকার বিষয়টি সাধারণ পরিস্থিতিতে আরও জটিল করে তোলে। ওয়েব অডিও API-এর শক্তি চিত্রিত করতে, নিম্নলিখিত গ্রাফটি বিবেচনা করুন:

বেসিক ওয়েব অডিও গ্রাফ
বেসিক ওয়েব অডিও গ্রাফ

যদিও উপরের উদাহরণটি ওয়েব অডিও API এর শক্তি দেখায়, আমার দৃশ্যকল্পে আমার এই শক্তির বেশিরভাগের প্রয়োজন ছিল না। আমি শুধু একটি শব্দ বাজাতে চেয়েছিলাম. যদিও এটির জন্য এখনও একটি গ্রাফ প্রয়োজন, গ্রাফটি খুবই সহজ।

গ্রাফ সহজ হতে পারে

HTML5 অডিও এলিমেন্টের তুলনায় ওয়েব অডিও এপিআইকে আরও জটিল (এবং আরও শক্তিশালী) করে তোলে প্রথম জিনিসটি হল ব্যবহারকারীর কাছে আউটপুট করার আগে অডিও প্রক্রিয়া/মিশ্রিত করার ক্ষমতা। শক্তিশালী হলেও, যে কোনো অডিও প্লেব্যাকে একটি গ্রাফ জড়িত থাকার বিষয়টি সাধারণ পরিস্থিতিতে আরও জটিল করে তোলে। ওয়েব অডিও API-এর শক্তি চিত্রিত করতে, নিম্নলিখিত গ্রাফটি বিবেচনা করুন:

তুচ্ছ ওয়েব অডিও গ্রাফ
তুচ্ছ ওয়েব অডিও গ্রাফ

উপরে দেখানো তুচ্ছ গ্রাফটি একটি শব্দ বাজাতে, বিরতি দিতে বা থামানোর জন্য প্রয়োজনীয় সবকিছু সম্পন্ন করতে পারে।

কিন্তু গ্রাফ সম্পর্কে চিন্তা করা যাক না

গ্রাফটি বোঝার সময় চমৎকার, এটি এমন কিছু নয় যা আমি প্রতিবার শব্দ বাজানোর সাথে মোকাবিলা করতে চাই। অতএব, আমি একটি সাধারণ মোড়ক ক্লাস "AudioClip" লিখেছিলাম। এই শ্রেণীটি এই গ্রাফটি অভ্যন্তরীণভাবে পরিচালনা করে, কিন্তু একটি অনেক সহজ ব্যবহারকারী-মুখী API উপস্থাপন করে।

অডিওক্লিপ
অডিওক্লিপ

এই ক্লাসটি একটি ওয়েব অডিও গ্রাফ এবং কিছু সহায়ক অবস্থা ছাড়া আর কিছুই নয়, তবে আমাকে প্রতিটি শব্দ চালানোর জন্য একটি ওয়েব অডিও গ্রাফ তৈরি করার চেয়ে অনেক সহজ কোড ব্যবহার করার অনুমতি দেয়।

// At startup time
var sound = new AudioClip("ping.wav");

// Later
sound.play();

বাস্তবায়নের বিবরণ

আসুন সাহায্যকারী ক্লাসের কোডটি দ্রুত দেখে নেওয়া যাক: কনস্ট্রাক্টর - কনস্ট্রাক্টর একটি XHR ব্যবহার করে সাউন্ড ডেটা লোড করার কাজ পরিচালনা করে। যদিও এখানে দেখানো হয়নি (উদাহরণটি সহজ রাখার জন্য), একটি HTML5 অডিও উপাদান উৎস নোড হিসাবেও ব্যবহার করা যেতে পারে। এটি বড় নমুনার জন্য বিশেষভাবে সহায়ক। মনে রাখবেন যে ওয়েব অডিও API-এর প্রয়োজন যে আমরা এই ডেটাটিকে "অ্যারেবাফার" হিসাবে আনব। একবার ডেটা প্রাপ্ত হলে, আমরা এই ডেটা থেকে একটি ওয়েব অডিও বাফার তৈরি করি (এটির মূল বিন্যাস থেকে এটিকে রানটাইম পিসিএম বিন্যাসে ডিকোড করে)।

/**
* Create a new AudioClip object from a source URL. This object can be played,
* paused, stopped, and resumed, like the HTML5 Audio element.
*
* @constructor
* @param {DOMString} src
* @param {boolean=} opt_autoplay
* @param {boolean=} opt_loop
*/
AudioClip = function(src, opt_autoplay, opt_loop) {
// At construction time, the AudioClip is not playing (stopped),
// and has no offset recorded.
this.playing_ = false;
this.startTime_ = 0;
this.loop_ = opt_loop ? true : false;

// State to handle pause/resume, and some of the intricacies of looping.
this.resetTimout_ = null;
this.pauseTime_ = 0;

// Create an XHR to load the audio data.
var request = new XMLHttpRequest();
request.open("GET", src, true);
request.responseType = "arraybuffer";

var sfx = this;
request.onload = function() {
// When audio data is ready, we create a WebAudio buffer from the data.
// Using decodeAudioData allows for async audio loading, which is useful
// when loading longer audio tracks (music).
AudioClip.context.decodeAudioData(request.response, function(buffer) {
    sfx.buffer_ = buffer;
    
    if (opt_autoplay) {
    sfx.play();
    }
});
}

request.send();
}

প্লে - আমাদের সাউন্ড বাজানোর দুটি ধাপ জড়িত: প্লেব্যাক গ্রাফ সেট আপ করা এবং গ্রাফের উৎসে "নোটঅন" এর একটি সংস্করণ কল করা। একটি উত্স শুধুমাত্র একবার প্লে করা যেতে পারে, তাই প্রতিবার যখন আমরা খেলি তখন আমাদের অবশ্যই উত্স/গ্রাফটি পুনরায় তৈরি করতে হবে। এই ফাংশনের বেশিরভাগ জটিলতা একটি বিরাম দেওয়া ক্লিপ পুনরায় শুরু করার প্রয়োজনীয়তা থেকে আসে ( this.pauseTime_ > 0 )। একটি বিরতি দেওয়া ক্লিপের প্লেব্যাক পুনরায় শুরু করতে, আমরা noteGrainOn ব্যবহার করি, যা একটি বাফারের একটি উপ-অঞ্চল চালানোর অনুমতি দেয়। দুর্ভাগ্যবশত, noteGrainOn এই দৃশ্যের জন্য পছন্দসই উপায়ে লুপ করার সাথে ইন্টারঅ্যাক্ট করে না (এটি উপ-অঞ্চল লুপ করবে, পুরো বাফার নয়)। অতএব, noteGrainOn দিয়ে ক্লিপটির বাকি অংশটি প্লে করে আমাদের এটিকে ঘিরে কাজ করতে হবে, তারপরে লুপিং সক্ষম করে শুরু থেকে ক্লিপটি পুনরায় চালু করতে হবে।

/**
* Recreates the audio graph. Each source can only be played once, so
* we must recreate the source each time we want to play.
* @return {BufferSource}
* @param {boolean=} loop
*/
AudioClip.prototype.createGraph = function(loop) {
var source = AudioClip.context.createBufferSource();
source.buffer = this.buffer_;
source.connect(AudioClip.context.destination);

// Looping is handled by the Web Audio API.
source.loop = loop;

return source;
}

/**
* Plays the given AudioClip. Clips played in this manner can be stopped
* or paused/resumed.
*/
AudioClip.prototype.play = function() {
if (this.buffer_ && !this.isPlaying()) {
// Record the start time so we know how long we've been playing.
this.startTime_ = AudioClip.context.currentTime;
this.playing_ = true;
this.resetTimeout_ = null;

// If the clip is paused, we need to resume it.
if (this.pauseTime_ > 0) {
    // We are resuming a clip, so it's current playback time is not correctly
    // indicated by startTime_. Correct this by subtracting pauseTime_.
    this.startTime_ -= this.pauseTime_;
    var remainingTime = this.buffer_.duration - this.pauseTime_;

    if (this.loop_) {
    // If the clip is paused and looping, we need to resume the clip
    // with looping disabled. Once the clip has finished, we will re-start
    // the clip from the beginning with looping enabled
    this.source_ = this.createGraph(false);
    this.source_.noteGrainOn(0, this.pauseTime_, remainingTime)

    // Handle restarting the playback once the resumed clip has completed.
    // *Note that setTimeout is not the ideal method to use here. A better 
    // option would be to handle timing in a more predictable manner,
    // such as tying the update to the game loop.
    var clip = this;
    this.resetTimeout_ = setTimeout(function() { clip.stop(); clip.play() },
                                    remainingTime * 1000);
    } else {
    // Paused non-looping case, just create the graph and play the sub-
    // region using noteGrainOn.
    this.source_ = this.createGraph(this.loop_);
    this.source_.noteGrainOn(0, this.pauseTime_, remainingTime);
    }

    this.pauseTime_ = 0;
} else {
    // Normal case, just creat the graph and play.
    this.source_ = this.createGraph(this.loop_);
    this.source_.noteOn(0);
}
}
}

সাউন্ড ইফেক্ট হিসাবে চালান - উপরের প্লে ফাংশনটি ওভারল্যাপ সহ একাধিকবার অডিও ক্লিপ চালানোর অনুমতি দেয় না (একটি দ্বিতীয় প্লেব্যাক শুধুমাত্র তখনই সম্ভব যখন ক্লিপটি শেষ বা বন্ধ হয়ে যায়)। কখনও কখনও একটি গেম প্রতিটি প্লেব্যাক সম্পূর্ণ হওয়ার জন্য অপেক্ষা না করে অনেকবার একটি শব্দ বাজাতে চাইবে (একটি খেলায় কয়েন সংগ্রহ করা ইত্যাদি)। এটি সক্ষম করতে, AudioClip ক্লাসে একটি playAsSFX() পদ্ধতি রয়েছে। যেহেতু একাধিক প্লেব্যাক একই সাথে ঘটতে পারে, তাই playAsSFX() থেকে প্লেব্যাক অডিওক্লিপের সাথে 1:1 আবদ্ধ নয়। অতএব, প্লেব্যাক বন্ধ করা যাবে না, বিরাম দেওয়া যাবে না বা রাজ্যের জন্য জিজ্ঞাসা করা যাবে না। লুপিংও অক্ষম, কারণ এইভাবে বাজানো একটি লুপিং শব্দ বন্ধ করার কোন উপায় নেই৷

/**
* Plays the given AudioClip as a sound effect. Sound Effects cannot be stopped
* or paused/resumed, but can be played multiple times with overlap.
* Additionally, sound effects cannot be looped, as there is no way to stop
* them. This method of playback is best suited to very short, one-off sounds.
*/
AudioClip.prototype.playAsSFX = function() {
if (this.buffer_) {
var source = this.createGraph(false);
source.noteOn(0);
}
}

স্টপ, পজ এবং ক্যোয়ারি করার অবস্থা - বাকি ফাংশনগুলি বেশ সোজা এবং খুব বেশি ব্যাখ্যার প্রয়োজন নেই:

/**
* Stops an AudioClip , resetting its seek position to 0.
*/
AudioClip.prototype.stop = function() {
if (this.playing_) {
this.source_.noteOff(0);
this.playing_ = false;
this.startTime_ = 0;
this.pauseTime_ = 0;
if (this.resetTimeout_ != null) {
    clearTimeout(this.resetTimeout_);
}
}
}

/**
* Pauses an AudioClip. The offset into the stream is recorded to allow the
* clip to be resumed later.
*/
AudioClip.prototype.pause = function() {
if (this.playing_) {
this.source_.noteOff(0);
this.playing_ = false;
this.pauseTime_ = AudioClip.context.currentTime - this.startTime_;
this.pauseTime_ = this.pauseTime_ % this.buffer_.duration;
this.startTime_ = 0;
if (this.resetTimeout_ != null) {
    clearTimeout(this.resetTimeout_);
}
}
}

/**
* Indicates whether the sound is playing.
* @return {boolean}
*/
AudioClip.prototype.isPlaying = function() {
var playTime = this.pauseTime_ +
            (AudioClip.context.currentTime - this.startTime_);

return this.playing_ && (this.loop_ || (playTime < this.buffer_.duration));
}

অডিও উপসংহার

আশা করি এই সহায়ক শ্রেণীটি আমার মতো একই অডিও সমস্যাগুলির সাথে সংগ্রামকারী বিকাশকারীদের জন্য দরকারী। এছাড়াও, ওয়েব অডিও API-এর আরও শক্তিশালী বৈশিষ্ট্যগুলির মধ্যে কিছু যোগ করার প্রয়োজন হলেও এই ধরনের একটি ক্লাস শুরু করার জন্য একটি যুক্তিসঙ্গত জায়গা বলে মনে হয়। যেভাবেই হোক, এই সমাধানটি বাউন্সি মাউসের চাহিদা পূরণ করেছে এবং গেমটিকে একটি সত্যিকারের HTML5 গেম হতে দিয়েছে, কোনো স্ট্রিং সংযুক্ত নেই!

কর্মক্ষমতা

জাভাস্ক্রিপ্ট পোর্টের ব্যাপারে আমাকে চিন্তিত আরেকটি ক্ষেত্র ছিল কর্মক্ষমতা। আমার পোর্টের v1 শেষ করার পরে, আমি দেখতে পেলাম যে আমার কোয়াড-কোর ডেস্কটপে সবকিছু ঠিকঠাক কাজ করছে। দুর্ভাগ্যবশত, একটি নেটবুক বা Chromebook-এ জিনিসগুলি ঠিকঠাক থেকে একটু কম ছিল৷ এই ক্ষেত্রে, আমার সমস্ত প্রোগ্রামের সময় ঠিক কোথায় ব্যয় করা হচ্ছে তা দেখিয়ে Chrome এর প্রোফাইলার আমাকে সংরক্ষণ করেছে। আমার অভিজ্ঞতা কোন অপ্টিমাইজেশান করার আগে প্রোফাইলিং এর গুরুত্ব তুলে ধরে। আমি আশা করছিলাম Box2D পদার্থবিদ্যা বা রেন্ডারিং কোডটি মন্থরতার একটি প্রধান উৎস হতে পারে; যাইহোক, আমার বেশিরভাগ সময় আমার Matrix.clone() ফাংশনে ব্যয় করা হয়েছিল। আমার গেমের গণিত-ভারী প্রকৃতির প্রেক্ষিতে, আমি জানতাম যে আমি প্রচুর ম্যাট্রিক্স তৈরি/ক্লোনিং করেছি, কিন্তু আমি কখনই আশা করিনি যে এটি বাধা হবে। শেষ পর্যন্ত, এটি দেখা গেল যে একটি খুব সাধারণ পরিবর্তন গেমটিকে তার CPU ব্যবহার 3x এর বেশি কম করতে দেয়, আমার ডেস্কটপে 6-7% CPU থেকে 2% এ চলে যায়। হয়তো জাভাস্ক্রিপ্ট ডেভেলপারদের কাছে এটা সাধারণ জ্ঞান, কিন্তু একজন C++ ডেভেলপার হিসেবে এই সমস্যাটি আমাকে অবাক করেছে, তাই আমি একটু বিস্তারিত জানাব। মূলত, আমার আসল ম্যাট্রিক্স ক্লাস ছিল একটি 3x3 ম্যাট্রিক্স: একটি 3 উপাদান অ্যারে, প্রতিটি উপাদানে একটি 3 উপাদান অ্যারে রয়েছে। দুর্ভাগ্যবশত, এর মানে হল যে যখন ম্যাট্রিক্স ক্লোন করার সময় ছিল, তখন আমাকে 4টি নতুন অ্যারে তৈরি করতে হয়েছিল। আমার শুধুমাত্র পরিবর্তন করা দরকার ছিল এই ডেটাটিকে একটি একক 9 উপাদান অ্যারেতে সরানো এবং সেই অনুযায়ী আমার গণিত আপডেট করা। এই একটি পরিবর্তনটি আমি দেখেছি এই 3x CPU হ্রাসের জন্য সম্পূর্ণরূপে দায়ী, এবং এই পরিবর্তনের পরে আমার সমস্ত পরীক্ষা ডিভাইস জুড়ে আমার কর্মক্ষমতা গ্রহণযোগ্য ছিল।

আরও অপ্টিমাইজেশান

যদিও আমার পারফরম্যান্স গ্রহণযোগ্য ছিল, আমি এখনও কয়েকটি ছোটখাটো হেঁচকি দেখছিলাম। একটু বেশি প্রোফাইলিং করার পরে, আমি বুঝতে পেরেছিলাম যে এটি জাভাস্ক্রিপ্টের আবর্জনা সংগ্রহের কারণে। আমার অ্যাপ 60fps এ চলছিল, যার মানে প্রতিটি ফ্রেমে আঁকার জন্য মাত্র 16ms ছিল। দুর্ভাগ্যবশত, যখন একটি ধীরগতির মেশিনে আবর্জনা সংগ্রহ করা হয়, তখন তা কখনও কখনও ~10ms পর্যন্ত খেয়ে ফেলত। এর ফলে কয়েক সেকেন্ডের মধ্যে তোতলামি দেখা দেয়, কারণ একটি পূর্ণ ফ্রেম আঁকতে গেমটির প্রায় 16ms লাগে। আমি কেন এত আবর্জনা তৈরি করছিলাম সে সম্পর্কে আরও ভাল ধারণা পেতে, আমি Chrome এর হিপ প্রোফাইলার ব্যবহার করেছি। আমার হতাশার জন্য, এটি প্রমাণিত হয়েছে যে বিশাল সংখ্যাগরিষ্ঠ আবর্জনা (70% এর বেশি) Box2D দ্বারা উত্পন্ন হচ্ছে। জাভাস্ক্রিপ্টে আবর্জনা অপসারণ করা একটি কঠিন ব্যবসা, এবং Box2D পুনরায় লেখা প্রশ্নের বাইরে ছিল, তাই আমি বুঝতে পেরেছিলাম যে আমি নিজেকে একটি কোণে নিয়ে এসেছি। সৌভাগ্যবশত, আমার কাছে এখনও উপলব্ধ বইটির সবচেয়ে পুরানো কৌশলগুলির মধ্যে একটি ছিল: আপনি যখন 60fps হিট করতে পারবেন না, 30fps এ চালান৷ এটি মোটামুটিভাবে একমত যে একটি ধারাবাহিক 30fps এ দৌড়ানো একটি ছটফটে 60fps এ দৌড়ানোর চেয়ে অনেক ভালো। প্রকৃতপক্ষে আমি এখনও একটি অভিযোগ বা মন্তব্য পাইনি যে গেমটি 30fps এ চলে (যদি না আপনি দুটি সংস্করণ পাশাপাশি তুলনা করেন তবে এটি বলা সত্যিই কঠিন)। এই অতিরিক্ত 16ms প্রতি ফ্রেমের অর্থ হল যে এমনকি একটি কুশ্রী আবর্জনা সংগ্রহের ক্ষেত্রেও, ফ্রেমটি রেন্ডার করার জন্য আমার কাছে এখনও প্রচুর সময় ছিল। 30fps-এ চলার সময় আমি যে টাইমিং API ব্যবহার করছিলাম (ওয়েবকিটের চমৎকার অনুরোধ অ্যানিমেশনফ্রেম ) দ্বারা স্পষ্টভাবে সক্রিয় করা হয়নি, এটি খুব তুচ্ছ পদ্ধতিতে সম্পন্ন করা যেতে পারে। যদিও একটি সুস্পষ্ট API এর মতো মার্জিত নাও হতে পারে, 30fps এই জেনে সম্পন্ন করা যেতে পারে যে RequestAnimationFrame এর ব্যবধান মনিটরের VSYNC (সাধারণত 60fps) এর সাথে সারিবদ্ধ। এর মানে হল যে আমাদের অন্য প্রতিটি কলব্যাক উপেক্ষা করতে হবে। মূলত, আপনার যদি একটি কলব্যাক "টিক" থাকে যা প্রতিবার "রিকোয়েস্ট অ্যানিমেশনফ্রেম" ফায়ার করার সময় কল করা হয়, এটি নিম্নরূপ সম্পন্ন করা যেতে পারে:

var skip = false;

function Tick() {
skip = !skip;
if (skip) {
return;
}

// OTHER CODE
}

আপনি যদি অতিরিক্ত সতর্ক হতে চান, আপনার পরীক্ষা করা উচিত যে কম্পিউটারের VSYNC স্টার্টআপের সময় ইতিমধ্যেই 30fps-এ বা তার নিচে নেই এবং এই ক্ষেত্রে এড়িয়ে যাওয়া অক্ষম করুন৷ যাইহোক, আমি পরীক্ষিত কোনো ডেস্কটপ/ল্যাপটপ কনফিগারেশনে এটি এখনও দেখিনি।

বিতরণ এবং নগদীকরণ

একটি চূড়ান্ত ক্ষেত্র যা আমাকে বাউন্সি মাউসের ক্রোম পোর্ট সম্পর্কে অবাক করেছিল তা হল নগদীকরণ। এই প্রজেক্টে গিয়ে, আমি HTML5 গেমগুলিকে নতুন এবং আসন্ন প্রযুক্তি শেখার জন্য একটি আকর্ষণীয় পরীক্ষা হিসাবে কল্পনা করেছি। আমি যা বুঝতে পারিনি তা হল বন্দরটি অনেক বড় শ্রোতাদের কাছে পৌঁছাবে এবং নগদীকরণের জন্য উল্লেখযোগ্য সম্ভাবনা রয়েছে৷

বাউন্সি মাউস অক্টোবরের শেষে ক্রোম ওয়েব স্টোরে লঞ্চ করা হয়েছিল। Chrome ওয়েব স্টোরে প্রকাশ করার মাধ্যমে আমি মোবাইল প্ল্যাটফর্মে অভ্যস্ত হয়ে ওঠার জন্য আবিষ্কারযোগ্যতা, সম্প্রদায়ের ব্যস্ততা, র‌্যাঙ্কিং এবং অন্যান্য বৈশিষ্ট্যগুলির জন্য একটি বিদ্যমান সিস্টেমের সুবিধা নিতে সক্ষম হয়েছি। দোকানের নাগাল কত বিস্তৃত ছিল তা আমাকে অবাক করে দিয়েছিল। প্রকাশের এক মাসের মধ্যে আমি প্রায় চার লক্ষ ইনস্টলে পৌঁছেছি এবং ইতিমধ্যেই সম্প্রদায়ের ব্যস্ততা (বাগ রিপোর্টিং, প্রতিক্রিয়া) থেকে উপকৃত হয়েছি। আরেকটি বিষয় যা আমাকে অবাক করেছিল তা হল একটি ওয়েব-অ্যাপের নগদীকরণের সম্ভাবনা।

বাউন্সি মাউসের একটি সহজ নগদীকরণ পদ্ধতি রয়েছে - গেমের বিষয়বস্তুর পাশে একটি ব্যানার বিজ্ঞাপন। যাইহোক, গেমটির বিস্তৃত পরিধির পরিপ্রেক্ষিতে, আমি দেখেছি যে এই ব্যানার বিজ্ঞাপনটি উল্লেখযোগ্য আয় তৈরি করতে সক্ষম হয়েছে এবং এটির শীর্ষ সময়কালে, অ্যাপটি আমার সবচেয়ে সফল প্ল্যাটফর্ম, অ্যান্ড্রয়েডের সাথে তুলনীয় আয় তৈরি করেছে। এটিতে অবদান রাখার একটি কারণ হল যে HTML5 সংস্করণে দেখানো বৃহত্তর AdSense বিজ্ঞাপনগুলি Android এ দেখানো ছোট Admob বিজ্ঞাপনগুলির তুলনায় ইম্প্রেশন প্রতি উল্লেখযোগ্যভাবে বেশি আয় করে৷ শুধু তাই নয়, HTML5 সংস্করণে ব্যানার বিজ্ঞাপনটি অ্যান্ড্রয়েড সংস্করণের তুলনায় অনেক কম অনুপ্রবেশকারী, যা একটি পরিষ্কার গেমপ্লে অভিজ্ঞতার জন্য অনুমতি দেয়। সামগ্রিকভাবে আমি এই ফলাফল দ্বারা খুব pleasantly বিস্মিত ছিল.

সময়ের সাথে সাথে স্বাভাবিক আয়।
সময়ের সাথে সাথে স্বাভাবিক আয়

যদিও গেমটি থেকে আয় প্রত্যাশার চেয়ে অনেক ভাল ছিল, এটি লক্ষণীয় যে Chrome ওয়েব স্টোরের নাগাল এখনও অ্যান্ড্রয়েড মার্কেটের মতো আরও পরিণত প্ল্যাটফর্মের তুলনায় ছোট। যদিও বাউন্সি মাউস দ্রুত ক্রোম ওয়েব স্টোরে #9 সর্বাধিক জনপ্রিয় গেম পর্যন্ত শুট করতে সক্ষম হয়েছিল, প্রাথমিক প্রকাশের পর থেকে সাইটে নতুন ব্যবহারকারীদের আসার হার উল্লেখযোগ্যভাবে কমে গেছে। এটি বলেছিল, গেমটি এখনও স্থিরভাবে বৃদ্ধি পাচ্ছে, এবং প্ল্যাটফর্মটি কী বিকাশ করছে তা দেখে আমি উত্তেজিত!

উপসংহার

আমি বলব যে বাউন্সি মাউসকে ক্রোমে পোর্ট করা আমার প্রত্যাশার চেয়ে অনেক বেশি মসৃণ হয়েছে। কিছু ছোটখাট অডিও এবং পারফরম্যান্স সমস্যা ছাড়া, আমি দেখেছি যে Chrome একটি বিদ্যমান স্মার্টফোন গেমের জন্য একটি পুরোপুরি সক্ষম প্ল্যাটফর্ম। আমি যেকোন ডেভেলপারদের উৎসাহ দেব যারা অভিজ্ঞতা থেকে দূরে সরে যাচ্ছেন তারা এটিকে শট দিতে। পোর্টিং প্রক্রিয়ার পাশাপাশি নতুন গেমিং শ্রোতা উভয়ের সাথেই আমি খুব খুশি যে HTML5 গেমটি আমাকে সংযুক্ত করেছে। আপনার কোন প্রশ্ন থাকলে আমাকে একটি ইমেল অঙ্কুর নির্দ্বিধায়. অথবা শুধু নীচে একটি মন্তব্য ড্রপ করুন, আমি নিয়মিত ভিত্তিতে এগুলি পরীক্ষা করার চেষ্টা করব৷