質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.94%

RPG戦闘でクラスの継承を使ったまま、魔法攻撃の種類も追加したい

受付中

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,430

score 13

初心者です。初歩的な質問ですいません。
テキストでRPGの戦闘を題材にクラスの継承を勉強中です。
出題された問題は、勇者・戦士と魔法使いでは表示される文言を変えて出すために継承を利用していました。
print(self.name+"は"+enemy+"を攻撃した")     #戦士、勇者の時
print(self.name+"は"+enemy+"に魔法を唱えた") #魔法使いの時

問題はクリアしたのですが、できればついでにもう少しリアルにしたいので、
魔法使いの攻撃については、wizardクラスのattackメソッドにある「魔法」に種類を追加したいです。
「炎」
「風」
「水」
などで、「魔法使いはスライムに炎を唱えた」などと表示されるようにしたいです。

class Player():
    def __init__(self,name,spell):
        self.name=name
        self.spell=spell

    def attack(self,enemy):
        print(self.name+"は"+enemy+"を攻撃した")

class Wizard(Player):

    def attack(self,enemy,spell):
        print(self.name+"は"+enemy+"に"+self.spell+"を唱えた")


hero=Player("勇者")
warrior=Player("戦士")
wizard=Wizard("魔法使い")
party=[hero,warrior,wizard]

for player in party:
    if player==wizard:
        maho=["fire","water","riot"]
        fire=(Wizard("炎"))
        water=(Wizard("水"))
        riot=(Wizard("雷"))
        for spell in maho:
            spell.attack()
    else:
        player.attack("スライム")

spellの使い方でエラーが出てしまいます。
Traceback (most recent call last):
File "Main.py", line 15, in <module>
hero=Player("勇者")
TypeError: init() missing 1 required positional argument: 'spell'

どのようにspellを使えばいいのでしょうか?
上の文章だと、spellがオブジェクトになってしまっているのだと思います。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • taitai05

    2018/12/26 20:50

    ご指摘ありがとうございます。
    修正します。

    キャンセル

  • taitai05

    2018/12/28 20:25 編集

    魔法は一種類だけが唱えられる
    ってのが希望です。
    randomを使用して実現できました。
    <code>
    ```
    import random

    class Player:
    def __init__(self, name):
    self.name = name

    def attack(self, enemy):
    print(self.name+"は"+enemy+"を攻撃した")

    class Wizard(Player):
    def attack(self, enemy, spell):
    print(self.name+"は"+enemy+"に"+spell+"を唱えた")

    hero = Player("勇者")
    warrior = Player("戦士")
    wizard = Wizard("魔法使い")
    party = [hero, warrior, wizard]

    for player in party:
    if player == wizard:
    maho = ["炎", "水", "雷"]
    num=len(maho)
    num1=random.randrange(num)
    spell=maho[num1]
    wizard.attack("スライム",spell)
    else:
    player.attack("スライム")
    ```
    </code>

    キャンセル

  • yukkuri

    2018/12/29 13:32 編集

    すいません、私の伝え方が悪かったですね。
    hayataka2049さんの言っておられますとおり、質問を編集してください。
    誤解を与えてしまい、すみません。

    キャンセル

回答 4

+1

こういうことをやるのにはクラスの継承を使うこと自体が間違っているのでしょうか。

ということなので、オブジェクト指向的に答えます。(Pythonはほぼ無知なのでできない等ありましたら教えてもらえると嬉しいです。)

個人的には、三角です。

class Magic():
として、
効果を定義した時、その後、属性をつけた時、何を変えるでしょうか。

...どんなゲームを作るのかわかりませんが、相性程度なら自分の属性を渡して、継承しないのもありだと思います。
ただ、魔法ごとにMagicを継承したオブジェクトを作る、ということもできます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

コードがちゃんと読めないので憶測も多少混じりますが、

class Wizard(Player):
    def attack(self,enemy):
        print(self.name+"は"+enemy+"に魔法を唱えた")

は仮に継承をしなかったとしたら、こういうコードになります。

class Wizard:
    def __init__(self, name):
        self.name=name

    def attack(self, enemy):
        print(self.name+"は"+enemy+"に魔法を唱えた")

