مطالعه موردی - یورش! آرنا

جف بلر
Geoff Blair
مت هکت
Matt Hackett

مقدمه

در ژوئن سال 2010، متوجه شدیم که نشریه محلی بوئینگ بوینگ در حال برگزاری مسابقه توسعه بازی است. ما این را بهانه ای کاملاً خوب برای ساختن یک بازی سریع و ساده در جاوا اسکریپت و <canvas> دیدیم، بنابراین دست به کار شدیم. بعد از مسابقه هنوز ایده های زیادی داشتیم و می خواستیم کاری را که شروع کردیم به پایان برسانیم. در اینجا مطالعه موردی نتیجه، یک بازی کوچک به نام Onslaught است! آرنا .

ظاهر یکپارچهسازی با سیستمعامل و پیکسلی

مهم این بود که بازی ما شبیه یک بازی سیستم سرگرمی نینتندو یکپارچهسازی با سیستمعامل باشد، با توجه به فرضیه مسابقه برای توسعه یک بازی مبتنی بر چیپ‌تون . اکثر بازی‌ها این نیاز را ندارند، اما به دلیل سهولت ایجاد دارایی و جذابیت طبیعی برای گیمرهای نوستالژیک، همچنان یک سبک هنری رایج (به ویژه در میان توسعه‌دهندگان مستقل) است.

هجوم! اندازه پیکسل آرنا
افزایش اندازه پیکسل می تواند کار طراحی گرافیکی را کاهش دهد.

با توجه به اینکه این اسپرایت ها چقدر کوچک هستند، تصمیم گرفتیم پیکسل های خود را دو برابر کنیم، به این معنی که یک اسپرایت 16x16 اکنون 32x32 پیکسل و غیره خواهد بود. از همان ابتدا، به جای اینکه مرورگر را وادار کنیم کارهای سنگین را انجام دهد، جنبه ایجاد دارایی را دوچندان کرده بودیم. اجرای این کار ساده‌تر بود، اما مزایای ظاهری مشخصی نیز داشت.

در اینجا یک سناریو است که ما در نظر گرفتیم:

<style>
canvas {
  width: 640px;
  height: 320px;
}
</style>
<canvas width="320" height="240">
  Sorry, your browser is not supported.
</canvas>

این روش به جای دو برابر کردن آنها در سمت ایجاد دارایی، از sprites 1x1 تشکیل شده است. از آنجا، CSS خود بوم را در دست گرفته و اندازه آن را تغییر می دهد. معیارهای ما نشان داد که این روش می‌تواند دو برابر سریع‌تر از رندر کردن تصاویر بزرگ‌تر (دوبرابر) باشد، اما متأسفانه تغییر اندازه CSS شامل anti-aliasing است، چیزی که ما نتوانستیم راهی برای جلوگیری از آن پیدا کنیم.

گزینه های تغییر اندازه بوم
سمت چپ: دارایی های پیکسل کامل در فتوشاپ دو برابر شده است. راست: تغییر اندازه CSS یک جلوه تار اضافه کرد.

این یک شکست برای بازی ما بود، زیرا تک تک پیکسل ها بسیار مهم هستند، اما اگر نیاز به تغییر اندازه بوم خود دارید و ضد aliasing برای پروژه شما مناسب است، می توانید این رویکرد را به دلایل عملکرد در نظر بگیرید.

ترفندهای جالب بوم نقاشی

همه ما می دانیم که <canvas> داغ جدید است، اما گاهی اوقات توسعه دهندگان هنوز استفاده از DOM را توصیه می کنند . اگر نمی‌خواهید از آن استفاده کنید، در اینجا نمونه‌ای از این است که چگونه <canvas> در زمان و انرژی ما صرفه‌جویی کرد.

هنگامی که یک دشمن در Onslaught مورد ضربه قرار می گیرد! Arena ، قرمز چشمک می زند و به طور خلاصه یک انیمیشن "درد" را نمایش می دهد. برای محدود کردن تعداد گرافیک‌هایی که باید ایجاد می‌کردیم، فقط دشمنان را در «درد» در جهت رو به پایین نشان می‌دهیم. این در بازی قابل قبول به نظر می رسد و در زمان ایجاد جن صرفه جویی می کند. با این حال، برای هیولاهای رئیس، دیدن یک اسپرایت بزرگ (با ابعاد 64×64 پیکسل یا بیشتر) که از سمت چپ یا بالا به طور ناگهانی به سمت پایین برای قاب درد می‌چرخد، سخت بود.

یک راه حل واضح می تواند ترسیم یک قاب درد برای هر رئیس در هر یک از هشت جهت باشد، اما این کار بسیار زمان بر خواهد بود. به لطف <canvas> ، ما توانستیم این مشکل را در کد حل کنیم:

Beholder در Onslaught آسیب می بیند! آرنا
اثرات جالبی را می توان با استفاده از context.globalCompositeOperation ایجاد کرد.

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

// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");

// Draw your image on the buffer
buffer.drawImage(image, 0, 0);

// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();

// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);

حلقه بازی

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

در اینجا نمونه ای از حلقه بازی آورده شده است:

function main () {
  handleInput();
  update();
  render();
};

setInterval(main, 1);

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

window.addEventListener("mousedown", function(e) {
  // A mouse click means the players wants to attack.
  // We don't actually do that yet, but instead tell the rest
  // of the program about the request.
  buttonStates[e.button] = true;
}, false);

function handleInput() {
  // Here is where we respond to the click
  if (buttonStates[LEFT_BUTTON]) {
    player.attacking = true;
    delete buttonStates[LEFT_BUTTON];
  }
};

اکنون ما در مورد ورودی می دانیم و می توانیم آن را در عملکرد update در نظر بگیریم، زیرا می دانیم که به بقیه قوانین بازی پایبند است.

function update() {
  // Check for collisions, states, whatever else is needed

  // If after that the player can still attack, do it!
  if (player.attacking && player.canAttack()) {
    player.attack();
  }
};

در نهایت، هنگامی که همه چیز محاسبه شد، نوبت به ترسیم مجدد صفحه می رسد! در DOM-land، مرورگر این افزایش را انجام می دهد. اما هنگام استفاده از <canvas> لازم است هر زمان که اتفاقی می افتد به صورت دستی دوباره ترسیم شود (که معمولاً هر فریم است!).

function render() {
  // First erase everything, something like:
  context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  // Draw the player (and whatever else you need)
  context.drawImage(
    player.getImage(),
    player.x, player.y
  );
};

مدلسازی مبتنی بر زمان

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

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

function update() {

  // NOTE: You'll need to initially seed this.lastUpdate
  // with the current time when your game loop starts
  // this.lastUpdate = Date.now();

  // Calculate elapsed time since last frame
  var now = Date.now();
  var elapsed = (now - this.lastUpdate);
  this.lastUpdate = now;

  // Do stuff with elapsed

};

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

var Sprite = function() {

  // The sprite's position relative to the top left of the game world
  this.position = {x: 0, y: 0};

  // The sprite's direction. A positive x value indicates moving to the right
  this.direction = {x: 1, y: 0};

  // How many pixels the sprite moves per second
  this.speed = 50;
};

با در نظر گرفتن این متغیرها، در اینجا نحوه انتقال نمونه ای از کلاس sprite بالا با استفاده از مدل سازی مبتنی بر زمان آورده شده است:

// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;

// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);

توجه داشته باشید که مقادیر direction.x و direction.y باید نرمال شوند، به این معنی که همیشه باید بین -1 و 1 قرار گیرند.

کنترل ها

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

به منظور مقایسه با بازی های مشابه در ژانر خود، ما پشتیبانی از ماوس را برای کنترل یک شبکه هدف قرار دادیم که شخصیت از آن برای هدف گیری حملات خود استفاده می کند. هنوز هم می‌توان شخصیت را با صفحه کلید جابه‌جا کرد، اما پس از این تغییر می‌توانست به طور همزمان در هر جهت کامل 360 درجه شلیک کند. بازیکنان هاردکور از این ویژگی قدردانی می‌کردند، اما عواقب ناخوشایند ناامید کردن کاربران ترک پد را داشت.

هجوم! کنترل‌های آرنا مودال (منسوخ شده)
یک کنترل قدیمی یا مدال "چگونه بازی کنیم" در Onslaught! آرنا.

برای جا دادن به کاربران ترک‌پد، کنترل‌های کلید پیکان را بازگردانیم، این بار تا امکان شلیک در جهت(های) فشار داده شده را فراهم کنیم. در حالی که ما احساس می‌کردیم که به انواع بازیکنان غذا می‌دهیم، ناخودآگاه پیچیدگی زیادی را به بازی خود وارد می‌کردیم. در کمال تعجب، بعداً شنیدیم که برخی از بازیکنان از کنترل‌های اختیاری ماوس (یا صفحه کلید!) برای حمله آگاه نبودند، علی‌رغم مدال‌های آموزشی، که تا حد زیادی نادیده گرفته شدند.

هجوم! آموزش کنترل های آرنا
بازیکنان عمدتاً پوشش آموزشی را نادیده می گیرند. آنها ترجیح می دهند بازی کنند و لذت ببرند!

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

با این طرح کنترل پیچیده ای که ما اجرا کرده ایم، مشکل بازی در دستگاه های تلفن همراه نیز وجود دارد. در واقع، یکی از رایج ترین درخواست های ما این است که Onslaught را بسازیم! Arena در Android، iPad و سایر دستگاه های لمسی (جایی که صفحه کلید وجود ندارد) موجود است. یکی از نقاط قوت اصلی HTML5 قابل حمل بودن آن است، بنابراین نصب بازی روی این دستگاه‌ها قطعاً قابل انجام است، فقط باید بسیاری از مشکلات (به ویژه کنترل‌ها و عملکرد) را حل کنیم.

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

// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
  // Found one! Shoot in its direction
  var shoot = hostile.boundingBox().center().subtract(
    player.boundingBox().center()
  ).normalize();
}

// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).magnitude();

// Prevent jittering if the character is close enough
if (distance < 3) {
  move.zero();
}

// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
  player.setDirection(move);
}

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

صوتی

در میان کنترل‌ها و عملکرد، یکی از بزرگترین مشکلات ما در حین توسعه Onslaught! Arena تگ <audio> HTML5 بود. احتمالاً بدترین جنبه تأخیر است: تقریباً در همه مرورگرها بین فراخوانی .play() و صدای واقعی پخش می شود. این می تواند تجربه یک گیمر را خراب کند، به خصوص زمانی که با یک بازی سریع مانند بازی ما بازی می کنید.

مشکلات دیگر عبارتند از : رویداد "پیشرفت" فعال نشد ، که می تواند باعث شود جریان بارگذاری بازی به طور نامحدود متوقف شود. به این دلایل، ما روشی را که ما آن را "سقوط به جلو" می نامیم، اتخاذ کردیم، که در آن اگر Flash بارگیری نشد، به HTML5 Audio تغییر می کنیم. کد چیزی شبیه به این است:

/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/

// Default to sm2 (Flash)
var api = "sm2";

function initAudio (callback) {
  switch (api) {
    case "sm2":
      soundManager.onerror = (function (init) {
        return function () {
          api = "html5";
          init(callback);
        };
      }(arguments.callee));
      break;
    case "html5":
      var audio = document.createElement("audio");

      if (
        audio
        && audio.canPlayType
        && audio.canPlayType("audio/mpeg;")
      ) {
        callback();
      } else {
        // No audio support :(
      }
      break;
  }
};

همچنین ممکن است برای یک بازی مهم باشد که از مرورگرهایی که فایل های MP3 را پخش نمی کنند (مانند Mozilla Firefox) پشتیبانی کند. در این صورت، پشتیبانی را می توان شناسایی کرد و به چیزی مانند Ogg Vorbis با کدی مانند زیر تغییر داد:

/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/

var audio = document.createElement("audio");

if (audio && audio.canPlayType) {
  if (!audio.canPlayType("audio/mpeg;")) {
    // Here you know you CANNOT use .mp3 files
    if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
      // Here you know you CAN use .ogg files
    }
  }
}

ذخیره داده ها

شما نمی توانید بدون نمرات بالا یک بازی به سبک آرکید داشته باشید! ما می‌دانستیم که برای ادامه دادن به برخی از داده‌های بازی خود نیاز داریم، و در حالی که می‌توانستیم از چیزی قدیمی مانند کوکی‌ها استفاده کنیم، می‌خواستیم به فناوری‌های سرگرم‌کننده جدید HTML5 بپردازیم. مطمئناً هیچ کمبودی در گزینه‌ها از جمله ذخیره‌سازی محلی، ذخیره‌سازی جلسه و پایگاه‌های داده وب SQL وجود ندارد.

ALT_TEXT_HERE
نمرات بالا ذخیره می شود، و همچنین جایگاه شما در بازی پس از شکست دادن هر رئیس ذخیره می شود.

ما تصمیم گرفتیم از localStorage استفاده کنیم زیرا جدید، عالی و آسان برای استفاده است. از ذخیره جفت‌های کلید/مقدار اصلی پشتیبانی می‌کند که تمام بازی ساده ما مورد نیاز است. در اینجا یک مثال ساده از نحوه استفاده از آن آورده شده است:

if (typeof localStorage == "object") {
  localStorage.setItem("foo", "bar");
  localStorage.getItem("foo"); // Value is "bar"
  localStorage.removeItem("foo");
  localStorage.getItem("foo"); // Value is now null
}

برخی از "گوچاها" وجود دارد که باید از آنها آگاه بود. مهم نیست که چه چیزی را ارسال می کنید، مقادیر به صورت رشته ای ذخیره می شوند که می تواند منجر به نتایج غیرمنتظره ای شود:

localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
  // It's true!
}

// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)

// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}

خلاصه

کار با HTML5 شگفت انگیز است. بیشتر پیاده‌سازی‌ها همه چیزهایی را که یک توسعه‌دهنده بازی نیاز دارد، از گرافیک گرفته تا ذخیره وضعیت بازی، انجام می‌دهند. در حالی که مشکلات فزاینده ای وجود دارد (مانند مشکلات تگ <audio> )، توسعه دهندگان مرورگر به سرعت در حال حرکت هستند و با چیزهایی که در حال حاضر عالی هستند، آینده برای بازی های ساخته شده بر روی HTML5 روشن به نظر می رسد.

