← Nesim Apps

Oyun Motoru Olmadan Jetpack Compose ile Oyun Hissi

Nesim Razon, bağımsız Android geliştiricisi · 2026-11-12

#Jetpack Compose#Oyun tasarımı#Android

Cubbles, blokların yüzü olan bir blok yerleştirme bulmacası ve cazibesinin neredeyse tamamı geri bildirimden geliyor: yaylanarak oturan bir parça, dalga gibi eriyen bir satır, serinle birlikte büyüyen ses ve titreşim. Bunların hiçbiri oyun motoru istemiyor. Bu yazı işin düz Jetpack Compose ile nasıl yapıldığını, sayıları, kestirme yolları ve başka iki oyunumuzun aynı sorunu nasıl farklı çözdüğünü anlatıyor.

Bir bulmaca neden motor istemez

8'e 8'lik bir tahta, tepsideki üç parça ve bir sürükleme hareketi fizik motoru, sahne ağacı ya da oyun döngüsü kütüphanesi istemez. İstediği şey bir saat ve çizim yeteneğidir. Compose ikisini de verir: üzerine boyayacağın bir Canvas ve zamanı okuyacağın bir kare geri çağrısı. Aşağıdakilerin hepsi bu ikisinin üstünde duruyor.

Tek Canvas, tek saat

Oyun ekranı tahtayı, tepsiyi, efektleri ve yüzen yazıları tek bir Canvas içinde çizer. Bir launched effect ile başlatılan bir eş yordam sonsuza kadar döner: withFrameNanos ile sonraki kareyi bekler, verilen kare zamanını yok sayar, o anki çalışma süresini milisaniye olarak now adlı bir durum değişkenine yazar ve biten efektleri bir listeden atar.

Önemli olan now'un nerede okunduğu. Canvas'ın çizim lambdası içinde okunuyor; bu yüzden her kare yalnızca çizimi geçersiz kılıyor, ekranın tamamının kompozisyonunu değil. Skor yazısı ve tahtanın çevresindeki düğmeler saniyede altmış kez yeniden oluşmuyor. Tahtanın kendisi gözlemlenebilir durum değil, düz bir değişebilir nesne; bu yüzden her hamleden sonra küçük bir sayaç artırılıp Canvas'ın yeniden çizilmesi sağlanıyor. Bu tasarımın bedeli açık: efekt çalışmıyorken bile döngü durmuyor, çünkü göz kırpan gözler ve hafifçe sallanan tepsi parçaları da bir saate ihtiyaç duyuyor.

Efektler animasyon değil, veridir

Her blok için bir Animatable yerine oyun küçük efekt nesnelerinden oluşan bir liste tutuyor. Başlangıç zamanı ve süresi olan bir mühürlü sınıf var; alt sınıfları yerleşme sıçraması, satır silme, yüzen yazı, ekran sarsıntısı, parçacık patlaması, renk parlaması ve süpüren ışık. Parça bırakıldığı anda oluşturulup listeye ekleniyorlar.

Çizim kodu her birini, now'dan başlangıç zamanını çıkarıp süreye bölerek sıfır ile bir arasında bir ilerleme değerine çeviriyor ve her şeyi bu sayıdan türetiyor. Efekt başına eş yordam yok, iptal edilecek bir şey yok ve tahtayla aynı hizadan kayabilecek hiçbir şey yok. Katmanlama bedava: büyük bir silme listeye birkaç efekt daha ekliyor.

Ezilme, gerilme ve dalga

Yerleştirilen parça 480 milisaniye boyunca sıçrıyor. Ölçeği, animasyonun ilk kısmı üzerinde bir artı 0,28 çarpı bir sinüs, bunun da bir eksi ilerleme ile çarpılmış hâli; yani şişip sönen bir genlikle oturuyor. Doğrusunu söylemek gerekirse bu hacmi koruyan klasik ezilme-gerilme değil, sönümlenen bir şişme: bloklar yuvarlak köşeli kareler ve tek biçimli ölçekleniyor; bu ucuz ve telefon boyutunda net okunuyor.

Karakter silme animasyonunda. Silinen her hücre, koyduğun parçanın merkezine olan Manhattan uzaklığının her adımı için 28 milisaniye bekliyor; böylece silme dışarı doğru bir dalga gibi yayılıyor ve gözüne nereden başladığını söylüyor. Sonra hücre 380 milisaniyenin ilk yüzde 30'unda boyunun 1,4 katına şişiyor, sonra sıfıra küçülüyor, beyaza doğru karışıyor, saydamlığı sönüyor ve yolun yüzde 70'ine kadar mutlu bir yüz takıyor. Dosyada kullanılan tek yumuşatma eğrisi smoothstep, yani t karenin üç katı eksi t küpünün iki katı.

