多くのユニークな武器/呪文/力のコードを構成する方法


22

私は、Pythonを使用してFTLの「ローグライクのような」ゲームを作成する経験の浅いプログラマーです(まだテキストのみを扱っているため、PyGameはまだありません)。

私のゲームには、ユニークな能力を生み出す多数の武器(初心者には約50個)が含まれます。オブジェクトコードを構造化する方法を理解するのに苦労しています(強力な(武器に根本的に異なる効果を持たせることを可能にする)点で)拡張可能な(たとえば、フォルダーにそれらをドロップすることによって後でより多くの武器を簡単に追加できるように) )。

私の最初の本能は、BasicWeaponクラスを持ち、そのクラスから異なる武器を継承することでした。しかし、これは私には問題があるようです:BasicWeaponクラスを基本的に役に立たないようにする必要があります(すべての武器に共通する機能は名前とタイプ(ピストル、aなど)のみです)、またはすべてを予測する必要があります独自の効果を考え出し、BasicWeaponにコーディングします。

後者は明らかに不可能ですが、前者はまだ機能します。しかし、それでは疑問が残ります。個々の武器のコードはどこに置けばいいのでしょうか?

plasmarifle.py、rocketlauncher.py、swarmofbees.pyなどを作成し、それらをすべてゲームにインポートできるフォルダーにドロップしますか?

または、eval / execに頼らずに、各武器の一意のコードを何らかの形で含むデータベーススタイルのファイル(Excelスプレッドシートのような単純なもの)を作成する方法はありますか?

後者のソリューション(データベース)に関して、私が苦労している根本的な問題は、コードとデータの分離を維持することが望ましいことを理解している一方で、武器が「コード」の境界を曖昧にしているように感じることだと思いますおよび「データ」を少し。それらはゲームで見られる多種多様な類似物を表しており、その意味ではデータに似ていますが、ほとんどの場合、他のアイテムと共有されていない少なくともいくつかのユニークなコードが必要です。コード。

このサイトの他の場所で見つけた部分的な解決策は、BasicWeaponクラスに空のメソッドの束(on_round_start()、on_attack()、on_move()など)を与え、各武器のそれらのメソッドをオーバーライドすることをお勧めします。戦闘サイクルの関連フェーズで、ゲームはすべてのキャラクターの武器に適切なメソッドを呼び出し、メソッドが定義されているもののみが実際に何かを実行します。これは役立ちますが、それでも各武器のコードやデータをどこに配置する必要があるかはわかりません。

ある種のハーフデータ、ハーフコードのキメラとして使用できる別の言語やツールはありますか?優れたプログラミングプラクティスを完全に実行していますか?

私はありません回答いただければ幸いですので、OOPの私の理解では、最高の状態で大ざっぱですあまりにもコンピュータ科学-yと。

編集: Vaughan Hiltsは、下の投稿で、私が本質的に話しているのはデータ駆動型プログラミングであることを明らかにしました。私の質問の本質は次のとおりです。データにスクリプトを含めることができるようにデータ駆動設計を実装し、メインプログラムコードを変更せずに新しい武器で新しいことを行えるようにする方法はありますか。



@ Byte56関連; しかし、これはOPが回避しようとしていることだと思います。彼らはもっとデータ駆動型のアプローチを見つけようとしていると思います。私が間違っている場合は修正してください。
ヴォーンヒルツ

私は彼らがよりデータ指向のアプローチを見つけようとしていることに同意します。具体的には、その質問に対するジョシュの答えが好きです:gamedev.stackexchange.com/a/17286/7191
MichaelHouse

あ、ごめんなさい。:)「受け入れられた答え」を読むのが悪い癖があります。
ヴォーンヒルツ

回答:


17

ゲームが完全に予期しないものであるか、コアに対して手続き的に生成されない限り、ほぼ確実にデータ駆動型のアプローチが必要です。

基本的に、これには、武器に関する情報をマークアップ言語または選択したファイル形式で保存することが含まれます。XMLとJSONはどちらも優れた読みやすい選択肢であり、クイックスタートをしようとしている場合に複雑なエディターを必要とせずに編集をかなり簡単にするために使用できます。(そして、Pythonも非常に簡単にXMLを解析できます!)「power」、「defense」、「cost」、「stats」など、すべて関連する属性を設定します。データの構造化方法はあなた次第です。

武器にステータスエフェクトを追加する必要がある場合は、ステータスエフェクトノードを指定し、別のデータ駆動型オブジェクトを通じてステータスエフェクトの効果を指定します。これにより、コードが特定のゲームに依存しなくなり、ゲームの編集とテストが簡単になります。常に再コンパイルする必要がないこともボーナスです。

補足資料は以下から入手できます。


2
コンポーネントベースのシステムのようなもので、スクリプトを介してコンポーネントが読み込まれます。:このようなgamedev.stackexchange.com/questions/33453/...
MichaelHouse

2
そして、そのデータのスクリプト部分を作成して、メインのコードを変更せずに新しい武器で新しいことを行えるようにします。
パトリックヒューズ

