İçeriğe Geç
Tüm Yazılar

Oyun6 Dakika Okuma

Dungeon Mates: Dört Kişilik Tarayıcı Zindanı

Dungeon Mates, oda koduyla dört kişi birlikte oynanan, her katı yeniden üretilen bir zindan oyunu. Oyunda ne var, sunucu neden her şeyi kendisi hesaplıyor ve haritaları hem rastgele hem oynanabilir yapmak için ne yaptım.

Dungeon Mates arayüzünün ekran görüntüsü
Bu Yazıda7 Bölüm

Dungeon Mates, tarayıcıda oynanan piksel grafikli bir zindan oyunu. Bir oda açıyorsun, dört haneli kodu arkadaşlarına veriyorsun, herkes sınıfını seçiyor ve birlikte on katlık bir zindana iniyorsunuz. Kurulum yok, indirme yok; tek başına da oynanabiliyor. Her kat o an üretiliyor, yani aynı zindanı iki kez görmüyorsun.

Arkadaşlarımla akşam bir saat oynayabileceğimiz, kimsenin bir şey kurmasını gerektirmeyen bir oyun istiyordum. Bir de merak ettiğim bir şey vardı: hiç hazır sprite ve ses dosyası kullanmadan, her şeyi kodla çizip kodla sentezleyerek bir oyun ne kadar iyi olabilir? Oyundaki bütün karakterler, canavarlar ve efektler piksel piksel Canvas ile çiziliyor; seslerin hiçbiri dosya değil, Web Audio ile o an üretiliyor.

Oyunda Neler Var

  1. Dört Sınıf

    Savaşçı yakın mesafede dayanıklı, Büyücü alan hasarı veriyor, Okçu uzaktan vuruyor, Şifacı takımı ayakta tutuyor. Her sınıfın kendi yeteneği ve kendi saldırı sesi var.

  2. On Kat

    Her kat bir öncekinden zor. Üçüncü, beşinci, yedinci ve sekizinci katlarda birer boss, onuncu katta Demon bekliyor. Kata göre değişen kurallar, güçlendirilmiş canavarlar, yetenek seçimi ve bir dükkân her turu bir öncekinden ayırıyor.

  3. Dokuz Canavar

    Fare sürüyle gelir, hayalet duvardan geçer, örümcek ağ atıp yavaşlatır, goblin canı azalınca kaçar. Her birinin kendi yapay zekâsı ve kendi davranışı var.

  4. Birlikte Oynamak

    Oda, sohbet, minimap ve sandıklar. Zorluk oyuncu sayısına göre ölçekleniyor; dört kişiyle canavarlar daha kalabalık ve daha dayanıklı, tek başına daha çok loot düşüyor.

Oyunun bir de hikâyesi var: yerin yedi kat altında kurulmuş Zephara şehri, kendi halkını kurtarmak isterken onu lanetleyen kral ve en altta onu bekleyen son savaş. Hikâye oyunun içinde satır satır anlatılmıyor; katlar, bosslar ve dükkândaki eşyalar ona göre adlandırılmış.

Nasıl Çalışıyor

Arayüz Next.js, oyun ise bir Node sunucusunda çalışıyor ve ikisi Socket.io ile konuşuyor. Bütün oyun mantığı sunucuda: canavarların yapay zekâsı, çarpışmalar, hasar, loot ve harita üretimi saniyede yirmi kez işleniyor. Tarayıcı yalnızca tuşları gönderiyor, gelen durumu Canvas’a çiziyor. Bu hem hileyi zorlaştırıyor hem de dört oyuncunun aynı dünyayı görmesini garanti ediyor.

Çizim tarafı mantıksal olarak 480×270 piksellik küçük bir ekrana yapılıyor ve sonra büyütülüyor; piksel grafiğin keskinliği oradan geliyor. Zemin, dekor, loot, canavarlar, mermiler, oyuncular, parçacıklar ve görüş sisi ayrı katmanlarda çiziliyor. Cihaz yavaş kalırsa oyun kare hızını izleyip kaliteyi kendisi düşürüyor. Telefonda ekranda bir joystick ve düğmeler var.

Bu yazının geri kalanı oyunun en çok uğraştıran parçası olan harita üretimine ayrılıyor; çünkü rastgele üretimin sorunu şu: çoğu zaman çalışıyor.

Haritayı Nasıl Üretiyorum

Akla ilk gelen yol, rastgele yerlere rastgele boyutta odalar koyup sonra hepsini birbirine bağlamak. Bunun iki sorunu var. Odalar üst üste biner; çakışma kontrolü eklersen bu kez bazı odalar hiç yerleşemez. Odalar arasındaki bağlantılar da kopuk kalabilir: iki oda grubu oluşur ve birinden ötekine geçilemez.

Ben alanı ikiye bölerek ilerleyen yöntemi kullandım (BSP). Haritayı ikiye böl, her yarıyı yine ikiye böl, en alttaki parçalara birer oda koy. Odalar ayrı parçaların içinde olduğu için çakışamıyor. Bağlarken de ağacın kendisini izliyorsun: sol daldaki bir oda sağ daldaki bir odayla birleşiyor, köke çıkınca bütün harita bağlanmış oluyor. Her odaya ulaşılabilmesi yapının kendisinden geliyor.

server/dungeon/DungeonGenerator.ts
if (depth > 5) return;

const canSplitH = node.height >= this.minBspSize * 2;
const canSplitV = node.width >= this.minBspSize * 2;
if (!canSplitH && !canSplitV) return;

