در این مقاله ، من توضیح می دهم که چگونه یک بازی JavaScript “Wheel of Fortune” ایجاد کردم تا جلسات آنلاین را از طریق بزرگنمایی در طول همه گیری جهانی کمی سرگرم کننده تر کنم.

همه گیری کنونی بسیاری از فعالیت های اجتماعی را مجبور به مجازی سازی کرده است. به عنوان مثال ، گروه اسپرانتو محلی ما اکنون برای جلسات مطالعه ماهانه زبان ، بصورت آنلاین (به جای مراجعه حضوری) ملاقات می کند. و بعنوان سازماندهنده گروه ، مجبور شدم به دلیل ویروس کرونا ، درباره بسیاری از فعالیتهای خود دوباره فکر کنم. پیش از این ، من می توانم تماشای یک فیلم ، یا حتی یک قدم زدن در پارک ، به ترکیبی از فعالیت های خود برای تلاش برای جلوگیری از خستگی اضافه کنم (تمرین های ثابت گرامر باعث تکرار حضور نمی شوند).

بازی جدید Wheel of Fortune ما با استقبال خوبی روبرو شد. البته ، SitePoint یک وبلاگ فنی است ، بنابراین من در جلسات آنلاین ما مروری بر آنچه در ساخت نسخه ابتدایی بازی برای اشتراک صفحه نمایش انجام شده است ، ارائه می دهم. من در مورد برخی از تجارت هایی که در این راه انجام دادم ، بحث خواهم کرد و همچنین برخی از امکانات برای بهبود و مواردی را که باید در نظر داشتم متفاوت انجام می دادم ، برجسته می کنم.

اول کار های مهم

اگر اهل ایالات متحده هستید ، احتمالاً قبلاً با آن آشنایی دارید چرخ روزگار، چون طولانی ترین نمایش بازی آمریکایی در طول تاریخ است. (حتی اگر در ایالات متحده نباشید ، احتمالاً با انواع مختلف نمایش آشنا هستید ، زیرا در بیش از 40 بازار بین المللی اقتباس و پخش شده است.) این بازی اساساً Hangman است: شرکت کنندگان سعی می کنند یک کلمه پنهان را حل کنند یا عبارت را با حدس زدن حروف آن. مبلغ جایزه برای هر حرف صحیح با چرخاندن یک چرخ بزرگ به سبک رولت حاوی دلار – و نقاط ترسناک ورشکستگی تعیین می شود. یک مسابقه دهنده چرخ را می چرخاند ، نامه ای را حدس می زند و هر مورد از نامه گفته شده در پازل مشخص می شود. حدس های درست فرصتی دیگر برای چرخش و حدس زدن به مسابقه دهنده می دهد ، در حالی که حدس های نادرست بازی را به مسابقه بعدی می رساند. این معما وقتی حل می شود که یک مسابقه دهنده با موفقیت کلمه یا عبارت را حدس بزند. قوانین و عناصر مختلف بازی طی این سالها تغییر داده شده است و مطمئناً می توانید نسخه خود را با نیاز بازیکنان تطبیق دهید.

برای من ، اولین سفارش کاری این بود که تصمیم بگیرم چگونه از نظر جسمی (به صورت مجازی) بازی را انجام دهیم. من فقط برای یک یا دو جلسه به این بازی احتیاج داشتم و حاضر نبودم زمان زیادی را برای ساخت یک پلتفرم بازی تمام عیار سرمایه گذاری کنم ، بنابراین ساختن برنامه به عنوان یک صفحه وب که می توانم آن را به صورت محلی بارگیری کنم و اشتراک صفحه نمایش با دیگران خوب بود . من می توانم فعالیت را کم کنم و گیم پلی بازی را با ضربه زدن به کلیدهای مختلف بر اساس آنچه بازیکنان می خواستند ، هدایت کنم. من همچنین تصمیم گرفتم با استفاده از مداد و کاغذ نمره را حفظ کنم – چیزی که بعدا پشیمان شدم. اما در پایان ، جاوا اسکریپت ساده ، کمی بوم و تعداد انگشت شماری از تصاویر و فایل های جلوه های صوتی تمام چیزی بود که برای ساخت بازی لازم داشتم.

حلقه بازی و حالت بازی

