Bagaimana menghindari pengkodean yang sulit di mesin game

22

Pertanyaan saya bukan pertanyaan pengkodean; itu berlaku untuk semua desain mesin game pada umumnya.

Bagaimana Anda menghindari pengkodean yang sulit?

Pertanyaan ini jauh lebih dalam dari yang terlihat. Katakanlah, jika Anda ingin menjalankan game yang memuat file yang diperlukan untuk operasi, bagaimana Anda menghindari mengatakan sesuatu seperti load specificfile.waddalam kode mesin? Juga, ketika file dimuat, bagaimana Anda menghindari mengatakan load aspecificmap in specificfile.wad?

Pertanyaan ini berlaku untuk hampir semua desain mesin, dan sesedikit mungkin mesin harus dikodekan dengan keras. Apa cara terbaik untuk mencapainya?

Marcus Cramer
sumber

Jawaban:

42

Pengodean berbasis data

Setiap hal yang Anda sebutkan adalah sesuatu yang dapat ditentukan dalam data. Mengapa Anda memuat aspecificmap? Karena konfigurasi gim mengatakan bahwa itu adalah level pertama ketika seorang pemain memulai gim baru, atau karena itulah nama titik penyimpanan saat ini dalam file penyimpanan pemain yang baru saja mereka muat, dll.

Bagaimana Anda menemukan aspecificmap? Karena ada dalam file data yang mencantumkan id peta dan sumber daya pada-disk mereka.

Hanya perlu ada satu set kecil sumber daya "inti" yang secara sah sulit atau tidak mungkin untuk menghindari hard-coding. Dengan sedikit kerja, ini dapat dibatasi untuk satu nama aset default seperti main.wadatau sejenisnya. File ini berpotensi dapat diubah saat runtime dengan meneruskan argumen baris perintah ke gim, alias game.exe -wad mymain.wad.

Menulis kode berbasis data bergantung pada beberapa prinsip lain. Sebagai contoh, seseorang dapat menghindari sistem atau modul meminta sumber daya tertentu dan sebaliknya membalikkan dependensi tersebut. Artinya, jangan membuat DebugDrawermemuat debug.fontdalam kode inisialisasi; sebagai gantinya, telah DebugDrawermengambil pegangan sumber daya dalam kode inisialisasi. Pegangan itu mungkin diambil dari file konfigurasi game utama.

Sebagai contoh nyata dari basis kode kami, kami memiliki objek "data global" yang diambil dari basis data sumber daya (yang secara default ./resourcesfolder itu tetapi dapat dibebani dengan argumen baris perintah). ID databse sumber daya dari data global ini adalah satu-satunya nama sumber daya hard-coded yang diperlukan dalam basis kode (kami memiliki yang lain karena kadang-kadang programmer merasa malas, tetapi pada akhirnya kami pada akhirnya memperbaiki / menghapusnya). Objek data global ini penuh dengan komponen yang tujuan utamanya adalah untuk menyediakan data konfigurasi. Salah satu komponen adalah komponen Data Global UI yang berisi sumber daya menangani semua sumber daya UI utama (font, file Flash, ikon, data pelokalan, dll.) Di antara sejumlah item konfigurasi lainnya. Ketika pengembang UI memutuskan untuk mengubah nama aset UI utama dari /ui/mainmenu.swfmenjadi/ui/lobby.swfmereka hanya memperbarui referensi data global itu; tidak ada kode mesin yang perlu diubah sama sekali.

Kami menggunakan data global ini untuk semuanya. Semua karakter yang dapat dimainkan, semua level, UI, audio, aset inti, konfigurasi jaringan, semuanya. (yah, tidak semuanya , tetapi hal-hal lain itu adalah bug yang harus diperbaiki.)

Pendekatan ini memiliki banyak keunggulan lain. Untuk satu, itu membuat pengemasan sumber daya dan bundling menjadi bagian integral dari keseluruhan proses. Jalur hard-coding dalam mesin juga cenderung berarti bahwa jalur yang sama harus dikodekan dalam skrip atau alat apa pun yang mengemas aset game, dan jalur tersebut kemudian dapat keluar dari sinkronisasi. Mengandalkan pada aset inti tunggal dan rantai referensi dari sana, kita dapat membangun bundel aset dengan perintah tunggal seperti bundle.exe -root config.data -out main.waddan tahu bahwa itu akan mencakup semua aset yang kita butuhkan. Lebih lanjut, karena bundler hanya akan mengikuti referensi sumber daya, kami tahu bahwa itu hanya akan mencakup aset yang kami butuhkan dan lewati semua sisa bulu yang tak terelakkan terakumulasi selama masa proyek (ditambah kita dapat secara otomatis menghasilkan daftar yang bulu untuk pemangkasan).