なので、Wizard("炎")などとすると炎は<敵>に魔法を唱えたってなっちゃう、ってことかな?

これの修正はゲームデザインによります。炎属性の魔法しか使えない魔法使い、みたいな設定にするなら、__init__をオーバーライドして属性を引き受けることになります。

class Wizard(Player):
    def __init__(self, name, magic_type):
        self.name = name
        self.magic_type = magic_type
    def attack(self,enemy):
        print(self.name + "は" + enemy + "に" + self.magic_type + "を唱えた")

みたいな感じですかね。インスタンス化するときはWizard("魔法使い", "炎")みたいにしてください。

あるいは、attackに追加するというのもあるでしょう。そうするといろいろな種類の魔法が使える魔法使いになれます。

class Wizard(Player):
    def attack(self, enemy, magic_type):
        print(self.name + "は" + enemy + "に" + magic_type + "を唱えた")

ただし、attackの呼び方が変わります。なので質問文のようなループでは対応できないですね。

あるいは、「覚えてる魔法」を保持するsetでもインスタンスに持たせて、その中から相性を見てベストなのを選択するとか。やり方はいろいろあるかと。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/27 13:21

    いろんな種類の魔法を使える魔法使いが希望です。
    説明不足で申し訳ありませんでした。
    ご回答ありがとうございました!

    キャンセル

+1

Error内容について

Traceback (most recent call last):
File "Main.py", line 15, in <module>
hero=Player("勇者")
TypeError: init() missing 1 required positional argument: 'spell' 


こちら、何とかかれているかというと、
15行目で書かれているhero=Player("勇者")の部分について、
init関数で要求されている位置変数「spell」がないよ、といわれています。

Playerクラスを見てみると、

class Player():
    def __init__(self,name,spell):


と、インスタンス化する際に、namespellとの2つの引数を必要としているのですが、
hero=Player("勇者") では「勇者」という1つの位置引数しか渡していません。
なので、spell部分は?といわれています。

どうすればよいか

上記のエラー箇所を回避するためだけであれば、
インスタンス化する際に、hero=Player("勇者","適当な呪文")の様に指定すれば
次(のエラー)に進めると思います。
ただ、やりたいことはそうではなく、

  • 魔法使いだけ呪文が使えるようにしたい
  • 使える呪文をころころ変えたい

なのかな、と想像しましたので、
spell は Player のコンストラクタで持たせるのではなく、
Wizard の attack methodで持たせたほうがよいと思います。

class Player:
    def __init__(self, name):
        self.name = name

    def attack(self, enemy):
        print(self.name+"は"+enemy+"を攻撃した")


class Wizard(Player):
    def attack(self, enemy, spell):
        print(self.name+"は"+enemy+"に"+spell+"を唱えた")


hero = Player("勇者")
warrior = Player("戦士")
wizard = Wizard("魔法使い")
party = [hero, warrior, wizard]

for player in party:
    if player == wizard:
        maho = ["炎", "水", "雷"]
        for spell in maho:
            wizard.attack("スライム", spell)
    else:
        player.attack("スライム")

これでとりあえず実行はできると思います。
ただ、上記だと、Wizardでattack methodをオーバライドしていることになるのですが、
引数が親クラスのPlayerと合っていません。
(これはあんまりよろしくないみたいです。)

なので、別methodとして定義してあげたほうがベターかもしれません。

...
class Wizard(Player):
    def attack_spell(self, enemy, spell):
        print(self.name+"は"+enemy+"に"+spell+"を唱えた")
...

            wizard.attack_spell("スライム", spell)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/28 20:23

    返信遅くなり、申し訳ありません。
    引数を全く理解してませんでした。
    位置、キーワード、デフォルトとかいろいろあるのが理解できました。ありがとうございます。

    親クラスと子クラスが同じ方がいいというのは理由は分かりませんが、
    都合が悪いことが出るケースが少なからずある。
    rstripを付けるのと同じような感覚でしょうか。悪さすることが多いからrstripで改行を削除すると習いました。
    詳細はわかりませんが、そのように努めます。
    丁寧なご回答ありがとうございました。

    キャンセル

