PhaserJS ile Oyun Yapımı ve Canlı Yayınlar

Oğuzhan Selçuk Bülbül
7 min readSep 30, 2019

--

Uzun süredir yazmadığım (yazamadığım) bu blog için güzel bir konuyla tekrar yazalım dedim :) Phaser ile oyun yapmak aslında gerçekten kolay. Sadece basit düzeyde JavaScript bilginiz varsa, bunu kullanarak basit oyunlar yapabilirsiniz. Bugün Phaser ile oyun yapmaya nasıl başlarız onu anlatacağız, yazının sonunda canlı yayınlar konusuna geri döneriz.

Yeni Bir Proje Oluşturalım

ExampleGame isimli bir klasör oluşturalım ve ExampleGame klasörünü kod editörümüzde açarak öncelikle index.html dosyamızı oluşturalım. Daha sonra https://phaser.io/download/stable adresinden min.js dosyamızı indirelim. Son olarak game.js dosyası oluşturalım ve index.html dosyasına basit HTML etiketleriyle beraber js dosyalarımızı ekleyelim.

HTML dosyasının son hali şu şekilde;

<!DOCTYPE html>
<html>
<head>
<title>ExampleGame</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, minimal-ui">
<style type="text/css">
body{margin:0;padding:0;}
</style>
<script type="text/javascript" src="phaser.min.js"></script>
<script type="text/javascript" src="game.js"></script>
</head>
<body></body>
</html>

Gördüğünüz gibi aslında çok basit bir HTML dosyamız olacak ve body etiketleri arasına hiçbir şey yazmıyoruz. Title etiketi sonrasında yazdığımız meta etiketi önemli bir kod. Çünkü bu kod ekran küçüldüğünde dosyamızı scale etmek yerine bire bir boyutta göstermeye devam ediyor. Ayrıca scale yapmayı iptal ediyor, yani mobilde iki parmakla web sayfasına yakınlaştırma uzaklaştırma yapılmasına izin vermiyor.

Style etiketi ile body’nin margin ve padding değerlerini sıfırlıyoruz. Yoksa tarayıcılar body’e boşluk ekliyor ve bu boşluklar oyun ekranımızda gözükmeye başlıyor. Kodu silip deneyerek farkı görebilirsiniz.

Oyun Başlasın!

Oyunu çalıştırmak için HTML dosyasına çift tıklamamız yetmiyor maalesef. Bir local sunucuya ihtiyacımız var ve JavaScript dosyalarının doğru çalışması için HTML dosyamızı local sunucudan çağırmalıyız. Ben NodeJS ile http-server kullanacağım. Siz daha basit olması için http://fenixwebserver.com/ WAMP veya MAMP gibi geliştirme ortamlarını da kullanabilirsiniz.

NodeJS için https://nodejs.org adresine, http-server için https://www.npmjs.com/package/http-server adresine bakabilirsiniz.

Ben komut satırından ExampleGame klasörüne gittikten sonra, "http-server ." komutunu çalıştırıyorum. Ardından http://127.0.0.1:8080/index.html adresinden oluşturduğum HTML dosyasına ulaşabilirim. Eğer buraya kadar herşeyi doğru yaptıysanız, oyunu geliştirmek için ortamı hazırladınız demektir!

Ama sayfamız boş, gelin şimdi Phaser’ı nasıl çalıştırabiliriz ona bakalım. Şimdi game.js dosyanızı açın ve içerisine şu kodları ekleyin;

window.onload = function(){
new Phaser.Game();
}

Burada sayfamız tamamen yüklendikten sonra yeni bir Phaser.Game objesi istediğimizi söylüyoruz. Eğer tarayıcınızı yenilerseniz siyah bir ekran ve tarayıcınızın console bölümünde "Phaser v3.19.0 (WebGL | Web Audio)" benzeri bir yazı ile karşılaşacaksınız. Bendeki durum şöyle;

Bu şekilde siyah bir ekran ve tarayıcının Console kısmındaki yazıyı gördüyseniz her şeyi doğru yaptınız ve oyunu geliştirmeye başlamaya hazırsınız.

FlappyBird Prototipi

Hemen bir FlappyBird benzeri prototip oyunumuzu yapmaya çalışalım. Böylece kod yazdığımız bölümleri daha rahat anlayabilirsiniz. Yazacağımız tüm kodlar game.js dosyasında olacak.