اگرچه من این را “سریع و کثیف” تصور می کردم؟ پروژه به جای برخی از شاهکارهای درخشان رمزگذاری شده به دنبال بهترین روش شناخته شده ، اولین فکر من این بود که شروع به ساخت حلقه بازی کنم. به طور کلی ، کد بازی یک ماشین حالت است که متغیرها و موارد دیگر را حفظ می کند ، و نشان دهنده وضعیت فعلی بازی با برخی از کدهای اضافی است که برای کنترل ورودی کاربر ، مدیریت / به روزرسانی وضعیت و ارائه حالت با جلوه های صوتی و زیبایی کاربر ، فعال است. کدی که به عنوان حلقه بازی شناخته می شود بارها و بارها اجرا می شود ، باعث ایجاد چک های ورودی ، به روزرسانی وضعیت و ارائه می شود. اگر می خواهید یک بازی را به درستی بسازید ، به احتمال زیاد از این الگو پیروی خواهید کرد. اما خیلی زود فهمیدم که به نظارت / به روزرسانی / رندر مداوم وضعیت احتیاج ندارم ، بنابراین به دلیل مدیریت اصلی رویداد ، از حلقه بازی چشم پوشی کردم.

از نظر حفظ حالت ، کد برای دانستن معمای فعلی ، کدامیک از حروف قبلاً حدس زده شده و کدام نمای برای نمایش (چه صفحه پازل یا چرخ در حال چرخش) لازم بود. این موارد در سطح جهانی در اختیار هر منطق پاسخگویی قرار می گیرند. هنگام انجام فشار دادن کلید ، هر فعالیتی در بازی ایجاد می شود.

این کد اصلی شروع به شکل زیر می کند:

(function (appId) {
  
  const canvas = document.getElementById(appId);
  const ctx = canvas.getContext('2d');

  
  let puzzles = [];
  let currentPuzzle = -1;
  let guessedLetters = [];
  let isSpinning = false;

  
  window.addEventListener('keypress', (evt) => {
    
  });
})('app');

صفحه بازی و پازل

صفحه بازی Wheel of Fortune اساساً یک شبکه است که هر سلول در یکی از سه حالت قرار دارد:

  • خالی: از سلولهای خالی در پازل استفاده نمی شود (سبز)
  • خالی: سلول نشان دهنده یک حرف پنهان در پازل است (سفید)
  • قابل مشاهده: سلول نامه ای را در پازل نشان می دهد

یک روش برای نوشتن بازی استفاده از آرایه ای است که صفحه بازی را نشان می دهد ، و هر عنصر به عنوان یک سلول در یکی از این حالت هاست و ارائه این آرایه به چندین روش مختلف انجام می شود. در اینجا یک مثال آورده شده است:

let puzzle = [...'########HELLO##WORLD########'];

const cols = 7;
const width = 30;
const height = 35;

puzzle.forEach((letter, index) => {
  
  let x = width * (index % cols);
  let y = height * Math.floor(index / cols);

  
  ctx.fillStyle = (letter === '#') ? 'green' : 'white';
  ctx.fillRect(x, y, width, height);

  
  ctx.strokeStyle = 'black';
  ctx.strokeRect(x, y, width, height);

  
  if (guessedLetters.includes(letter)) {
      ctx.fillStyle = 'black';
      ctx.fillText(letter, x + (width / 2), y + (height / 2));
  }
});

این روش از طریق هر حرف در یک پازل تکرار می شود ، مختصات شروع را محاسبه می کند ، یک مستطیل برای سلول فعلی بر اساس شاخص و سایر جزئیات ترسیم می کند – مانند تعداد ستون های یک ردیف و عرض و ارتفاع هر سلول. با فرض ، شخصیت را بررسی می کند و سلول را متناسب با آن رنگ می کند #؟؟ برای نشان دادن یک سلول خالی و یک حرف به معنی یک خالی استفاده می شود. سپس حروف حدس زده شده بر روی سلول کشیده می شود تا آنها را فاش کند.

یک صفحه بازی بالقوه با استفاده از کد بالا ارائه شده است

روش دیگر تهیه یک تصویر ثابت از تابلو برای هر پازل از قبل است که به بوم کشیده می شود. این روش می تواند تلاش منصفانه ای را برای آماده سازی پازل اضافه کند ، زیرا شما باید تصاویر اضافی ایجاد کنید ، احتمالاً موقعیت هر حرف را روی صفحه سفارشی تعیین کنید و همه آن اطلاعات را در یک ساختار داده مناسب برای ارائه رمزگذاری کنید. . معامله گرافیکی با ظاهر بهتر و شاید موقعیت نامه بهتر باشد.

پیروی از این رویکرد دوم یک معما ممکن است به نظر برسد:

