ساخت بازی مبتنی بر مرورگر با Vanilla JS و CSS

توسعه برای وب این روزها می تواند طاقت فرسا به نظر برسد. انتخاب تقریباً بی نهایت غنی از کتابخانه ها و چارچوب ها برای انتخاب وجود دارد.
احتمالاً باید یک مرحله ساخت، کنترل نسخه و یک خط لوله توسعه نیز پیاده سازی کنید. همه قبل از اینکه یک خط کد بنویسید. یک پیشنهاد سرگرم کننده چطور؟ بیایید یک قدم به عقب برگردیم و به خود یادآوری کنیم که جاوا اسکریپت و CSS مدرن چقدر موجز و قدرتمند هستند، بدون نیاز به هیچ چیز اضافی براق.
علاقه مندید؟ پس با من بیایید، در سفری که فقط با استفاده از وانیلی JS و CSS یک بازی مبتنی بر مرورگر بسازید.
ایده
ما یک بازی حدس زدن پرچم خواهیم ساخت. به بازیکن یک پرچم و یک لیست چند گزینه ای از پاسخ ها ارائه می شود.
مرحله 1. ساختار اساسی
اول از همه، ما به فهرستی از کشورها و پرچم های مربوطه آنها نیاز داریم. خوشبختانه، ما میتوانیم از قدرت ایموجیها برای نمایش پرچمها استفاده کنیم، به این معنی که نیازی نیست خودمان آنها را منبع یا حتی بدتر از آن بسازیم. من این را به فرم JSON آماده کرده ام.
در ساده ترین حالت رابط کاربری یک ایموجی پرچم و پنج دکمه را نشان می دهد:
خط تیره ای از CSS که از شبکه استفاده می کند تا همه چیز و اندازه های نسبی را در مرکز قرار دهد تا از کوچکترین صفحه تا بزرگترین نمایشگر به خوبی نمایش داده شود.
اکنون یک کپی از شیم استارتر خود را بردارید، ما در تمام طول این کار را انجام خواهیم داد
آموزش
ساختار فایل پروژه ما به صورت زیر است:
step1 . html step2 . html js / data . json
helpers /
css / i /
در پایان هر بخش، پیوندی به کد ما در وضعیت فعلی وجود خواهد داشت.
مرحله 2. یک نمونه اولیه ساده
بیایید کرک کنیم. ابتدا باید فایل data.json خود را بگیریم.
async function loadCountries ( file ) { try { const response = await fetch ( file ) ; return await response . json ( ) ; } catch ( error ) { throw new Error ( error ) ; } }
loadCountries ( './js/data.json' ) . then ( ( data ) => { startGame ( data . countries ) } ) ;
اکنون که داده ها را در اختیار داریم، می توانیم بازی را شروع کنیم. کد زیر با سخاوتمندانه نظر داده شده است. چند دقیقه وقت بگذارید تا همه چیز را بخوانید و به آنچه در حال رخ دادن است رسیدگی کنید.
function startGame ( countries ) {
shuffle ( countries ) ;
let answer = countries . shift ( ) ;
let selected = shuffle ( [ answer , ... countries . slice ( 0 , 4 ) ] ) ;
document . querySelector ( 'h2.flag' ) . innerText = answer . flag ;
document . querySelectorAll ( '.suggestions button' ) . forEach ( ( button , index ) => { const countryName = selected [ index ] . name ; button . innerText = countryName ;
button . dataset . correct = ( countryName === answer . name ) ; button . onclick = checkAnswer ; } ) }
و چند منطق برای بررسی پاسخ:
function checkAnswer ( e ) { const button = e . target ; if ( button . dataset . correct === 'true' ) { button . classList . add ( 'correct' ) ; alert ( 'Correct! Well done!' ) ; } else { button . classList . add ( 'wrong' ) ; alert ( 'Wrong answer try again' ) ; } }
احتمالاً متوجه شده اید که تابع startGame
ما یک تابع shuffle را فراخوانی می کند. در اینجا یک پیاده سازی ساده از الگوریتم فیشر-یتس آورده شده است:
function shuffle ( array ) { var m = array . length , t , i ;
while ( m ) {
i = Math . floor ( Math . random ( ) * m -- ) ;
t = array [ m ] ; array [ m ] = array [ i ] ; array [ i ] = t ; }
return array ;
}
مرحله 3. کمی کلاس
وقت آن است که کمی خانه داری کنیم. کتابخانهها و چارچوبهای مدرن اغلب قوانین خاصی را اعمال میکنند که به اعمال ساختار در برنامهها کمک میکنند. همانطور که همه چیز شروع به رشد می کند این امر منطقی است و داشتن همه کدها در یک فایل به زودی کثیف می شود.
بیایید از قدرت ماژول ها برای حفظ کد، errm، ماژولار خود استفاده کنیم. فایل HTML خود را به روز کنید و اسکریپت درون خطی را با این جایگزین کنید:
< script type = " module " src = " ./js/step3.js " > </ script >
اکنون در js/step3.js میتوانیم کمکهای خود را بارگیری کنیم:
import loadCountries from "./helpers/loadCountries.js" ; import shuffle from "./helpers/shuffle.js" ;
حتماً توابع shuffle و loadCountries را به فایلهای مربوطه منتقل کنید.
توجه: در حالت ایده آل، ما data.json خود را نیز به عنوان یک ماژول وارد می کنیم، اما، متأسفانه، فایرفاکس از ادعاهای واردات پشتیبانی نمی کند.
همچنین باید هر تابع را با پیشفرض صادرات شروع کنید. به عنوان مثال:
export default function shuffle ( array ) { ...
ما همچنین منطق بازی خود را در یک کلاس Game کپسوله می کنیم. این به حفظ یکپارچگی داده ها کمک می کند و کد را ایمن تر و قابل نگهداری تر می کند. یک دقیقه وقت بگذارید و نظرات کد را بخوانید.
loadCountries ( 'js/data.json' ) . then ( ( data ) => { const countries = data . countries ; const game = new Game ( countries ) ; game . start ( ) ; } ) ;
class Game { constructor ( countries ) {
this . masterCountries = countries ;
this . DOM = { flag : document . querySelector ( 'h2.flag' ) , answerButtons : document . querySelectorAll ( '.suggestions button' ) }
this . DOM . answerButtons . forEach ( ( button ) => { button . onclick = ( e ) => { this . checkAnswer ( e . target ) ; } } )
}
start ( ) {
this . countries = shuffle ( [ ... this . masterCountries ] ) ;
const answer = this . countries . shift ( ) ;
const selected = shuffle ( [ answer , ... this . countries . slice ( 0 , 4 ) ] ) ;
this . DOM . flag . innerText = answer . flag ;
selected . forEach ( ( country , index ) => { const button = this . DOM . answerButtons [ index ] ;
button . classList . remove ( 'correct' , 'wrong' ) ; button . innerText = country . name ; button . dataset . correct = country . name === answer . name ; } ) ; }
checkAnswer ( button ) { const correct = button . dataset . correct === 'true' ;
if ( correct ) { button . classList . add ( 'correct' ) ; alert ( 'Correct! Well done!' ) ; this . start ( ) ; } else { button . classList . add ( 'wrong' ) ; alert ( 'Wrong answer try again' ) ; } } }
مرحله 4. امتیازدهی و صفحه نمایش بازی
بیایید سازنده بازی را به روز کنیم تا چندین دور را مدیریت کند:
class Game { constructor ( countries , numTurns = 3 ) { // number of turns in a game this.numTurns = numTurns ; ...
DOM ما باید به روز شود تا بتوانیم بازی را در حالت کنترل کنیم، یک دکمه پخش اضافه کنیم و امتیاز را نمایش دهیم.
< main > < div class = " score " > 0 </ div >
< section class = " play " > ... </ section >
< section class = " gameover hide " > < h2 > Game Over </ h2 > < p > You scored: < span class = " result " > </ span > </ p > < button class = " replay " > Play again </ button > </ section > </ main >
ما فقط بازی را روی بخش پنهان می کنیم تا زمانی که مورد نیاز باشد.
اکنون، ارجاعاتی را به این عناصر DOM جدید در سازنده بازی ما اضافه کنید:
this . DOM = { score : document . querySelector ( '.score' ) , play : document . querySelector ( '.play' ) , gameover : document . querySelector ( '.gameover' ) , result : document . querySelector ( '.result' ) , flag : document . querySelector ( 'h2.flag' ) , answerButtons : document . querySelectorAll ( '.suggestions button' ) , replayButtons : document . querySelectorAll ( 'button.replay' ) , }
ما همچنین روش شروع بازی خود را مرتب می کنیم و منطق نمایش کشورها را به یک روش جداگانه منتقل می کنیم. این کمک می کند تا همه چیز تمیز و قابل کنترل باشد.
start ( ) { this.countries = shuffle ( [...this.masterCountries] ) ; this.score = 0 ; this.turn = 0 ; this. updateScore ( ) ; this. showCountries ( ) ; }
showCountries ( ) { // get our answer const answer = this.countries. shift ( ) ; // pick 4 more countries , merge our answer and shuffle const selected = shuffle ( [answer , ...this.countries. slice ( 0 , 4 ) ] ) ;
// update the DOM , starting with the flag this.DOM.flag.innerText = answer.flag ; // update each button with a country name selected .forEach ( ( country , index ) = > { const button = this.DOM.answerButtons[index] ; // remove any classes from previous turn button.classList. remove ( 'correct' , 'wrong' ) ; button.innerText = country.name ; button.dataset.correct = country.name === answer.name ; } ) ;
}
nextTurn ( ) { const wrongAnswers = document. querySelectorAll ( 'button.wrong' ) .length ; this.turn += 1 ; if ( wrongAnswers === 0 ) { this.score += 1 ; this. updateScore ( ) ; }
if ( this .turn === this .numTurns ) { this. gameOver ( ) ; } else { this. showCountries ( ) ; } }
updateScore ( ) { this.DOM.score.innerText = this.score ; }
gameOver ( ) { this.DOM.play.classList. add ( 'hide' ) ; this.DOM.gameover.classList. remove ( 'hide' ) ; this .DOM .result .innerText = `$ { this.score } out of $ { this.numTurns } ` ; }
در انتهای متد سازنده Game، این کار را انجام خواهیم داد
برای کلیک روی دکمه(های) پخش مجدد گوش دهید. در
در صورت یک کلیک، با فراخوانی متد شروع مجددا راه اندازی می کنیم.
this . DOM . replayButtons . forEach ( ( button ) => { button . onclick = ( e ) => { this . start ( ) ; } } ) ;
در نهایت، بیایید یک خط تیره از سبک به دکمه ها اضافه کنیم، امتیاز را در موقعیت قرار دهیم و
کلاس .hide خود را اضافه کنید تا در صورت نیاز بازی را تغییر دهید.
button .correct { background : darkgreen ; color : #fff ; } button .wrong { background : darkred ; color : #fff ; }
.score { position : absolute ; top : 1 rem ; left : 50 % ; font-size : 2 rem ; } .hide { display : none ; }
پیشرفت! ما اکنون یک بازی بسیار ساده داریم.
هرچند کمی بی مزه است. بیایید به آن بپردازیم
در مرحله بعدی
از این مرحله کد کنید
مرحله 5. Bling را بیاورید!
انیمیشن های CSS روشی بسیار ساده و مختصر هستند
عناصر و رابط های استاتیک را زنده کنید.
فریم های کلیدی
به ما اجازه می دهد تا فریم های کلیدی یک دنباله انیمیشن را با تغییر تعریف کنیم
ویژگی های CSS این را برای لغزش لیست کشور ما روی صفحه و خاموش کردن صفحه در نظر بگیرید:
.slide-off { animation : 0.75 s slide-off ease-out forwards ; animation-delay : 1 s ; } .slide-on { animation : 0.75 s slide-on ease-in ; }
@keyframes slide-off { from { opacity : 1 ; transform : translateX ( 0 ) ; } to { opacity : 0 ; transform : translateX ( 50 vw ) ; } } @keyframes slide-on { from { opacity : 0 ; transform : translateX ( -50 vw ) ; } to { opacity : 1 ; transform : translateX ( 0 ) ; } }
می توانیم هنگام شروع بازی از افکت لغزشی استفاده کنیم…
start ( ) { // reset dom elements this.DOM.gameover.classList. add ( 'hide' ) ; this.DOM.play.classList. remove ( 'hide' ) ; this.DOM.play.classList. add ( 'slide-on' ) ; ... }
و در روش nextTurn
nextTurn ( ) { ... if ( this .turn === this .numTurns ) { this. gameOver ( ) ; } else { this.DOM.play.classList. remove ( 'slide-on' ) ; this.DOM.play.classList. add ( 'slide-off' ) ; } }
همچنین باید پس از بررسی پاسخ، متد nextTurn را فراخوانی کنیم. برای رسیدن به این هدف، روش checkAnswer را به روز کنید:
checkAnswer ( button ) { const correct = button.dataset.correct === 'true' ;
if ( correct ) { button.classList. add ( 'correct' ) ; this. nextTurn ( ) ; } else { button.classList. add ( 'wrong' ) ; } }
هنگامی که انیمیشن اسلاید آف تمام شد، باید آن را دوباره روی آن بکشیم و لیست کشورها را به روز کنیم. میتوانیم بر اساس طول انیمیشن، زمانبندی تعیین کنیم و این منطق را انجام دهیم. خوشبختانه، راه سادهتری برای استفاده از رویداد پایان انیمیشن وجود دارد:
// listen to animation end events // in the case of .slide-on , we change the card , // then move it back on screen this .DOM .play .addEventListener ( 'animationend' , ( e ) = > { const targetClass = e.target.classList ; if ( targetClass .contains ( 'slide-off' ) ) { this. showCountries ( ) ; targetClass. remove ( 'slide-off' , 'no-delay' ) ; targetClass. add ( 'slide-on' ) ; } } ) ;
از این مرحله کد کنید
مرحله 6. لمس نهایی
آیا اضافه کردن صفحه عنوان خوب نیست؟ به این ترتیب به کاربر کمی زمینه داده می شود و مستقیماً به بازی پرتاب نمی شود.
نشانه گذاری ما به این صورت خواهد بود:
< div class = " score hide " > 0 </ div >
< section class = " intro fade-in " > < h1 > Guess the flag </ h1 > < p class = " guess " >
</ p > < p > How many can you recognize? </ p > < button class = " replay " > Start </ button > </ section >
< section class = " play hide " > ...
بیایید صفحه معرفی را به بازی متصل کنیم.
ما باید یک مرجع به آن در عناصر DOM اضافه کنیم:
this . DOM = { intro : document . querySelector ( '.intro' ) , ... .
سپس هنگام شروع بازی به سادگی آن را پنهان کنید:
start ( ) {
this . DOM . intro . classList . add ( 'hide' ) ;
this . DOM . score . classList . remove ( 'hide' ) ; ...
همچنین، فراموش نکنید که یک ظاهر طراحی جدید را اضافه کنید:
section .intro p { margin-bottom : 2 rem ; } section .intro p .guess { font-size : 8 rem ; } .fade-in { opacity : 0 ; animation : 1 s fade-in ease-out forwards ; } @keyframes fade-in { from { opacity : 0 ; } to { opacity : 1 ; } }
حالا خوب نیست که امتیازی هم به بازیکن بر اساس امتیازش بدهیم؟ این بسیار آسان برای پیاده سازی است. همانطور که مشاهده می شود، در روش به روز شده gameOver:
const ratings = [ '
' , '
' , '
' , '
' , '
' , '
' , '
' , '
' , '
' , '
' , '
' ] ; const percentage = ( this . score / this . numTurns ) * 100 ;
const rating = Math . ceil ( percentage / ratings . length ) ;
this . DOM . play . classList . add ( 'hide' ) ; this . DOM . gameover . classList . remove ( 'hide' ) ;
this . DOM . gameover . classList . add ( 'fade-in' ) ; this . DOM . result . innerHTML = `
${ this . score } out of ${ this . numTurns }
Your rating: ${ this . ratings [ rating ] } ` ; }
یک لمس نهایی؛ یک انیمیشن خوب زمانی که بازیکن درست حدس بزند. برای دستیابی به این اثر می توانیم یک بار دیگر به انیمیشن های CSS روی بیاوریم.
button ::before { content : ' ' ; background : url ( ../i/star.svg ) ; height : 32 px ; width : 32 px ; position : absolute ; bottom : -2 rem ; left : -1 rem ; opacity : 0 ; } button ::after { content : ' ' ; background : url ( ../i/star.svg ) ; height : 32 px ; width : 32 px ; position : absolute ; bottom : -2 rem ; right : -2 rem ; opacity : 0 ; }
button { position : relative ; }
button .correct ::before { animation : sparkle .5 s ease-out forwards ; } button .correct ::after { animation : sparkle2 .75 s ease-out forwards ; }
@keyframes sparkle { from { opacity : 0 ; bottom : -2 rem ; scale : 0.5 } to { opacity : 0.5 ; bottom : 1 rem ; scale : 0.8 ; left : -2 rem ; transform : rotate ( 90 deg ) ; } }
@keyframes sparkle2 { from { opacity : 0 ; bottom : -2 rem ; scale : 0.2 } to { opacity : 0.7 ; bottom : -1 rem ; scale : 1 ; right : -3 rem ; transform : rotate ( -45 deg ) ; } }
ما از عناصر شبه ::before و ::after برای پیوست کردن تصویر پسزمینه (star.svg) استفاده میکنیم، اما با تنظیم opacity روی 0، آن را مخفی نگه میداریم. سپس با فراخوانی انیمیشن sparkle زمانی که دکمه نام کلاس درست باشد، فعال میشود. به یاد داشته باشید، زمانی که پاسخ صحیح انتخاب شد، ما قبلاً این کلاس را روی دکمه اعمال می کنیم.
از این مرحله کد کنید
جمع بندی و چند ایده اضافی
در کمتر از 200 خط جاوا اسکریپت (با نظر آزادانه)، ما یک جاوا اسکریپت کاملاً داریم
بازی کارآمد و سازگار با موبایل و نه یک وابستگی یا کتابخانه در چشم!
البته، ویژگی ها و پیشرفت های بی پایانی وجود دارد که می توانیم به بازی خود اضافه کنیم.
اگر به چالشی علاقه دارید در اینجا چند ایده وجود دارد:
افکت های صوتی اولیه را برای پاسخ های صحیح و نادرست اضافه کنید.
با استفاده از وب کارگران، بازی را به صورت آفلاین در دسترس قرار دهید
آمارهایی مانند تعداد بازیها، رتبهبندی کلی در ذخیرهسازی محلی و نمایش را ذخیره کنید
راهی برای به اشتراک گذاشتن امتیاز خود و به چالش کشیدن دوستان در رسانه های اجتماعی اضافه کنید.
خبرکاو
ارسال نظر