Öncelikle bir ‘game’ değişkeni ve ‘gameOptions’ değişkeni oluşturarak başlıyorum. ‘game’ değişkenini Phaser’ı tutmak için ‘gameOptions’ değişkenini ise oyunla ilgili temel ayarlarımızı tutmak için kullanacağız. Şu kodları ekleyerek devam edelim;

var game;
var gameOptions = {
width:480,
height:640,
};
window.onload = function(){
game = new Phaser.Game({
width: gameOptions.width,
height: gameOptions.height,
backgroundColor: 0x000000,
scene: playGame,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
},
physics: {
default: "arcade",
arcade: {
gravity: {y:500},
debug: true
}
}
});
window.focus();
}

İlk satırlarda herhangi bir değer atamadan game değişkenimizi oluşturduk, ve gameOptions değişkenine bir obje atayarak iki tane özelik ekledik. Bunlar genişlik ve yükselik yerine geçecek olan width ve height. Ben şimdilik 480x640 bir oyun yapıyorum.

new Phaser.Game satırını şimdi game değişkenimize atayarak devam ettik. Ve Phaser.Game’e parantez içerisinde bir obje gönderiyoruz. Yine bu objede bir çok gönderebileceğimiz özellik var. Biz genişlik, yükseklik, arkaplan rengi, sahne ve boyutlandırma özelliklerini gönderiyoruz.

Scene ile gönderdiğimiz sahneyi birazdan oluşturacağız, playGame ise bizim verdiğimiz bir isim. Scale ile yeniden bir obje tanımlıyoruz ve ‘mode’, ‘autoCenter’ isminde iki özelliği var. Mode ile boyutlandırmanın tam oturacak şekilde olmasını istediğimizi söylüyoruz. Diyelim ki oyunu 480 değilde 500 genişlik ve 640 yükseklikte açtınız. Bu sefer oyun yüksekliği daha fazla arttıramadığı için genişliğini 480 de tutmaya devam edecek ve ekranda 20PX boşluk kalacak. Yine yükseklik artar genişlik aynı kalırsa bu seferde yükseklikte boşluk oluşacak.

autoCenter ile biz bu kalan boşluklarda oyunun ortalanmasını istediğimizi söyledik. Yani 500PX genişlikte kalan 20PX boşluk her iki yana 10PX şeklinde dağılmış olacak.

Physics özelliğinde önce default ile fizik motorumuzun Phaser içerisindeki arcade physics olmasını istediğimizi söylüyoruz. Daha sonra arcade ile bu fizik motorunda bir yer çekimi değeri belirliyoruz ve debug çizimlerini göstermesini söylüyoruz.

En son eklediğimiz window.focus() ise tarayıcının oluşturduğumuz HTML dosyasına odaklanmasını sağlıyor. Bazı durumlarda iframe ile oyunu bir sayfaya dahil ettiğinizde problemsiz bir şekilde oynamayı sağlıyor.

Oyunu çalıştırmaya çalışırsanız büyük ihtimalle hata alacaksınız çünkü henüz sahneyi oluşturmadık. Sahneyi oluşturarak devam edelim.

PlayGame

Hemen kodları ekleyerek anlatmaya devam edelim;

class playGame extends Phaser.Scene{
constructor(){
super("PlayGame");
}
preload(){

}
create(){}update(){}
}

playGame isimli bir sınıf oluşturduk ve bu sınıfın özelliklerini Phaser.Scene sınıfını genişleterek oluştur dedik. İlk yazdığımız constructor methodu bu sınıf oluşturulduğunda otomatik olarak çalışacak methoddur. Burada sadece kullandığımız Phaser.Scene sınıfının constructor methodunu çalıştırmak için super fonksiyonunu kullanıyoruz. Değer olarak PlayGame gönderiyoruz ki oyun oynama sahnesini oluşturduğumuzu anlasın.

Şimdi üç farklı temel fonksiyonumuz var. Preload sahne yüklenirken ilk çalıştırılacak fonksiyonlardan birisidir. Bu fonksiyonu eğer oyunda kullanacağınız bir grafik, resim, ses dosyası varsa o zaman kullanmalısınız. Phaser buradaki dosyalar yüklenene kadar bekleyecektir, böylece sahne başladıktan sonra kullanıcı resimlerin yüklenmesini oyun içinde beklemek zorunda kalmaz.

Bugünkü örnekte herhangi bir medya öğesi yüklemeyeceğimiz için bu fonksiyonu boş bırakacağız. Daha sonra create fonksiyonumuz var. Bu fonksiyon sahne başladığında sadece bir kez çalıştırılır. Bu fonksiyonun içerisinde objeleri oluşturma, yerleştirme, değişkenleri oluşturma gibi işlemleri yapabiliriz.