+1

いろいろ、基礎がわからないまま突っ走ってらっしゃる感じがしますので、面白さを感じてもらうためにちょっと回り道した説明をさせてもらいます。

class Player():
    def __init__(self,name,spell):
        self.name=name
        self.spell=spell


Playerクラスは、勇者、戦士、魔法使いに共通するクラスです。そしてinitはクラスのインスタンスが作られるとき必ず動作するメソッド(コンストラクタと言います)です。
つまりここにspellを足してしまうと、勇者と戦士も魔法を使えることにしたいという意味になってしまいます。

これは意図とは違うはずですので、initは変更する前の状態に戻しましょう。代わりに、spellはWizardのinitに入れてみます。
attackメソッドもspell引数を取っていますが、attackのself.spellはinitのとき書き込んであるので引数なくても動きます。とりあえず取っちゃいましょう。

class Wizard(Player):
    def __init__(self,name,spell):
        self.name=name
        self.spell=spell
    def attack(self,enemy):
        print(self.name+"は"+enemy+"に"+self.spell+"の呪文を唱えた")

さてWizardクラスのinitがどこで使われるかピンときてらっしゃらないようですが、ここをこう書き換えると使われるようになります。

hero = Player("勇者")
warrior = Player("戦士")
wizard = Wizard("炎の魔法使い","炎")
party = [hero, warrior, wizard]


ここはクラスのインスタンスを作っています。initが呼ばれてクラスの中身が作られます。
これで炎の魔法使いが誕生しました。

最後に、ここをシンプルにこうします。

for player in party:
        player.attack("スライム")

これを実行すると「炎の魔法使いはスライムに炎の呪文を唱えた」と表示されるようになると思います。
initと継承のイメージが少しは湧いたでしょうか?

ここまでやってみて納得できたようでしたら、以下のことに取り組めば、希望されるような動きをすると思います。
・Wizardのinitでspellに渡すのを文字列ではなくて、配列にする。maho = ["炎", "水", "雷"] のmahoを渡せばいいでしょう。
・Wizardのattackの処理を変更して、spell配列の中からランダムにひとつを選び表示するようにする
・Wizardをインスタンスにするとき名前を「大魔法使い」とかに変えておくのも忘れない:)

ちなみに、この改修をすると継承の意味がなくなってしまいます。とはいえ、ちゃんとゲームにするならPlayerにdefenceとかrunとかitemとかメソッドを足す可能性は高いので、まったくの無意味でもないです。このあたりは興味があればまたご回答します。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/28 21:57

    返信が大変遅くなっております。誠に申し訳ありません。
    皆さまのおかげで、自分のやりたいことは達成できました。
    ありがとうございました。

    ですが、
    >基礎がわからないまま突っ走ってらっしゃる感じがしますので、
    まさにこの通りです。
    ご教授頂いた内容が、自分がなんとなくうやむやなまま置いてきた部分ですので、
    再復習しています。
    かなり時間がかかっておりますが、まだ時間がかかりそうです。
    コンストラクタの存在価値から本当は分かっていませんので。(とりあえず初期化するらしいが、とにかくインスタンス変数を利用する際に必要なものだから書いておくことというぐらいの理解です。)
    もう少し理解が深まったら、正式な返信をさせていただきます。
    ご丁寧なご回答ありがとうございます。

    キャンセル

  • 2018/12/28 23:42 編集

    みなさんの回答に沿ってコードが書けるところまで来ているのでしたら、もっと書いてみるほうが理解の近道になると思います。アイデア作って、仮説立てて、それに沿ってもっとコード改変して、動いても動かなくても誰かに見せましょう。
    オススメの方向性としては、この例にさらに機能を増やして、内容が複雑になってくると継承やクラスやインスタンスがどういう影響を見せてくるか、追いかけてみることです。

    それにちょうどいい問題を思いついたので、気が向いたらやってみてください。
    ・勇者が攻撃すると、今までの「攻撃した」の代わりに「ギガブレイクでスライムを攻撃」と表示するようにしてみましょう。ただし、戦士の攻撃メッセージを変えてはいけません。また、for: player in party 以下を修正してはいけません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.94%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る