HTML5 oyunları hakkında göz atın

Doğan X. Moore
Doğan X. Moore

Giriş

Tuval ve HTML5 kullanarak bir oyun yapmak mı istiyorsunuz? Bu eğiticiyi takip ederseniz çok kısa sürede yolunuza başlayabilirsiniz.

Eğiticinin JavaScript konusunda en az orta düzeyde bilgi sahibi olduğu varsayılır.

Önce oyunu oynayabilir veya doğrudan makaleye atlayarak oyunun kaynak kodunu görüntüleyebilirsiniz.

Tuval oluşturuluyor

Bir şeyler çizmek için tuval oluşturmamız gerekir. Bu bir Tears kılavuzu olduğu için jQuery kullanacağız.

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

Oyun döngüsü

Akıcı ve kesintisiz oyun görünümünü simüle etmek için oyunu güncellemek ve ekranı insan zihni ve gözünün algılayabileceğinden daha hızlı bir şekilde yeniden çizmek istiyoruz.

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

Şimdilik güncelleme ve çizim yöntemlerini boş bırakabiliriz. Bilmeniz gereken önemli bir nokta, setInterval() adlı markanın düzenli aralıklarla telefon etmesidir.

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

Hello world

Artık devam eden bir oyun döngümüz olduğuna göre, ekranda gerçekten bir şeyler çizecek şekilde çizim yöntemimizi güncelleyelim.

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

Sabit metinler için bu harika bir özellik, ancak zaten ayarlanmış bir oyun döngümüz olduğundan bunu kolayca hareket ettirebilmeliyiz.

var textX = 50;
var textY = 50;

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

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

Şimdi deneyelim. Gidişatı takip ediyorsanız hareket ediyor, aynı zamanda ekranda çizildiği önceki zamanları da bırakıyor olmalıdır. Sorunun nedenini tahmin etmek için bir dakikanızı ayırın. Bunun nedeni ekranı temizlemememizdir. Şimdi, çizim yöntemine biraz ekran temizleme kodu ekleyelim.

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

Ekranda hareket eden metinler olduğuna göre, gerçek bir oyuna başlamanın yarısını tamamladınız. Bunun için kontrolleri sıkılaştırmanız, oynanabilirliği iyileştirmeniz ve grafiklere dokunmanız yeterli. Belki de gerçek bir oyuna ulaşmanın 7'sinde 1'i tamam, ancak eğitimin çok daha fazlasının da olması gerekiyor.

Oynatıcıyı oluşturma

Oyuncu verilerini tutmak ve çizim gibi şeylerden sorumlu olmak için bir nesne oluşturun. Burada, tüm bilgileri tutmak için basit bir nesne değişmez değerini kullanarak bir oynatıcı nesnesi oluşturuyoruz.

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

Şimdilik oynatıcıyı temsil etmek için basit renkli bir dikdörtgen kullanıyoruz. Oyunu çizdiğimizde tuvali temizleyip oyuncuyu çizeriz.

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

Klavye kontrolleri

jQuery Kısayol Tuşlarını Kullanma

jQuery Hotkeys eklentisi, tarayıcılarda anahtarların işlenmesini çok daha kolay hale getirir. Farklı tarayıcılardaki çözülemeyen keyCode ve charCode sorunları nedeniyle ağlamak yerine, aşağıdaki gibi etkinlikleri bağlayabiliriz:

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

Hangi anahtarların hangi kodlara sahip olduğu konusunda endişelenmenize gerek kalmaz. Sadece "Oynatıcı yukarı düğmesine bastığında bir şey yap" gibi şeyler söyleyebilmek istiyoruz. jQuery Hotkeys bunu güzel bir şekilde sağlar.

Oyuncu hareketi

JavaScript'in klavye etkinliklerini işleme şekli tamamen olaylara bağlıdır. Bu, bir anahtarın devre dışı olup olmadığını kontrol etmek için yerleşik bir sorgu olmadığı anlamına gelir. Bu yüzden, kendi anahtarımızı kullanmamız gerekir.

"Neden anahtar yönetimi için etkinliğe dayalı bir yöntem kullanmıyorsunuz?" diye soruyor olabilirsiniz. Bunun nedeni, klavye tekrarlama hızının sistemler arasında farklılık göstermesi ve oyun döngüsünün zamanlamasına bağlı olmamasıdır. Bu nedenle, oynanabilirlik sistemden sisteme büyük ölçüde değişebilir. Tutarlı bir deneyim oluşturmak için klavye etkinliği algılamanın oyun döngüsüne sıkı bir şekilde entegre edilmesi önemlidir.

