Panduan tanpa air mata untuk game HTML5

Daniel X. Moore
Daniel X. Moore

Pengantar

Jadi, Anda ingin membuat game menggunakan Canvas dan HTML5? Ikuti tutorial ini dan Anda akan segera siap.

Tutorial ini mengasumsikan bahwa Anda memiliki pengetahuan JavaScript minimal tingkat menengah.

Anda dapat memainkan game terlebih dahulu atau langsung membuka artikel dan melihat kode sumber untuk game tersebut.

Membuat kanvas

Untuk menggambar, kita harus membuat kanvas. Karena ini adalah panduan Tanpa Air Mata, kita akan menggunakan 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');

Game loop

Untuk menyimulasikan tampilan gameplay yang lancar dan berkelanjutan, kita ingin memperbarui game dan menggambar ulang layar lebih cepat daripada yang dapat dirasakan oleh pikiran dan mata manusia.

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

Untuk saat ini, kita dapat membiarkan metode update dan gambar kosong. Hal yang penting untuk diketahui adalah setInterval() akan memanggilnya secara berkala.

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

Halo dunia

Setelah kita menjalankan game loop, mari perbarui metode gambar untuk benar-benar menggambar beberapa teks di layar.

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

Hal ini cukup keren untuk teks diam, tetapi karena kita sudah menyiapkan loop game, kita seharusnya dapat membuatnya bergerak dengan cukup mudah.

var textX = 50;
var textY = 50;

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

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

Sekarang, coba lihat. Jika Anda mengikutinya, gambar akan bergerak, tetapi juga meninggalkan waktu sebelumnya saat gambar digambar di layar. Luangkan waktu untuk menebak alasannya. Hal ini karena kita tidak menghapus layar. Jadi, mari tambahkan beberapa kode penghapusan layar ke metode gambar.

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

Setelah memiliki beberapa teks yang bergerak di layar, Anda sudah setengah jalan untuk memiliki game yang sebenarnya. Cukup perkuat kontrol, tingkatkan gameplay, dan tingkatkan kualitas grafis. Oke, mungkin 1/7 dari cara untuk memiliki game sungguhan, tetapi kabar baiknya adalah ada banyak hal lain dalam tutorial ini.

Membuat pemutar

Buat objek untuk menyimpan data pemain dan bertanggung jawab atas hal-hal seperti menggambar. Di sini kita membuat objek pemain menggunakan literal objek sederhana untuk menyimpan semua info.

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);
  }
};

Untuk saat ini, kita menggunakan persegi panjang berwarna sederhana untuk mewakili pemain. Saat menggambar game, kita akan menghapus kanvas dan menggambar pemain.

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

Kontrol keyboard

Menggunakan Hotkey jQuery

Plugin Hotkeys jQuery mempermudah penanganan tombol di seluruh browser. Daripada menangisi masalah keyCode dan charCode lintas browser yang tidak dapat diuraikan, kita dapat mengikat peristiwa seperti ini:

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

Tidak perlu khawatir tentang detail kunci mana yang memiliki kode mana adalah keuntungan besar. Kita hanya ingin dapat mengucapkan hal-hal seperti "saat pemain menekan tombol atas, lakukan sesuatu". Hotkey jQuery memungkinkan hal itu dengan baik.

Pergerakan pemain

Cara JavaScript menangani peristiwa keyboard sepenuhnya berbasis peristiwa. Artinya, tidak ada kueri bawaan untuk memeriksa apakah kunci tidak berfungsi, jadi kita harus menggunakan kueri kita sendiri.

Anda mungkin bertanya, "Mengapa tidak menggunakan cara penanganan kunci berbasis peristiwa saja?" Hal ini karena frekuensi pengulangan keyboard bervariasi di seluruh sistem dan tidak terikat dengan pengaturan waktu loop game, sehingga gameplay dapat sangat bervariasi dari sistem ke sistem. Untuk menciptakan pengalaman yang konsisten, penting untuk mengintegrasikan deteksi peristiwa keyboard dengan game loop secara ketat.