Sonraki fonksiyonumuz update. Bu fonksiyon ise sahne çalıştığı sürece sürekli çalışmaya devam eder. Bu yüzden bu fonksiyonda yapacağınız bir hata oyunun kilitlenmesine, çökmesine yada performans sorunlarına neden olabilir. Şimdi bizim oyunumuz için create fonksiyonunu doldurarak devam edelim. Create fonksiyonumuzun şuan ki hali;

create(){
this.player = this.add.graphics();
this.player.fillStyle(0xffffff, 1.0);
this.player.fillRect(0, 0, 50, 50);
this.physics.add.existing(this.player);
this.player.body.setSize(50,50);
this.player.setPosition(215, 295);
this.player.body.collideWorldBounds = true;
this.input.on("pointerdown", this.jump, this);
}

İlk satırlarda this.player şeklinde bir değişken oluşturuyoruz ve bunu graphic olarak atıyoruz. Böylece HTML5 canvasdaki gibi çizimler yapabiliriz. fillStyle ile beyaz bir renk belirliyoruz ve fillRect ile bir dikdörtgen çiziyoruz.

Daha sonra this.physics.add.existing ile var olan bir objeyi fizik motorumuza eklemek istediğimiz söyleyip objemizi veriyoruz. Ardından fizik özelliklerindeki boyutunu graphic de çizdiğimizi boyuta eşitleyip ekranın ortasına yerleştiriyoruz. Son olarak ekrandan dışarıya gitmemesi için sınırlara çarpma özelliğini etkinleştiriyoruz.

Son satırda yeni bir event oluşturarak ekrana tıklandığında jump fonksiyonunu çalıştırmasını söylüyoruz. Jump fonksiyonumuz ise şöyle;

jump(){
this.player.body.setVelocityY(-250);
}

Oldukça basit tek satırda sadece fizik objemize yer çekiminin tersine bir kuvvet uyguluyoruz. Şimdi oyunu çalıştırırsanız eğer ortadaki karenin aşağıya düştüğünü ve ekrana tıkladığınızda flappyBird oyunundaki gibi zıpladığını görebilirsiniz.

Şimdi ekranda akacak olan blokları oluşturarak devam edelim. Create fonksiyonumuzun son hali;

create(){
this.player = this.add.graphics();
this.player.fillStyle(0xffffff, 1.0);
this.player.fillRect(0, 0, 50, 50);
this.physics.add.existing(this.player);
this.player.body.setSize(50,50);
this.player.setPosition(215, 295);
this.player.body.collideWorldBounds = true;
this.input.on("pointerdown", this.jump, this);this.blockPool = [];
for(var i=0; i<4; i++){
var posX = i*200;
var topHeight = (Math.random()*100)+150;
var bottomHeight = (Math.random()*100)+150;
var topBlock = this.add.graphics();
topBlock.fillStyle(0xffffff, 1.0);
topBlock.fillRect(0, 0, 50, topHeight);
this.physics.add.existing(topBlock);
topBlock.body.moves = false;
topBlock.body.setSize(50,topHeight);
topBlock.setPosition(posX, 0);
var bottomBlock = this.add.graphics();
bottomBlock.fillStyle(0xffffff, 1.0);
bottomBlock.fillRect(0, 0, 50, bottomHeight);
this.physics.add.existing(bottomBlock);
bottomBlock.body.moves = false;
bottomBlock.body.setSize(50,bottomHeight);
bottomBlock.setPosition(posX, 640-bottomHeight);
this.blockPool.push([topBlock, bottomBlock]);
}
}

İşler biraz karışmış gibi gelebilir ama aslında kodların hepsi basit işlemler. Tek tek anlamaya çalışırsanız bütünü anlamak da daha kolay olabilir.

Ekrana dokunma event’imizden sonra bir this.blockPool değişkeni oluşturduk. Dizi olarak tüm blocklarımızı burada tutacağız. Ardından bir for döngüsü ile 4 tane block oluşturacağım. For döngüsünün içerisinde öncelikle X pozisyonunu buluyoruz. Bunuda döngüde kaçıncı sıradaysak onu 200 ile çarparak yapıyoruz. Blockların genişliği 50PX yani blocklar arasında 150PX bir boşluk oluşacak.