Ancak iyi bir haberimiz var. Etkinlik sorgularının gerçekleştirilmesini sağlayacak 16 satırlık bir JS sarmalayıcı ekledim. Bu kod key_status.js olarak adlandırılır ve keydown.left öğesini kontrol ederek istediğiniz zaman bir anahtarın durumunu sorgulayabilirsiniz.

Artık tuşların çalışıp çalışmadığını sorgulayabileceğimize göre, oynatıcıyı hareket ettirmek için bu basit güncelleme yöntemini kullanabiliriz.

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

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

Şimdi deneyelim.

Oynatıcının ekranın dışına taşınabildiğini fark edebilirsiniz. Sınırların içinde kalması için oyuncunun konumunu sabitleyelim. Ayrıca, oynatıcı biraz yavaş görünüyor, o yüzden hızı da artıralım.

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

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

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

Daha fazla giriş eklemek de o kadar kolay olacak, o yüzden biraz mermi ekleyelim.

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

Daha fazla oyun nesnesi ekleme

Mermiler

Şimdi mermileri gerçekten ekleyelim. Öncelikle, bunların tümünü şurada depolayacak bir koleksiyona ihtiyacımız var:

var playerBullets = [];

Ardından, madde işareti örnekleri oluşturmak için bir kurucuya ihtiyacımız vardır.

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

Oyuncu ateş ettiğinde bir madde işareti örneği oluşturup mermi koleksiyonuna eklemeliyiz.

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

Şimdi, güncelleme adımı işlevine madde işaretlerinin güncellenmesini eklememiz gerekiyor. Madde işaretlerinin toplanmasının süresiz olarak dolmasını önlemek için madde listesi listesini yalnızca etkin madde işaretlerini içerecek şekilde filtreleriz. Bu aynı zamanda düşmanla çarpışan mermileri kaldırmamızı da sağlıyor.

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

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

Son adım maddeleri çizmektir:

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

Düşmanlar

Şimdi sıra, mermilerimizi koyduğumuz gibi düşmanları eklemeye geldi.

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

Resim yükleme ve çizim

Kutuların etrafta uçuştuğunu seyretmek güzel olsa da resimlere yer vermek daha güzel olur. Tuvale resim yüklemek ve çizmek genellikle gözyaşı döken bir deneyimdir. Bu acı ve sıkıntıyı önlemek için basit bir yardımcı programdan yararlanabiliriz.

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

  ...
}

Çarpışma algılama

Ekranda uçuşan tüm bu fırsatlar sunuluyor ama birbiri ile etkileşim halinde değiller. Her şeyin ne zaman havaya patlayacağını bilmesi için bir tür çarpışma algılama özelliği eklememiz gerekiyor.

Basit bir dikdörtgen çarpışma algılama algoritması kullanalım:

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

Kontrol etmek istediğimiz birkaç çakışma var:

  1. Player Bullets => Düşman Gemiler
  2. Oyuncu => Düşman Gemileri

Güncelleme yönteminden çağırabileceğimiz çakışmaları ele almak için bir yöntem geliştirelim.

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

Şimdi oyuncuya ve düşmanlara patlama yöntemlerini eklememiz gerekiyor. Bu işlem, kaldırılmak üzere işaretlenir ve patlama meydana gelir.

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

Ses

Deneyimi tamamlamak için birkaç hoş ses efekti ekleyeceğiz. Sesler, tıpkı resimler gibi HTML5'te kullanmak biraz zor olabilir, ancak sihirli no-tears formula sound.js sayesinde ses son derece basit hale getirilebilir.

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

function Enemy(I) {
  ...

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

API artık sorunsuz bir şekilde kullanılabilmesine rağmen, şu anda uygulamanızın kilitlenmesini sağlamanın en hızlı yolu ses eklemektir. Sesler genellikle tarayıcı sekmesinin

Elveda

Tam işlevsel oyun demosunu burada da bulabilirsiniz. Kaynak kodu posta kodu olarak da indirebilirsiniz.

Umarım JavaScript ve HTML5'te basit bir oyun yapmanın temellerini öğrenmekten keyif almışsınızdır. Doğru soyutlama düzeyinde programlama yaparak hem API'lerin daha zor kısımlarından izole edebilir hem de gelecekteki değişimlere karşı dayanıklı olabiliriz.

Referanslar