let puzzle = {
  background: 'img/puzzle-01.png',
  letters: [
    {chr: 'H', x: 45,  y: 60},
    {chr: 'E', x: 75,  y: 60},
    {chr: 'L', x: 105, y: 60},
    {chr: 'L', x: 135, y: 60},
    {chr: 'O', x: 165, y: 60},
    {chr: 'W', x: 45,  y: 100},
    {chr: 'O', x: 75,  y: 100},
    {chr: 'R', x: 105, y: 100},
    {chr: 'L', x: 135, y: 100},
    {chr: 'D', x: 165, y: 100}
  ]
};

به منظور کارآیی ، توصیه می کنم آرایه دیگری را نیز برای ردیابی حروف منطبق وارد کنید. فقط با guessedLetters آرایه در دسترس است ، شما باید نامه های پازل را به طور مکرر برای چندین مسابقه اسکن کنید. درعوض ، می توانید یک آرایه تنظیم کنید تا حروف حل شده را ردیابی کند و فقط هنگامی که بازیکن حدس می زند تعاریف منطبق را در آن کپی کنید ، مانند این موارد:

const solvedLetters = [];

puzzle.letters.forEach((letter) => {
  if (letter.chr === evt.key) {
    solvedLetters.push(letter);
  }
});

ارائه این پازل به این شکل است:


const imgPuzzle = new Image();
imgPuzzle.onload = function () {
  ctx.drawImage(this, 0, 0);
};
imgPuzzle.src = puzzle.background;


solvedLetters.forEach((letter) => {
  ctx.fillText(letter.chr, letter.x, letter.y);
});

یک صفحه بازی بالقوه با استفاده از روش جایگزین ارائه شده است

برای ضبط ، هنگام نوشتن بازی خودم رویکرد دوم را در پیش گرفتم. اما نکته مهم در اینجا این است که اغلب چندین راه حل برای یک مسئله وجود دارد. هر راه حل با مزایا و معایب خاص خود همراه است و تصمیم گیری در مورد یک راه حل خاص به طور حتم در طراحی برنامه شما تأثیر می گذارد.

چرخاندن چرخ

در ابتدا رژگونه ، چرخاندن چرخ چالش برانگیز به نظر می رسد: یک دایره از بخش های رنگی را با مقادیر جایزه ارائه دهید ، آن را متحرک کنید و در حال چرخش باشد و انیمیشن را با مقدار جایزه تصادفی متوقف کنید. اما کمی تفکر خلاقانه این کار را به ساده ترین کار در کل پروژه تبدیل کرده است.

صرف نظر از رویکرد شما برای رمزگذاری پازل و ارائه صفحه بازی ، احتمالاً چرخ چیزی است که شما می خواهید برای آن از یک گرافیک استفاده کنید. چرخاندن یک تصویر بسیار آسان تر از کشیدن (و متحرک سازی) یک دایره تقسیم شده با متن است. استفاده از تصویر بیشتر پیچیدگی های جلو را از بین می برد. سپس ، چرخاندن چرخ محاسبه یک عدد تصادفی بزرگتر از 360 و چرخش مکرر تصویر در درجه های مختلف می شود:

const maxPos = 360 + Math.floor(Math.random() * 360);
for (let i = 1; i < maxPos; i++) {
  setTimeout(() => {
    ctx.save();
    ctx.translate(640, 640);
    ctx.rotate(i * 0.01745); 
    ctx.translate(-640, -640);
    ctx.drawImage(imgWheel, 0, 0);
    ctx.restore();
  }, i * 10);
}

من با استفاده از یک اثر انیمیشن خام ایجاد کردم setTimeout برای برنامه ریزی چرخش ها ، با برنامه ریزی هر چرخش بیشتر و بیشتر در آینده. در کد بالا ، چرخش 1 درجه اول قرار است بعد از 10 میلی ثانیه ، دومی بعد از 20 میلی ثانیه ارائه شود ، و غیره. اثر خالص یک چرخش چرخشی است که تقریباً با یک چرخش در هر 360 میلی ثانیه انجام می شود. و اطمینان از اینکه تعداد تصادفی اولیه بیشتر از 360 باشد ، حداقل یک چرخش کامل را تحریک می کنم.

یک یادداشت کوتاه که قابل ذکر است این است که شما باید آزادانه بازی کنید و با “ارزش های جادویی” بازی کنید؟ ارائه شده برای تنظیم / تنظیم مجدد نقطه مرکزی که بوم در اطراف آن چرخانده شده است. بسته به اندازه تصویر شما و اینکه آیا می خواهید کل تصویر یا فقط قسمت بالای چرخ قابل مشاهده باشد ، ممکن است نقطه میانی دقیق آنچه را که در ذهن دارید تولید نکند. خوب است مقادیر را تغییر دهید تا زمانی که به یک نتیجه رضایت بخش برسید. همین امر برای ضرب تایم اوت نیز صادق است ، که می توانید برای تغییر سرعت انیمیشن چرخش تغییر دهید.