Daha sonra arası boş olacak şekilde üst ve alt bloğun yüksekliğini rastgele bir şekilde oluşturuyoruz. Bu yüksekliği de kullanarak daha önceki gibi graphic oluşturup blockları sahneye ekliyoruz. Burada yeni olan sadece bottomBlock.body.moves değeri var, bu da aslında false olarak işaretliyerek yerçekimi gibi kuvvetlerden etkilenmemesini ve fizik dünyasında hareket etmemesini sağlıyor. Böylece bu kutuları elle kendimiz hareket ettireceğiz.

For döngüsünün sonunda üst ve alt blocku bir dizi yapıp blockPool dizimize gönderiyoruz.

Gelelim bu blockları hareketlendirmeye. Tabiki bunu update fonksiyonu içerisinde yapıyoruz, fonksiyonun son hali;

update(){
this.blockPool.forEach(function(block){
var topBlock = block[0];
var bottomBlock = block[1];
topBlock.setPosition(topBlock.body.x-1, topBlock.body.y);
bottomBlock.setPosition(bottomBlock.body.x-1, bottomBlock.body.y);
if(topBlock.body.x < -50){
var latestX = this.blockPool[this.blockPool.length-1][0].x;
this.blockPool = this.blockPool.slice(1, this.blockPool.length);
topBlock.setPosition(latestX+200, topBlock.body.y);
bottomBlock.setPosition(latestX+200, bottomBlock.body.y);
this.blockPool.push([topBlock, bottomBlock]);
}
}.bind(this));
}

Burada da blockPool daki blockları bir döngü ile X yönünde hareket ettiriyoruz. Öncelikle üst ve alt blockları yeniden değişken oluşturup atıyorum. Daha sonra üst ve alt blockların X değerlerini 1 er 1 er azaltıyorum. Sonra eğer X pozisyonu -50'den küçükse ekrandan çıkmış olduğu için yeniden X’i en sağ tarafa alıyorum.

Bunun için öncelikle blockPool daki en son block’un X’ine bakıyorum ve bu X’e 200 ekleyerek ekrandan çıkmış olan block’a atıyorum. Tabiki bu block şimdi en sonda olduğu için blockPool değişkeninde de en sona atmamız gerekiyor. Bunun için blockPooldan ilk değeri alıp sonuna ekliyoruz.

Şimdi oyunu çalıştırırsanız aynı flappybird gibi zıplayan bir karemiz ve ekranda farklı yüksekliklerde giden diğer karelerimiz olduğunu görebilirsiniz.

Henüz karelerin birbirine çarpması ve yanmayı yapmadık. Hemen onunla devam edelim. Blockları oluşturduğumuz for döngüsünde this.blockPool.push satırından önce şu kodu ekleyelim;

this.physics.add.overlap(this.player, [topBlock, bottomBlock], this.gameOver, null, this);

Böylece player üst block ve alt block ile üst üste geldiğinde gameOver fonksiyonumuz çalışacak. Şimdi gameOver fonksiyonunu oluşturalım;

gameOver(){
this.scene.restart();
}

Bu fonksiyonda da sadece şuanki sahneyi yeniden başlatıyoruz, böylece oyunumuz baştan başlamış olacak. Şimdi oyunu çalıştırıp deneyebilirsiniz. FlappyBird benzeri oyunumuz hazır :) game.js dosyasının tam kodlarına https://gist.github.com/osb0/eb6ac4240022cd71f7bd878d682aa6e8 bu adresten ulaşabilirsiniz.

Phaser’da Kendinizi Geliştirin

https://phaser.io adresinde Phaser ile yapılmış bir çok örneğe ve kaynaklara ulaşabilirsiniz. Ayrıca gelelim canlı yayın konusuna :)

Kısa bir zamandır her akşam saat 21:00'da twitch üzerinden beraber Phaser ile oyun kodluyoruz. Eğer bu yazıyı okuduysanız ve Phaser ile ilgili iseniz yada bir programlama yayını izlemek isterseniz her akşam https://www.twitch.tv/retif adresinde bekliyoruz.

Yayınlardan haberdar olmak için takip et butonuna tıklayabilirsiniz. Ayrıca discord kanalımıza da gelebilirsiniz. Hem yayın ile ilgili güncellemeleri oradan yayınlıyorum hem de takıldığınız yerlerde sorular sorabileceğiniz bir topluluğumuz var.

Yazıyı beğendiyseniz, alkışlamayı ve paylaşmayı unutmayın!

Görüşmek üzere 👋

--

--