Bu yazı, resimli kayan bulmacaların mühendislik tarafı hakkında: rastgele bir fotoğraf nasıl oynanabilir bir N×N bulmacaya dönüşür. Oyuncuların hiç görmediği, geliştiricilerin ise beklediklerinden çok daha fazla vakit harcadığı kısım budur.
İşlem hattı
Sırasıyla üç aşama:
1. Kare kırpma
Çoğu fotoğraf kare değildir. iPhone HEIC dosyaları 4:3'tür; dikey çekilen iPhone fotoğrafları 3:4'tür. Kayan bulmaca tahtası ise 1:1'dir. İlk adım, fotoğrafın hangi kare bölgesinin oynanacağını seçmektir.
İki yaygın yaklaşım:
Merkez kırpma. Sığan en büyük merkezlenmiş kareyi al. Hızlı, öngörülebilir, ara sıra yanlış (konu merkezde değilse).
Etkileşimli kırpma. Kullanıcıya sürüklenebilir bir kare katman göster. Seçimi ona bırak. Daha yavaş, ama kullanıcı her zaman istediğini alır.
Slide Puzzle etkileşimli kırpma kullanır ve varsayılan olarak merkezi seçer. Kırpma tahribatsızdır — kullanıcının fotoğraf kitaplığındaki orijinal dosya asla değiştirilmez.
2. Çalışma çözünürlüğüne küçültme
Kırpıldıktan sonra görsel hâlâ tipik olarak 3000×3000 ya da daha büyüktür (kameraya bağlı). Bulmaca oyunu için bu aşırıdır. 3× bir iPhone ekranında 320pt olarak gösterilen 6×6 bir tahtada her taş yaklaşık 53pt = 160 cihaz pikseli olarak çizilir. 1024×1024 bir kaynak, taş başına yaklaşık 170 piksel verir — ekranın gösterebileceğinden daha keskin.
Standart olan, 1024'e (bazen 2048'e) küçültmektir. Daha büyük kaynaklar görünür sonucu iyileştirmeden bellek harcar ve yüklemeyi yavaşlatır.
3. Anlık taş çizimi
İlk denemede çoğu kişinin yanlış yaptığı kısım burasıdır. Görsel gerçekte 16 ayrı dosyaya kesilmez. Bu yavaş, savurgan ve gereksiz olurdu.
Bunun yerine uygulama her taşı, aynı kaynak görseli taş boyutunda bir dikdörtgene, kaynak dikdörtgeni taşın hedef görseldeki konumuna göre kaydırarak çizerek üretir. CSS bunu "background-position" numarası olarak bilir; iOS'ta CGImage kırpma dikdörtgenidir; SwiftUI'da bir Image üzerinde Rectangle().clipped() kullanılır.
Sözde kodla, N×N bir tahtada kenarı S olan görselden (satır, sütun) hedef konumundaki taşı çizmek:
görseli (-sütun * S/N, -satır * S/N) konumunda çiz
(S/N × S/N) boyutunda bir kırpma dikdörtgeni içinde
Hepsi bu. 4×4 için on altı taş, aynı kaynak görseli 16 farklı kaydırmayla çizen 16 çağrı demektir. Uygulamanın dosyayı kesmesine hiç gerek yoktur.
6×6 bir tahtayı dilimlemenin anlık olmasının nedeni de budur: 36 dosya işlemi değil, aynı çizicinin 36 kez çağrılmasıdır.
Peki ya animasyon
Bir taşı kaydırmak, çizilmiş taş üzerinde bir translate dönüşümüdür. Taşın içindeki görsel içerik değişmez — yalnızca kırpma çerçevesinin ekrandaki konumu değişir. Bu yüzden kaydırma animasyonu GPU için önemsizdir ve ProMotion ekranlarda 120 Hz'de çalışır.
Önemli üç uygulama ayrıntısı:
- Kırpma ile içerik kardeş olmalı, ebeveyn/çocuk değil. İç içe geçerlerse içerik kırpmayla birlikte hareket eder ve kaydırma bozulur.
- Yerleşim değil, transform animasyonu kullanın. x/y'yi CSS transform ya da SwiftUI offset ile canlandırmak GPU hızlandırmalıdır; left/top'u canlandırmak değildir.
- İlk görünümde önceden rasterleştirin. Bir taşın ilk karesi yavaş çizilebilir, çünkü kaynak görselin kodunun çözülmesi gerekir. Oyun başında tüm taşları bir kez çizerek ısıtın.
Depolama
Aktarılan görsel gerçekte nerede yaşar?
Gizliliğe saygılı bir uygulamada, iOS tarafından beklemede şifrelenen uygulamanın kendi korumalı alanında. Fotoğraf kitaplığı orijinali tutmaya devam eder; uygulama kendi Documents klasöründe 1024×1024'lük bir çalışma kopyası saklar. Kullanıcı uygulamayı sildiğinde çalışma kopyası da silinir.
Bulut tabanlı bir uygulamada çalışma kopyası bir sunucuda yaşar. Bunun sonuçları çok farklıdır — gizlilik için, çevrimdışı oyun için ve sunucu ortadan kalktığında olacaklar için. Slide Puzzle korumalı alan türündendir.
Bellek bütçesi
Tipik bir 4×4 resimli kayan bulmaca:
- Kaynak görsel: ~3 MB JPEG.
- Bellekte çözülmüş görsel: 1024 × 1024 × 4 bayt = 4 MB.
- Etkin oyun başına bir kopya.
Bu sorun değil. 6×6'da kaynak görsel ve çözülmüş tampon aynı boyuttadır. Tahta daha fazla bellek istemez.
Bellek bütçesinin sıkıştığı yer kapak kitaplıklarıdır — hepsi aynı anda çözülürse 300 kapak × 4 MB = 1,2 GB. Uygulamalar bunu isteğe bağlı çözme (yalnızca kapak gösterildiğinde) ve arka plana geçince serbest bırakma (yalnızca etkin oyunun görseli bellekte tutulur) ile önler.
Sınır durumları
Pratikte üç şey ters gider:
EXIF yön etiketli fotoğraflar. Dikey çekilip yatay kaydedilen ve döndürme etiketi taşıyan bir fotoğraf, uygulama EXIF döndürmesini uygulamayı unutursa fotoğraf kitaplığında doğru, bulmacada yanlış görünür. Her fotoğraf-bulmaca uygulamasının ilk sürümünde bu hata vardır.
Çok büyük kaynak görseller. Bazı HEIC dosyaları 6000×8000 pikseldir. Bunları tam çözünürlükte belleğe yüklemek küçük iPhone'larda uygulamayı çökertir. Çözüm, küçültülmüş hedef boyutta akışlı çözmedir — Apple'ın ImageIO'su bunu destekler. Diskten doğrudan 2048×2048 olarak çözün; asla tam boyutta çözmeyin.
Alt piksel çizimi. Ekranın piksel ızgarasına tam bölünmeyen taş boyutları, her taşın bir kenarında kesirli bir piksel sütunu üretir. Saf kırpmayla bu, kıl inceliğinde bir boşluk olarak görünür. Ya taş boyutlarını tam piksele oturtarak (standart dışı tahta boyutlarında görünür titreme) ya da taşların 0,5 piksel üst üste binmesine izin vererek (boşluk yok, titreme yok) düzeltin.
Bunlar derin sorunlar değildir ama ilk kez uygulayan herkes tarafından istisnasız unutulur.
Özet
İşlem hattı şudur: kırp → küçült → taşları çizmek için kırp-ve-kaydır → kaydırmaları canlandırmak için translate dönüşümleri. Gizliliğe saygılı bir uygulamada görsel cihazdan asla çıkmaz. Tüm hat yaklaşık 200 satır koda sığar; artı herkesin ilk seferde unuttuğu EXIF döndürme işlemesi için 50 satır daha.
Resimli kayan bulmacalar dışarıdan göründüklerinden daha basittir. Görsel tek bir görsel olarak kalır; taşlar yalnızca onun çerçevelenmiş görünümleridir.