هجوم! آرنا با لوگوی پنهان HTML5
هنگام بازی Onslaught می توانید با تایپ "html5" یک سپر HTML5 دریافت کنید! آرنا.
،

جف بلر
Geoff Blair
مت هکت
Matt Hackett

مقدمه

در ژوئن سال 2010، متوجه شدیم که نشریه محلی بوئینگ بوینگ در حال برگزاری مسابقه توسعه بازی است. ما این را بهانه ای کاملاً خوب برای ساختن یک بازی سریع و ساده در جاوا اسکریپت و <canvas> دیدیم، بنابراین دست به کار شدیم. بعد از مسابقه هنوز ایده های زیادی داشتیم و می خواستیم کاری را که شروع کردیم به پایان برسانیم. در اینجا مطالعه موردی نتیجه، یک بازی کوچک به نام Onslaught است! آرنا .

ظاهر یکپارچهسازی با سیستمعامل و پیکسلی

مهم این بود که بازی ما شبیه یک بازی سیستم سرگرمی نینتندو یکپارچهسازی با سیستمعامل باشد، با توجه به فرضیه مسابقه برای توسعه یک بازی مبتنی بر چیپ‌تون . اکثر بازی‌ها این نیاز را ندارند، اما به دلیل سهولت ایجاد دارایی و جذابیت طبیعی برای گیمرهای نوستالژیک، همچنان یک سبک هنری رایج (به ویژه در میان توسعه‌دهندگان مستقل) است.

هجوم! اندازه پیکسل آرنا
افزایش اندازه پیکسل می تواند کار طراحی گرافیکی را کاهش دهد.

با توجه به اینکه این اسپرایت ها چقدر کوچک هستند، تصمیم گرفتیم پیکسل های خود را دو برابر کنیم، به این معنی که یک اسپرایت 16x16 اکنون 32x32 پیکسل و غیره خواهد بود. از همان ابتدا، به جای اینکه مرورگر را وادار کنیم کارهای سنگین را انجام دهد، جنبه ایجاد دارایی را دوچندان کرده بودیم. اجرای این کار ساده‌تر بود، اما مزایای ظاهری مشخصی نیز داشت.

در اینجا یک سناریو است که ما در نظر گرفتیم:

<style>
canvas {
  width: 640px;
  height: 320px;
}
</style>
<canvas width="320" height="240">
  Sorry, your browser is not supported.
</canvas>

این روش به جای دو برابر کردن آنها در سمت ایجاد دارایی، از sprites 1x1 تشکیل شده است. از آنجا، CSS خود بوم را در دست گرفته و اندازه آن را تغییر می دهد. معیارهای ما نشان داد که این روش می‌تواند دو برابر سریع‌تر از رندر کردن تصاویر بزرگ‌تر (دوبرابر) باشد، اما متأسفانه تغییر اندازه CSS شامل anti-aliasing است، چیزی که ما نتوانستیم راهی برای جلوگیری از آن پیدا کنیم.

گزینه های تغییر اندازه بوم
سمت چپ: دارایی های پیکسل کامل در فتوشاپ دو برابر شده است. راست: تغییر اندازه CSS یک جلوه تار اضافه کرد.

این یک شکست برای بازی ما بود، زیرا تک تک پیکسل ها بسیار مهم هستند، اما اگر نیاز به تغییر اندازه بوم خود دارید و ضد aliasing برای پروژه شما مناسب است، می توانید این رویکرد را به دلایل عملکرد در نظر بگیرید.

ترفندهای جالب بوم نقاشی

همه ما می دانیم که <canvas> داغ جدید است، اما گاهی اوقات توسعه دهندگان هنوز استفاده از DOM را توصیه می کنند . اگر نمی‌خواهید از آن استفاده کنید، در اینجا نمونه‌ای از این است که چگونه <canvas> در زمان و انرژی ما صرفه‌جویی کرد.

هنگامی که یک دشمن در Onslaught مورد ضربه قرار می گیرد! Arena ، قرمز چشمک می زند و به طور خلاصه یک انیمیشن "درد" را نمایش می دهد. برای محدود کردن تعداد گرافیک‌هایی که باید ایجاد می‌کردیم، فقط دشمنان را در «درد» در جهت رو به پایین نشان می‌دهیم. این در بازی قابل قبول به نظر می رسد و در زمان ایجاد جن صرفه جویی می کند. با این حال، برای هیولاهای رئیس، دیدن یک اسپرایت بزرگ (با ابعاد 64×64 پیکسل یا بیشتر) که از سمت چپ یا بالا به طور ناگهانی به سمت پایین برای قاب درد می‌چرخد، سخت بود.

یک راه حل واضح می تواند ترسیم یک قاب درد برای هر رئیس در هر یک از هشت جهت باشد، اما این کار بسیار زمان بر خواهد بود. به لطف <canvas> ، ما توانستیم این مشکل را در کد حل کنیم:

Beholder در Onslaught آسیب می بیند! آرنا
اثرات جالبی را می توان با استفاده از context.globalCompositeOperation ایجاد کرد.

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

// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");

// Draw your image on the buffer
buffer.drawImage(image, 0, 0);

// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();

// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);

حلقه بازی

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

در اینجا نمونه ای از حلقه بازی آورده شده است:

function main () {
  handleInput();
  update();
  render();
};

setInterval(main, 1);

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

window.addEventListener("mousedown", function(e) {
  // A mouse click means the players wants to attack.
  // We don't actually do that yet, but instead tell the rest
  // of the program about the request.
  buttonStates[e.button] = true;
}, false);

function handleInput() {
  // Here is where we respond to the click
  if (buttonStates[LEFT_BUTTON]) {
    player.attacking = true;
    delete buttonStates[LEFT_BUTTON];
  }
};

اکنون ما در مورد ورودی می دانیم و می توانیم آن را در عملکرد update در نظر بگیریم، زیرا می دانیم که به بقیه قوانین بازی پایبند است.

function update() {
  // Check for collisions, states, whatever else is needed

  // If after that the player can still attack, do it!
  if (player.attacking && player.canAttack()) {
    player.attack();
  }
};

در نهایت، هنگامی که همه چیز محاسبه شد، نوبت به ترسیم مجدد صفحه می رسد! در DOM-land، مرورگر این افزایش را انجام می دهد. اما هنگام استفاده از <canvas> لازم است هر زمان که اتفاقی می افتد به صورت دستی دوباره ترسیم شود (که معمولاً هر فریم است!).

function render() {
  // First erase everything, something like:
  context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  // Draw the player (and whatever else you need)
  context.drawImage(
    player.getImage(),
    player.x, player.y
  );
};

مدلسازی مبتنی بر زمان

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

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

function update() {

  // NOTE: You'll need to initially seed this.lastUpdate
  // with the current time when your game loop starts
  // this.lastUpdate = Date.now();

  // Calculate elapsed time since last frame
  var now = Date.now();
  var elapsed = (now - this.lastUpdate);
  this.lastUpdate = now;

  // Do stuff with elapsed

};

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

var Sprite = function() {

  // The sprite's position relative to the top left of the game world
  this.position = {x: 0, y: 0};

  // The sprite's direction. A positive x value indicates moving to the right
  this.direction = {x: 1, y: 0};

  // How many pixels the sprite moves per second
  this.speed = 50;
};