Yüzler birkaç yaydan ibaret

Her bloğun yüzü daire, çizgi ve yaylardan çiziliyor ve ruh hâli her karede seçiliyor: normal, mutlu, heyecanlı ya da üzgün. Göz kırpmak için durum gerekmiyor. Şu anki zaman artı hücre numarası çarpı 911'in 3700 milisaniyeye göre modu 130'dan küçükse gözler kapanıyor; böylece her blok kendi takviminde kırpıyor. Sürüklerken gözler bloğun boyunun yüzde dördü kadar parmağa doğru kayıyor; küçük bir şey ama tahtayı canlı hissettiriyor.

Parçacık sistemi olmadan parçacıklar

Burada bir parçacık; başlangıç konumu, hızı, boyutu, rengi ve dönüşü olan değişmez bir nesne. Kare başına hiçbir şey güncellenmiyor. Çizim kodu konumu geçen süreden kapalı formülle hesaplıyor: başlangıç artı hız çarpı t, dikey eksene de yarım g çarpı t kare eklenerek; g saniye karede on altı hücre boyu. Havuz yok, parçacık başına bakılacak durum yok.

Sayılar tasarım gereği sınırlı. Her silinen hücre 2 artı heyecan düzeyi kadar, en çok 4 olmak üzere parçacık saçıyor; heyecan düzeyi silinen satır sayısı artı seri eksi bir. Tahtada 64 hücre var, yani tek bir patlama 384 parçacığı geçemez. Mükemmel temizleme 90 konfeti, yeni rekor 70 konfeti ekliyor. Bir patlama 1,1 saniye yaşıyor, boyutunun yüzde 40'ına küçülüyor ve sönüyor. Kareler dönüyor, daireler dönmüyor. Rastgelelik olay zamanıyla tohumlanmış bir üreteçten geliyor; böylece hata ayıklarken efektler yeniden üretilebiliyor.

Okunur kalan yüzen yazılar

Skor, seri ve övgü gibi yüzen yazılar aynı smoothstep ile yükseliyor, hızlı bir taşmayla beliriyor ve 1,9 saniye kalıyor. Eskiden bir saniyede kayboluyordu ve oyuncular kaçırıyordu. Yazılar sekiz kaydırılmış kopyadan oluşan koyu bir konturla çiziliyor, çünkü açık banyo fayansı zemininde beyaz yazı kontursuz okunmuyor. Burada gerçek bir hata da yaşadı: çok küçük ekranlarda uzun bir övgü sözcüğü kullanılabilir alandan geniş çıkıyordu ve ters aralıklı bir sınırlama çağrısı uygulamayı çökertti. Çözüm aralığı asla ters çevirmeyen küçük bir yardımcı ve yazıyı sığacak kadar küçültmek oldu.

Ses dosyası olmadan ses

Hiç ses varlığı yok. İlk kullanımda bir ses, 22.050 Hz tek kanallı 16 bitlik kısa bir örnek dizisine üretiliyor: nota frekansında bir sinüs artı yüzde 20 oranında ikinci harmonik, üstel bir sönümle çarpılmış ve tam genlikle başlamaktan doğan tıkırtıyı önlemek için 60 örneklik, yaklaşık üç milisaniyelik bir yükselme rampasıyla. Diziler adıyla önbelleğe alınıyor; her çalmada bu diziyle statik kipte bir AudioTrack kuruluyor, oyun kullanımı olarak etiketleniyor ve bitince bir konum işaretçisinden serbest bırakılıyor.

Gerçekten hissedilen titreşim

Titreşim sınıfı titreşim motorunu doğrudan sürüyor. Koddaki bir yorum nedenini açıklıyor: standart görünüm dokunsal geri bildirimi kullanıcının dokunma geri bildirimi ayarına bağlı ve ilk sürümde birçok telefonda neredeyse hissedilmiyordu. Bunun yerine oyun kendi dalga biçimlerini kuruyor; donanım bildiriyorsa genlik denetimiyle, bildirmiyorsa yalnızca sürelerle; çağrıyı da sarıyor, böylece titreşim motoru olmayan cihaz hiçbir şeyi çökertmiyor.

