Bagaimana menyusun kode untuk banyak senjata / mantra / kekuatan unik

22

Saya seorang programmer yang tidak berpengalaman membuat game "seperti roguelike" di nada FTL , menggunakan Python (tidak ada PyGame sampai sekarang karena saya masih hanya peduli dengan teks).

Gim saya akan berisi sejumlah besar senjata (sekitar 50 sebagai permulaan) yang menghasilkan kemampuan unik. Saya berjuang untuk memahami bagaimana menyusun kode objek dengan cara yang sangat kuat (dalam hal memungkinkan senjata untuk memiliki efek yang sangat berbeda) dan dapat dikembangkan (sehingga saya dapat dengan mudah menambahkan lebih banyak senjata nanti dengan mis. Menjatuhkan mereka ke dalam folder ).

Naluri pertamaku adalah memiliki kelas BasicWeapon, dan memiliki berbagai senjata yang diturunkan dari kelas itu. Namun, ini tampaknya bermasalah bagi saya: apakah saya harus membuat kelas BasicWeapon begitu barebones sehingga pada dasarnya tidak berguna (satu-satunya fitur yang dimiliki semua senjata adalah nama dan tipe (pistol, kapak, dll)), atau saya harus memprediksi setiap efek unik yang pernah saya buat dan kode itu ke BasicWeapon.

Yang terakhir jelas tidak mungkin, tetapi yang pertama masih bisa dikerjakan. Namun, itu membuat saya pertanyaan: di mana saya meletakkan kode untuk senjata individu?

Apakah saya membuat plasmarifle.py, rocketlauncher.py, swarmofbees.py, dll dll, dan meletakkan semuanya ke dalam folder dari mana game dapat mengimpornya?

Atau apakah ada cara untuk memiliki file gaya database (mungkin sesuatu yang sederhana seperti spreadsheet Excel) yang entah bagaimana berisi kode unik untuk setiap senjata - tanpa perlu menggunakan eval / exec?

Dalam hal solusi terakhir (database), saya pikir masalah mendasar yang saya perjuangkan adalah bahwa sementara saya mengerti bahwa diinginkan untuk menjaga pemisahan antara kode dan data, saya merasa seperti senjata mengaburkan garis antara "kode" dan "data" sedikit; mereka mewakili berbagai macam hal serupa yang dapat ditemukan dalam permainan, dalam arti mereka seperti data, tetapi kebanyakan dari mereka akan memerlukan setidaknya beberapa kode unik yang tidak dibagi dengan item lain, di mana dalam arti mereka, secara alami, kode.

Solusi parsial yang saya temukan di tempat lain di situs ini menyarankan untuk memberikan kelas BasicWeapon banyak metode kosong - on_round_start (), on_attack (), on_move () dll - dan kemudian mengganti metode tersebut untuk setiap senjata. Pada fase yang relevan dari siklus pertempuran, gim akan memanggil metode yang tepat untuk senjata setiap karakter, dan hanya yang memiliki metode yang ditentukan yang benar-benar akan melakukan sesuatu. Ini membantu, tetapi masih tidak memberi tahu saya di mana saya harus meletakkan kode dan / atau data untuk setiap senjata.

Apakah ada bahasa atau alat yang berbeda di luar sana yang dapat saya gunakan sebagai semacam setengah-data, setengah kode chimera? Apakah saya benar-benar membantai praktik pemrograman yang baik?

Pemahaman saya tentang OOP kurang lengkap, jadi saya akan menghargai tanggapan yang tidak terlalu ilmu komputer-y.

EDIT: Vaughan Hilts telah menjelaskan di posnya di bawah ini bahwa apa yang saya bicarakan pada dasarnya adalah pemrograman berbasis data. Inti dari pertanyaan saya adalah ini: bagaimana saya bisa menerapkan desain berbasis data sedemikian rupa sehingga data dapat berisi skrip, memungkinkan senjata baru untuk melakukan hal-hal baru tanpa mengubah kode program utama?

henrebotha
sumber
@ Byte56 Terkait; tapi saya pikir inilah yang ingin dihindari OP. Saya pikir mereka sedang mencoba untuk menemukan pendekatan yang lebih didorong data. Koreksi saya jika saya salah.
Vaughan Hilts
Saya setuju mereka mencoba menemukan pendekatan yang lebih berorientasi data. Khususnya, saya suka jawaban Josh untuk pertanyaan itu: gamedev.stackexchange.com/a/17286/7191
MichaelHouse
Ah, maaf soal itu. :) Saya memiliki kebiasaan buruk membaca "jawaban yang diterima".
Vaughan Hilts

