Geri

Oturma Odası Atari Salonu

Tarayıcıda gerçekten oynanabilen retro bir kartuş atari salonu: konsol, CRT ekran ve kumandadan oluşan 3D masanın etrafında dönün, gücü açıp kartuş kütüphanesine girin ve Snake, Breakout ya da Blocks’u seçin — kurulum yok, kayıt dosyası yok, yalnızca bir öğleden sonra ve bir tur daha.

Yazar: Theodore
Canlı deneyin
Oturma Odası Atari SalonuCanlı deneyin

Bu proje hakkında

Playroom, tarayıcıda çalışan bir oturma odası atari salonu. Sayfa, 1989 tarzı bir oyun masasıyla açılıyor — bir CRT ekran, bir konsol, bir kumanda ve yerine takılı bir Snake kartuşu — bunu sürükleyip inceleyebilirsiniz; konsola ya da POWER ON’a tıklayınca kamera ekrana yaklaşır, tüpün içinden geçer ve üç kartuşluk bir kütüphaneye iner.

Üç oyun da Playroom’a özgü: Snake ritimle art arda yemek üzerine, Breakout üç tuğla aşaması ve güçlendirmeler getiriyor, Blocks ise iniş hayaleti ve yedi parçalık torbalarla dizmeyi öngörülebilir kılıyor. Hesap yok, güncelleme yok, kayıt dosyası yok — en yüksek skorlar tarayıcınızda kalıyor, müzik ve efektler tek düğmeyle kapanıyor.

Projenin tamamı yalnızca Three.js ve Vite’a bağlı; arka uç yok, ortam değişkeni yok. Altı GLB donanım modeli ve tek bir Tripo yüz işareti çıkartması, varlık listesinin tamamı. WebGL kullanılamadığında devreye bir metin menüsü giriyor ve oyunlar tamamen oynanabilir kalıyor.

Öne çıkanlar

  1. Tüm oturma odası düzeninin — CRT ekran, konsol, kumanda ve kartuş — etrafında dönmek için sürükleyin; ardından konsola ya da POWER ON’a tıklayın: kamera ekrana doğru yaklaşır ve CRT’nin içinden geçerek kartuş kütüphanesine girer.
  2. Üç kartuş, üç özgün oyun: beş saniyede 5× zincir ve 50 puanlık altın meyveli Snake; üç tuğla aşaması ile W (geniş raket) ve 3 (üçlü top) güçlendirmeleri olan Breakout; yedi parçalık torbalar, iniş hayaleti ve kombo bonuslarıyla Blocks.
  3. Masaüstünde ok tuşları / WASD ve boşluk, duraklatmak için P, çıkmak için ESC; mobilde dokunmatik kontroller. En yüksek skorlar yalnızca kendi tarayıcınızda saklanır.
  4. Her oyunun kendi chiptune döngüsü ve ses efektleri var; menülerde CC BY 4.0 lisanslı “Trip Of Your Smile” parçası çalıyor. WebGL kullanılamıyorsa atari salonu metin düğmelerine geçer ve oynanabilir kalır.