Dürüst kalmanın kuralı

Geri bildirim eklemek ucuz ve abartmak kolay; bu yüzden proje notlarında bir kural var: kendi hatırı için efekt yok. Bulmaca okunur kalmalı ve kötü bir an asla ceza gibi hissettirmemeli. Kodda bu şöyle görünüyor. Bırakmadan önce parçanın hayaleti ve sileceği satırların vurgusu görünüyor. Sürüklenen parça parmağının 1,6 hücre üstünde duruyor ki elin onu gizlemesin. Tepsiler her zaman yasal bir hamle kalacak biçimde üretiliyor. Oyun sonu ekranı da en iyi skoruna ne kadar yaklaştığını yalnızca onun yüzde 60'ı içindeysen söylüyor; değilse yalnızca rekoru gösteriyor, böylece kötü bir oyunun ardından azar işitmiyorsun.

Açıkça söyleyeyim: Cubbles'ta henüz hareketi azaltma düğmesi yok. Bingo oyunumuzda var ve kalıbı gösteriyor. Ayar bir composition local ile açılıyor; konfeti ve ana ekrandaki döngüsel animasyonlar ona bakıp geri çekiliyor.

Aynı fikirler iki başka oyunda

Ekran tek bir Canvas yerine bileşenlerden oluştuğunda standart animasyon API'si daha iyi uyuyor. Bingo oyununda çekilen top, bir Animatable ve zıplayan bir yayla beliriyor; dönerken 0,2'den 1'e ölçekleniyor. Bingo kartındaki işaret, sönüm oranı 0,42 olan bir yayla animate-as-state çağrısı kullanıyor ve titreyerek oturuyor. Reddedilen bir dokunuş bir tamsayı anahtarı artırıyor, o anahtardaki bir launched effect sarsıntı değerini bire çekip yayla sıfıra döndürüyor; değer bir graphics layer içinde okunduğu için hiçbir şey yeniden oluşmuyor.

Bir uzay bulmacamızda ise kare döngüsü her karede her kapıyı hedef hücreye kalan mesafenin sabit yüzde 38'i kadar yaklaştırıyor. Bu mümkün en basit yumuşatma ve iyi hissettiriyor; tek çekincesi hızın kare hızına bağlı olması. Üçünden çıkan pratik kural: bileşenler için animasyon API'si, serbest sahneler için zamandan türetilen çizim.

Akılda kalanlar

Efektleri başlangıç zamanı ve süresi olan küçük veri nesneleri olarak tut, her şeyi ilerlemeden türet, saatini çizim lambdasında oku, her efekte tavan koy ve oyun kurallarının animasyonun önünde koşmasına izin ver. Birkaç yüz satır Canvas kodu ve bir sinüs fonksiyonundan şaşırtıcı miktarda canlılık çıkıyor. Bu sayıların iş başında nasıl göründüğünü merak edersen Cubbles, hepsinin ayarlandığı oyun.

Sık sorulan sorular

Oyun motoru olmadan Jetpack Compose'ta oyun hissi yapılabilir mi?

Evet, bulmaca ve hafif oyunlar için. Tek Canvas, withFrameNanos ile bir kare döngüsü ve zamandan türetilen çizim ezilmeyi, parçacıkları ve sarsıntıyı karşılıyor.

Her karede tüm ekranın yeniden oluşması nasıl önlenir?

Saat durumunu yalnızca Canvas çizim lambdasında oku. O zaman her kare kompozisyonu değil, çizimi geçersiz kılar.

Ses dosyası olmadan sesler nasıl yapılıyor?

Bir kez kısa örnek dizilerine üretiliyor, önbelleğe alınıyor ve çalma bitince serbest bırakılan statik kipte bir AudioTrack ile çalınıyor.

Titreşim motoru neden doğrudan sürülüyor?

Standart dokunsal çağrı kullanıcının dokunma geri bildirimi ayarına bağlı ve birçok telefonda neredeyse hissedilmedi; bu yüzden oyun kendi dalga biçimlerini kuruyor.

Daha çok geri bildirim oyunu her zaman daha iyi yapar mı?

Hayır. Efektler bulmacayı gizlememeli ya da oyuncuyu cezalandırmamalı; bu yüzden buradaki her efektin bir tavanı var ve oyun sonu ekranı cesaret kırıcı mesajlardan kaçınıyor.

Diğer yazılar