Menerapkan perilaku dalam game petualangan sederhana

11

Saya menghibur diri belakangan ini dengan memprogram game petualangan berbasis teks yang sederhana, dan saya terjebak pada apa yang tampaknya seperti masalah desain yang sangat sederhana.

Untuk memberikan gambaran singkat: permainan dipecah menjadi Roomobjek. Masing-masing Roommemiliki daftar Entitybenda yang ada di ruangan itu. Masing-masing Entitymemiliki keadaan peristiwa, yang merupakan peta string-> boolean sederhana, dan daftar tindakan, yang merupakan peta fungsi string->.

Input pengguna berupa [action] [entity]. The Roommenggunakan nama entitas untuk mengembalikan sesuai Entityobjek, yang kemudian menggunakan nama tindakan untuk menemukan fungsi yang benar, dan mengeksekusinya.

Untuk menghasilkan deskripsi kamar, setiap Roomobjek menampilkan string deskripsi sendiri, lalu menambahkan string deskripsi masing-masing Entity. The Entitydeskripsi dapat berubah berdasarkan keadaan ( "Pintu terbuka", "Pintu tertutup", "Pintu terkunci", dll).

Inilah masalahnya: menggunakan metode ini, jumlah fungsi deskripsi dan tindakan yang perlu saya implementasikan dengan cepat menjadi tidak terkendali. Ruang awal saya sendiri memiliki sekitar 20 fungsi antara 5 entitas.

Saya bisa menggabungkan semua tindakan menjadi satu fungsi dan jika-ada / beralih melalui mereka, tapi itu masih dua fungsi per entitas. Saya juga dapat membuat Entitysub-kelas spesifik untuk objek umum / umum seperti pintu dan kunci, tetapi itu hanya membuat saya sejauh ini.

EDIT 1: Seperti yang diminta, contoh pseudo-code dari fungsi aksi ini.

string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
    if thisEntity["is_searched"] then
        return "There was nothing more in the bushes."
    else
        thisEntity["is_searched"] := true
        currentRoom.setEntity("dungeonDoorKey")
        return "You found a key in the bushes."
    end if

string dungeonDoorKeyUse(currentRoom, thisEntity, player)
    if getEntity("outsideDungeonDoor")["is_locked"] then
        getEntity("outsideDungeonDoor")["is_locked"] := false
        return "You unlocked the door."
    else
        return "The door is already unlocked."
    end if

Fungsi deskripsi bekerja dengan cara yang hampir sama, memeriksa status dan mengembalikan string yang sesuai.

EDIT 2: Merevisi kata-kata saya. Asumsikan bahwa mungkin ada sejumlah besar objek dalam game yang tidak berbagi perilaku umum (respons berbasis negara untuk tindakan tertentu) dengan objek lain. Apakah ada cara saya dapat mendefinisikan perilaku unik ini dengan cara yang lebih bersih dan lebih dapat dipertahankan daripada menulis fungsi khusus untuk setiap tindakan entitas tertentu?

Eric
sumber
1
Saya pikir Anda perlu menjelaskan apa yang "fungsi aksi" lakukan dan mungkin memposting beberapa kode, karena saya tidak yakin apa yang Anda bicarakan di sana.
jhocking
Menambahkan kode.
Eric

Jawaban:

5

Alih-alih membuat fungsi terpisah untuk setiap kombinasi kata benda dan kata kerja, Anda harus mengatur arsitektur di mana ada satu antarmuka umum yang semua objek dalam gim implementasikan.

Salah satu pendekatan dari atas kepala saya adalah mendefinisikan objek Entitas yang diperluas oleh semua objek spesifik dalam game Anda. Setiap Entitas akan memiliki tabel (struktur data apa pun yang digunakan bahasa Anda untuk array asosiatif) yang mengaitkan tindakan yang berbeda dengan hasil yang berbeda. Tindakan dalam tabel kemungkinan adalah Strings (mis. "Open") sementara hasil terkait bahkan bisa menjadi fungsi pribadi dalam objek jika bahasa Anda mendukung fungsi kelas satu.

Demikian pula, keadaan objek disimpan di berbagai bidang objek. Jadi misalnya, Anda dapat memiliki array barang di Bush, dan kemudian fungsi yang terkait dengan "pencarian" akan bertindak pada array itu, baik mengembalikan objek yang ditemukan atau string "Tidak ada yang lebih di semak-semak."

Sementara itu, salah satu metode publik adalah sesuatu seperti Entity.actOn (tindakan String) Kemudian dalam metode itu membandingkan tindakan yang dikirimkan dengan tabel tindakan untuk objek itu; jika tindakan itu ada dalam tabel maka kembalikan hasilnya.

Sekarang semua fungsi berbeda yang diperlukan untuk setiap objek akan terkandung di dalam objek, sehingga mudah untuk mengulangi objek itu di ruangan lain (mis. Instantiate objek Door di setiap kamar yang memiliki pintu)

Terakhir, tentukan semua kamar dalam XML atau JSON atau apa pun sehingga Anda dapat memiliki banyak kamar unik tanpa perlu menulis kode terpisah untuk setiap kamar. Muat file data ini saat game dimulai, dan parsing data untuk membuat instance objek yang mengisi game Anda. Sesuatu seperti:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

TAMBAHAN: aha, saya baru saja membaca jawaban FxIII dan bit ini mendekati saya:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

Meskipun saya tidak setuju bahwa jebakan api yang dipicu adalah sesuatu yang hanya akan terjadi sekali (saya bisa melihat jebakan ini digunakan kembali untuk banyak objek yang berbeda). Saya pikir saya akhirnya mendapatkan apa yang Anda maksudkan tentang entitas yang bereaksi secara unik terhadap input pengguna. Saya mungkin akan membahas hal-hal seperti membuat satu pintu di ruang bawah tanah Anda memiliki perangkap bola api dengan membangun semua entitas saya dengan arsitektur komponen (dijelaskan secara rinci di tempat lain).