Jawaban:

17

Anda ingin pendekatan berbasis data hampir pasti kecuali permainan Anda benar-benar tidak diharapkan dan / atau prosedural dihasilkan ke inti.

Pada dasarnya, ini melibatkan penyimpanan informasi tentang senjata Anda dalam bahasa markup atau format file pilihan Anda. XML dan JSON keduanya adalah pilihan yang baik dan mudah dibaca yang dapat digunakan untuk membuat pengeditan cukup sederhana tanpa perlu editor yang rumit jika Anda hanya mencoba untuk memulai dengan cepat. ( Dan Python dapat mem-parsing XML dengan sangat mudah juga! ) Anda akan menetapkan atribut seperti 'power', 'defense', 'cost', dan 'stats' yang semuanya relevan. Cara Anda menyusun data akan terserah Anda.

Jika senjata perlu menambahkan efek status, berikan simpul efek Status, dan kemudian tentukan efek efek status melalui objek lain yang digerakkan oleh data. Ini akan membuat kode Anda kurang bergantung pada game tertentu dan membuat pengeditan dan pengujian sepele game Anda. Tidak harus mengkompilasi ulang setiap saat adalah bonus juga.

Bacaan tambahan tersedia di bawah ini:

Vaughan Hilts
sumber
2
Jenis seperti sistem berbasis komponen, di mana komponen dibaca melalui skrip. Seperti ini: gamedev.stackexchange.com/questions/33453/…
MichaelHouse
2
Dan saat Anda melakukannya, buat skrip bagian dari data itu sehingga senjata baru dapat melakukan hal-hal baru tanpa perubahan kode utama.
Patrick Hughes
@Vaughan Hilts: terima kasih, data-driven tampaknya persis seperti yang saya pahami secara intuitif yang saya butuhkan. Saya membiarkan pertanyaan terbuka untuk sementara waktu karena saya masih membutuhkan jawaban, tetapi mungkin akan memilih ini sebagai jawaban terbaik.
henrebotha
@ Patrick Hughes: itulah yang saya inginkan! Bagaimana aku melakukan itu? Bisakah Anda menunjukkan kepada saya contoh atau tutorial sederhana?
henrebotha
1
Pertama Anda membutuhkan mesin skrip di mesin Anda, banyak orang memilih LUA, yang mengakses sistem gameplay seperti efek dan statistik. Kemudian karena Anda sudah membuat ulang objek Anda dari deskripsi data, Anda dapat menyematkan skrip yang dipanggil oleh mesin Anda setiap kali objek baru Anda diaktifkan. Di masa lalu MUD ini disebut "proc," (kependekan dari Proses). Bagian yang sulit adalah membuat fitur gameplay Anda di mesin cukup fleksibel untuk dipanggil dari luar dan dengan fitur yang cukup.
Patrick Hughes
6

(Saya minta maaf untuk mengirimkan jawaban alih-alih komentar, tetapi saya belum memiliki perwakilan.)

Jawaban Vaughan sangat bagus, tetapi saya ingin menambahkan dua sen saya.

Salah satu alasan utama Anda ingin menggunakan XML atau JSON dan menguraikannya dalam runtime adalah untuk mengubah dan bereksperimen dengan nilai-nilai baru tanpa harus mengkompilasi ulang kode. Karena Python ditafsirkan dan, menurut pendapat saya, cukup mudah dibaca, Anda dapat memiliki data mentah dalam file dengan kamus dan semua yang diatur:

weapons = {
           'megaLazer' : {
                          'name' : "Mega Lazer XPTO"
                          'damage' : 100
                       },
           'ultraCannon' : {
                          'name' : "Ultra Awesome Cannon",
                          'damage' : 200
                       }
          }

Dengan cara ini Anda hanya mengimpor file / modul dan menggunakannya sebagai kamus normal.

Jika Anda ingin menambahkan skrip, Anda dapat memanfaatkan sifat dinamis fungsi Python dan kelas 1. Anda dapat melakukan sesuatu seperti ini:

def special_shot():
    ...

weapons = { 'megalazer' : { ......
                            shoot_gun = special_shot
                          }
          }

