راهنمای بدون اشک برای بازی های HTML5

معرفی

بنابراین می خواهید با استفاده از Canvas و HTML5 یک بازی بسازید؟ این آموزش را دنبال کنید و در کمترین زمان در راه خواهید بود.

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

ابتدا می توانید بازی را انجام دهید یا مستقیماً به مقاله بروید و کد منبع بازی را مشاهده کنید .

ایجاد بوم

برای ترسیم چیزها، باید یک بوم بسازیم. از آنجا که این یک راهنمای No Tears است، ما از jQuery استفاده خواهیم کرد.

var CANVAS_WIDTH = 480;
var CANVAS_HEIGHT = 320;

var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
                      "' height='" + CANVAS_HEIGHT + "'></canvas>");
var canvas = canvasElement.get(0).getContext("2d");
canvasElement.appendTo('body');

حلقه بازی

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

var FPS = 30;
setInterval(function() {
  update();
  draw();
}, 1000/FPS);

در حال حاضر می‌توانیم روش‌های به‌روزرسانی و ترسیم را خالی بگذاریم. نکته مهمی که باید بدانید این است که setInterval() از فراخوانی دوره ای آنها مراقبت می کند.

function update() { ... }
function draw() { ... }

سلام دنیا

اکنون که یک حلقه بازی داریم، بیایید روش قرعه کشی خود را به روز کنیم تا در واقع مقداری متن روی صفحه بکشیم.

function draw() {
  canvas.fillStyle = "#000"; // Set color to black
  canvas.fillText("Sup Bro!", 50, 50);
}

این برای متن ثابت بسیار جالب است، اما چون ما یک حلقه بازی از قبل تنظیم کرده‌ایم، باید بتوانیم آن را به راحتی حرکت دهیم.

var textX = 50;
var textY = 50;

function update() {
  textX += 1;
  textY += 1;
}

function draw() {
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

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

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

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

ایجاد پخش کننده

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

var player = {
  color: "#00A",
  x: 220,
  y: 270,
  width: 32,
  height: 32,
  draw: function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  }
};

ما در حال حاضر از یک مستطیل رنگی ساده برای نمایش بازیکن استفاده می کنیم. وقتی بازی را ترسیم می کنیم، بوم را پاک می کنیم و بازیکن را می کشیم.

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  player.draw();
}

کنترل های صفحه کلید

استفاده از کلیدهای میانبر جی کوئری

پلاگین jQuery Hotkeys مدیریت کلید در مرورگرها را بسیار ساده تر می کند. به جای گریه کردن بر سر مسائل غیرقابل کشف keyCode و charCode بین مرورگر، می‌توانیم رویدادهایی مانند موارد زیر را پیوند دهیم:

$(document).bind("keydown", "left", function() { ... });

عدم نگرانی در مورد جزئیات اینکه کدام کلید دارای کدام کد است، یک پیروزی بزرگ است. ما فقط می خواهیم بتوانیم جملاتی مانند "وقتی بازیکن دکمه بالا را فشار می دهد، کاری انجام بده" بگوییم. jQuery Hotkeys این امکان را به خوبی می دهد.

حرکت بازیکن

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

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

خبر خوب این است که من یک بسته بندی JS 16 خطی قرار داده ام که پرس و جوی رویداد را در دسترس قرار می دهد. این کلید key_status.js نامیده می شود و می توانید وضعیت یک کلید را در هر زمان با علامت زدن keydown.left و غیره جویا شوید.

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

function update() {
  if (keydown.left) {
    player.x -= 2;
  }

  if (keydown.right) {
    player.x += 2;
  }
}

برو جلو و به آن چرخشی بده.

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