ورشکست شدن

من فکر می کنم وقتی چرخش یک بازیکن به ورشکستگی می افتد ، همه ما مقداری schadenfreude را تجربه می کنیم. سرگرم کننده است وقتی یک مسابقه دهنده حریص چرخ می چرخد ​​تا چند حرف دیگر جمع کند ، وقتی مشخص است که آنها از قبل راه حل معما را می دانند – فقط برای از دست دادن همه چیز. و جلوه صدای جالب ورشکستگی نیز وجود دارد! هیچ بازی Wheel of Fortune بدون آن کامل نخواهد بود.

برای این منظور ، از Audio استفاده کردم که به ما امکان پخش صدا در JavaScript را می دهد:

function playSound(sfx) {
  sfx.currentTime = 0;
  sfx.play();
}

const sfxBankrupt = new Audio('sfx/bankrupt.mp3');


playSound(sfxBankrupt);

اما چه چیزی باعث ایجاد جلوه صوتی می شود؟

یک راه حل این است که یک دکمه را فشار دهید تا جلوه ایجاد شود ، زیرا من می توانم گیم پلی بازی را کنترل کنم ، اما بهتر است بازی صدا را به صورت خودکار بازی بازی کند. از آنجا که گوه های ورشکسته تنها گوه های سیاه چرخ هستند ، می توان به راحتی با مشاهده رنگ پیکسل دانست که آیا چرخ روی ورشکسته متوقف می شود:

const maxPos = 360 + Math.floor(Math.random() * 360);
for (let i = 1; i < maxPos; i++) {
  setTimeout(() => {
    ctx.save();
    ctx.translate(640, 640);
    ctx.rotate(i * 0.01745); 
    ctx.translate(-640, -640);
    ctx.drawImage(imgWheel, 0, 0);
    ctx.restore();

    if (i === maxPos - 1) {
      
      const color = ctx.getImageData(640, 12, 1, 1).data;
      if (color[0] === 0 && color[1] === 0 && color[2] === 0) {
        playSound(sfxBankrupt);
      }
    }
  }, i * 10);
}

من فقط در ورشکستگی کد خود تمرکز کردم ، اما این روش می تواند برای تعیین مبلغ جایزه نیز گسترش یابد. اگرچه مقادیر متعدد دارای یک رنگ گوه هستند – به عنوان مثال 600 ، 700 دلار و 800 دلار همه در گوه های قرمز ظاهر می شوند – شما می توانید از سایه های کمی متفاوت برای تمایز مقادیر استفاده کنید: rgb(255, 50, 50)، rgb(255, 51, 50)، و rgb(255, 50, 51) از نظر چشم انسان قابل تشخیص نیستند اما به راحتی توسط برنامه شناسایی می شوند. از نظر گذشته ، این چیزی است که باید بیشتر دنبال می کردم. به نظر من مالیات ذهنی این است که دستی را در حالی که فشار می دهی کلیدها و بازی را فشار می دهی ، حفظ کنی و تلاش اضافی برای اتوماتیک نگه داشتن امتیاز مطمئناً ارزشش را داشت.

تفاوت این سایه های قرمز برای چشم انسان قابل تشخیص نیست

خلاصه

اگر کنجکاو هستید ، می توانید کد من را در آن پیدا کنید GitHub. این نمونه تجربی و بهترین شیوه ها نیست و اشکالات زیادی وجود دارد (درست مثل بسیاری از کدهای دنیای واقعی که در محیط های تولیدی اجرا می شوند!) اما هدف خود را تأمین می کند. اما در نهایت هدف این مقاله الهام بخشیدن به شما و دعوت شما به تفکر انتقادی درباره گزینه های معامله خود بود.

اگر در حال ساخت یک بازی مشابه بودید ، چه معامله هایی انجام می دادید؟ چه ویژگی هایی را مهم می دانید؟ شاید شما می خواهید انیمیشن مناسب ، نگه داشتن امتیاز داشته باشید یا حتی ممکن است از سوکت های وب استفاده کنید تا شرکت کنندگان بتوانند در مرورگرهای خود با یکدیگر بازی کنند تا اینکه از طریق اشتراک صفحه نمایش emcee.

با نگاهی فراتر از این مثال خاص ، در کارهای روزمره با چه انتخاب هایی روبرو هستید؟ چگونه اولویت های تجاری ، شیوه های مناسب کدگذاری و بدهی های فنی را متعادل می کنید؟ چه زمانی تمایل به کامل شدن امور به مانعی برای حمل کالا تبدیل می شود؟ خبرم کن در توییتر.