Meskipun saya percaya itu akan bertentangan dengan desain data-driven. Untuk menjadi 100% DDD Anda akan memiliki informasi (data) yang menentukan fungsi dan kode apa yang akan digunakan senjata tertentu. Dengan cara ini Anda tidak merusak DDD, karena Anda tidak mencampur data dengan fungsionalitas.

Vasco Correia
sumber
Terima kasih. Hanya dengan melihat contoh kode sederhana membantunya mengklik.
henrebotha
1
Memberi +1 untuk jawaban yang menyenangkan dan bagi Anda untuk memiliki cukup perwakilan untuk memberikan komentar. ;) Selamat datang.
ver
4

Desain Berbasis Data

Saya mengirimkan sesuatu seperti pertanyaan ini ke tinjauan kode baru-baru ini.

Setelah beberapa saran dan peningkatan, hasilnya adalah kode sederhana yang memungkinkan fleksibilitas relatif pada pembuatan senjata berdasarkan kamus (atau JSON). Data ditafsirkan pada saat runtime dan verifikasi sederhana dilakukan oleh Weaponkelas itu sendiri, tanpa perlu bergantung pada seluruh juru bahasa skrip.

Desain Berbasis Data, meskipun Python menjadi bahasa yang ditafsirkan (baik sumber dan file data dapat diedit tanpa perlu mengkompilasi ulang), terdengar seperti hal yang benar untuk dilakukan dalam kasus-kasus seperti yang Anda sajikan. Pertanyaan ini menjelaskan lebih lanjut tentang konsep, pro dan kontra. Juga ada presentasi yang bagus tentang Universitas Cornell tentang hal itu.

Dibandingkan dengan bahasa lain, seperti C ++, yang mungkin akan menggunakan bahasa scripting (seperti LUA) untuk menangani data x interaksi mesin dan skrip pada umumnya, dan format data tertentu (seperti XML) untuk menyimpan data, Python sebenarnya bisa melakukan itu semua dengan sendirinya (mengingat standar dicttetapi juga weakref, yang terakhir khusus untuk pemuatan sumber daya dan caching).

Namun, pengembang independen mungkin tidak menggunakan pendekatan berbasis data secara ekstrim seperti yang disarankan pada artikel ini :

Seberapa banyak tentang desain berbasis data? Saya tidak berpikir mesin game harus berisi satu baris kode khusus game. Bukan satu. Tidak ada jenis senjata yang dikode dengan keras. Tidak ada tata letak HUD hardcoded. Tidak ada unit hardcoded AI. Nada. Zip. Zilch.

Mungkin, dengan Python, orang dapat mengambil manfaat dari pendekatan berorientasi objek dan data-driven, yang bertujuan untuk produktivitas dan ekstensibilitas.

Pemrosesan sampel sederhana

Dalam kasus khusus yang dibahas pada tinjauan kode, kamus akan menyimpan "atribut statis" dan logika yang akan ditafsirkan - jika senjata memiliki perilaku kondisional.

Pada contoh di bawah pedang harus memiliki beberapa kemampuan dan statistik di tangan karakter kelas 'antipaladin', dan tidak ada efek, dengan statistik lebih rendah ketika digunakan oleh karakter lain):

WEAPONS = {
    "bastard's sting": {
        # magic enhancement, weight, value, dmg, and other attributes would go here.
        "magic": 2,

        # Those lists would contain the name of effects the weapon provides by default.
        # They are empty because, in this example, the effects are only available in a
        # specific condition.    
        "on_turn_actions": [],
        "on_hit_actions": [],
        "on_equip": [
            {
                "type": "check",
                "condition": {
                    'object': 'owner',
                    'attribute': 'char_class',
                    'value': "antipaladin"
                },
                True: [
                    {
                        "type": "action",
                        "action": "add_to",
                        "args": {
                            "category": "on_hit",
                            "actions": ["unholy"]
                        }
                    },
                    {
                        "type": "action",
                        "action": "add_to",
                        "args": {
                            "category": "on_turn",
                            "actions": ["unholy aurea"]
                        }
                    },
                    {
                        "type": "action",
                        "action": "set_attribute",
                        "args": {
                            "field": "magic",
                            "value": 5
                        }
                    }
                ],
                False: [
                    {
                        "type": "action",
                        "action": "set_attribute",
                        "args": {
                            "field": "magic",
                            "value": 2
                        }
                    }
                ]
            }
        ],
        "on_unequip": [
            {
                "type": "action",
                "action": "remove_from",
                "args": {
                    "category": "on_hit",
                    "actions": ["unholy"]
                },
            },
            {
                "type": "action",
                "action": "remove_from",
                "args": {
                    "category": "on_turn",
                    "actions": ["unholy aurea"]
                },
            },
            {
                "type": "action",
                "action": "set_attribute",
                "args": ["magic", 2]
            }
        ]
    }
}