با در نظر گرفتن این متغیرها، در اینجا نحوه انتقال نمونه ای از کلاس sprite بالا با استفاده از مدل سازی مبتنی بر زمان آورده شده است:

// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;

// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);

توجه داشته باشید که مقادیر direction.x و direction.y باید نرمال شوند، به این معنی که همیشه باید بین -1 و 1 قرار گیرند.

کنترل ها

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

به منظور مقایسه با بازی های مشابه در ژانر خود، ما پشتیبانی از ماوس را برای کنترل یک شبکه هدف قرار دادیم که شخصیت از آن برای هدف گیری حملات خود استفاده می کند. هنوز هم می‌توان شخصیت را با صفحه کلید جابه‌جا کرد، اما پس از این تغییر می‌توانست به طور همزمان در هر جهت کامل 360 درجه شلیک کند. بازیکنان هاردکور از این ویژگی قدردانی می‌کردند، اما عواقب ناخوشایند ناامید کردن کاربران ترک پد را داشت.

هجوم! کنترل‌های آرنا مودال (منسوخ شده)
یک کنترل قدیمی یا مدال "چگونه بازی کنیم" در Onslaught! آرنا.

برای جا دادن به کاربران ترک‌پد، کنترل‌های کلید پیکان را بازگردانیم، این بار تا امکان شلیک در جهت(های) فشار داده شده را فراهم کنیم. در حالی که ما احساس می‌کردیم که به انواع بازیکنان غذا می‌دهیم، ناخودآگاه پیچیدگی زیادی را به بازی خود وارد می‌کردیم. در کمال تعجب، بعداً شنیدیم که برخی از بازیکنان از کنترل‌های اختیاری ماوس (یا صفحه کلید!) برای حمله آگاه نبودند، علی‌رغم مدال‌های آموزشی، که تا حد زیادی نادیده گرفته شدند.

هجوم! آموزش کنترل های آرنا
بازیکنان عمدتاً پوشش آموزشی را نادیده می گیرند. آنها ترجیح می دهند بازی کنند و لذت ببرند!

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

با این طرح کنترل پیچیده ای که ما اجرا کرده ایم، مشکل بازی در دستگاه های تلفن همراه نیز وجود دارد. در واقع، یکی از رایج ترین درخواست های ما این است که Onslaught را بسازیم! Arena در Android، iPad و سایر دستگاه های لمسی (جایی که صفحه کلید وجود ندارد) موجود است. یکی از نقاط قوت اصلی HTML5 قابل حمل بودن آن است، بنابراین نصب بازی روی این دستگاه‌ها قطعاً قابل انجام است، فقط باید بسیاری از مشکلات (به ویژه کنترل‌ها و عملکرد) را حل کنیم.

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

// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
  // Found one! Shoot in its direction
  var shoot = hostile.boundingBox().center().subtract(
    player.boundingBox().center()
  ).normalize();
}

// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).magnitude();

// Prevent jittering if the character is close enough
if (distance < 3) {
  move.zero();
}

// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
  player.setDirection(move);
}

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

صوتی

در میان کنترل‌ها و عملکرد، یکی از بزرگترین مشکلات ما در حین توسعه Onslaught! Arena تگ <audio> HTML5 بود. احتمالاً بدترین جنبه تأخیر است: تقریباً در همه مرورگرها بین فراخوانی .play() و صدای واقعی پخش می شود. این می تواند تجربه یک گیمر را خراب کند، به خصوص زمانی که با یک بازی سریع مانند بازی ما بازی می کنید.

مشکلات دیگر عبارتند از : رویداد "پیشرفت" فعال نشد ، که می تواند باعث شود جریان بارگذاری بازی به طور نامحدود متوقف شود. به این دلایل، ما روشی را که ما آن را "سقوط به جلو" می نامیم، اتخاذ کردیم، که در آن اگر Flash بارگیری نشد، به HTML5 Audio تغییر می کنیم. کد چیزی شبیه به این است:

/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/

// Default to sm2 (Flash)
var api = "sm2";

function initAudio (callback) {
  switch (api) {
    case "sm2":
      soundManager.onerror = (function (init) {
        return function () {
          api = "html5";
          init(callback);
        };
      }(arguments.callee));
      break;
    case "html5":
      var audio = document.createElement("audio");

      if (
        audio
        && audio.canPlayType
        && audio.canPlayType("audio/mpeg;")
      ) {
        callback();
      } else {
        // No audio support :(
      }
      break;
  }
};

همچنین ممکن است برای یک بازی مهم باشد که از مرورگرهایی که فایل های MP3 را پخش نمی کنند (مانند Mozilla Firefox) پشتیبانی کند. در این صورت، پشتیبانی را می توان شناسایی کرد و به چیزی مانند Ogg Vorbis با کدی مانند زیر تغییر داد:

/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/

var audio = document.createElement("audio");

if (audio && audio.canPlayType) {
  if (!audio.canPlayType("audio/mpeg;")) {
    // Here you know you CANNOT use .mp3 files
    if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
      // Here you know you CAN use .ogg files
    }
  }
}

ذخیره داده ها

شما نمی توانید بدون نمرات بالا یک بازی به سبک آرکید داشته باشید! ما می‌دانستیم که برای ادامه دادن به برخی از داده‌های بازی خود نیاز داریم، و در حالی که می‌توانستیم از چیزی قدیمی مانند کوکی‌ها استفاده کنیم، می‌خواستیم به فناوری‌های سرگرم‌کننده جدید HTML5 بپردازیم. مطمئناً هیچ کمبودی در گزینه‌ها از جمله ذخیره‌سازی محلی، ذخیره‌سازی جلسه و پایگاه‌های داده وب SQL وجود ندارد.

ALT_TEXT_HERE
نمرات بالا ذخیره می شود، و همچنین جایگاه شما در بازی پس از شکست دادن هر رئیس ذخیره می شود.

ما تصمیم گرفتیم از localStorage استفاده کنیم زیرا جدید، عالی و آسان برای استفاده است. از ذخیره جفت‌های کلید/مقدار اصلی پشتیبانی می‌کند که تمام بازی ساده ما مورد نیاز است. در اینجا یک مثال ساده از نحوه استفاده از آن آورده شده است:

if (typeof localStorage == "object") {
  localStorage.setItem("foo", "bar");
  localStorage.getItem("foo"); // Value is "bar"
  localStorage.removeItem("foo");
  localStorage.getItem("foo"); // Value is now null
}

برخی از "گوچاها" وجود دارد که باید از آنها آگاه بود. مهم نیست که چه چیزی را ارسال می کنید، مقادیر به صورت رشته ای ذخیره می شوند که می تواند منجر به نتایج غیرمنتظره ای شود:

localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
  // It's true!
}

// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)

// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}

خلاصه

کار با HTML5 شگفت انگیز است. بیشتر پیاده‌سازی‌ها همه چیزهایی را که یک توسعه‌دهنده بازی نیاز دارد، از گرافیک گرفته تا ذخیره وضعیت بازی، انجام می‌دهند. در حالی که مشکلات فزاینده ای وجود دارد (مانند مشکلات تگ <audio> )، توسعه دهندگان مرورگر به سرعت در حال حرکت هستند و با چیزهایی که در حال حاضر عالی هستند، آینده برای بازی های ساخته شده بر روی HTML5 روشن به نظر می رسد.

