Pada pertengahan 1980-an, Nintendo Entertainment System (NES) adalah konsol yang harus dimiliki. Suara terbaik, grafis terbaik, dan permainan terbaik di antara semua konsol pada waktu itu - konsol memperluas batas yang dimungkinkan. Sejauh ini, proyek-proyek seperti
Super Mario Bros ,
The Legend of Zelda dan
Metroid dianggap beberapa game terbaik sepanjang masa.
Lebih dari 30 tahun setelah rilis NES, game klasik terasa hebat, yang tidak bisa dikatakan tentang perangkat keras tempat mereka bekerja. Dengan resolusi hanya 256x240, konsol NES tidak dapat memberikan ruang permainan yang cukup. Namun demikian, pengembang yang tak kenal takut berhasil masuk ke dalam game NES yang luar biasa, dunia yang tak terlupakan: ruang bawah tanah seperti Labirin
Zelda yang seperti labirin, ruang luas planet ini di
Metroid , tingkat terang
Super Mario Bros. . Namun, karena keterbatasan perangkat keras NES, pemain tidak pernah bisa melampaui 256x240 ...
Sampai saat ini.
Saya mempersembahkan kepada Anda proyek
wideNES - cara baru untuk memainkan NES klasik!
wideNES adalah teknologi baru untuk
secara otomatis dan
interaktif menandai game NES
secara real time .
Ketika pemain bergerak di sekitar level, wideNES merekam layar, secara bertahap membangun peta bagian dunia yang dijelajahi. Di level berikutnya, wideNES menyinkronkan gameplay di layar dengan peta yang dihasilkan, yang pada dasarnya memungkinkan pemain untuk melihat lebih banyak dengan "melihat" di luar batas layar NES! Yang terbaik dari semuanya, cara Anda menandai game wideNES
benar-benar universal , yang memungkinkan berbagai game NES bekerja dengan wideNES tanpa konfigurasi apa pun!
Tapi bagaimana cara kerjanya?
Jika Anda ingin memeriksa cara kerja wideNES sebelum membaca artikel, silakan!
ANESE adalah emulator NES yang saya tulis, dan saat ini merupakan satu-satunya emulator yang mengimplementasikan wideNES. Namun, patut diingat bahwa ANESE
bukan emulator NES terbaik di dunia, dalam hal UI dan akurasi emulasi. Sebagian besar fitur (termasuk masuknya wideNES) hanya tersedia melalui baris perintah, dan meskipun banyak game populer berfungsi dengan baik, beberapa lainnya mungkin berperilaku dengan cara yang tidak terduga.
Cara kerja wideNES
Sebelum mempelajari detail, penting untuk menjelaskan secara singkat bagaimana NES membuat grafik.
Transfer Pixel Menggunakan PPU
Inti dari NES adalah prosesor MOS 6502 yang terhormat. Di akhir 70-an dan awal 80-an, 6502 digunakan di
mana -
mana dan bekerja di mesin legendaris seperti Commodore 64, Apple II dan banyak lainnya. Itu murah, mudah diprogram dan
cukup kuat untuk berbahaya.
Melengkapi 6502 di konsol NES adalah coprocessor grafis yang kuat yang disebut
Picture Processing Unit (PPU). Dibandingkan dengan koprosesor video sederhana yang digunakan pada sistem yang lebih lama, PPU adalah peningkatan besar dalam hal kegunaan. Sebagai contoh, lima tahun sebelum rilis NES, prosesor Atari 2600 6502 digunakan untuk mengirimkan instruksi grafis ke coprocessor
untuk setiap baris raster , yang membuat prosesor sangat sedikit waktu untuk menjalankan logika permainan. Sebagai perbandingan: PPU hanya membutuhkan beberapa perintah
per frame , dan ini memberi 6502 cukup waktu untuk membuat gameplay yang menarik dan inovatif.
PPU adalah chip yang luar biasa, caranya rendering grafik hampir tidak seperti karya GPU modern, dan
serangkaian artikel lengkap akan diperlukan untuk sepenuhnya menjelaskan fungsinya. Karena wideNES hanya menggunakan sebagian kecil fungsi PPU, cukup untuk mempertimbangkannya secara singkat:
- Resolusi: 256x240 piksel, 60 Hz
- Ini bekerja secara independen dari CPU
- Berkomunikasi dengan CPU menggunakan I / O dengan pemetaan memori (kisaran alamat 0x2000 - 0x2007)
- 2 lapisan render: lapisan sprite dan lapisan latar belakang
- Lapisan sprite
- Setiap sprite individu dapat ditempatkan di mana saja di layar.
- Bagus untuk objek bergerak: pemain, musuh, kerang
- Hingga 64 8x8 piksel sprite
- Lapisan latar belakang
- Terikat ke grid
- Bagus untuk elemen statis: platform, hambatan besar, dekorasi
- Memori video cukup untuk menyimpan 64x30 ubin ukuran 8x8 piksel
- Resolusi internal sejati 512x240, dengan viewport 256x240
- Mendukung pengguliran perangkat keras untuk mengubah viewport 256x240
- Register PPUSCROLL (alamat 0x2005) mengontrol perubahan viewport dalam X / Y
Setelah berurusan dengan ikhtisar yang
sangat singkat ini, mari kita beralih ke yang paling menarik: bagaimana cara kerja wideNES?
Ide utama
Di akhir setiap frame, CPU mengirimkan informasi perubahan ke PPU. Ini termasuk posisi sprite baru, data level baru dan, yang sangat penting untuk wideNES,
offset viewport baru . Karena wideNES bekerja di emulator, sangat mudah bagi kami untuk melacak nilai-nilai yang dituliskan ke register PPUSCROLL, yang artinya sangat mudah untuk menghitung berapa banyak layar telah bergerak di antara dua frame!
Hmm, apa yang akan terjadi jika, alih-alih menggambar setiap frame baru
langsung di atas frame lama, frame baru akan digambar di atas frame sebelumnya, tetapi bergeser ke nilai gulir saat ini? Kemudian, seiring berjalannya waktu, bagian level yang semakin besar akan tetap ada di layar, secara bertahap membangun gambar level lengkap!
Untuk memeriksa apakah ide ini ada nilainya, saya dengan cepat membuat sketsa implementasi pertama.
Menyusun ...
Meluncurkan ...
Unduh
Super Mario Bros. ...
Voila!
Berhasil!
Tampaknya ...
Pendekatan lain: mengapa tidak mengekstrak level langsung dari file ROM?
Tanpa mempertimbangkan rincian implementasi, menjadi jelas bahwa teknik ini memiliki batasan serius: peta permainan penuh hanya dapat dikumpulkan ketika pemain secara independen menjelajahi seluruh permainan.
Bagaimana jika ada cara untuk mengekstraksi level dari ROM NES
mentah ?!
Bisakah teknik semacam itu ada?
Yah, kemungkinan besar tidak.
Jika Anda mengambil dua game untuk NES, Anda dapat menjamin bahwa mereka hanya memiliki satu kesamaan - keduanya bekerja untuk NES. Yang lainnya bisa sangat berbeda! Ketidakcocokan seperti itu adalah bencana nyata, karena game NES pada dasarnya memiliki jumlah pilihan tak terbatas untuk menyimpan data level!
Beberapa orang telah mengekstraksi level penuh dengan teknik terbalik cara mereka menyimpan data level dari
beberapa game (kadang-kadang dengan penciptaan
editor peta berfitur lengkap!), Tapi ini adalah tugas yang sulit, membutuhkan banyak pekerjaan, ketekunan dan kecerdasan.
Untuk mengekstraksi data level dari ROM, perlu untuk menentukan bagian ROM mana yang berkode (bukan data), dan ini sulit dilakukan, karena
menemukan semua kode dalam file biner setara dengan masalah berhenti !
WideNES menggunakan pendekatan yang jauh lebih sederhana: alih-alih menebak bagaimana game mengemas data level dalam ROM, wideNES hanya meluncurkan game dan melacak hasilnya!
Menggulir melebihi 255
NES adalah sistem 8-bit, yaitu, register PPUSCROLL hanya dapat menerima nilai 8-bit. Ini membatasi offset gulir maksimum hingga 255 piksel, yaitu, jumlah maksimum 8-bit. Tidak ada kebetulan bahwa resolusi layar NES adalah 240x256 piksel, mis. Pergeseran 255-piksel
hanya cukup untuk menggulir seluruh layar.
Tetapi apa yang terjadi ketika menggulir
lebih dari 255?
Pertama, game mengatur ulang register PPUSCROLL menjadi 0. Ini menjelaskan mengapa
SMB dijalankan sampai awal ketika Mario bergerak terlalu jauh ke kanan.
Kemudian, untuk mengkompensasi pembatasan PPUSCROLL 8-bit, game memperbarui register PPU lain: PPUCTRL (alamat 0x2000). 2 bit terbawah dari PPUCTRL mengatur "titik awal" dari adegan saat ini dalam peningkatan layar penuh. Misalnya, menulis nilai 1 menggeser viewport ke kanan sebesar 256 piksel, nilai 2 menggeser viewport turun menjadi 240 piksel. Offset PPUCTRL didorong ke
tumpukan dengan register PPUSCROLL, yang memungkinkan Anda untuk menggulir layar secara horizontal dalam 512 piksel atau secara vertikal dalam 480 piksel.
Tapi build, apakah hanya ada cukup memori video untuk dua layar level? Apa yang terjadi ketika viewport menggulir terlalu jauh ke kanan dan "melampaui" VRAM? Untuk menangani kasus ini, PPU mengimplementasikan konvolusi: semua bagian viewport di luar memori video yang dipilih hanya diciutkan ke tepi yang berlawanan dari memori video.
Lipat seperti itu, dikombinasikan dengan manipulasi register PPUSCROLL dan PPUCTRL yang cerdas, memungkinkan game NES untuk menciptakan ilusi dunia yang sangat tinggi / lebar! Berkat pemuatan malas dari tingkat di luar jendela tampilan dan pengguliran bertahap ke dalamnya, pemain tidak pernah menyadari bahwa di dalam VRAM mereka benar-benar “berlari dalam lingkaran”!
Ilustrasi yang sangat baik dari wiki nesdev menunjukkan bagaimana
Super Mario Bros menggunakan properti ini untuk membuat level lebih dari dua layar:
Mari kita kembali ke pertanyaan yang sedang kita diskusikan: bagaimana wideNES menangani pengguliran di luar 256?
Yah, sejujurnya, wideNES
sepenuhnya mengabaikan register PPUCTRL dan hanya melacak perbedaan PPUSCROLL antara frame!
Jika PPUSCROLL tiba-tiba melompat ke sekitar 256, yang biasanya berarti bahwa karakter pemain telah bergerak ke kiri / atas pada layar, dan jika ia tiba-tiba melompat ke sekitar 0, ini biasanya berarti bahwa pemain telah bergerak di sekitar layar ke kanan / ke bawah.
Meskipun heuristik ini mungkin terlihat sederhana - dan memang - sebenarnya, ia bekerja dengan baik!
Setelah menerapkan heuristik ini,
Super Mario Bros ,
Metroid dan banyak game lain telah bekerja dengan hampir sempurna!
Saya sangat senang, jadi saya melanjutkan dan mengunggah klasik NES lainnya -
Super Mario Bros. 3 ...
Hmm ... Tidak terlalu cantik.
Mengabaikan elemen layar statis
Banyak permainan memiliki elemen UI statis di sekitar tepi layar. Dalam kasus
SMB3, ini adalah kolom di sebelah kiri dan bilah status di bagian bawah status.
Secara default, sampel wideNES dengan kenaikan 16-piksel dari tepi layar, yaitu, semua elemen statis di tepi diambil sampelnya! Tidak bagus!
Untuk mengatasi masalah ini, wideNES mengimplementasikan aturan dan heuristik yang mencoba mengenali dan menutupi elemen layar statis secara otomatis.
Secara umum, game NES menggunakan tiga jenis elemen layar statis: HUD, mask, dan bilah status.
HUD - tidak masalah
Jika sebuah game memaksakan HUD di atas level, maka kemungkinan HUD terdiri dari beberapa sprite. Contoh: HUD di
Metroid .
Untungnya, HUD tersebut tidak menimbulkan masalah, karena wideNES saat ini mengabaikan lapisan sprite. Hebat!
Masker - tidak ada tempat yang lebih mudah
PPU memiliki fitur yang memungkinkan game untuk menutupi 8 piksel paling kiri dari lapisan latar belakang. Ini diaktifkan dengan mengatur bit kedua dari register (alamat 0x2001). Banyak game menggunakan fitur ini, tetapi menjelaskan
mengapa mereka melakukannya di luar ruang lingkup artikel ini.
Mengenali topeng yang disertakan sangat sederhana: wideNES hanya melacak nilai PPUMASK dan mengabaikan 8 piksel paling kiri ketika bit kedua diatur dalam register!
Tampaknya menerapkan aturan sederhana ini
memperbaiki masalah dengan
SMB3 :
... baik, atau
hampir dihilangkan.
Bilah status adalah yang paling sulit
Karena keterbatasan PPU pada waktu tertentu di layar tidak boleh ada lebih dari 64 sprite; apalagi, setiap saat di
setiap baris raster tidak boleh ada lebih dari 8 sprite. Pembatasan ini mencegah pengembang membuat HUD kompleks dari sprite dan memaksa mereka untuk menggunakan bagian-bagian dari lapisan latar belakang untuk menampilkan informasi.
Selain topeng, tidak ada cara mudah di PPU untuk memisahkan lapisan latar belakang ke area permainan dan area status. Oleh karena itu, para pengembang pergi ke trik, yang mengarah ke banyak cara
ortodoks untuk membuat panel status ...
WideNES menggunakan berbagai heuristik untuk mengenali berbagai jenis panel status, tetapi untuk menghemat waktu, saya hanya akan mempertimbangkan salah satu yang paling menarik: pelacakan IRQ mid-frame.
Pelacakan IRQ Mid-Frame
Tidak seperti GPU modern dengan buffer bingkai internal yang besar, PPU
umumnya tidak memiliki buffer bingkai! Untuk menghemat ruang, PPU menyimpan adegan sebagai kisi 64x32 petak 8x8 piksel. Alih-alih menghitung data piksel, ubin disimpan sebagai
petunjuk ke Memori CHR (Memori Karakter), yang berisi semua data piksel.
Karena NES dikembangkan pada tahun 80-an, PPU dibuat tanpa mempertimbangkan teknologi tampilan modern. Alih-alih merender frame penuh secara bersamaan, PPU mengeluarkan sinyal video NTSC, yang harus ditampilkan pada layar CRT yang menampilkan
piksel video
demi piksel ,
baris demi baris , dari atas ke bawah, dari atas ke bawah, dari kiri ke kanan, dari kiri ke kanan.
Mengapa semua ini penting?
Karena PPU membuat frame dari atas ke bawah, baris demi baris, Anda dapat mengirim instruksi PPU ke
mid-frame untuk membuat efek video yang tidak mungkin dilakukan dengan pendekatan lain! Efek-efek ini dapat berupa sederhana (misalnya, mengubah palet), atau cukup kompleks (misalnya, Anda dapat menebaknya, membuat bilah status!).
Untuk menjelaskan bagaimana penulisan PPU bingkai tengah dapat membuat bilah status, saya mencatat dump video slice PPU dan CHR memori mentah untuk satu bingkai
SMB3 :
Semuanya terlihat baik-baik saja, tidak ada yang istimewa ... tetapi lihat saja bilah status! Dia benar-benar terdistorsi!
Sekarang lihat dump mentah yang sama, tetapi dibuat setelah baris 196 ...
Ya, levelnya terlihat mengerikan, tetapi bilah status terlihat hebat!
Apa yang sedang terjadi di sini?
SMB3 mengatur timer untuk memicu IRQ (interupsi) tepat setelah rendering baris raster 195. Ini melewati instruksi berikut ke penangan IRQ:
- Setel PPUSCROLL ke (0,0) (sehingga bilah status tetap di tempatnya)
- Kami mengganti kartu ubin di Memori CHR (kami menyusun grafik bilah status)
Karena sisa layer sudah dirender, PPU tidak akan "memperbarui" frame. Alih-alih, ia akan terus merender dengan opsi ini, menampilkan bilah status yang tidak terdistorsi!
Mari kita kembali ke wideNES: dengan mengamati semua IRQ di tengah frame dan mengingat garis raster di mana mereka terjadi, wideNES dapat mengabaikan semua garis raster berikutnya dalam catatan! Jika IRQ terjadi pada garis raster di atas 240/2, maka semua baris
sebelumnya diabaikan, karena gangguan awal pada garis raster berarti bahwa bilah status mungkin ada
di bagian atas layar.
Setelah menerapkan heuristik ini,
Super Mario Bros 3 didapat sempurna!
Saya secara singkat mempertimbangkan kemungkinan menggunakan perpustakaan visi komputer, seperti OpenCV, untuk mengenali panel status (atau sebagian besar area statis layar), tetapi sebagai hasilnya saya memutuskan untuk mengabaikannya. Menggunakan perpustakaan visi komputer yang besar, kompleks dan buram bertentangan dengan cita-cita wideNES, di mana saya mencoba menggunakan aturan dan heuristik yang ringkas, sederhana dan transparan untuk mendapatkan hasil.
Pengenalan adegan
Dengan pengecualian dari beberapa contoh yang menonjol (misalnya,
Metroid ), game untuk NES biasanya
tidak lulus dalam satu level besar yang tidak dapat dipisahkan. Sebaliknya, sebagian besar game NES dibagi menjadi banyak “adegan” independen kecil dengan pintu atau layar transisi di antara mereka.
Karena wideNES tidak memiliki konsep "adegan," hal-hal buruk terjadi ketika mengubah adegan ...
Sebagai contoh, ini adalah transisi pertama dari adegan
Castlevania , di mana Simon Belmont memasuki istana Dracula:
Wow, semuanya buruk! wideNES sepenuhnya menulis ulang bagian terakhir dari level dengan layar pertama dari level baru!
Jelas, wideNES membutuhkan cara mengenali perubahan adegan. Tapi yang mana?
Hashing perseptual!Tidak seperti fungsi hash
kriptografis , yang cenderung mendistribusikan data input yang serupa secara merata di seluruh ruang informasi output, fungsi hash
persepsi mencoba untuk menjaga data input yang serupa “berdekatan” di ruang data output. Oleh karena itu, hash persepsi ideal untuk mengenali gambar yang serupa!
Fungsi hash perseptual dapat menjadi sangat kompleks, beberapa di antaranya dapat mengenali gambar yang serupa jika salah satunya diputar, diskalakan, direntangkan, dan warna diubah di dalamnya. Untungnya, wideNES tidak memerlukan fungsi hash yang kompleks karena setiap frame dijamin memiliki ukuran yang sama. Oleh karena itu, wideNES menggunakan hash perceptual yang ada yang paling sederhana:
menjumlahkan semua piksel pada layar!Ini sederhana, tetapi bekerja dengan cukup baik!
Misalnya, lihat bagaimana transisi antar adegan menonjol jika Anda merencanakan hash perseptual dari waktu ke waktu dalam
The Legend of Zelda :
Saat ini, wideNES menggunakan ambang batas tetap antara nilai hash perseptual untuk menyelesaikan transisi antara adegan, tetapi hasilnya jauh dari ideal. Gim yang berbeda menggunakan palet yang berbeda, dan ada banyak kasus di mana wideNES berpikir bahwa transisi telah terjadi, tetapi kenyataannya tidak. Idealnya, wideNES harus menggunakan nilai ambang batas dinamis, tetapi sejauh ini yang tetap akan dilakukan.
Setelah menerapkan heuristik baru ini, wideNES berhasil mengenali pintu masuk Simon dari
Castlevania ke kastil dan karenanya menciptakan kanvas baru.
Dan dengan keputusan ini, kami menempatkan potongan besar terakhir dari puzzle wideNES.
Setelah menerapkan serialisasi paling sederhana, saya akhirnya dapat menjalankan game untuk NES, bermain di beberapa level dan secara otomatis menghasilkan peta level!
Apa yang menanti wideNES di masa depan?
wideNES terdiri dari dua bagian yang terpisah:
kernel wideNES, yang merupakan aturan / heuristik yang mendasari teknologi, dan implementasi spesifik dari wideNES di dalam emulator ANESE.
Peningkatan inti WideNES
Pertama, wideNES rentan terhadap pengenalan transisi yang terlalu agresif antar adegan. Jumlah positif palsu dapat diminimalkan dengan menggunakan algoritma hashing persepsi yang lebih cocok atau dengan beralih ke nilai ambang batas dinamis antara hash persepsi.
Pekerjaan tambahan juga diperlukan untuk mengenali elemen layar statis.
Misalnya, Megaman IV memiliki IRQ di tengah frame, tetapi tidak ada bilah status, itulah sebabnya wideNES secara keliru mengabaikan bagian solid dari lapangan permainan. Meskipun kasus khusus ini dapat diperbaiki dengan penyetelan manual, lebih baik menggunakan heuristik yang lebih cerdas.Beberapa game NES menggulir layar dengan cara "unik". Salah satu contoh yang paling terkenal adalah The Legend of Zelda , yang menggunakan PPUSCROLL untuk gulir horizontal, tetapi menggunakan register yang sama sekali berbeda untuk gulir vertikal - PPUADDR. Zelda adalah game yang cukup populer, jadi wideNES mengimplementasikan heuristik khusus untuk Zelda. Ada gim lain dengan mode gulir “unik” yang serupa, yang juga membutuhkan heuristik tersendiri.Akan bermanfaat untuk menemukan beberapa cara untuk "menjahit" adegan yang identik. Misalnya, jika pengguna memainkan Super Mario Bros. Level 1, tetapi merangkak ke pipa untuk masuk ke gua bawah tanah dengan koin, wideNES akan membuat dua adegan terpisah untuk Level 1: adegan A, level sampai Mario memasuki zona dengan koin, dan adegan B, level, dari saat ini ketika Mario keluar dari pipa dan naik ke tiang bendera. Jika game kemudian dinyalakan kembali dan Level 1 diputar ulang tanpa masuk ke dalam pipa, maka wideNES hanya akan memperbarui adegan A, yang akan berisi peta tingkat penuh, tetapi adegan B akan "putus".Akhirnya, wideNES harus melacak transisi antar adegan. Tanpa data ini, tidak akan mungkin untuk membuat grafik transisi antara adegan untuk menghasilkan peta dunia permainan yang tidak terdiri dari satu dunia besar.Meningkatkan implementasi wideNES di ANESE
Saat ini wideNES diimplementasikan hanya dalam emulator NES yang saya tulis dengan nama ANESE. ANESE adalah emulator yang sangat sederhana: sebagian besar opsi disembunyikan di belakang flag CLI, dan satu-satunya UI yang diterapkan adalah overlay pemilihan file paling sederhana! Ia masih sangat jauh dari level "produksi".Terlepas dari kurangnya UI, ANESE dan wideNES, peningkatan kompatibilitas dan kecepatan tidak akan merugikan. ANESE adalah emulator pertama yang saya tulis, dan ini terlihat!Ada beberapa masalah kompatibilitas di dalamnya - banyak game tidak bekerja dengan benar atau tidak memulai sama sekali. Untungnya, ketidaksempurnaan ANESE tidak berarti bahwa wideNES adalah teknologi yang buruk. wideNES dibangun di atas prinsip-prinsip terbukti yang akan mudah diimplementasikan di emulator lain!Dalam hal kecepatan, ANESE dan wideNES tidak sempurna, dan bahkan pada PC yang relatif kuat, kinerja kadang-kadang bisa turun di bawah 60fps! ANESE dan wideNES perlu mengimplementasikan banyak optimasi. Selain perbaikan umum dari kernel ANESE, ada kebutuhan untuk meningkatkan perekaman frame wideNES, rendering peta, dan pengambilan sampel hash.Kesimpulan
Dalam artikel itu, saya berbicara tentang aspek-aspek utama wideNES, tetapi tidak dapat menjelaskan banyak fitur kecil. Misalnya, wideNES menyimpan peta nilai hash dan gulir sebenarnya dari setiap frame, yang digunakan untuk mengaktifkan adegan berulang. Ini dan banyak fitur lainnya dijelaskan dalam kode sumber yang dikomentari secara luas untuk wideNES, yang diposting pada halaman proyek wideNES .Bekerja dengan wideNES adalah pengalaman yang benar-benar luar biasa, tetapi dengan pendekatan semester akademik baru di Waterloe University, saya ragu bahwa dalam waktu dekat saya akan dapat terus mengembangkan wideNES. Saat ini, fungsi utama wideNES sedang bekerja, dan saya senang saya bisa menulis posting ini menjelaskan beberapa teknologinya!Coba gunakan wideNES dan bagikan perasaan Anda! Unduh ANESE , luncurkan Super Mario Bros. , The Legend of Zelda atau Metroid , dan mainkanlah dengan cara baru!