Untuk tujuan pengujian, saya membuat kelas Playerdan sederhana Weapon: yang pertama memegang / melengkapi senjata (dengan demikian memanggil pengaturan on_equip kondisional) dan yang terakhir sebagai kelas tunggal yang akan mengambil data dari kamus, berdasarkan pada nama item yang dilewatkan sebagai argumen selama Weaponinisialisasi. Mereka tidak mencerminkan desain kelas game yang tepat, tetapi masih bisa berguna untuk menguji data:

class Player:
    """Represent the player character."""

    inventory = []

    def __init__(self, char_class):
        """For this example, we just store the class on the instance."""
        self.char_class = char_class

    def pick_up(self, item):
        """Pick an object, put in inventory, set its owner."""
        self.inventory.append(item)
        item.owner = self


class Weapon:
    """A type of item that can be equipped/used to attack."""

    equipped = False
    action_lists = {
        "on_hit": "on_hit_actions",
        "on_turn": "on_turn_actions",
    }

    def __init__(self, template):
        """Set the parameters based on a template."""
        self.__dict__.update(WEAPONS[template])

    def toggle_equip(self):
        """Set item status and call its equip/unequip functions."""
        if self.equipped:
            self.equipped = False
            actions = self.on_unequip
        else:
            self.equipped = True
            actions = self.on_equip

        for action in actions:
            if action['type'] == "check":
                self.check(action)
            elif action['type'] == "action":
                self.action(action)

    def check(self, dic):
        """Check a condition and call an action according to it."""
        obj = getattr(self, dic['condition']['object'])
        compared_att = getattr(obj, dic['condition']['attribute'])
        value = dic['condition']['value']
        result = compared_att == value

        self.action(*dic[result])

    def action(self, *dicts):
        """Perform action with args, both specified on dicts."""
        for dic in dicts:
            act = getattr(self, dic['action'])
            args = dic['args']
            if isinstance(args, list):
                act(*args)
            elif isinstance(args, dict):
                act(**args)

    def set_attribute(self, field, value):
        """Set the specified field with the given value."""
        setattr(self, field, value)

    def add_to(self, category, actions):
        """Add one or more actions to the category's list."""
        action_list = getattr(self, self.action_lists[category])

        for action in actions:
            if action not in action_list:
                action_list.append(action)

    def remove_from(self, category, actions):
        """Remove one or more actions from the category's list."""
        action_list = getattr(self, self.action_lists[category])

        for action in actions:
            if action in action_list:
                action_list.remove(action)

Dengan beberapa perbaikan di masa depan saya berharap bahwa ini bahkan akan memungkinkan saya untuk memiliki sistem kerajinan dinamis suatu hari nanti, memproses komponen senjata daripada seluruh senjata ...

Uji

  1. Character A mengambil senjata, melengkapinya (kami mencetak statistiknya), lalu menjatuhkannya;
  2. Karakter B mengambil senjata yang sama, melengkapinya (dan kami mencetak statistiknya lagi untuk menunjukkan perbedaannya).

Seperti ini:

def test():
    """A simple test.

    Item features should be printed differently for each player.
    """
    weapon = Weapon("bastard's sting")
    player1 = Player("bard")
    player1.pick_up(weapon)
    weapon.toggle_equip()
    print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
        weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))
    weapon.toggle_equip()

    player2 = Player("antipaladin")
    player2.pick_up(weapon)
    weapon.toggle_equip()
    print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
        weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))

if __name__ == '__main__':
    test()

Itu harus mencetak:

Untuk seorang bard

Peningkatan: 2, efek Hit: [], Efek lainnya: []

Untuk antipaladin

Peningkatan: 5, efek Hit: ['tidak suci'], Efek lainnya: ['unholy aurea']

Lucas Siqueira
sumber