هجوم! آرنا با لوگوی مخفی HTML5
هنگام بازی Onslaught می توانید با تایپ "html5" یک سپر HTML5 دریافت کنید! آرنا.
،

جف بلر
Geoff Blair
مت هکت
Matt Hackett

مقدمه

در ژوئن سال 2010، متوجه شدیم که نشریه محلی بوئینگ بوینگ در حال برگزاری مسابقه توسعه بازی است. ما این را بهانه ای کاملاً خوب برای ساختن یک بازی سریع و ساده در جاوا اسکریپت و <canvas> دیدیم، بنابراین دست به کار شدیم. بعد از مسابقه هنوز ایده های زیادی داشتیم و می خواستیم کاری را که شروع کردیم به پایان برسانیم. در اینجا مطالعه موردی نتیجه، یک بازی کوچک به نام Onslaught است! آرنا .

ظاهر یکپارچهسازی با سیستمعامل و پیکسلی

مهم این بود که بازی ما شبیه یک بازی سیستم سرگرمی نینتندو یکپارچهسازی با سیستمعامل باشد، با توجه به فرضیه مسابقه برای توسعه یک بازی مبتنی بر چیپ‌تون . اکثر بازی‌ها این نیاز را ندارند، اما به دلیل سهولت ایجاد دارایی و جذابیت طبیعی برای گیمرهای نوستالژیک، همچنان یک سبک هنری رایج (به ویژه در میان توسعه‌دهندگان مستقل) است.

هجوم! اندازه پیکسل آرنا
افزایش اندازه پیکسل می تواند کار طراحی گرافیکی را کاهش دهد.

با توجه به اینکه این اسپرایت ها چقدر کوچک هستند، تصمیم گرفتیم پیکسل های خود را دو برابر کنیم، به این معنی که یک اسپرایت 16x16 اکنون 32x32 پیکسل و غیره خواهد بود. از همان ابتدا، به جای اینکه مرورگر را وادار کنیم کارهای سنگین را انجام دهد، جنبه ایجاد دارایی را دوچندان کرده بودیم. اجرای این کار ساده‌تر بود، اما مزایای ظاهری مشخصی نیز داشت.

در اینجا یک سناریو است که ما در نظر گرفتیم:

<style>
canvas {
  width: 640px;
  height: 320px;
}
</style>
<canvas width="320" height="240">
  Sorry, your browser is not supported.
</canvas>

این روش به جای دو برابر کردن آنها در سمت ایجاد دارایی، از sprites 1x1 تشکیل شده است. از آنجا، CSS خود بوم را در دست گرفته و اندازه آن را تغییر می دهد. معیارهای ما نشان داد که این روش می‌تواند دو برابر سریع‌تر از رندر کردن تصاویر بزرگ‌تر (دوبرابر) باشد، اما متأسفانه تغییر اندازه CSS شامل anti-aliasing است، چیزی که ما نتوانستیم راهی برای جلوگیری از آن پیدا کنیم.

گزینه های تغییر اندازه بوم
سمت چپ: دارایی های پیکسل کامل در فتوشاپ دو برابر شده است. راست: تغییر اندازه CSS یک جلوه تار اضافه کرد.

این یک شکست برای بازی ما بود، زیرا تک تک پیکسل ها بسیار مهم هستند، اما اگر نیاز به تغییر اندازه بوم خود دارید و ضد aliasing برای پروژه شما مناسب است، می توانید این رویکرد را به دلایل عملکرد در نظر بگیرید.

ترفندهای جالب بوم نقاشی

همه ما می دانیم که <canvas> داغ جدید است، اما گاهی اوقات توسعه دهندگان هنوز استفاده از DOM را توصیه می کنند . اگر نمی‌خواهید از آن استفاده کنید، در اینجا نمونه‌ای از این است که چگونه <canvas> در زمان و انرژی ما صرفه‌جویی کرد.

هنگامی که یک دشمن در Onslaught مورد ضربه قرار می گیرد! Arena ، قرمز چشمک می زند و به طور خلاصه یک انیمیشن "درد" را نمایش می دهد. برای محدود کردن تعداد گرافیک‌هایی که باید ایجاد می‌کردیم، فقط دشمنان را در «درد» در جهت رو به پایین نشان می‌دهیم. این در بازی قابل قبول به نظر می رسد و در زمان ایجاد جن صرفه جویی می کند. با این حال، برای هیولاهای رئیس، دیدن یک اسپرایت بزرگ (با ابعاد 64×64 پیکسل یا بیشتر) که از سمت چپ یا بالا به طور ناگهانی به سمت پایین برای قاب درد می‌چرخد، سخت بود.

یک راه حل واضح می تواند ترسیم یک قاب درد برای هر رئیس در هر یک از هشت جهت باشد، اما این کار بسیار زمان بر خواهد بود. به لطف <canvas> ، ما توانستیم این مشکل را در کد حل کنیم:

Beholder در Onslaught آسیب می بیند! آرنا
اثرات جالبی را می توان با استفاده از context.globalCompositeOperation ایجاد کرد.

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

// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");

// Draw your image on the buffer
buffer.drawImage(image, 0, 0);

// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();

// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);

حلقه بازی

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

در اینجا نمونه ای از حلقه بازی آورده شده است:

function main () {
  handleInput();
  update();
  render();
};

setInterval(main, 1);

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

window.addEventListener("mousedown", function(e) {
  // A mouse click means the players wants to attack.
  // We don't actually do that yet, but instead tell the rest
  // of the program about the request.
  buttonStates[e.button] = true;
}, false);

function handleInput() {
  // Here is where we respond to the click
  if (buttonStates[LEFT_BUTTON]) {
    player.attacking = true;
    delete buttonStates[LEFT_BUTTON];
  }
};

اکنون ما در مورد ورودی می دانیم و می توانیم آن را در عملکرد update در نظر بگیریم، زیرا می دانیم که به بقیه قوانین بازی پایبند است.

function update() {
  // Check for collisions, states, whatever else is needed

  // If after that the player can still attack, do it!
  if (player.attacking && player.canAttack()) {
    player.attack();
  }
};

در نهایت، هنگامی که همه چیز محاسبه شد، نوبت به ترسیم مجدد صفحه می رسد! در DOM-land، مرورگر این افزایش را انجام می دهد. اما هنگام استفاده از <canvas> لازم است هر زمان که اتفاقی می افتد به صورت دستی دوباره ترسیم شود (که معمولاً هر فریم است!).

function render() {
  // First erase everything, something like:
  context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  // Draw the player (and whatever else you need)
  context.drawImage(
    player.getImage(),
    player.x, player.y
  );
};

مدلسازی مبتنی بر زمان

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

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

function update() {

  // NOTE: You'll need to initially seed this.lastUpdate
  // with the current time when your game loop starts
  // this.lastUpdate = Date.now();

  // Calculate elapsed time since last frame
  var now = Date.now();
  var elapsed = (now - this.lastUpdate);
  this.lastUpdate = now;

  // Do stuff with elapsed

};

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

var Sprite = function() {

  // The sprite's position relative to the top left of the game world
  this.position = {x: 0, y: 0};

  // The sprite's direction. A positive x value indicates moving to the right
  this.direction = {x: 1, y: 0};

  // How many pixels the sprite moves per second
  this.speed = 50;
};