Kabar baiknya, saya telah menyertakan wrapper JS 16 baris yang akan menyediakan kueri peristiwa. File ini disebut key_status.js dan Anda dapat mengkueri status kunci kapan saja dengan memeriksa keydown.left, dll.

Setelah memiliki kemampuan untuk membuat kueri apakah tombol ditekan, kita dapat menggunakan metode pembaruan sederhana ini untuk memindahkan pemain.

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

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

Silakan coba.

Anda mungkin melihat bahwa pemutar dapat dipindahkan dari layar. Mari kita batasi posisi pemain agar tetap berada dalam batas. Selain itu, pemain tampaknya agak lambat, jadi mari kita tingkatkan kecepatannya juga.

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

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

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

Menambahkan input lainnya akan sama mudahnya, jadi mari kita tambahkan semacam proyektil.

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...
};

Menambahkan lebih banyak objek game

Projectile

Sekarang, mari kita tambahkan proyektil yang sebenarnya. Pertama, kita memerlukan koleksi untuk menyimpan semuanya:

var playerBullets = [];

Selanjutnya, kita memerlukan konstruktor untuk membuat instance peluru.

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;
}

Saat pemain menembak, kita harus membuat instance peluru dan menambahkannya ke koleksi peluru.

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
  };
};

Sekarang kita perlu menambahkan pembaruan buletin ke fungsi langkah pembaruan. Untuk mencegah kumpulan peluru terisi tanpa batas, kita memfilter daftar peluru agar hanya menyertakan peluru yang aktif. Hal ini juga memungkinkan kita menghapus peluru yang telah bertabrakan dengan musuh.

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

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

Langkah terakhir adalah menggambar peluru:

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

Musuh

Sekarang saatnya menambahkan musuh dengan cara yang sama seperti saat kita menambahkan peluru.

  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();
  });
}

Memuat dan menggambar gambar

Melihat semua kotak itu terbang-terbang memang keren, tetapi memiliki gambar untuk kotak tersebut akan lebih keren lagi. Memuat dan menggambar gambar di kanvas biasanya merupakan pengalaman yang penuh emosi. Untuk mencegah rasa sakit dan kesengsaraan tersebut, kita dapat menggunakan class utilitas sederhana.

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);
  };

  ...
}

Deteksi bentrokan

Kita memiliki semua hal ini yang berkeliaran di layar, tetapi tidak berinteraksi satu sama lain. Agar semuanya tahu kapan harus meledak, kita harus menambahkan semacam deteksi tabrakan.

Mari kita gunakan algoritma deteksi tabrakan persegi panjang sederhana:

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;
}

Ada beberapa konflik yang ingin kita periksa:

  1. Peluru Pemain => Kapal Musuh
  2. Pemain => Pesawat Musuh

Mari kita buat metode untuk menangani konflik yang dapat kita panggil dari metode update.

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();
}

Sekarang kita perlu menambahkan metode meledak ke pemain dan musuh. Tindakan ini akan menandainya untuk dihapus dan menambahkan ledakan.

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
};

Suara

Untuk melengkapi pengalaman, kita akan menambahkan beberapa efek suara yang bagus. Suara, seperti gambar, mungkin agak sulit digunakan di HTML5, tetapi berkat formula ajaib sound.js, suara dapat dibuat sangat sederhana.

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

function Enemy(I) {
  ...

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

Meskipun API kini bebas tear, menambahkan suara saat ini adalah cara tercepat untuk membuat aplikasi Anda error. Suara terkadang terputus atau menutup seluruh tab browser, jadi siapkan tisu.

Farewell

Sekali lagi, berikut adalah demo game yang berfungsi penuh. Anda juga dapat mendownload kode sumber sebagai zip.

Semoga Anda menikmati pembelajaran dasar-dasar pembuatan game sederhana di JavaScript dan HTML5. Dengan memprogram pada tingkat abstraksi yang tepat, kita dapat mengisolasi diri dari bagian API yang lebih sulit, serta menjadi tangguh dalam menghadapi perubahan di masa mendatang.

Referensi