Teknik inceleme

  • 3D varlıklar · Çalışma zamanında yeniden bağlanan altı GLB donanım parçası

    CRT ekran, konsol, kumanda ve üç kartuş altı GLB dosyası olarak geliyor (Three.js GLTFExporter ile dışa aktarılmış, her biri 128–384 üçgenlik low-poly modeller). GLTFLoader bunları paralel yüklüyor ve her örnek derin bir klon: dışa aktarımdaki merkezleme kaydırmaları sıfırlanıyor, materyaller örnek başına klonlanıyor ve her mesh gölge düşürüp gölge alıyor. Kartuşun etiket plakası dışa aktarılan yerel kaydırmasından bulunuyor, cartridge-label olarak adlandırılıyor ve Canvas’ta çizilen bir kapak dokusuna yeniden eşleniyor; piksel maskot, adlandırılmış düğümlerden — mascot-body, iki kol, anten ve Tripo yüz işaretini taşıyan bir logo çıkartması — kuruluyor; düz çıkartmanın net kalması için tone mapping ve derinlik yazımı kapatılmış.

    Varlık katmanı ayrıca bir değişim yuvası bırakıyor: public/models/manifest.json içindeki controller alanını herhangi bir GLB’ye yönlendirin, fitProp onu sınırlayıcı kutusuna göre 2,54 birim genişliğe ölçekler, zemine oturtup ortalar ve orijinal kumandanın konumunu devralır — özel (örneğin Tripo ile üretilmiş) bir kumanda, koda dokunmadan yerine oturur.

  • Ekran ve etiketler · Arayüz olarak Canvas dokuları

    Sahne hiçbir harici doku haritası yüklemiyor. CRT’nin yüzü 1024×700 boyutunda bir CanvasTexture: bekleme ekranı (PLAYROOM SYSTEM / PLAYER ONE / PRESS START) bir 2D bağlamda kare kare çiziliyor ve tüpün parlaması için hem map hem de emissiveMap olarak kullanılıyor. Her kartuş kapağı, katalogdaki o oyunun rengi, mürekkebi, numarası ve sloganından anında boyanan 640×610 boyutunda ayrı bir canvas — piksel sanatı ve başlık yazısı dahil.

    Güç açıldığında bir ekran yaşam döngüsü denetleyicisi materyali veri akışı dokusuyla değiştirirken kamera, ekran merkezi etrafındaki küresel bir yol boyunca tüpün önüne geri döner, yaklaşır ve camdan geçerken kütüphaneye keser; prefers-reduced-motion altında tüm bu hareket atlanıp doğrudan geçiş yapılır.

  • Etkileşim · İşaretçi hareketleri, raycast seçimi ve sinematik yörünge

    Masa görünümünde tek bir işaretçi dizisi hem yörünge sürüklemesi hem de tıklamadır: hareket denetleyicisi basıldığı anda theta / phi değerlerini kaydeder, bir eşiği aşan hareketi sürükleme, gerisini tıklama sayar. İsabetler bir Raycaster ile çözülür — masada tüm düzen güç düğmesi gibi davranır, kütüphanede ise isabet, gameId taşıyan düğüme kadar üst zinciri tırmanır. Bırakıldığında yörünge asla yerinde donmaz; sinematik bir denetleyici elle yapılan kaydırmayı yavaş, otomatik bir salınıma geri karıştırır.

    Bir kartuşun üzerine gelmek ya da odaklanmak onu 0,24 yükseltir, hafif bir yalpalamayla öne eğer ve piksel maskotu o oyunun pozuna geçirir; her kartuş kendi sinüs fazında salınır. Render döngüsündeki her ara değerleme, kare hızından bağımsız 1 − e^(−5·dt) yumuşatmasını kullanır; böylece yavaş kareler asla sıçrama yaratmaz.

  • Oyunlar ve ses · Bağımsız 2D oturumlar ve sentezlenmiş ses

    Üç oyun da saf mantık modülleri (create / step / input); bir oturum katmanı onları bağımsız bir 2D canvas’a çiziyor — Snake ve Breakout için 480×480, Blocks için 300×540. requestAnimationFrame yalnızca oyun sürerken planlanıyor, yani duraklatma kareleri gerçekten durduruyor; adım başına 250 ms üst sınırı ise sekme değişiminden sonra tünellemeyi engelliyor. Oyun olayları (yeme, tuğla, satır, güçlendirme, seviye) bir parçacık efekti katmanını besliyor ve önceliğe göre tek bir ses işaretine indirgeniyor.

    Tüm ses, Web Audio osilatörleriyle canlı sentezleniyor: oyun başına 16 adımlık bir chiptune melodisi (82–120 BPM) ve frekans kaydırmaları ile zarflardan kurulu yirmi küsur işaret, en fazla 64 sesle sınırlı. Menü parçası CC BY 4.0 lisanslı bir kayıt; indirme başarısız olursa sentezlenmiş döngüye düşüyor. Sekme gizlendiğinde ses kapanıyor ve bağlam yalnızca ilk tıklamadan sonra açılıyor. En yüksek skorlar localStorage’a yazılıyor, depolama reddedilirse belleğe düşüyor — başarısız bir kayıt asla oyunu kesmiyor.