Kasus sudut rumit dari semua ini ada di skrip. Membuat engine-driven data mudah secara konseptual, tetapi saya telah melihat begitu banyak proyek (hobi untuk AAA) di mana skrip dianggap data dan karenanya "diizinkan" untuk hanya menggunakan jalur sumber daya tanpa pandang bulu. Jangan lakukan itu. Jika file Lua membutuhkan sumber daya dan itu hanya memanggil fungsi seperti textures.lua("/path/to/texture.png")maka pipa aset akan mengalami banyak kesulitan mengetahui bahwa skrip membutuhkan /path/to/texture.pnguntuk beroperasi dengan benar dan mungkin menganggap tekstur itu tidak digunakan dan tidak perlu. Skrip harus diperlakukan seperti kode lain: data apa pun yang mereka butuhkan termasuk sumber daya atau tabel harus ditentukan dalam entri konfigurasi yang dapat diperiksa oleh mesin dan sumber daya pipa untuk dependensi. Data yang mengatakan "muat skrip foo.lua" seharusnya mengatakan "foo.luadan berikan parameter ini "di mana parameter menyertakan sumber daya yang diperlukan. Jika skrip secara acak memunculkan musuh misalnya, masukkan daftar musuh yang mungkin ke dalam skrip dari file konfigurasi tersebut. Mesin kemudian dapat memuat musuh sebelumnya dengan level ( karena ia mengetahui daftar lengkap dari kemungkinan spawns) dan pipa sumber daya tahu untuk menggabungkan semua musuh dengan game (karena mereka secara definitif dirujuk oleh data konfigurasi). Jika skrip menghasilkan string nama path dan hanya memanggil loadfungsi maka tidak ada mesin atau pipa sumber daya memiliki cara untuk mengetahui secara spesifik aset mana yang mungkin dicoba dimuat oleh skrip.

Sean Middleditch
sumber
Jawaban yang bagus, sangat praktis, dan juga menjelaskan jebakan dan kesalahan yang dilakukan orang saat mengimplementasikan ini! +1
whn
+1. Akan menambahkan bahwa mengikuti pola menunjuk ke sumber daya yang berisi data konfigurasi juga sangat membantu jika Anda ingin mengaktifkan modding. Sangat sulit dan berisiko untuk memodifikasi game yang mengharuskan Anda untuk mengubah file data asli daripada membuat sendiri dan mengarahkannya. Lebih baik lagi jika Anda dapat menunjuk beberapa file dengan urutan prioritas yang ditentukan.
Jeutnarg
12

Cara yang sama Anda menghindari hardcoding pada fungsi umum.

Anda melewatkan parameter dan menyimpan informasi Anda dalam file konfigurasi.

Dalam situasi itu, sama sekali tidak ada perbedaan dalam rekayasa perangkat lunak antara menulis mesin dan menulis kelas.

MgrAssets
public:
  errorCode loadAssetFromDisk( filePath )
  errorCode getMap( mapName, map& )

private:
  maps[name, map]

Kemudian kode klien Anda membaca file konfigurasi "master" (yang ini baik kode atau dilewatkan sebagai argumen baris perintah) yang berisi informasi yang memberitahu di mana file aset dan peta apa yang dikandungnya.

Dari sana, semuanya didorong oleh file konfigurasi "master".

Vaillancourt
sumber
1
Yap, itu ditambah semacam mekanisme untuk membawa logika kustom. Mungkin dengan menanamkan bahasa seperti C #, python, dll. Untuk memperluas fitur inti mesin berdasarkan fungsi yang ditentukan pengguna
qCring
3

Saya suka jawaban yang lain jadi saya akan sedikit bertolak belakang. ;)

Anda tidak dapat menghindari pengkodean pengetahuan tentang data Anda ke mesin Anda. Dari mana pun informasi itu berasal, mesin harus tahu untuk mencarinya. Namun, Anda dapat menghindari penyandian informasi aktual itu sendiri ke mesin Anda.

Pendekatan berbasis data "murni" akan membuat Anda memulai yang dapat dieksekusi dengan parameter baris perintah yang diperlukan untuk memuat konfigurasi awal tetapi mesin harus diberi kode untuk mengetahui bagaimana menafsirkan informasi itu. Misalnya jika file konfigurasi Anda JSON, Anda perlu kode keras variabel Anda mencari, misalnya mesin harus tahu untuk mencari "intro_movies"dan "level_list"dan sebagainya.

