Rill Valley: fonksiyonel bir dille hesaplanan, tarayıcıda çok oyunculu bir WebGPU vadisi
Tarayıcıda yürünen, alacakaranlıkta bir vadi: orman, göl, kristaller, silahlar, sesli sohbet ve botlar. Arazi, fizik ve oyun mantığı Rill ile yazıldı; aynı dosya hem tarayıcı için WebAssembly'ye hem sunucu için yerel programa derleniyor, görüntüyü WebGPU çiziyor.

Rill Valley, tarayıcıda yürünen, alacakaranlıkta bir vadi: çam ve kavak ormanları, ortasında bir göl, yamaçlarda parlayan kristaller ve gökyüzünde Satürn. Birkaç kişi aynı vadide dolaşabiliyor, birbirleriyle konuşabiliyor ve birbirlerine ateş edebiliyor. Vadinin kendisi, yani arazi, ağaçların ve kristallerin yeri, yürüme, düşme, mermilerin uçuşu ve kameranın matrisleri, kendi yazdığım fonksiyonel dil Rill ile hesaplanıyor. Görüntüyü WebGPU çiziyor.
Oyna: rill.baltavista.com/rillwebgpu. WebGPU destekleyen bir tarayıcı gerekiyor (Chrome ya da Edge). Fareyi yakalamak için tıkla; WASD yürü, boşluk zıpla, iki kez boşluk jetpack, shift koş, C çömel, sol tık ateş, sağ tık nişan, 1-4 silah değiştir, R şarjör, M müzik.

Bir dosya, iki derleme
Vadinin ne olduğuna dair her şey tek bir dosyada: valley.rill, yaklaşık 8.800 satır. Bu dosya iki kez derleniyor:
world.rillonun üzerine 56 fonksiyon açıyor ve WebAssembly'ye derleniyor. Tarayıcıdaki oyun motoru bu, 232 KB.server.rillonu bir WebSocket sunucusuyla sarıyor ve Linux için yerel bir programa derleniyor.
İkisi de vadinin ne olduğuna dair hiçbir şeye kendisi karar vermiyor. Sunucuyla tarayıcının bir merminin nereye gittiği konusunda anlaşabilmesinin tek nedeni bu: ikisi de aynı kodu çalıştırıyor.
Tarayıcı tarafında JavaScript yalnızca bir WebAssembly modülünün yapamadığı işleri yapıyor: WebGPU cihazını kurmak, shader'ları yüklemek ve çizim geçişlerini sırayla çalıştırmak. Ses, mikrofon ve ağ bağlantısı da onda.
Vadi nasıl oluşuyor
Vadi indirilmiyor, hesaplanıyor. Arazi on iki sinüsün toplamı. Her çamın, kavağın, kristalin ve ot öbeğinin yeri bir hash üzerinde deterministik bir yürüyüşle seçiliyor: bir aday, bir hash, zemin hakkında bir kural, ve geriye kalan nereye düştüyse orada duruyor. Sunucu her istemciye yalnızca üç sayı söylüyor (kaç çam, kaç kristal, kaç ot öbeği) ve herkes aynı vadiyi kendisi kuruyor.
Bütün vadi yaklaşık 4 milisaniyede yeniden kuruluyor. Bu sayede oyun kendini makineye göre ayarlayabiliyor: ekran kartına göre bir kalite seviyesiyle başlıyor, sonra ölçtüğü kare sürelerine bakıp vadiyi daha sık ya da daha seyrek yeniden kuruyor (ağaç, ot, ateş böceği sayısı, gölge haritası, dalga sayısı, piksel oranı), ta ki ekranın kare bütçesi dolana kadar. Yavaş bir makine daha kaba bir arazi ağı çiziyor ama tam olarak aynı zeminde duruyor, çünkü bütün fizik soruları ağa değil, yükseklik fonksiyonuna soruluyor.

Çizim
WebGPU ile, her biri ayrı bir WGSL dosyası olan bir dizi geçiş:
- Sabit bir altın saat güneşinden gölge geçişi.
- 4x MSAA ile ileri çizim, prosedürel gökyüzü ve sis.
- Instancing ile çizilen çamlar, kavaklar, kristaller ve rüzgârda sallanan otlar. Rüzgâr suyu, çamları ve otları aynı iki sayıyla oynatıyor, çünkü üçü de Rill'in hesapladığı aynı değerleri okuyor.
- Gerstner dalgalarıyla göl, kıyı ve dalga tepesi köpüğüyle. Gölün su gibi görünmesini asıl sağlayan, yavaşça kayan bir gürültü alanının bütün dalgaları birlikte ölçeklemesi: yüzeyde dalgalı yamalar ve neredeyse cam gibi düz yamalar oluşuyor.
- Compute shader'da bir ateş böceği sürüsü.
Ekran görüntülerindeki makinede (Apple M serisi, en yüksek kalite) sahne 342 bin üçgen, 60 fps ve kare başına 4-9 ms GPU süresi.
Bütün dokular, modeller, sesler, shader'lar ve sayfanın kendi kodu tek bir dosyada, valley.rpak içinde: 103 dosya, 6,1 MB. Biçimi bu projeye özel. Sıkışanlar LZ ile sıkıştırılıyor, sıkıştırmayı açan da tarayıcı değil, WebAssembly'deki motor. Bir ziyaretçi yalnızca iki şey indiriyor: index.html ve valley.rpak.
Oyun
Oyun bir brifingle başlıyor: bilinmeyen bir gezegene gönderildin, görevin şirket adına Dünya için tehdit olduğu düşünülen kristalleri yok etmek, ve aynı görevi alan rakip şirketlerin ajanlarıyla karşılaşabilirsin.
- Kristaller: üç renk var ve renk her şeyi söylüyor. Kehribar en yaygını ve kırılınca oyuncuyu %3 iyileştiriyor, camgöbeği %6, mor (en nadiri) %12. Nadirlik ve değer aynı sırada, böylece iki yüz metreden görülen bir renk vadiyi geçmek için bir sebep oluyor. Kristaller ayrıca ateş topu fırlatıyor: uçan, yavaşlayan, düşen ve düştüğü yerde yakınındakileri yakan bir krater bırakan meteorlar.
- Silahlar: sürgülü tüfek, RPG-7, AK ve MG3. Tüfek mermisi 420 m/s ile çıkıyor; yerçekimi ve hava direnci yolu boyunca hızını azalttığı için düşüşü ders kitabındaki paraboldan daha hızlı artıyor. Nişan alınca görüş beş kat yaklaşıyor ve dürbün ekranı kaplıyor. Sürgü her atıştan sonra dört ayrı hareketle çekilip itiliyor, boş kovan omzunun üstünden uçuyor.
- Roketatar: roket ilk çarptığı katı şeyde patlıyor ve altı metre içindeki kristalleri de götürüyor. Namluda dolu roket görünüyor; ateşlenince kayboluyor ve yeniden doldurulana kadar geri gelmiyor.
- Yerçekimi: bir sabit değil, sunucu ayarı. Vadinin varsayılanı 26 m/s², Dünya'nınkinin yaklaşık iki buçuk katı. Bacaklar her gezegende aynı kuvvetle itiyor; zayıf bir gezegen onları güçlendirmiyor, aynı itişin daha uzağa taşımasına izin veriyor.