function update() {
  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

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

function update() {
  if (keydown.space) {
    player.shoot();
  }

  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

player.shoot = function() {
  console.log("Pew pew");
  // :) Well at least adding the key binding was easy...
};

اضافه کردن اشیاء بازی بیشتر

پرتابه ها

بیایید اکنون پرتابه ها را به صورت واقعی اضافه کنیم. اول، ما به مجموعه ای نیاز داریم تا همه آنها را در آنها ذخیره کنیم:

var playerBullets = [];

در مرحله بعد، ما به یک سازنده برای ایجاد نمونه های گلوله نیاز داریم.

function Bullet(I) {
  I.active = true;

  I.xVelocity = 0;
  I.yVelocity = -I.speed;
  I.width = 3;
  I.height = 3;
  I.color = "#000";

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.active = I.active && I.inBounds();
  };

  return I;
}

هنگامی که بازیکن شلیک می کند، باید یک نمونه گلوله ایجاد کنیم و آن را به مجموعه گلوله ها اضافه کنیم.

player.shoot = function() {
  var bulletPosition = this.midpoint();

  playerBullets.push(Bullet({
    speed: 5,
    x: bulletPosition.x,
    y: bulletPosition.y
  }));
};

player.midpoint = function() {
  return {
    x: this.x + this.width/2,
    y: this.y + this.height/2
  };
};

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

function update() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.update();
  });

  playerBullets = playerBullets.filter(function(bullet) {
    return bullet.active;
  });
}

مرحله آخر کشیدن گلوله ها است:

function draw() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.draw();
  });
}

دشمنان

اکنون زمان آن است که دشمنان را به همان روشی که گلوله ها را اضافه کردیم اضافه کنیم.

  enemies = [];

function Enemy(I) {
  I = I || {};

  I.active = true;
  I.age = Math.floor(Math.random() * 128);

  I.color = "#A2B";

  I.x = CANVAS_WIDTH / 4 + Math.random() * CANVAS_WIDTH / 2;
  I.y = 0;
  I.xVelocity = 0
  I.yVelocity = 2;

  I.width = 32;
  I.height = 32;

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.xVelocity = 3 * Math.sin(I.age * Math.PI / 64);

    I.age++;

    I.active = I.active && I.inBounds();
  };

  return I;
};

function update() {
  ...

  enemies.forEach(function(enemy) {
    enemy.update();
  });

  enemies = enemies.filter(function(enemy) {
    return enemy.active;
  });

  if(Math.random() < 0.1) {
    enemies.push(Enemy());
  }
};

function draw() {
  ...

  enemies.forEach(function(enemy) {
    enemy.draw();
  });
}

بارگذاری و ترسیم تصاویر

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

player.sprite = Sprite("player");

player.draw = function() {
  this.sprite.draw(canvas, this.x, this.y);
};

function Enemy(I) {
  ...

  I.sprite = Sprite("enemy");

  I.draw = function() {
    this.sprite.draw(canvas, this.x, this.y);
  };

  ...
}

تشخیص برخورد

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

بیایید از یک الگوریتم تشخیص برخورد مستطیلی ساده استفاده کنیم:

function collides(a, b) {
  return a.x < b.x + b.width &&
         a.x + a.width > b.x &&
         a.y < b.y + b.height &&
         a.y + a.height > b.y;
}

چند برخورد وجود دارد که می‌خواهیم بررسی کنیم:

  1. گلوله های بازیکن => کشتی های دشمن
  2. بازیکن => کشتی های دشمن

بیایید روشی برای رسیدگی به برخوردها ایجاد کنیم که می توانیم از روش به روز رسانی فراخوانی کنیم.

function handleCollisions() {
  playerBullets.forEach(function(bullet) {
    enemies.forEach(function(enemy) {
      if (collides(bullet, enemy)) {
        enemy.explode();
        bullet.active = false;
      }
    });
  });

  enemies.forEach(function(enemy) {
    if (collides(enemy, player)) {
      enemy.explode();
      player.explode();
    }
  });
}

function update() {
  ...
  handleCollisions();
}

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

function Enemy(I) {
  ...

  I.explode = function() {
    this.active = false;
    // Extra Credit: Add an explosion graphic
  };

  return I;
};

player.explode = function() {
  this.active = false;
  // Extra Credit: Add an explosion graphic and then end the game
};

صدا

برای کامل کردن این تجربه، می‌خواهیم جلوه‌های صوتی شیرینی را اضافه کنیم. صداها، مانند تصاویر، می توانند تا حدودی برای استفاده در HTML5 دردسرساز باشند، اما به لطف فرمول جادویی بدون اشک sound.js ما، صدا را می توان بسیار ساده ساخت.

player.shoot = function() {
  Sound.play("shoot");
  ...
}

function Enemy(I) {
  ...

  I.explode = function() {
    Sound.play("explode");
    ...
  }
}

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

بدرود

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

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

منابع