Namun, mesin "dibangun dengan baik" dapat bekerja untuk banyak game yang berbeda hanya dengan menukar data konfigurasi dan data yang dirujuk.

Jadi mantra ini tidak terlalu banyak untuk menghindari pengkodean yang sulit karena itu untuk memastikan bahwa Anda dapat membuat perubahan dengan jumlah usaha seminimal mungkin.

Berbeda dengan pendekatan file data (yang saya dukung dengan sepenuh hati), mungkin Anda baik-baik saja mengompilasi data ke dalam mesin Anda. Jika "biaya" untuk melakukannya lebih rendah, maka tidak ada salahnya; jika Anda satu-satunya yang mengerjakannya, maka Anda dapat menunda penanganan file di kemudian hari dan tidak harus menipu diri sendiri. Beberapa proyek gim pertamaku memiliki tabel besar data yang di-hardcode ke dalam gim itu sendiri, misalnya daftar senjata dan berbagai macam data mereka:

struct Weapon
{
    enum IconID icon;
    enum ModelID model;
    int damage;
    int rateOfFire;
    // etc...
};

const struct Weapon g_weapons[] =
{
    { ICON_PISTOL, MODEL_PISTOL, 5, 6 },
    { ICON_RIFLE, MODEL_RIFLE, 10, 20 },
    // etc...
};

Jadi Anda meletakkan data ini di suatu tempat yang mudah dirujuk dan mudah diedit seperlunya. Yang ideal adalah untuk meletakkan barang-barang ini ke dalam file konfigurasi, tetapi kemudian Anda perlu melakukan parsing dan terjemahan dan semua jazz itu, ditambah dengan menghubungkan referensi antar-struktur mungkin menjadi rasa sakit tambahan yang Anda benar-benar tidak ingin melakukannya. berurusan dengan.

dash-tom-bang
sumber
Ini tidak terlalu sulit untuk mengurai json. Satu-satunya "biaya" yang terlibat adalah belajar. (Secara khusus, belajar menggunakan modul atau pustaka yang sesuai. Go memiliki dukungan json yang baik, misalnya.)
Wildcard
Ini tidak terlalu sulit tetapi membutuhkan melakukannya di luar hanya belajar. Misalnya saya tahu bagaimana cara mem-parsing JSON, saya telah menulis parser untuk banyak format file lainnya, tetapi saya harus mencari dan menginstal solusi pihak ketiga (dan mencari tahu dependensi dan cara membangunnya) atau roll sendiri. Membutuhkan lebih banyak waktu daripada tidak melakukannya.
dash-tom-bang
4
Semuanya membutuhkan lebih banyak waktu daripada tidak melakukannya. Tetapi alat yang Anda butuhkan sudah ditulis. Sama seperti Anda tidak perlu mendesain kompiler untuk menulis gim, atau bermain-main dengan kode mesin, tetapi Anda harus belajar bahasa untuk platform yang Anda gunakan. Jadi, belajarlah untuk menggunakan json parser, juga.
Wildcard
Saya tidak yakin apa argumen Anda. Dalam jawaban ini saya menganjurkan YAGNI; jika Anda tidak perlu menghabiskan / membuang waktu melakukan sesuatu yang tidak akan membantu Anda maka jangan lakukan. Jika Anda ingin menghabiskan waktu untuk itu bagus. Mungkin Anda harus menghabiskan waktu nanti, mungkin tidak, tetapi melakukannya di depan hanya mengalihkan perhatian Anda dari tugas membuat game. Pengembangan game itu sepele; setiap tugas yang dilakukan untuk membuat game itu sederhana. Hanya saja sebagian besar game memiliki sejuta tugas sederhana dan seorang pengembang yang bertanggung jawab memilih yang paling cepat mencapai sasaran itu.
dash-tom-bang
2
Sebenarnya, saya membatalkan jawaban Anda; tidak ada argumen nyata seperti itu. Saya hanya ingin mencatat bahwa JSON tidak sulit untuk diuraikan. Membaca lagi, saya kira saya sebagian besar merespons cuplikan "tetapi kemudian Anda perlu melakukan parsing dan terjemahan dan semua jazz itu." Tapi saya setuju untuk game proyek pribadi dan semacamnya, YAGNI. :)
Wildcard