با در نظر گرفتن این متغیرها، در اینجا نحوه انتقال نمونه ای از کلاس sprite بالا با استفاده از مدل سازی مبتنی بر زمان آورده شده است:

// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;

// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);

توجه داشته باشید که مقادیر direction.x و direction.y باید نرمال شوند، به این معنی که همیشه باید بین -1 و 1 قرار گیرند.

کنترل ها

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

به منظور مقایسه با بازی های مشابه در ژانر خود، ما پشتیبانی از ماوس را برای کنترل یک شبکه هدف قرار دادیم که شخصیت از آن برای هدف گیری حملات خود استفاده می کند. هنوز هم می‌توان شخصیت را با صفحه کلید جابه‌جا کرد، اما پس از این تغییر می‌توانست به طور همزمان در هر جهت کامل 360 درجه شلیک کند. بازیکنان هاردکور از این ویژگی قدردانی می‌کردند، اما عواقب ناخوشایند ناامید کردن کاربران ترک پد را داشت.

هجوم! کنترل‌های آرنا مودال (منسوخ شده)
یک کنترل قدیمی یا مدال "چگونه بازی کنیم" در Onslaught! آرنا.

برای جا دادن به کاربران ترک‌پد، کنترل‌های کلید پیکان را بازگردانیم، این بار تا امکان شلیک در جهت(های) فشار داده شده را فراهم کنیم. در حالی که ما احساس می‌کردیم که به انواع بازیکنان غذا می‌دهیم، ناخودآگاه پیچیدگی زیادی را به بازی خود وارد می‌کردیم. در کمال تعجب، بعداً شنیدیم که برخی از بازیکنان از کنترل‌های اختیاری ماوس (یا صفحه کلید!) برای حمله آگاه نبودند، علی‌رغم مدال‌های آموزشی، که تا حد زیادی نادیده گرفته شدند.

هجوم! آموزش کنترل های آرنا
بازیکنان عمدتاً پوشش آموزشی را نادیده می گیرند. آنها ترجیح می دهند بازی کنند و لذت ببرند!

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

با این طرح کنترل پیچیده ای که ما اجرا کرده ایم، مشکل بازی در دستگاه های تلفن همراه نیز وجود دارد. در واقع، یکی از رایج ترین درخواست های ما این است که Onslaught را بسازیم! Arena در Android، iPad و سایر دستگاه های لمسی (جایی که صفحه کلید وجود ندارد) موجود است. یکی از نقاط قوت اصلی HTML5 قابل حمل بودن آن است، بنابراین نصب بازی روی این دستگاه‌ها قطعاً قابل انجام است، فقط باید بسیاری از مشکلات (به ویژه کنترل‌ها و عملکرد) را حل کنیم.

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

// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
  // Found one! Shoot in its direction
  var shoot = hostile.boundingBox().center().subtract(
    player.boundingBox().center()
  ).normalize();
}

// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).magnitude();

// Prevent jittering if the character is close enough
if (distance < 3) {
  move.zero();
}

// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
  player.setDirection(move);
}

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

صوتی

در میان کنترل‌ها و عملکرد، یکی از بزرگترین مشکلات ما در حین توسعه Onslaught! Arena تگ <audio> HTML5 بود. احتمالاً بدترین جنبه تأخیر است: تقریباً در همه مرورگرها بین فراخوانی .play() و صدای واقعی پخش می شود. این می تواند تجربه یک گیمر را خراب کند، به خصوص زمانی که با یک بازی سریع مانند بازی ما بازی می کنید.

مشکلات دیگر عبارتند از : رویداد "پیشرفت" فعال نشد ، که می تواند باعث شود جریان بارگذاری بازی به طور نامحدود متوقف شود. به این دلایل، ما روشی را که ما آن را "سقوط به جلو" می نامیم، اتخاذ کردیم، که در آن اگر Flash بارگیری نشد، به HTML5 Audio تغییر می کنیم. کد چیزی شبیه به این است:

/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/

// Default to sm2 (Flash)
var api = "sm2";

function initAudio (callback) {
  switch (api) {
    case "sm2":
      soundManager.onerror = (function (init) {
        return function () {
          api = "html5";
          init(callback);
        };
      }(arguments.callee));
      break;
    case "html5":
      var audio = document.createElement("audio");

      if (
        audio
        && audio.canPlayType
        && audio.canPlayType("audio/mpeg;")
      ) {
        callback();
      } else {
        // No audio support :(
      }
      break;
  }
};

همچنین ممکن است برای یک بازی مهم باشد که از مرورگرهایی که فایل های MP3 را پخش نمی کنند (مانند Mozilla Firefox) پشتیبانی کند. در این صورت، پشتیبانی را می توان شناسایی کرد و به چیزی مانند Ogg Vorbis با کدی مانند زیر تغییر داد:

/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/

var audio = document.createElement("audio");

if (audio && audio.canPlayType) {
  if (!audio.canPlayType("audio/mpeg;")) {
    // Here you know you CANNOT use .mp3 files
    if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
      // Here you know you CAN use .ogg files
    }
  }
}

ذخیره داده ها

شما نمی توانید بدون نمرات بالا یک بازی به سبک آرکید داشته باشید! ما می‌دانستیم که برای ادامه دادن به برخی از داده‌های بازی خود نیاز داریم، و در حالی که می‌توانستیم از چیزی قدیمی مانند کوکی‌ها استفاده کنیم، می‌خواستیم به فناوری‌های سرگرم‌کننده جدید HTML5 بپردازیم. مطمئناً هیچ کمبودی در گزینه‌ها از جمله ذخیره‌سازی محلی، ذخیره‌سازی جلسه و پایگاه‌های داده وب SQL وجود ندارد.

ALT_TEXT_HERE
نمرات بالا ذخیره می شود، و همچنین جایگاه شما در بازی پس از شکست دادن هر رئیس ذخیره می شود.

ما تصمیم گرفتیم از localStorage استفاده کنیم زیرا جدید، عالی و آسان برای استفاده است. از ذخیره جفت‌های کلید/مقدار اصلی پشتیبانی می‌کند که تمام بازی ساده ما مورد نیاز است. در اینجا یک مثال ساده از نحوه استفاده از آن آورده شده است:

if (typeof localStorage == "object") {
  localStorage.setItem("foo", "bar");
  localStorage.getItem("foo"); // Value is "bar"
  localStorage.removeItem("foo");
  localStorage.getItem("foo"); // Value is now null
}

برخی از "گوچاها" وجود دارد که باید از آنها آگاه بود. مهم نیست که چه چیزی را ارسال می کنید، مقادیر به صورت رشته ای ذخیره می شوند که می تواند منجر به نتایج غیرمنتظره ای شود:

localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
  // It's true!
}

// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)

// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}

خلاصه

کار با HTML5 شگفت انگیز است. بیشتر پیاده‌سازی‌ها همه چیزهایی را که یک توسعه‌دهنده بازی نیاز دارد، از گرافیک گرفته تا ذخیره وضعیت بازی، انجام می‌دهند. در حالی که مشکلات فزاینده ای وجود دارد (مانند مشکلات تگ <audio> )، توسعه دهندگان مرورگر به سرعت در حال حرکت هستند و با چیزهایی که در حال حاضر عالی هستند، آینده برای بازی های ساخته شده بر روی HTML5 روشن به نظر می رسد.