@Vaughan Hilts:ありがとう、データ駆動型は私が必要だと直感的に理解したものです。私はまだ答えが必要なので、質問をしばらく開いたままにしますが、おそらくこれを最良の答えとして選択します。
henrebotha

@パトリックヒューズ:それはまさに私が欲しいものです!それ、どうやったら出来るの?簡単な例やチュートリアルを教えてください。
henrebotha

1
まず、エンジンにスクリプトエンジンが必要です。多くの人は、エフェクトや統計などのゲームプレイシステムにアクセスするLUAを選択します。次に、データ記述からオブジェクトを既に再作成しているため、新しいオブジェクトがアクティブ化されるたびにエンジンが呼び出すスクリプトを埋め込むことができます。昔のMUDでは、これは「プロセス」と呼ばれていました(プロセスの略)。難しい部分は、エンジンのゲームプレイ機能を、外部から十分な機能で呼び出せるように十分な柔軟性を持たせることです。
パトリックヒューズ

6

(コメントの代わりに回答を送信して申し訳ありませんが、まだ担当者がいません。)

Vaughanの答えは素晴らしいですが、2セントを加算したいと思います。

XMLまたはJSONを使用して実行時に解析する主な理由の1つは、コードを再コンパイルせずに新しい値を変更して実験することです。Pythonが解釈され、私の意見ではかなり読みやすいので、辞書とすべてが整理されたファイルに生データを含めることができます。

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

この方法では、ファイル/モジュールをインポートして、通常の辞書として使用します。

スクリプトを追加する場合は、Pythonおよび第1クラスの関数の動的な性質を利用できます。次のようなことができます:

def special_shot():
    ...

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

私はそれがデータ駆動設計に反すると信じていますが。100%DDDになるには、特定の武器が使用する機能とコードを指定する情報(データ)が必要です。この方法では、データを機能と混在させないため、DDDを壊すことはありません。


ありがとうございました。簡単なコード例を見るだけでクリックできました。
henrebotha

1
素敵な答えとコメントするのに十分な担当者がいるために+1。;) ようこそ。

4

データ駆動設計

最近、この質問のようなものをコードレビューに提出しました。

いくつかの提案と改善を行った結果、辞書(またはJSON)に基づいた武器の作成に比較的柔軟性を持たせることができるシンプルなコードが作成されました。データは実行時に解釈さWeaponれ、スクリプトインタープリター全体に依存する必要なく、クラス自体によって簡単な検証が行われます。

データ駆動設計は、Pythonがインタープリター言語であるにもかかわらず(ソースファイルとデータファイルの両方を再コンパイルすることなく編集できます)、提示したような場合に行うべき正しいことのように聞こえます。この質問では、コンセプト、その長所と短所についてさらに詳しく説明します。また、コーネル大学に関する素晴らしいプレゼンテーションもあります。

おそらくスクリプト言語(LUAなど)を使用してデータxエンジンの相互作用と一般的なスクリプトを処理するC ++などの他の言語、およびデータを格納する特定のデータ形式(XMLなど)と比較して、Pythonは実際に行うことができますそれだけですべて(標準を考慮しますdictweakref、後者は特にリソースのロードとキャッシング用です)。

しかし、独立した開発者は、この記事で提案されているように、データ駆動型のアプローチを極端に取り入れることはできません。

データ駆動型の設計はどのくらいですか?ゲームエンジンにゲーム固有のコードが1行含まれているとは思わない。ない1。ハードコードされた武器の種類はありません。ハードコーディングされたHUDレイアウトはありません。ハードコーディングされたユニットAIはありません。N。郵便番号 ジルチ。

おそらく、Pythonを使用すると、生産性と拡張性の両方を目指して、オブジェクト指向アプローチとデータ駆動アプローチの両方のメリットを享受できます。

簡単なサンプル処理

コードレビューで説明した特定のケースでは、辞書に「静的属性」と解釈されるロジックの両方が格納されます-武器に条件付き動作がある場合。

以下の例では、剣はクラス 'antipaladin'のキャラクターの手の中にいくつかの能力とステータスを持ち、他のキャラクターが使用する場合はステータスが低くなります。

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]
            }
        ]
    }
}

テストの目的で、私は単純なクラスを作成PlayerWeaponました:最初に武器を保持/装備する(条件付きon_equip設定を呼び出す)ものと、後者を辞書としてデータを取得する単一のクラスとして、渡されたアイテム名に基づいてWeapon初期化中の引数。これらは適切なゲームクラスの設計を反映していませんが、データをテストするのにまだ役立ちます。

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)

将来の改善により、いつかは武器全体ではなく武器コンポーネントを処理する動的なクラフティングシステムを使用できるようになることを願っています...

テスト

  1. キャラクターAは武器を選択し、装備(その統計を印刷)してからドロップします。
  2. キャラクターBは同じ武器を選び、装備します(そして、その統計を再度印刷して、それらがどのように異なるかを示します)。

このような:

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()

それは印刷する必要があります:

吟遊詩人のために

拡張:2、ヒット効果:[]、その他の効果:[]

アンチパラディン用

エンハンスメント:5、ヒット効果:['unholy']、その他の効果:['unholy aurea']

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.