Başka insanlarla
Sunucu her bağlantı için bir strand ve dünyanın sahibi olan tek bir merkez strand'den oluşuyor. Dünyaya yalnızca o merkez dokunuyor, geri kalan her şey bir kanal; bu yüzden sunucunun hiçbir yerinde kilit yok. Bir tik de tuş basışlarıyla aynı kanaldan gelen bir mesaj, yani bir girdi ya tikten önce ya da sonra işleniyor, asla ortasında.
- Saniyede 20 tik, ikili protokol. Bir anlık görüntü 31 bayt artı oyuncu başına 37 bayt. Protokol doğru oturana kadar metin olarak yazılmıştı; ikiliye geçerken harfleri mesaj kodu olarak tuttu, bu yüzden bir hexdump hâlâ okunuyor: anlık görüntü hâlâ
sile başlıyor. - İstemci de yürüyor. Tarayıcı, sunucunun çalıştırdığı aynı
walkfonksiyonuyla kendini de yürütüyor; dönmek ve yürümek gidiş-dönüş süresini değil tuşu takip ediyor. Sunucudan gelen konum birkaç kareye yayılarak oturuyor. Nişan asla düzeltilmiyor. - İstemcinin gönderdiği hiçbir şeye inanılmıyor. Her sayının sonlu olduğu kontrol ediliyor (
NaNkonumundaki bir oyuncuya hiçbir şey isabet etmez), atış yönü birim vektöre çevriliyor, bir mermi ancak sahibinin baktığı yönün 20 derece yakınına gidebiliyor. - Sınırlar: bağlantı başına saniyede 150 mesaj, aynı anda 64 bağlantı, bir adresten en fazla 16.
- İsimler: her oyuncu ilk gelişinde bir jeton alıyor; tarayıcı onu saklıyor ve aldığı isim, sahibi yokken bile başkasına verilmiyor.
- Ses: oyuncular birbirleriyle konuşabiliyor ve ses sunucudan geçmiyor. İki tarayıcı kendi aralarında WebRTC bağlantısı kuruyor; sunucu yalnızca tanıştırıyor.
- Muhafızlar: vadide insan olmayan zırhlı botlar da dolaşıyor; yamaçta yürüyor, ışık vurunca parlıyor ve yalnızca saldırmak için sıçrıyorlar.
Şu anda dört vadi açık (kuzey, güney, doğu, batı); sayfa açılınca hangisine girmek istediğini soruyor. Her biri ayrı bir Docker konteynerinde çalışıyor ve yaklaşık 5 MB bellek kullanıyor. ?solo ile sayfa kimsenin olmadığı, sana ait bir vadi açıyor; orada sunucu yok ve her şey tarayıcıda dönüyor.

Rakamlarla
valley.rill (vadinin kendisi) | ~8.800 satır Rill |
server.rill (çok oyunculu sunucu) | ~2.300 satır Rill |
host.js (WebGPU sahnesi) | ~4.800 satır JavaScript |
| Shader'lar | her geçiş için bir WGSL dosyası |
world.wasm | 232 KB |
valley.rpak | 103 dosya, 6,1 MB |
| Sunucu başına bellek | ~5 MB |
Teşekkürler
Dokular, modeller ve sesler CC0 lisanslı kaynaklardan: dokular Poly Haven ve ambientCG'den; tüfek Poly Haven'dan, RPG-7 OpenGameArt'tan; insan figürü ve animasyonları Quaternius'un Universal Animation Library'sinden. Müzik pauliuw'un The Field of Dreams parçası (OpenGameArt, CC0). Hiçbiri olduğu gibi kullanılmadı: dokular gündüz çekilmiş fotoğraflar, vadi ise alacakaranlıkta, bu yüzden her biri vadinin ışığına göre yeniden işlendi.
Rill Valley hâlâ bir demo. Sorunsuz çalışıyor ama bir öğleden sonra boyunca vurulmaktan daha ağır bir testten geçmedi. Uğrarsan ve bir şey bozulursa haber ver.