Panduan tanpa air mata untuk game HTML5

Dania 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 setidaknya tingkat pengetahuan menengah tentang JavaScript.

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

Membuat kanvas

Untuk menggambar sesuatu, kita perlu membuat kanvas. Karena ini adalah panduan {i>No Tears<i}, kita akan menggunakan jQuery.

var CANVAS_WIDTH = 480;
var CANVAS_HEIGHT = 320;

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

Game loop

Untuk menyimulasikan tampilan gameplay yang lancar dan berkelanjutan, kami ingin mengupdate game dan menggambar ulang layar lebih cepat daripada yang dapat dipahami oleh pikiran dan mata manusia.

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

Untuk saat ini, kita dapat membiarkan metode update dan menggambar kosong. Hal penting yang perlu diketahui adalah bahwa setInterval() menangani panggilan secara berkala.

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

Halo dunia

Setelah menjalankan game loop, mari kita 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);
}

Ini cukup bagus untuk teks stasioner, tetapi karena sudah disiapkan game loop, 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, berputarlah. Jika Anda mengikutinya, judul seharusnya bergerak, tetapi juga meninggalkan waktu sebelumnya yang digambar di layar. Luangkan waktu sejenak untuk menebak mengapa hal itu terjadi. Hal ini terjadi karena kita tidak membersihkan layar. Jadi, mari tambahkan beberapa kode pembersihan layar ke metode gambar.

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

Sekarang setelah melihat beberapa teks di layar, Anda sudah setengah jalan untuk menciptakan permainan yang sesungguhnya. Cukup perketat kontrol, tingkatkan gameplay, perbaikan grafis. Oke, mungkin 1/7 dari cara kita memainkan game sungguhan, tetapi untungnya 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 pemutar. Saat menggambar game, kita akan mengosongkan kanvas dan menggambar pemain.

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

Kontrol {i>keyboard<i}

Menggunakan Hotkey jQuery

Plugin Hotkeys jQuery membuat penanganan kunci di seluruh browser jauh lebih mudah. Daripada menangis karena masalah keyCode dan charCode lintas browser yang tidak terbaca, kita dapat mengikat peristiwa seperti ini:

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

Tidak perlu khawatir dengan detail kunci mana yang kodenya merupakan kemenangan besar. Kita cuma ingin bisa mengatakan hal-hal seperti "ketika pemain menekan tombol atas, lakukan sesuatu." Hotkey jQuery memungkinkan hal itu dengan baik.

Gerakan pemain

Cara JavaScript menangani peristiwa keyboard sepenuhnya didasarkan pada peristiwa. Artinya, tidak ada kueri bawaan untuk memeriksa apakah sebuah kunci sedang turun, sehingga kita harus menggunakan kueri kita sendiri.

Anda mungkin bertanya, "Mengapa tidak menggunakan cara yang dipicu oleh peristiwa untuk menangani kunci saja?" Ini karena kecepatan pengulangan keyboard bervariasi di berbagai sistem dan tidak terikat dengan waktu game loop, sehingga gameplay dapat sangat bervariasi dari sistem ke sistem. Untuk menciptakan pengalaman yang konsisten, penting untuk memiliki deteksi peristiwa keyboard yang terintegrasi erat dengan game loop.

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

Setelah dapat mengkueri apakah tombol sedang mati, kita dapat menggunakan metode update sederhana ini untuk menggerakkan pemutar.

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

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

Coba saja.

Anda mungkin memperhatikan bahwa pemutar dapat dipindahkan dari layar. Mari kita jepit posisi pemain untuk membuatnya tetap dalam batas. Selain itu, pemain tampak agak lambat, jadi mari 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 lebih banyak input juga akan mudah, 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 objek game lainnya

Proyektil

Sekarang, mari tambahkan proyektilnya dengan nyata. Pertama, kita memerlukan koleksi untuk menyimpan semuanya di:

var playerBullets = [];

Selanjutnya, kita memerlukan konstruktor untuk membuat instance butir.

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 &amp;&amp; I.x &lt;= CANVAS_WIDTH &amp;&amp;
      I.y &gt;= 0 &amp;&amp; I.y &lt;= 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 &amp;&amp; I.inBounds();
  };

  return I;
}

Saat pemain menembak, kita harus membuat instance butir dan menambahkannya ke kumpulan poin.

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 poin ke fungsi langkah pembaruan. Untuk mencegah pengumpulan poin terisi tanpa batas waktu, kita memfilter daftar butir agar hanya menyertakan butir aktif. Ini juga memungkinkan kita untuk menghilangkan peluru yang bertabrakan dengan musuh.

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

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

Langkah terakhir adalah menggambar poin-poin:

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

Musuh

Sekarang saatnya menambahkan musuh dengan cara yang sama seperti kita menambahkan poin.

  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 &gt;= 0 &amp;&amp; I.x &lt;= CANVAS_WIDTH &amp;&amp;
      I.y &gt;= 0 &amp;&amp; I.y &lt;= 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 &amp;&amp; I.inBounds();
  };

  return I;
};

function update() {
  ...

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

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

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

function draw() {
  ...

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

Memuat dan menggambar gambar

Memang keren bisa melihat semua kotak yang beterbangan, tetapi memiliki gambarnya akan lebih keren lagi. Memuat dan menggambar gambar di kanvas biasanya merupakan pengalaman yang menegangkan. Untuk mencegah rasa sakit dan penderitaan itu, kita dapat menggunakan kelas 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

Semua kesepakatan ini ada di layar, tetapi mereka tidak berinteraksi satu sama lain. Untuk memberi tahu kapan harus meledak, kita perlu menambahkan semacam deteksi tabrakan.

Mari kita gunakan algoritma deteksi tabrakan persegi panjang yang sederhana:

function collides(a, b) {
  return a.x &lt; b.x + b.width &amp;&amp;
         a.x + a.width &gt; b.x &amp;&amp;
         a.y &lt; b.y + b.height &amp;&amp;
         a.y + a.height &gt; b.y;
}

Ada beberapa tabrakan yang ingin kita periksa:

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

Mari kita buat metode untuk menangani tabrakan 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 ledakan pada pemain dan musuh. Hal 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
};

Audio

Untuk melengkapi pengalaman ini, kami akan tambahkan efek suara yang nikmat. Suara, seperti gambar, bisa menjadi agak merepotkan untuk digunakan dalam HTML5, namun berkat formula ajaib tanpa air mata kami sound.js, suara dapat menjadi sangat sederhana.

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

function Enemy(I) {
  ...

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

Meskipun API sekarang sudah bebas gangguan, menambahkan suara saat ini merupakan cara tercepat untuk menimbulkan error pada aplikasi Anda. Tidak jarang suara yang memotong atau menghapus seluruh tab browser, jadi siapkan tisu.

Perpisahan

Sekali lagi, berikut adalah demo game lengkap yang berfungsi. Anda juga dapat mendownload kode sumber dalam bentuk zip.

Yah, saya harap Anda senang mempelajari dasar-dasar pembuatan permainan sederhana di JavaScript dan HTML5. Dengan pemrograman di tingkat abstraksi yang tepat, kita dapat melindungi diri dari bagian API yang lebih sulit, serta tangguh dalam menghadapi perubahan pada masa mendatang.

Referensi