هجوم! آرنا با لوگوی پنهان HTML5
هنگام بازی Onslaught می توانید با تایپ "html5" یک سپر HTML5 دریافت کنید! آرنا.
،

جف بلر
Geoff Blair
مت هکت
Matt Hackett

مقدمه

در ژوئن سال 2010، متوجه شدیم که نشریه محلی بوئینگ بوینگ در حال برگزاری مسابقه توسعه بازی است. ما این را بهانه ای کاملاً خوب برای ساختن یک بازی سریع و ساده در جاوا اسکریپت و <canvas> دیدیم، بنابراین دست به کار شدیم. بعد از مسابقه هنوز ایده های زیادی داشتیم و می خواستیم کاری را که شروع کردیم به پایان برسانیم. در اینجا مطالعه موردی نتیجه، یک بازی کوچک به نام Onslaught است! آرنا .

ظاهر یکپارچهسازی با سیستمعامل و پیکسلی

مهم این بود که بازی ما شبیه یک بازی سیستم سرگرمی نینتندو یکپارچهسازی با سیستمعامل باشد، با توجه به فرضیه مسابقه برای توسعه یک بازی مبتنی بر چیپ‌تون . بیشتر بازی ها این شرط را ندارند ، اما به دلیل سهولت در ایجاد دارایی و جذابیت طبیعی برای گیمرهای نوستالژیک ، هنوز هم یک سبک هنری رایج (به ویژه در بین توسعه دهندگان ایندی) است.

هجوم! اندازه پیکسل آرنا
افزایش اندازه پیکسل می تواند کار طراحی گرافیک را کاهش دهد.

با توجه به اینکه این اسپریت ها چقدر کوچک هستند ، تصمیم گرفتیم پیکسل های خود را دو برابر کنیم ، به این معنی که یک اسپری 16x16 اکنون 32x32 پیکسل و غیره خواهد بود. از ابتدا ما به جای اینکه مرورگر را انجام دهیم ، در سمت ایجاد دارایی دو برابر شده بودیم. این کار ساده تر بود اما مزایای مشخصی نیز داشت.

در اینجا سناریویی وجود دارد که ما در نظر گرفتیم:

<style>
canvas {
  width: 640px;
  height: 320px;
}
</style>
<canvas width="320" height="240">
  Sorry, your browser is not supported.
</canvas>

این روش به جای دو برابر شدن آنها در سمت ایجاد دارایی شامل 1x1 Sprites است. از آنجا ، CSS خود بوم را به دست می گیرد و تغییر اندازه می دهد. معیارهای ما نشان داد که این روش می تواند تقریباً دو برابر سریعتر از تصاویر بزرگتر (دو برابر) باشد ، اما متأسفانه تغییر اندازه CSS شامل ضد آلیاژ است ، چیزی که ما نتوانستیم راهی برای جلوگیری از آن پیدا کنیم.

گزینه های تغییر اندازه بوم
سمت چپ: دارایی های پیکسل در فتوشاپ دو برابر شدند. سمت راست: تغییر اندازه CSS یک اثر مبهم اضافه کرد.

این یک معامله گر برای بازی ما بود زیرا پیکسل های فردی بسیار مهم هستند اما اگر نیاز به تغییر اندازه بوم خود دارید و ضد ابراز نظر برای پروژه شما مناسب است ، می توانید این رویکرد را به دلایل عملکرد در نظر بگیرید.

ترفندهای بوم سرگرم کننده

همه ما می دانیم که <canvas> گرمای جدید است ، اما گاهی اوقات توسعه دهندگان هنوز هم استفاده از DOM را توصیه می کنند . اگر در حصار استفاده از آن قرار دارید ، در اینجا نمونه ای از چگونگی نجات <canvas> وقت و انرژی زیادی را برای ما صرفه جویی می کند.

وقتی دشمن در یورش برخورد می کند! آرنا ، قرمز چشمک می زند و به طور خلاصه یک انیمیشن "درد" را به نمایش می گذارد. برای محدود کردن تعداد گرافیک هایی که باید ایجاد کنیم ، فقط دشمنان را در "درد" در جهت رو به پایین نشان می دهیم. این به نظر می رسد قابل قبول در بازی و صرفه جویی در وقت ایجاد Sprite. با این حال ، برای هیولاهای رئیس ، دیدن یک اسپری بزرگ (در 64x64 پیکسل یا بیشتر) از رو به سمت چپ یا بالا تا ناگهان رو به پایین برای قاب درد دیدن می کرد.

یک راه حل آشکار این است که برای هر رئیس در هر یک از هشت جهت یک قاب درد برای هر رئیس ترسیم شود ، اما این بسیار وقت گیر بود. به لطف <canvas> ، ما توانستیم این مشکل را در کد حل کنیم:

بیننده در اثر حمله آسیب دیده! آرنا
جلوه های جالب را می توان با استفاده از متن. globalcompositeoperation انجام داد.

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

// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");

// Draw your image on the buffer
buffer.drawImage(image, 0, 0);

// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();

// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);

حلقه بازی

توسعه بازی تفاوتهای قابل توجهی از توسعه وب دارد. در پشته وب ، معمولاً واکنش به رویدادهایی که از طریق شنوندگان رویداد اتفاق می افتد ، واکنش نشان می دهد. بنابراین کد اولیه سازی ممکن است کاری غیر از گوش دادن به رویدادهای ورودی انجام دهد. منطق یک بازی متفاوت است ، زیرا لازم است دائماً خود را به روز کند. به عنوان مثال ، اگر یک بازیکن جابجا نشده باشد ، نباید جلوی او را بگیرد!

در اینجا نمونه ای از حلقه بازی آورده شده است:

function main () {
  handleInput();
  update();
  render();
};

setInterval(main, 1);

اولین تفاوت مهم این است که عملکرد handleInput در واقع بلافاصله کاری انجام نمی دهد. اگر کاربر یک کلید را در یک برنامه وب معمولی فشار دهد ، منطقی است که بلافاصله عمل مورد نظر را انجام دهیم. اما در یک بازی ، اتفاقات باید به ترتیب زمانی اتفاق بیفتد تا به درستی جریان یابد.

window.addEventListener("mousedown", function(e) {
  // A mouse click means the players wants to attack.
  // We don't actually do that yet, but instead tell the rest
  // of the program about the request.
  buttonStates[e.button] = true;
}, false);

function handleInput() {
  // Here is where we respond to the click
  if (buttonStates[LEFT_BUTTON]) {
    player.attacking = true;
    delete buttonStates[LEFT_BUTTON];
  }
};

اکنون ما در مورد ورودی می دانیم و می توانیم آن را در عملکرد update در نظر بگیریم ، با دانستن اینکه به بقیه قوانین بازی پایبند خواهد بود.

function update() {
  // Check for collisions, states, whatever else is needed

  // If after that the player can still attack, do it!
  if (player.attacking && player.canAttack()) {
    player.attack();
  }
};

سرانجام ، هنگامی که همه چیز محاسبه شد ، وقت آن است که دوباره صفحه را دوباره انتخاب کنید! در Dom-Land ، مرورگر این بالابر را اداره می کند. اما هنگام استفاده از <canvas> لازم است هر زمان که اتفاقی بیفتد (که معمولاً هر قاب است!) به صورت دستی دوباره جابجا شوید.