Dengan cara ini setiap entitas Door dibangun sebagai satu bundel komponen, dan saya dapat secara fleksibel mencampur dan mencocokkan komponen antara entitas yang berbeda. Sebagai contoh, sebagian besar pintu akan memiliki konfigurasi seperti

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

tetapi satu pintu dengan perangkap bola api akan menjadi

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

dan satu-satunya kode unik yang harus saya tulis untuk pintu itu adalah komponen FireballTrap. Itu akan menggunakan komponen Kunci dan Portal yang sama dengan semua pintu lainnya, dan jika saya kemudian memutuskan untuk menggunakan FireballTrap pada peti harta karun atau sesuatu yang sesederhana menambahkan komponen FireballTrap ke peti itu.

Apakah Anda mendefinisikan semua komponen dalam kode yang dikompilasi atau dalam bahasa scripting yang terpisah bukanlah perbedaan besar dalam pikiran saya (baik Anda akan menulis kode di suatu tempat ) tetapi yang penting adalah bahwa Anda dapat secara signifikan mengurangi jumlah kode unik yang perlu Anda tulis. Heck, jika Anda tidak khawatir tentang fleksibilitas untuk perancang tingkat / modder (Anda sendiri yang menulis game ini), Anda bahkan dapat membuat semua entitas mewarisi dari Entitas dan menambahkan komponen dalam konstruktor daripada file konfigurasi atau skrip atau Masa bodo:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}
jhocking
sumber
1
Itu bekerja untuk item yang umum dan berulang. Tetapi bagaimana dengan entitas yang merespons input pengguna secara unik? Membuat subkelas Entityhanya untuk satu objek mengelompokkan kode bersama tetapi tidak mengurangi jumlah kode yang harus saya tulis. Atau apakah itu perangkap yang tidak terhindarkan dalam hal ini?
Eric
1
Saya membahas contoh yang Anda berikan. Saya tidak bisa membaca pikiran Anda; objek dan input apa yang ingin Anda miliki?
jhocking
Mengedit pos saya untuk lebih menjelaskan maksud saya. Jika saya memahami contoh Anda dengan benar, sepertinya setiap tag entitas terkait dengan beberapa subkelas Entitydan atribut menentukan status awalnya. Saya menduga tag anak dari entitas berfungsi sebagai parameter untuk tindakan apa pun yang terkait dengan tag, bukan?
Eric
ya, itulah idenya.
jhocking
Saya seharusnya tahu bahwa komponen akan menjadi bagian dari solusi. Terima kasih untuk bantuannya.
Eric
1

Masalah dimensi yang Anda tangani cukup normal dan hampir tidak dapat dihindari. Anda ingin menemukan cara untuk mengekspresikan entitas Anda yang bertepatan dan fleksibel .

"Wadah" (semak di jawaban jhocking) adalah cara yang bersamaan tetapi Anda lihat itu tidak cukup fleksibel .

Saya tidak menyarankan Anda untuk mencoba menemukan antarmuka generik dan kemudian menggunakan file konfigurasi untuk menentukan perilaku, karena Anda akan selalu memiliki sensasi yang tidak menyenangkan berada di antara batu (entitas standar dan membosankan, mudah digambarkan) dan tempat yang sulit ( entitas fantastis yang unik tetapi terlalu lama untuk diterapkan).

Saran saya adalah untuk menggunakan sebuah bahasa ditafsirkan perilaku kode.

Pikirkan contoh semak: itu adalah wadah tetapi semak kami perlu memiliki barang-barang tertentu di dalamnya; objek kontainer mungkin memiliki:

  • metode untuk pendongeng untuk menambahkan item,
  • metode untuk mesin untuk menunjukkan item yang dikandungnya,
  • metode bagi pemain untuk memilih item.

Salah satu item ini memiliki tali yang memicu alat yang pada gilirannya menyalakan api membakar semak-semak ... (Anda lihat, saya dapat membaca pikiran Anda sehingga saya tahu barang-barang yang Anda suka).

Anda dapat menggunakan skrip untuk menggambarkan semak ini, bukan file konfigurasi yang meletakkan kode tambahan yang relevan di dalam kait yang Anda jalankan dari program utama Anda setiap kali seseorang mengambil item dari sebuah wadah.

Sekarang Anda memiliki banyak pilihan arsitektur: Anda dapat mendefinisikan alat perilaku sebagai kelas dasar menggunakan bahasa kode Anda atau bahasa scripting (hal-hal seperti wadah, seperti pintu dan sebagainya). The Tujuan dari hal theese adalah untuk membiarkan Anda untuk menggambarkan entitas easely menggabungkan perilaku sederhana dan mengkonfigurasi mereka menggunakan binding pada bahasa scripting .

Semua entitas harus dapat diakses oleh skrip: Anda dapat mengaitkan pengenal untuk setiap entitas dan menempatkannya dalam wadah yang diekspor dalam perluasan skrip bahasa skriping.

Dengan menggunakan strategi scripting, Anda dapat membuat konfigurasi Anda tetap sederhana (tidak ada hal-hal seperti <item triggerFlamesOnPicking="true">itu yang akan Anda gunakan hanya sekali) sambil membiarkan Anda mengekspresikan beaviour aneh (yang menyenangkan) menambahkan beberapa baris kode

Dalam beberapa kata: skrip sebagai file konfigurasi yang dapat menjalankan kode.

FxIII
sumber