طراحی یک بازی ساده با استفاده از HTML5 - قسمت دوم و آخر
اچ تی ام الجاوا اسکریپتموبایلطراحی یک بازی ساده با استفاده از HTML5 - قسمت دوم و آخرمحمد حسین موسی زاده - 2013-02-19 11:50در قسمت قبل روند ساخت بازی را از کُدنویسی قالب اولیه HTML، تا ساخت آبجکت جاوااسکریپت Draw، برای ایجاد شکل های مورد نیاز در بازی پیش بردیم. در این مطلب کار را با به کارگیری رویدادهای لمسی (Touch Events) دنبال می کنیم. پس در ادامه مطلب با تیک تاک همراه باشید.قبل از ادامه کار، کُدهای تست آبجکت Draw را از انتهای تابع POP.init حذف کنید.۳. رویدادهای لمسیقسمت های مهم کُد زیر touchstart, touchmove و touchend هستند. وقتی از رویداد استاندارد click استفاده کنید، برای گرفتن مختصات دقیق محل کلیک از e.pageX و e.pageY استفاده می شود. اما رویدادهای لمسی کمی متفاوت هستند. رویدادهای لمسی حاوی آرایه touches هستند که علاوه بر مختصات محل لمس شدن صفحه نمایش، اطلاعات دیگری را در اختیار ما می دهد.نکته: Android دسترسی جاوااسکریپت به رویدادهای لمسی را از ورژن 4 به بعد پشتیبانی می کند.از ()e.preventDefault برای غیرفعال کردن اسکرول، زوم و هر عملیات لمسی دیگری در طی بازی استفاده می شود.کُد زیر را در تابع POP.init اضافه کنید.// listen for clickswindow.addEventListener('click', function(e) { e.preventDefault(); POP.Input.set(e);}, false); // listen for toucheswindow.addEventListener('touchstart', function(e) { e.preventDefault(); // the event object has an array // named touches; we just want // the first touch POP.Input.set(e.touches[0]);}, false);window.addEventListener('touchmove', function(e) { // we're not interested in this, // but prevent default behaviour // so the screen doesn't scroll // or zoom e.preventDefault();}, false);window.addEventListener('touchend', function(e) { // as above e.preventDefault();}, false);قطعا متوجه شده اید، کُد بالا اطلاعات رویداد لمسی را برای آبجکت input ارسال می کند، پس در ادامه به تعریف این آبجکت خواهیم پرداخت. برای این منظور کُد زیر را قبل از window.addEventListeners قرار دهید.POP.Input = { x: 0, y: 0, tapped :false, set: function(data) { this.x = (data.pageX - POP.offset.left) / POP.scale; this.y = (data.pageY - POP.offset.top) / POP.scale; this.tapped = true; }};نکته مهم: عناصر pageX و pageY مختصات محل کلیک یا تَپ (در گجت های لمسی) در صفحه نمایش را در اختیار ما قرار می دهند. اما اگر اندازه بوم (canvas) کوچک تر از صفحه نمایش (مرورگر) باشد چه اتفاقی می افتد؟ همانطور که قبلا گفته شد تابع POP.resize اندازه بوم را با تغییر مقیاس به اندازه مورد نظر ما تبدیل می کند. این موضوع سبب ایجاد آفسِت بالا و چپ نسبت به لبه های بالا و پایین صفحه نمایش می شود. بنابراین نیاز است که مقادیر pageX و pageY را از آفسِت ها کم کنیم و بر مقدار مقیاس تقسیم کنیم تا محل دقیق کلیک یا تَپ در بوم را در اختیار داشته باشیم.برای تعیین میزان آفسِت و مقیاس، کُدهای زیر را در تابع POP.resize بعد از POP.canvas.style.height قرار دهید.POP.scale = POP.currentWidth / POP.WIDTH;POP.offset.top = POP.canvas.offsetTop;POP.offset.left = POP.canvas.offsetLeft;برای تعیین مقادیر اولیه آفسِت ها و مقیاس کُدهای زیر را در ابتدای POP بعد از HEIGHT: 480 اضافه کنید.scale: 1,// the position of the canvas// in relation to the screenoffset: {top: 0, left: 0},حالا برای تست عملکرد تشخیص محل کلیک و تَپ، کُد زیر را در آبجکت input بعد از this.tapped = true اضافه کنید.POP.Draw.circle(this.x, this.y, 10, 'red');حالا بازی را در مرورگر خود باز کنید هرجا که کلیک یا تَپ کنید، درست در زیر انگشتتان باید یک دایره قرمز رنگ ایجاد شود.۴. حلقه (Loop) بازیهمانطور که می دانید در این بازی حباب هایی از پایین صفحه به سمت بالا حرکت می کنند. برای اینکه بتوانیم در دوره های زمانی مختلف این حباب ها را تولید کنیم می توانیم از setInterval استفاده کنیم. اما قصد داریم تابع جدید requestAnimationFrame را برای این منظور بکار بگیریم، گفته ها حاکی از این است که این تابع در مصرف باطری نسبت به setInterval بهینه تر عمل می کند. با وجود مزیت های requestAnimationFrame، مرورگر ها هنوز به صورت یک پارچه از این تابع پشتیبانی نمی کنند. به همین دلیل باید از پیشوندهای مخصوص مرورگرها برای بکارگیری این تابع استفاده کنیم.window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); };})();کُدهای بالا را در ابتدای تگ script و قبل از تعریف نام بازی قرار دهید.برای اینکه حلقه بازی به طور مداوم ادامه پیدا کند، کُد زیر را در انتهای تابع POP.init قرار می دهیم.POP.loop();برای تکمیل حلقه بازی باید چند تابع جدید به POP اضافه کنیم. بنابراین توابع زیر به آبجکت POP اضافه کنید. در ادامه به نحوه کارکرد و تکمیل این توابع خواهیم پرداخت.// this is where all entities will be moved// and checked for collisions, etc.update: function() { }, // this is where we draw all the entitiesrender: function() { POP.Draw.clear(); }, // the actual loop// requests animation frame,// then proceeds to update// and renderloop: function() { requestAnimFrame( POP.loop ); POP.update(); POP.render();}تابع POP.loop در انتهای POP.init صدا زده می شود و سپس توابع POP.update و POP.render اجرا خواهند شد. تابع ( requestAnimFrame( POP.loop باعث می شود که تابع POP.loop مرتبا اجرا شود (۶۰ فریم در ثانیه). توجه کنید که ما نباید نگران رویدادهای لمسی در حلقه بازی باشیم. در قسمت قبل گفته شد که به محض کلیک و یا تَپ شدن بازی رویداد مورد نظر را دریافت می کند.۵. روح بازیدر ابتدا آرایه entities را تعریف می کنیم. این آرایه وظیفه پیگیری تمام عناصر پویا (Dynamic) بازی، از جمله تماس های لمسی، حباب ها و ذرات حباب ها را بر عهده دارد.entities: [],کُد بالا را در ابتدای POP در کنار سایر متغییرها قرار دهید.حالا زمان آن رسیده است که تابع Touch را ایجاد کنیم، این تابع در محل کلیک و یا تَپ شدن یک دایره ایجاد می کند که بعد از چند لحظه محو می شود.POP.Touch = function(x, y) { this.type = 'touch'; // we'll need this later this.x = x; // the x coordinate this.y = y; // the y coordinate this.r = 5; // the radius this.opacity = 1; // initial opacity; the dot will fade out this.fade = 0.05; // amount by which to fade on each game tick this.remove = false; // flag for removing this entity. POP.update // will take care of this this.update = function() { // reduce the opacity accordingly this.opacity -= this.fade; // if opacity if 0 or less, flag for removal this.remove = (this.opacity 0) ? true : false; }; this.render = function() { POP.Draw.circle(this.x, this.y, this.r, 'rgba(255,0,0,'+this.opacity+')'); }; };تابع Touch در زمان فراخوانی خواص مختلفی را معرفی می کند. x , y به عنوان argument برای تابع ارسال می شوند، شعاع دایره مورد نظر (this.r) را ۵ پیکسل تعریف می کنیم. این آبجکت دو تابع دیگر را در درون خود جای داده است که از این توابع در حلقه اصلی بازی برای رسم دوایر و حذف آنها از آرایه entities استفاده خواهد شد.حالا زمان تکمیل تابع POP.update رسیده است.// POP.update functionupdate: function() { var i; // spawn a new instance of Touch // if the user has tapped the screen if (POP.Input.tapped) { POP.entities.push(new POP.Touch(POP.Input.x, POP.Input.y)); // set tapped back to false // to avoid spawning a new touch // in the next cycle POP.Input.tapped = false; } // cycle through all entities and update as necessary for (i = 0; i POP.entities.length; i += 1) { POP.entities[i].update(); // delete from array if remove property // flag is set to true if (POP.entities[i].remove) { POP.entities.splice(i, 1); } } },اگر POP.Input.tapped برابر true باشد آنگاه نمونه جدید POP.Touch به آرایه entities اضافه خواهد شد. سپس تابع update تمام عناصر entities را فراخوانی می کند و در نهایت اگر عنصری نشانه remove را داشته باشد از آرایه حذف می شود.اکنون تغییرات زیر را در تابع POP.render اعمال کنید.// POP.render functionrender: function() { var i; POP.Draw.rect(0, 0, POP.WIDTH, POP.HEIGHT, '#036'); // cycle through all entities and render to canvas for (i = 0; i POP.entities.length; i += 1) { POP.entities[i].render(); } },مانند تابع POP.update تابع render تمام عناصر آرایه entities را فراخوانی می کند و این عناصر اشکال را در بوم بازی رسم می کند.اکنون اگر بازی را در مرورگر خود اجرا کنید، با هر بار کلیک و یا تَپ، یک دایره کوچک قرمز رنگ در بوم بازی ایجاد می شود که به مرور زمان حذف می شود.اگر تا به اینجا تمام مراحل با موفقیت طی کرده اید زمان آن رسیده است که یک تابع برای ایجاد حباب های بازی ایجاد کنیم. این تابع را Bubble می نامیم.POP.Bubble = function() { this.type = 'bubble'; this.x = 100; this.r = 5; // the radius of the bubble this.y = POP.HEIGHT + 100; // make sure it starts off screen this.remove = false; this.update = function() { // move up the screen by 1 pixel this.y -= 1; // if off screen, flag for removal if (this.y -10) { this.remove = true; } }; this.render = function() { POP.Draw.circle(this.x, this.y, this.r, 'rgba(255,255,255,1)'); }; };تابع POP.Bubble بسیار شبیه به تابع Touch عمل می کند با این تفاوت که دایره های ایجاد شده به سمت بالا حرکت می کنند. این حرکت با به روز شدن مختص (y (this.y در تابع update حاصل می شود.قصد داریم که در حلقه اول بازی صد حباب تولید شود و در حلقه دوم این تعداد به طور تصادفی افزایش پیدا کنند. برای این منظور ابتدا کُد زیر را در قسمت تعریف متغییرهای بازی قرار دهید.// the amount of game ticks until// we spawn a bubblenextBubble: 100,سپس کُد زیر را در ابتدای تابع POP.update قرار دهید.// decrease our nextBubble counterPOP.nextBubble -= 1;// if the counter is less than zeroif (POP.nextBubble 0) { // put a new instance of bubble into our entities array POP.entities.push(new POP.Bubble()); // reset the counter with a random value POP.nextBubble = ( Math.random() * 100 ) + 100;}نتیجه این تغییرات تصویر زیر می باشد. (گاهی چند ثانیه طول می کشه تا اولین حباب ظاهر شود.)۶. قراردادن اجزا بازی در کنار همتا به اینجا توانسته ایم حباب های بازی را تولید کنیم و همچنین محل کلیک و یا تَپ کاربران را شناسایی کنیم. اما این پایان کار نیست چون تشخیص برخورد بین محل تَپ شدن و حباب های بازی امکانی است که ما در اختیار نداریم. برای این منظور می توانید از تابع ساده زیر که بر اساس هندسه نوشته شده است استفاده کنید.// this function checks if two circles overlapPOP.collides = function(a, b) { var distance_squared = ( ((a.x - b.x) * (a.x - b.x)) + ((a.y - b.y) * (a.y - b.y))); var radii_squared = (a.r + b.r) * (a.r + b.r); if (distance_squared radii_squared) { return true; } else { return false; }};اگر ما هر یک از حباب ها را لمس کنیم و یا بر روی آنها کلیک کنیم تابع بالا مقدار true در خروجی تحویل می دهد. این عمل با بررسی محل قرار گرفتن دو دایره نسبت به هم در تابع بالا صورت می پذیرد.کُدهای زیر را در ابتدای تابع POP.update قرار دهید، وظیفه این کُد این است که اگر یک حباب هدف انگشتان ما قرار گرفت آنرا از صفحه نمایش حذف کند. // at the start of POP.update, we set a flag for checking collisionsvar i,checkCollision = false; // we only need to check for a collision// if the user tapped on this game tick// and then incorporate into the main logicif (POP.Input.tapped) { POP.entities.push(new POP.Touch(POP.Input.x, POP.Input.y)); // set tapped back to false // to avoid spawning a new touch // in the next cycle POP.Input.tapped = false; checkCollision = true;}// cycle through all entities and update as necessaryfor (i = 0; i POP.entities.length; i += 1) { POP.entities[i].update(); if (POP.entities[i].type === 'bubble' && checkCollision) { hit = POP.collides(POP.entities[i], {x: POP.Input.x, y: POP.Input.y, r: 7}); POP.entities[i].remove = hit; } // delete from array if remove property // is set to true if (POP.entities[i].remove) { POP.entities.splice(i, 1); }}خب شاید فکر بدی نباشد که مانند همه بازی های دیگر با ثبت رکورد و عملکرد بازیکنان کمی هیجان آنرا بیشتر کنیم. برای این منظور ابتدا آرایه زیر را در ابتدای بازی تعریف کنید.// to track players's progressPOP.score = { taps: 0, hit: 0, escaped: 0, accuracy: 0},حالا کُد زیر را در تابع POP.Bubble قبل از ;this.remove = true قرار دهید. این کُد تعداد حباب هایی که از دست کاربر فرار کرده اند را ذخیره می کند. POP.score.escaped += 1; // update scoreهمچنین کُد زیر را در تابع POP.update قبل از ;POP.entities[i].remove = hit قرار دهید. این کًد تعداد حباب هایی که توسط کاربر منفجر شده اند را ذخیره می کند.if (hit) { POP.score.hit += 1;}برای ذخیره تعداد تَپ های کاربر کُد زیر را در تابع update و در دستور شرطی (if (POP.Input.tapped) قرار دهید.// keep track of taps; needed to // calculate accuracyPOP.score.taps += 1;برای محاسبه درصد موفقیت بازی کن باید تعداد حباب های منفجر شده را بر تعداد تمام تَپ ها تقسیم کنیم. برای این منظور از کُد زیر در انتهای تابع POP.update استفاده کنید.// calculate accuracyPOP.score.accuracy = (POP.score.hit / POP.score.taps) * 100;POP.score.accuracy = isNaN(POP.score.accuracy) ? 0 : ~~(POP.score.accuracy); // a handy way to round floatsحالا برای چاپ اطلاعات مربوط به عملکرد کاربران کُد زیر را در انتهای تابع POP.render قرار دهید.// display scoresPOP.Draw.text('Hit: ' + POP.score.hit, 20, 30, 14, '#fff');POP.Draw.text('Escaped: ' + POP.score.escaped, 20, 50, 14, '#fff');POP.Draw.text('Accuracy: ' + POP.score.accuracy + '%', 20, 70, 14, '#fff');تا به اینجا اساس بازی و منطق آن به پایان رسیده است. بازی را در مرورگر باز کنید. می بینید که نسبت به مرحله قبل تفاوت زیادی کرده است و بازی جذاب تر و سخت تر شده است.۷. پولیش و ریزه کاریمشاهده کردید که در طی چند ساعت یک نسخه اولیه از این بازی ساده تهیه شد، اما پولیش و ریزه کاری و پیاده سازی ایده های نو برای بهبود بازی می تواند هفته ها ویا ماه ها و یا حتی سالها به طول بیانجامد. در ادامه سعی داریم دو امکان کوچک برای زیبایی بیشتر به بازی اضافه کنیم. ذرات حباب های منفجر شده برای ایجاد ذرات حباب بعد از انفجار آنها، ابتدا تابع زیر را به بازی اضافه می کنید.POP.Particle = function(x, y,r, col) { this.x = x; this.y = y; this.r = r; this.col = col; // determines whether particle will // travel to the right of left // 50% chance of either happening this.dir = (Math.random() * 2 > 1) ? 1 : -1; // random values so particles do not // travel at the same speeds this.vx = ~~(Math.random() * 4) * this.dir; this.vy = ~~(Math.random() * 7); this.remove = false; this.update = function() { // update coordinates this.x += this.vx; this.y += this.vy; // increase velocity so particle // accelerates off screen this.vx *= 0.99; this.vy *= 0.99; // adding this negative amount to the // y velocity exerts an upward pull on // the particle, as if drawn to the // surface this.vy -= 0.25; // off screen if (this.y 0) { this.remove = true; } }; this.render = function() { POP.Draw.circle(this.x, this.y, this.r, this.col); }; };سپس باید چند ذره را به آرایه entities اضافه کنیم تا در محل مناسب آنها را نمایش دهد. برای این منظور تغییرات زیر را در تابع POP.update اعمال کنید. (بعد از خط hit = POP.collides(POP.entities[i],)if (hit) { // spawn an explosion for (var n = 0; n 5; n +=1 ) { POP.entities.push(new POP.Particle( POP.entities[i].x, POP.entities[i].y, 2, // random opacity to spice it up a bit 'rgba(255,255,255,'+Math.random()*1+')' )); } POP.score.hit += 1;}امواج با توجه به اینکه بازی در زیر آب انجام می شود. ایجاد امواج آب در بالای بازی می تواند باعث زیبایی بازی شود. این کار را می توانیم با کشیدن نیم دایره های متداخل انجام دهیم. برای این منظور ابتدا کُد زیر را به تابع POP.init اضافه کنید.// set up our wave effect;// basically, a series of overlapping circles// across the top of screenPOP.wave = { x: -25, // x coordinate of first circle y: -40, // y coordinate of first circle r: 50, // circle radius time: 0, // we'll use this in calculating the sine wave offset: 0 // this will be the sine wave offset}; // calculate how many circles we need to // cover the screen's widthPOP.wave.total = Math.ceil(POP.WIDTH / POP.wave.r) + 1;سپس کُدهای زیر را به تابع POP.update اضافه کنید.// update wave offset// feel free to play with these values for// either slower or faster wavesPOP.wave.time = new Date().getTime() * 0.002;POP.wave.offset = Math.sin(POP.wave.time * 0.8) * 5;تنها کار باقی مانده رسم این امواج است، برای این منظور کُدهای زیر را به تابع POP.render اضافه کنید.// display snazzy wave effectfor (i = 0; i POP.wave.total; i++) { POP.Draw.circle( POP.wave.x + POP.wave.offset + (i * POP.wave.r), POP.wave.y, POP.wave.r, '#fff'); }ایده های خلاقانهنردبان به شما تبریک می گوید، شما توانستید در طول چند ساعت یک بازی HTML5 و تحت وب ساده و قابل قبول بسازید. اما به خاطر داشته باشید که این بازی به پایان نرسیده است. ما برای توسعه این بازی برای شما دو پیشنهاد داریم که انجام آنها را برعهده شما می گذاریم.- دخیره بیشترین امتیازی که بازیکن بدست آورده است - اعمال محدودیت در تعداد حباب های مجاز و نمایش Game over ایده های خود را در بخش نظرات با دیگران به بحث بگذارید.دریافت کامل بازی برای دیدن قسمت اول اموزش کلیک کنید
+ نوشته شده در سه شنبه یکم اسفند ۱۳۹۱ ساعت 15:11 توسط تیک تاک
|
با عضویت در گروه تفریحی تیک تاک دیگر نیازی نیست