function render() {
  // First erase everything, something like:
  context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  // Draw the player (and whatever else you need)
  context.drawImage(
    player.getImage(),
    player.x, player.y
  );
};

مدل سازی مبتنی بر زمان

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

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

function update() {

  // NOTE: You'll need to initially seed this.lastUpdate
  // with the current time when your game loop starts
  // this.lastUpdate = Date.now();

  // Calculate elapsed time since last frame
  var now = Date.now();
  var elapsed = (now - this.lastUpdate);
  this.lastUpdate = now;

  // Do stuff with elapsed

};

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

var Sprite = function() {

  // The sprite's position relative to the top left of the game world
  this.position = {x: 0, y: 0};

  // The sprite's direction. A positive x value indicates moving to the right
  this.direction = {x: 1, y: 0};

  // How many pixels the sprite moves per second
  this.speed = 50;
};

با توجه به این متغیرها در اینجا نحوه جابجایی نمونه ای از کلاس SPRITE فوق با استفاده از مدل سازی مبتنی بر زمان آورده شده است:

// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;

// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);

توجه داشته باشید که مقادیر direction.x و direction.y باید عادی شوند و این بدان معنی است که همیشه باید بین -1 تا 1 سقوط کنند.

کنترل ها

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

به منظور مقایسه با بازی های مشابه در ژانر خود ، ما پشتیبانی ماوس را برای کنترل یک شبکیه هدفمند اضافه کردیم ، که شخصیت از آن برای هدف حملات وی استفاده می کند. این شخصیت هنوز هم می تواند با صفحه کلید جابجا شود ، اما پس از این تغییر او می تواند همزمان در هر جهت کامل 360 درجه آتش بگیرد. بازیکنان هاردکور از این ویژگی قدردانی کردند اما این اثر ناگوار از کاربران ناامید کننده TrackPad را داشت.

هجوم! Arena Modal را کنترل می کند (مستهلک)
یک کنترل قدیمی یا "نحوه بازی کردن" در یورش در یورش! آرنا.

برای اسکان کاربران TrackPad ، ما کنترل های کلید Arrow را به دست آوردیم ، این بار اجازه می دهیم شلیک در جهت (های) فشرده شده. در حالی که احساس می کردیم که با انواع بازیکنان پذیرایی می کنیم ، ما همچنین ناآگاهانه پیچیدگی زیادی را برای بازی خود معرفی می کردیم. در کمال تعجب ما ، بعداً می شنویم که برخی از بازیکنان با وجود ماوس های آموزش ، که تا حد زیادی نادیده گرفته شده بودند ، از کنترل ماوس اختیاری (یا صفحه کلید!) برای حمله آگاه نبودند.

هجوم! آموزش کنترل آرنا
بازیکنان بیشتر پوشش آموزش را نادیده می گیرند. آنها ترجیح می دهند بازی کنند و از آن لذت ببرند!

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

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

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

// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
  // Found one! Shoot in its direction
  var shoot = hostile.boundingBox().center().subtract(
    player.boundingBox().center()
  ).normalize();
}

// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).magnitude();

// Prevent jittering if the character is close enough
if (distance < 3) {
  move.zero();
}

// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
  player.setDirection(move);
}

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

صوتی

در میان کنترل ها و عملکرد ، یکی از بزرگترین موضوعات ما در هنگام توسعه یورش! Arena برچسب HTML5 <audio> بود. احتمالاً بدترین جنبه تأخیر است: تقریباً در همه مرورگرها بین فراخوان تاخیر وجود دارد .play() و صدا در واقع پخش می شود. این می تواند تجربه یک گیمر را خراب کند ، به خصوص هنگام بازی با یک بازی سریع مانند ما.

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

/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/

// Default to sm2 (Flash)
var api = "sm2";

function initAudio (callback) {
  switch (api) {
    case "sm2":
      soundManager.onerror = (function (init) {
        return function () {
          api = "html5";
          init(callback);
        };
      }(arguments.callee));
      break;
    case "html5":
      var audio = document.createElement("audio");

      if (
        audio
        && audio.canPlayType
        && audio.canPlayType("audio/mpeg;")
      ) {
        callback();
      } else {
        // No audio support :(
      }
      break;
  }
};

همچنین ممکن است برای یک بازی برای پشتیبانی از مرورگرهایی که فایلهای MP3 را پخش نمی کنند (مانند Mozilla Firefox) مهم باشد. در این صورت ، پشتیبانی می تواند به چیزی مانند OGG Vorbis منتقل شود ، با کدی مانند این:

/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/

var audio = document.createElement("audio");

if (audio && audio.canPlayType) {
  if (!audio.canPlayType("audio/mpeg;")) {
    // Here you know you CANNOT use .mp3 files
    if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
      // Here you know you CAN use .ogg files
    }
  }
}

ذخیره داده ها

شما نمی توانید بدون نمرات بالا یک شاخه به سبک بازی داشته باشید! ما می دانستیم که برای ادامه دادن به برخی از داده های بازی خود نیاز خواهیم داشت ، و در حالی که می توانستیم مانند کوکی ها از چیزی کلاه قدیمی استفاده کنیم ، می خواستیم به فناوری های جدید HTML5 سرگرم کننده بپردازیم. مطمئناً هیچ کمبود گزینه هایی از جمله ذخیره سازی محلی ، ذخیره سازی جلسه و پایگاه داده های وب SQL وجود ندارد.

ALT_TEXT_HERE
نمرات بالا پس از شکست دادن هر رئیس ، در بازی و همچنین جایگاه شما در بازی ذخیره می شود.

ما تصمیم گرفتیم از localStorage استفاده کنیم زیرا این کار جدید ، عالی و آسان برای استفاده است. این پشتیبانی از صرفه جویی در جفت های اصلی/ارزش اصلی که تمام بازی ساده ما مورد نیاز است پشتیبانی می کند. در اینجا یک مثال ساده از نحوه استفاده از آن آورده شده است:

if (typeof localStorage == "object") {
  localStorage.setItem("foo", "bar");
  localStorage.getItem("foo"); // Value is "bar"
  localStorage.removeItem("foo");
  localStorage.getItem("foo"); // Value is now null
}

برخی از "Gotchas" وجود دارد که از آنها آگاه باشند. مهم نیست که از چه چیزی عبور می کنید ، مقادیر به عنوان رشته ها ذخیره می شوند ، که می تواند منجر به نتایج غیر منتظره شود:

localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
  // It's true!
}

// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)

// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}

خلاصه

HTML5 برای کار با آن شگفت انگیز است. بیشتر پیاده سازی ها همه چیزهایی را که یک توسعه دهنده بازی به آن نیاز دارد ، از گرافیک گرفته تا ذخیره وضعیت بازی اداره می کند. در حالی که برخی از دردهای در حال رشد (مانند <audio> Woes Tag) وجود دارد ، توسعه دهندگان مرورگر به سرعت در حال حرکت هستند و با چیزهایی که به همان اندازه بسیار زیاد هستند ، آینده برای بازی های ساخته شده در HTML5 روشن به نظر می رسد.

هجوم! Arena با یک آرم HTML5 پنهان
می توانید با تایپ کردن "HTML5" هنگام بازی در یورش ، یک سپر HTML5 دریافت کنید! آرنا.