Mem-porting Mesin Game JavaScript ke C Tanpa Alasan
(phoboslab.org)- high_impact, yang menghidupkan kembali struktur mesin JavaScript Impact dari 2010, adalah mesin C untuk game aksi 2D, mendukung Windows, Mac, Linux, serta WASM untuk Web
- Impact adalah mesin yang dibuat untuk menunjukkan bahwa game web juga bisa dibuat dengan Canvas2D di tengah arus iOS yang menyingkirkan Flash; setelah menjual lebih dari 3.000 lisensi berbayar seharga $99, mesin ini dirilis sebagai open source gratis
- Mesin baru ini adalah framework kecil yang menyatukan tilemap, entity, fisika dan collision, animasi sprite, teks, serta suara, dan menggunakan backend SDL atau Sokol
- Implementasinya menjaga kesederhanaan lewat penyimpanan entity berukuran tetap, aset QOI/QOA, satu memori hunk, renderer OpenGL dan software, serta editor level Weltmeister berbasis JavaScript
- Biolab Disaster dan Drop berhasil dijalankan dengan memindahkan kode mendekati sumber JS aslinya, dan dapat diperluas ke berbagai sistem lewat penambahan platform dan renderer
Gambaran umum high_impact
- high_impact adalah mesin game kecil untuk game aksi 2D
- Ditulis dalam C dan dapat dikompilasi untuk Windows, Mac, Linux, serta WASM untuk Web
- Terinspirasi dari mesin game JavaScript tahun 2010, Impact, dan namanya merujuk pada masa ketika C dianggap sebagai bahasa tingkat tinggi
- Dirilis dengan lisensi MIT dan source-nya tersedia di GitHub
Latar belakang dibuatnya Impact
- Pada April 2010, Steve Jobs menerbitkan surat terbuka “Thoughts on Flash” yang menyatakan bahwa iOS tidak akan mendukung Flash
- Saat itu Flash merupakan pusat budaya game web dan animasi berbasis plugin browser, dan situs seperti Newgrounds serta Kongregate sangat bergantung pada konten Flash
- Dukungan Flash di Android punya banyak masalah, dan Adobe juga dinilai tidak berupaya memperbaiki kelemahannya di perangkat mobile
- Ada anggapan bahwa “tanpa Flash, tidak ada game browser”, tetapi Canvas2D API sudah bisa menggambar gambar dan bentuk pada
<canvas> - Canvas2D dibuat oleh Apple/Safari untuk rendering widget desktop, kemudian didukung oleh Google dan Mozilla, sementara Internet Explorer milik Microsoft tertinggal
- Dalam arus inilah Biolab Disaster dibuat, dan untuk itu mesin game serta editor level juga dikembangkan bersama
Penjualan dan contoh penggunaan Impact
- Impact dirapikan kodenya dan didokumentasikan, lalu dijual seharga $99; keputusan untuk menjualnya secara berbayar sempat menuai penolakan, tetapi berujung pada penjualan lebih dari 3.000 lisensi
- Berbagai game web dibuat dengan Impact, dan mesin ini juga digunakan untuk judul komersial lintas platform
- Menjelang akhir masa hidupnya, Impact dirilis sebagai open source gratis
- high_impact adalah proyek yang membuat ulang Impact dari awal, tetapi ditulis dalam C, bukan JavaScript
Mengapa C
- C dipandang sebagai bahasa yang sederhana tetapi mendalam, mirip karakteristik game yang “mudah dipelajari, sulit dikuasai”
- Minat terhadap C kembali tumbuh melalui berbagai proyek
- Impact pada dasarnya bukan mesin berskala seperti Godot, Unreal, atau Unity, tetapi berfungsi sebagai fondasi kokoh untuk banyak game
- Menulis ulang Impact dalam C dimulai sebagai latihan yang menyenangkan
Struktur mesin dan aset
- high_impact diimplementasikan dalam bentuk sesederhana mungkin, dengan arah untuk terdiri dari kode sesedikit mungkin
- Fitur dasarnya sama dengan mesin JavaScript asli
- Memuat tilemap
- Membuat, memperbarui, dan menggambar entity sebagai objek game
- Menangani fisika dan collision antar-entity
- Menangani collision dengan collision map
- Animasi sprite sheet
- Output teks
- Memutar efek suara dan musik
- Lebih dekat ke framework daripada library, dan logika game ditulis di dalam framework
- Di lapisan bawah ada backend
platform, yang saat ini dikompilasi dengan SDL atau Sokol - Kode game berada dalam satu atau lebih “scene”, dan scene adalah struct yang memiliki function pointer
- Setelah memanggil
engine_set_scene(&scene_game), mesin akan mengatur scene baru scene_game.init()dipanggil satu kaliscene_game.update()danscene_game.draw()dipanggil setiap frame
- Setelah memanggil
- Tilemap dan entity awal dapat dimuat dari file
.jsonatau dibuat secara dinamis - Alasan memilih JSON sebagai format level adalah kompatibilitas mundur dengan Impact asli
- high_impact menggunakan QOI untuk gambar, serta QOA untuk suara dan musik
- Makefile game demo otomatis mengonversi PNG ke QOI dan WAV ke QOA
- Tidak perlu menyertakan library decoding gambar dan suara terpisah
- Dukungan format aset lain mungkin ditambahkan di masa depan, tetapi kesederhanaan QOI/QOA sangat cocok dengan arah proyek ini
Sistem entity
- Semua entity berbagi
entity_t structyang sama, yang berisi properti yang dibutuhkan high_impact seperti posisi, kecepatan, dan ukuran - Karena ukuran byte semua entity sama, penyimpanan dan pengelolaannya menjadi sederhana
- Saat menggerakkan entity, cukup atur kecepatan atau akselerasinya, lalu sisanya ditangani mesin
- Melalui macro, properti khusus game dapat ditambahkan ke struct entity dasar
- Biolab Disaster menggunakan
unionyang memiliki struct untuk tiap tipe entity - Drop tidak perlu mendefinisikan properti tambahan
- Biolab Disaster menggunakan
- Setiap tipe entity harus memiliki
entity_vtab_tyang menyediakan function pointerupdatedipanggil setiap frametouchdipanggil ketika bertumpuk dengan entity lain yang memenuhi kondisi- Semua item bersifat opsional
- Penyimpanan entity berukuran tetap
- Jumlah entity aktif default adalah 1.024
- Dapat diatur melalui definisi
ENTITIES_MAX - Mesin dapat menangani hingga 64k entity dengan mudah
- Jika perlu menyimpan referensi entity lebih dari satu frame, gunakan
entity_ref_tentity_ref_tadalah struct yang memilikiuint16_t iddanindex- Dapat diinterpretasikan kembali menjadi pointer dengan
entity_by_ref() - Dapat membedakan ketika entity lain menempati alamat penyimpanan yang sama
- Karena indeks
uint16_t, batas maksimum entity aktif adalah 64k
- Bagian yang mengimplementasikan struktur seperti OOP sederhana, class, dan single inheritance dalam C memang agak canggung, tetapi high_impact berusaha membuatnya senyaman mungkin
- Pendekatan OOP yang “naif”, dengan menaruh logika entity di satu tempat per tipe, sejauh ini mudah dipahami dan bekerja baik pada game-game yang dibuat
Deteksi dan respons collision
- Penanganan collision sederhana hanya memeriksa apakah bisa bergerak ke posisi baru, lalu berhenti jika tidak bisa, tetapi perilaku aneh dapat muncul pada objek cepat
- Dalam platformer 2D, jika pemain berada 16px di atas tanah lalu pada gerakan berikutnya masuk ke dalam tanah, ini bisa terlihat seperti pendaratan halus: berhenti di udara lalu turun lagi pada frame berikutnya
- high_impact melacak kotak entity terhadap tilemap untuk menghitung titik collision yang akurat
- Cara ini lebih rumit daripada pemeriksaan ya/tidak sederhana, tetapi hasilnya lebih baik dan juga dapat menangani tile miring
- Setelah menabrak tile, pelacakan kedua mungkin diperlukan dengan sisa kecepatan
- Misalnya ketika menyentuh tanah secara miring,
vel.ymenjadi0, tetapivel.xdibiarkan agar objek meluncur di sepanjang lantai
- Misalnya ketika menyentuh tanah secara miring,
- Collision antar-entity ditangani secara terpisah
- Particle dapat dibuat bertabrakan dengan tilemap tetapi tidak dengan entity lain
- Platform bergerak bertabrakan dengan entity lain tetapi tidak boleh bergerak akibat respons collision
- Deteksi collision broad phase mengurutkan entity berdasarkan
pos.x- Karena dari frame sebelumnya sebagian besar sudah terurut, biaya insertion sort rendah
- Setelah diurutkan, pemindaian dilakukan dari kiri ke kanan dan hanya entity di antara
pos.xsampaipos.x + size.xyang diperiksa
- Metode sweep and prune ini cepat selama tidak ada banyak entity yang bertumpuk pada posisi x serupa
- Jika banyak entity berkumpul di posisi x yang sama, seperti menara kotak bertumpuk, itu menjadi kasus terburuk
- Untuk kasus seperti vertical shooter, ketika sumbu lain lebih cocok, sumbu sweep dapat diubah dengan
#define ENTITY_SWEEP_AXIS y
Rendering
- high_impact saat ini menyertakan renderer OpenGL dan renderer software yang belum selesai
- Semua rendering melewati API yang sangat tipis, dan panggilan gambar sebenarnya dilakukan melalui satu fungsi, sehingga implementasi backend lain relatif sederhana
- Fungsi inti yang harus didukung backend rendering tambahan adalah inisialisasi, pembersihan, pengaturan ukuran layar, persiapan dan penutupan frame, serta menggambar quad
- Untuk penanganan texture, fungsi mark, reset, dan create juga diperlukan
- Fiturnya sederhana: hanya dapat menggambar quad dan tidak dapat menggunakan efek shader, tetapi itu cukup untuk tujuan mesin ini
- Renderer software terdiri dari 140 baris kode dan hanya mendukung quad yang sejajar sumbu
- Renderer OpenGL berusaha memasukkan rendering satu frame ke dalam satu OpenGL draw call
- Semua quad dikumpulkan ke buffer besar dan dikirim sekaligus dengan
glDrawElements() - Semua texture digabungkan ke satu texture atlas untuk menghindari rebinding texture
- Semua quad dikumpulkan ke buffer besar dan dikirim sekaligus dengan
- Texture atlas adalah cara lama dan punya kelemahan, tetapi digunakan karena bindless texture belum didukung di semua tempat
- high_impact hanya mendukung satu texture atlas, tetapi ukurannya dapat diatur dengan
#define- GPU mobile biasanya mendukung texture 8k×8k
- GPU desktop modern tampaknya mendukung hingga 32k×32k
- Biolab Disaster dan Drop menggunakan atlas 512×512
Suara
- Output suara ditangani SDL2 atau Sokol, sementara mesin menangani loading, decoding, dan mixing beberapa suara
- Sistem suara dibagi menjadi
sound_source_tyang memiliki sampel dansound_tyang mewakili suara yang sedang diputar - Sistem ini didasarkan pada sistem yang dibuat saat penulisan ulang wipEout, dan dapat mendekompresi QOA saat diperlukan
- Semuanya dialokasikan secara statis
- Jumlah source yang dapat dimuat bersifat tetap
- Jumlah sound yang dapat diputar bersamaan bersifat tetap
- Sound yang selesai diputar otomatis dibuang dan dapat digunakan kembali
- Volume, panning kiri-kanan, dan pitch suara dapat diubah
- Jika pitch diatur negatif, suara diputar terbalik
- Resampling yang diperlukan untuk pitch variabel menggunakan metode berkualitas rendah dengan interpolasi nearest-neighbor
Manajemen memori
- high_impact beranggapan bahwa jika game tidak memiliki aset buatan pengguna, jumlah memori yang diperlukan dapat diketahui secara tepat
- Mesin mengalokasikan satu array byte bernama “hunk” secara statis, dan hanya memori inilah yang digunakan high_impact
- Ukuran hunk dapat diatur dengan
#define ALLOC_SIZE - Di dalam hunk, memori dialokasikan dengan dua cara
- Bump allocator yang tumbuh dari depan ke atas, atau arena, menyimpan aset game, entity, dan data scene saat ini
- Allocator sementara yang tumbuh dari ujung ke bawah bekerja seperti
malloc()danfree(), dan digunakan untuk penyimpanan sementara seperti setelah dekompresi gambar sebelum dikirim ke GPU
- Bump allocator memiliki beberapa “high water mark” dan otomatis kembali ke titik tertentu pada saat tertentu
- Memori dari bump allocation tidak perlu di-
free()secara eksplisit - Masa hidup konseptual dibagi menjadi
game,scene, danframe- Alokasi sebelum scene pertama diatur hanya dilepas saat program berakhir
- Alokasi selama
scene.load()dilepas saat scene berakhir - Alokasi selama scene berjalan dilepas di akhir frame
load()per tipe entity dipanggil pada tahap pertama karena belum diketahui sebelumnya entity apa yang akan dipakai dalam scene- Konteks alokasi tambahan dapat dibungkus dengan
alloc_pool(), yang secara internal merupakan bentuk singkat daribump_mark()danbump_reset(mark)
Editor level Weltmeister
- Impact asli memiliki editor level bernama Weltmeister, dan ini juga disertakan di high_impact
- Masih ditulis dalam JavaScript dan banyak menggunakan source lama, tetapi diperbarui untuk fitur browser modern
- Weltmeister berjalan sepenuhnya mandiri
- Anda dapat mulai membuat level dengan mengklik dua kali
weltmeister.html - Dulu, loading dan penyimpanan file memerlukan API backend PHP atau NodeJS
- Kini FileSystemAPI dapat meminta izin akses ke folder tertentu
- Anda dapat mulai membuat level dengan mengklik dua kali
- Safari dan Firefox belum sepenuhnya mendukung showDirectoryPicker(), sehingga browser keluarga Chrome diperlukan
- Weltmeister membaca file source C dan mengumpulkan tipe entity
- high_impact menyediakan macro yang dipahami editor tetapi tidak melakukan apa pun di kode C
EDITOR_SIZE(X, Y): ukuran di editor, default(8, 8)EDITOR_RESIZE(RESIZE): apakah ukuran dapat diubah di editorEDITOR_COLOR(R, G, B): warna kotak di editor, default(128, 255, 128)EDITOR_IGNORE(IGNORE): apakah dapat dibuat di editor
Game demo
- Untuk memastikan high_impact berfungsi sebagai mesin game nyata, dua game Impact asli di-porting ke C
- Pekerjaan porting lebih mirip memindahkan source JS lama secara “transliterasi” dan menggunakan kembali aset lama
- Fakta bahwa tantangannya sedikit dapat dianggap sebagai bukti bahwa high_impact bekerja sesuai maksudnya
-
Biolab Disaster
- Judul peluncuran Impact asli
- Game Jump'n'Gun side-scrolling
- Source tersedia di github.com/phoboslab/high_biolab
- Versi JS asli tersedia di playbiolab.com
-
Drop
- Game arcade yang sangat sederhana
- Source tersedia di github.com/phoboslab/high_drop
- Versi JS asli ada di impactjs.com/drop/
- Skor tertinggi saat ini disebut sebagai 26789 Points
Ekstensibilitas
- high_impact memiliki struktur seperti mesin game tradisional, tempat kode khusus game ditulis secara add-on
- Source mesin tidak perlu diubah, tetapi arahnya adalah membuat mesin cukup sederhana sehingga source mesin dapat diubah langsung bila diperlukan
- Platform dan renderer dirancang agar dapat diperluas tanpa mengubah bagian kode lainnya
- Jika berminat, pull request untuk dukungan renderer Vulkan, DirectX, Metal serta backend platform seperti PSX, N64, dan Dreamcast akan disambut baik
- Karena ditulis dalam C, arahnya adalah agar bisa berjalan di mana saja
1 komentar
Komentar Hacker News
Banyak dari pekerjaan pemrograman yang paling banyak mengajari saya adalah berkat Impact
Impact benar-benar mendahului zamannya, dan saya bangga pernah menjadi salah satu dari 3.000 pemegang lisensinya. Itu pembelian terbaik yang pernah saya lakukan, dan satu-satunya game yang benar-benar saya selesaikan sampai akhir juga dibuat dengan Impact
Saya suka karena lisensinya menyertakan kode sumber, dan saya sendiri memodifikasi engine serta editornya sesuai kebutuhan. Karena pengaruh itu, selama beberapa tahun saya membuat engine game JS sendiri, tetapi penyelesaian gamenya justru tertunda; tetap saja, saya belajar banyak dalam prosesnya dan juga membuat banyak game untuk game jam
Saya juga terinspirasi oleh Ejecta, dukungan iOS native dari Impact, tetapi saat itu frustrasi karena tidak berjalan di Android. Jadi, untuk menjalankan engine saya di Android tanpa webview, saya membuat binding JVM untuk V8 dan mengimplementasikan sebagian WebGL. Repositori binding V8 yang saya buka ke publik ternyata tanpa diduga dipakai dalam perangkat lunak komersial: https://github.com/namuol/jv8
Terinspirasi oleh model bisnis Impact, saya bahkan sempat mencoba startup bootstrap yang menjual akses ke repositori GitHub privat, tetapi cerita itu akan panjang. Bagaimanapun, melihat Impact diperbarui agar cocok dengan web “modern” lewat porting ke C membuat saya hangat hati dan senang. Saya ingin mengatakan bahwa web sedang berada di masa yang aneh, tetapi saya tidak ingat kapan web pernah tidak aneh
CrossCode adalah game yang hebat. Saya tahu game itu memakai teknologi web, dan saya terus takjub bagaimana performanya bisa sebagus itu di hardware Nintendo Switch
Engine ini mungkin juga punya andil sampai batas tertentu
Justru itu yang keren. Bagus ketika developer bisa mengubah engine agar sesuai dengan gamenya, dan begitu pula high_impact sebaiknya dilihat sebagai titik awal yang nyaman, bukan engine game yang “fiturnya lengkap”
Sebagai anekdot menarik, semua orang menginginkan versi Switch, tetapi karena keterbatasan teknis, tim menjawab bahwa “CrossCode akan hadir di Switch ketika Hedgehags belajar terbang”: https://www.radicalfishgames.com/?p=6581
Akhirnya ketika porting berhasil, ada quest tambahan berjudul “A switch in attitude”, dan seperti yang diduga, hedgehags yang terbang pun muncul: https://www.radicalfishgames.com/?p=6668
“Thoughts on Flash” mungkin telah menyelamatkan web pada saat platform web paling membutuhkannya, yaitu ketika dominasi satu perangkat lunak perlahan semakin besar
Di dalamnya sepertinya juga ada ketidakpuasan terhadap Adobe, yang tampak menelantarkan dukungan MacOS karena memprioritaskan basis pengguna Windows yang jauh lebih besar. Misalnya, versi Mac selalu tertinggal dari versi Windows
Jobs mungkin merasa Adobe juga berkontribusi pada Apple sebesar Apple memungkinkan Adobe, tetapi itu lebih dekat ke spekulasi. Gamenya sendiri terlihat benar-benar mulus
Meski begitu, bukan berarti sedekat bahasa assembly; saya sedang mencoba mengurangi berbagai hal ke arah yang membuat saya ingin menggali lebih dalam
Sebagai seseorang yang ingin suatu hari lepas dari pekerjaan korporat dan benar-benar terjun ke proyek sampingan, saya ingin mendengar lebih banyak tentang bagian yang menjadi mandiri lewat monetisasi
Gagasan menerima uang untuk pekerjaan yang semula dilakukan demi kesenangan terasa anehnya membebani, tetapi saya juga tahu itu bisa memungkinkan saya mengerjakan hal yang saya sukai secara penuh waktu
Karena itu, penting untuk memahami mengapa gagasan tersebut terasa membebani. Alasan yang umum misalnya orang-orang di sekitar sering melarang, kurangnya keterampilan untuk mengeksekusi hal yang ingin dilakukan dengan baik, merasa malu meminta bantuan atau merasa akan merepotkan orang lain, takut karya sendiri dinilai, atau kehilangan “rasa aman” dari penghasilan saat ini, terutama ketika ada keluarga yang ditanggung
Dalam kebanyakan kasus, alasan-alasan itu bukan alasan yang sangat baik, melainkan lebih seperti sesuatu yang perlu disetel ulang sampai tingkat tertentu, dan karena terasa seperti “risiko”, sulit untuk keluar dari zona nyaman. Dengan pola pikir seperti itu, setiap peluang akan terlihat seperti risiko, sehingga menjadi sangat sulit menemukan waktu yang tepat untuk memulai hal yang benar-benar ingin dilakukan
Terkait hal itu, memang penting untuk sekadar melakukan hal yang menurut Anda menyenangkan dan menunjukkannya kepada dunia, tetapi mengubahnya menjadi pekerjaan yang menopang hidup adalah tantangan yang sama sekali berbeda. Kebanyakan orang tidak bisa menjadikan hal yang mereka sukai sebagai mata pencaharian, dan bahkan jika berhasil, ekspektasi pelanggan berbayar serta tekanan menjaga pendapatan bisa merenggut rasa suka itu. Bukan berarti jangan dilakukan, hanya saja ini hal yang baik untuk diketahui sebelum terjun
Sampai-sampai saya login ke akun HN yang hampir tidak pernah saya pakai, karena beberapa tahun lalu saya terus memainkan Biolab Disaster berulang-ulang tetapi sudah lupa namanya
Cukup menakjubkan bisa menemukannya lagi secara kebetulan
Biasanya saya mengungkapkan hal ini dengan jauh lebih negatif. Karena saya menganggap “framework” hanyalah library yang tidak cocok dipadukan dengan hal-hal lain
Meski begitu, senang mendengar ungkapan positif yang masuk akal seperti ini
Menggunakan framework yang kaya dan dirancang dengan baik, yang menangani 99% kebutuhan dengan sangat baik, itu menyenangkan. Menambahkan kode saya sendiri juga termasuk salah satu pengalaman pengembangan termudah yang pernah saya lakukan, dan semuanya langsung bekerja dengan baik. Rasanya seperti sihir, dan sampai sekarang saya masih merindukannya
Framework ideal menurut saya, secara internal, adalah library atau sekumpulan library yang bekerja sama, dengan sifat framework seminimal mungkin
Misalnya Qt adalah framework dan Qt “memanggil saya”, tetapi saya tetap bisa menjalankan kode QPainter tanpa memulai event loop Qt atau memikirkan QObject secara mendalam. Idealnya, kita seharusnya bisa memakai event loop tanpa menerima seluruh konsep signal dan slot, meskipun pengalaman penggunaannya mungkin jadi kurang nyaman
Itu tidak selalu mungkin dan tidak selalu bernilai, tetapi jika faktor lain sama, saya lebih memilih tidak ada framework sama sekali
Untuk game engine, saya paham mengapa sedikit framework diperlukan. Saat mengompilasi untuk platform khusus seperti ponsel atau konsol, engine harus ikut menangani proses build, kadang bahkan bagian yang terkait libc, jadi kita tidak bisa sekadar membuat executable Win32 lalu menyebutnya game PlayStation
Menggabungkan format file lossless QOI dengan 7Zip menghasilkan performa yang lebih baik daripada PNG lossless. Pekerjaan yang mengesankan
“for No Reason” mungkin saja demi menghormati daya tahan baterai pemain
Saya suka bagian manajemen memorinya. Alokasi arena memang sangat sederhana
Server web mainan yang saya pakai juga awalnya dimulai dengan arena, tetapi tak lama kemudian saya sadar bahwa tidak ada kebutuhan untuk menambah dan mengurangi memori sama sekali
Sekarang saya mengalokasikan seluruh memori yang dibutuhkan di awal, lalu membaginya menjadi potongan-potongan untuk tiap modul. Saat memrogram, kita sering berasumsi mungkin membutuhkan jumlah memori yang sewenang-wenang, tetapi tidak selalu begitu
Banyak hal sebenarnya punya batas yang jelas, dan sisanya biasanya juga bisa kita definisikan batasnya. Jika batas-batas itu kita daftar, kita jadi tahu berapa banyak memori yang dibutuhkan. Memikirkan dan mendefinisikan batas seperti itu sejak awal itu menyenangkan, memberi rasa percaya diri, dan menumbuhkan kebiasaan berhemat yang sehat
Kalau mulai dari vector kosong lalu menambahkan ribuan item tanpa melakukan reservasi memori, tentu saja bisa begitu. Namun sangat sering kita bisa menemukan batas maksimum yang benar-benar dibutuhkan dan mengalokasikannya lebih dulu, dan kalau begitu semuanya baik-baik saja
Saya sangat suka cara mereka membuat struktur data ENTITY bertipe polimorfik dengan union. Desainnya bagus
Saya masih suka mengutak-atik C. Itu bahasa pertama yang saya pelajari, dan selama beberapa tahun saya benar-benar berjuang dengannya. Seperti yang dikatakan di artikel aslinya, C itu keren karena bahasanya ringkas, dan kalau mau kita bisa masuk sedalam apa pun
Gamenya terasa seperti Commander Keen lama, jadi saya suka; dulu saya cukup menyukai franchise yang dibuat Carmack pada masa sebelum 3D itu