// Uzun kenarı böl: yoksa içine oda sığmayan ince parçalar çıkıyor.
let splitHorizontally: boolean;
if (canSplitH && canSplitV) {
  splitHorizontally = node.height > node.width ? true
    : node.width > node.height ? false
    : Math.random() > 0.5;
} else {
  splitHorizontally = canSplitH;
}

Bir parçanın en küçük boyu, en büyük oda artı dört kare. Bir parçaya en büyük oda bile konsa kenarlarda koridorların geçebileceği ikişer karelik pay kalıyor; bu pay olmasa odalar parçanın kenarına yapışır ve koridorlar duvarların içinden geçmek zorunda kalırdı. Koridorlar L biçiminde ve iki kare genişliğinde; hangi kolun önce oyulacağına yazı tura karar veriyor, böylece aynı iki oda arasında iki farklı yol çıkabiliyor.

Oyuncu Sayısı Haritayı Değiştiriyor

Tek kişi için büyük bir harita fazla; dakikalarca boş koridorda yürürsün. Dört kişi için küçük bir harita da fazla; herkes birbirinin üstüne düşer. Bu yüzden harita boyutu oyuncu sayısına bağlı.

OyuncuHaritaOda Boyutu
148 × 486-11
256 × 567-12
364 × 647-13
472 × 728-14

Kat numarası ikinci bir ayar: yukarı çıktıkça oda sayısı, canavarların canı ve saldırı gücü artıyor. Bu iki ayarın birbirini ezebildiğini bir denge hatasıyla öğrendim. Oyuncu sayısına göre ölçekleyen fonksiyon kat ölçeğini siliyordu; dört kişilik oyunda canavarlar birinci kat seviyesinde kalmış, tek kişilik oyun ise iki kat zorlaşmıştı.

Hiçbir Odaya Ait Olmayan Boşluklar

Bazı katlarda tuhaf bir şey oluyordu: üzerinde yürünebilen ama içinde canavar doğmayan, oraya basınca hiçbir şey tetiklenmeyen boşluklar. Harita üretilmişti, odalar vardı, oyun çalışıyordu. Sebep şuydu: ağaç bazen katın istediğinden fazla oda üretiyor ve ben fazla odaları listeden çıkarıyordum. Ama odalar listeye girmeden önce haritaya oyulmuştu.

server/dungeon/DungeonGenerator.ts
// Fazla odaların zemini zaten oyulmuştu; geri duvara çevir.
// Yoksa hiçbir odaya ait olmayan, canavarsız boşluklar kalıyor.
if (this.rooms.length > targetMax) {
  for (let i = targetMax; i < this.rooms.length; i++) {
    this.uncarveRoom(this.rooms[i]);
  }
  this.rooms = this.rooms.slice(0, targetMax);
}

Diziden silmek odayı oyunun mantığından çıkarıyordu ama zeminden çıkarmıyordu. Oyuncu oraya yürüyebiliyor, oyun ise orayı tanımıyordu. Açılan alanı yeniden duvara çeviren küçük bir fonksiyon yazmam gerekti.

Haritayı Kim Görüyor

Harita sunucuda üretiliyor, çünkü canavarlar ve dövüş zaten orada. Yalnızca bir tohum paylaşıp her tarayıcının aynı haritayı kendisinin üretmesi cazip görünüyor; ağ trafiği neredeyse sıfıra iner. Ama JavaScript’in rastgele sayı üreteci tohum almıyor ve iki tarafın birebir aynı davrandığından emin olmak ayrı bir iş.

Burada bir itirafım var. Sunucu her katın başında haritanın tamamını her oyuncuya bir kez gönderiyor. Görüş sisi, yani görülmemiş, keşfedilmiş ve görünür ayrımı yalnızca çizimde uygulanıyor. Meraklı bir oyuncu tarayıcının ağ sekmesini açıp boss odasının nerede olduğunu görebilir. Co-op bir oyunda kandırılacak bir rakip yok; şimdilik sorun değil.

Sunucu haritayı üretiyor ama oyuncudan saklamıyor. Keşfetme hissi tamamen çizimden geliyor.

Geriye Dönüp Bakınca

Bugün başlasam üretimden hemen sonra bir kontrol adımı koyardım: her yürünebilir karenin bir odaya ya da koridora ait olduğunu ve her odadan merdivene ulaşılabildiğini kontrol eden basit bir dolaşma. Ağaç yapısı odaların bağlı olmasını garanti ediyor ama sahipsiz boşluklar bu garantinin kodun tamamını kapsamadığını gösterdi.

Emin olmadığım bir karar da harita boyutunun oyuncu sayısına bağlanması. Tek kişilik oyun ile dört kişilik oyun artık farklı haritalarda oynanıyor ve ikisinin aynı oyun olması gerekip gerekmediğini hâlâ bilmiyorum. Bir gün rekabetçi bir mod gelirse haritanın tamamını göndermeyi de yeniden düşünmem gerekecek.

İlgili Proje

Çok Oyunculu Oyun

Dungeon Mates

Tarayıcıda tek başına ya da oda koduyla en fazla dört kişi birlikte oynanan, piksel grafikli bir zindan oyunu.

Projeyi İncele

Diğer Yazılar

Tüm Yazılar
  1. ahmetakyapi.com: Bu Siteyi Nasıl Kurdum4 Ekim 2026 · 5 Dakika
  2. Açılış Zili: ABD Borsasını Doğru Takip Etmek3 Ekim 2026 · 7 Dakika
  3. One Piece Hub: Spoiler Vermeyen Türkçe Wiki1 Eylül 2026 · 6 Dakika