Python初心者でもコマンドRPGを作りたい!!#1「バトルの基礎」ver1.0.0

目次

関係ないかもあるかもしれない前置き「ひらめき」

最近、「TRICK」シリーズがアマプラで配信されてたのでドラマの初回シーズンから見直している。

TRICKをご存じない?わお。それは人生(f'{float(4.67)}%’)損してる。

TRICKというのはSPECとか20世紀少年3部作とか作った堤幸彦監督によるコメディ×ミステリー×オカルト系映像作品。仲間由紀恵と阿部寛がW主演を務め超常現象や難事件に挑んでいくお話。

私は小学生の時このTRICKに出会い同時にドハマリ。「劇場版TRICK霊能力者バトルロワイヤル」と「TRICK 新作スペシャル2」、「TRICK 劇場版2」はDVDにダビングしてあったので冗談抜きで100回以上見ていて小学生の時「よろしくーね!」とか「アダモステ」とか同級生に全く伝わらないギャグ?を披露していた。。。

とはいえ、ミステリーとかコメディジャンルの作品はいくら素晴らしくても、次第にネタやタネ明かしの鮮度が落ちてきてつまらなくなってしまうのが必然。世界で最も有名なミステリー作品の一つである「オリエント急行殺人事件」でさえ2回目以降は面白さ半減だ。(個人の感想)

けれど、ことTRICKに関しては全く面白さが衰えない。毎年のように見ているのにずっと初めての鮮度を保ったまま。仲間由紀恵もずっとかわいい

種も仕掛けも展開も全部わかっているけれど、この面白さの”鮮度が落ちないトリック”だけがわからない。

そしてTRICKを語るうえで欠かせないのはエンディングテーマである鬼束ちひろの「月光」だろう。私の中学時代の十八番でもある。

「流星群」や「私とワルツを」もいい曲だけれどやっぱり「月光」が一番好き。そして、その月光を風呂場で熱唱しながらダラダラとPythonのコードあるいは人生について考えていたその時、突如私の脳内に電撃が走った。

たった一つの冴えたやり方
(別に宇宙人とは会わない)

まさに「ひらめき」である。まんまサガシリーズみたいに頭の上に電球が灯ったように脳みそが明るくピカッと照らされた。

早々にカラオケを切り上げ髪を乾かしコーヒーと胡桃を用意して、そこからざっとマジに12時間ぐらいずっとキーボードを叩き今回ご紹介するコードが出来上がった。

前回の落書きみたいなコードよか100倍マシだと思うけれど、とはいえコードを書いたのは同じPythonを初めて1ヶ月半のド文系の初心者であるのは変わりないわけで、、、(この程度じゃ理系文系の差は出ない)

それに、12時間とは書いたけれどそのうちの10時間はお決まりの「あーでもない。こーでもないタイム」なので過度な期待は禁物、「あーこのくらいのレベルね」と鼻で笑いながらお菓子でもつまみながら見るくらいがちょうどいい。

まぁ、PythonでコマンドRPGを作りたいけど、何から初めたら言いかわからないという1週間前の私ぐらいのレベルの人なら雀の涙ほどの参考にはなると思う。なったらいいな。

また遊んでみた感想やアドバイス、フィーリングは涙が出るほど有り難いのでドシドシ送ってください。

ソースコード

ソースコードは長いのでGitHubに置いておきます。そこからダウンロードして遊んでみてください。

https://github.com/hello4679/super_python-rpg.git

ちなみに私の作業環境は

  • PC:macOS Sonoma 14.3
  • テキストエディター:Pycharm
  • Pythonのバージョン:Python 3.12

なのでMacなら動くと思いますがWindowsで正常に作動するかはわかりません。ごめんなさい。
※このコードはnumpyを一部使っているのでプレイ際にはあなたのパソコンにnumpyをインストールする必要があります。

また、今回のプログラムはクラスやクラス継承、クラスのオーバーライド等Pythonのオブジェクト指向の付け焼き刃な基礎知識が最低限必要です。

「クラスって関数をなんか呼び出すやつでしょ?」「クラス継承ってなんか受け継ぐやつでしょ?」「オーバーライドってなんか書き換えるやつでしょ?」

と、ぼんやり頭の中に浮かべば大丈夫。私も正確に1から10まで理解してるわけじゃないのでとりあえず聞いたことあればOK。

ゲームの仕様

最初に言っておくとこのプログラムはまだコマンド選択RPGの「コマンd」ぐらいしか完成してないです。

でも「RPGを作りたい!!」だから別にタイトル詐欺にはなってない。。。はず

で、「コマンd」っていうのを具体的に書いてみると

  • ステータスの表示
    • キャラクター名の表示
    • HP:ゼロになったら死ぬ
    • MP:「まほう」コマンドで使う
    • AP:「ジョブ」コマンドで使う
    • LIMIT:被ダメージ量に応じて上昇、必殺が使えるようになる
  • コマンドの選択
    • たたかう:攻撃する
    • まほう:魔法で攻撃する、各魔法を内包
    • ジョブ:職業固有のアクション
    • アイテム:ポーションとかエーテルとか
  • ターン制バトル
  • ジョブシステム

のようになる。

要するに基礎的な部分だけ。でも形にはなってると思う。

大まかな流れ

このプログラムは大まかに3つのグループから成り立っている。

BATTLE.py

コマンドバトルを構成するヤツ

  1. クラスの呼び出し
  2. 先行後攻の決定
  3. ターンの構成
  4. 勝敗の判定

を担う。

character.dir

キャラクターの「クラス」が格納されたヤツ。構成は下のような感じ

  • CHARACTER(クラスの親玉)
    • ENEMY(敵)
    • PLAYER(プレイヤーの元)
      • THIEF(盗賊ジョブ)
      • KNIGHT(騎士ジョブ)
      • SUMMONER(召喚士ジョブ)
MANAGEMENT.py

プレイヤーターン、エネミーターンそれぞれのダメージやターン数を格納するヤツ

一々、ダメージをココに保存する理由は後ほど解説。

それを踏まえて全体の流れはこう
※ジョブは盗賊、先行はプレイヤー、プレイヤーの勝利、という流れ

  • 盗賊ジョブ選択
  • 先行後攻の決定
  • プレイヤーのターン(たたかう・まほう・ぬすむ等)
    • エネミーにダメージ
  • エネミーのターン
    • プレイヤーにダメージ
  • エネミーのHPをゼロにする
    • 勝利

という感じ。もっと細かく書くと

  • main.py
    1. コードを実行
    2. ジョブの選択
  • BATTLE.pyの処理
    1. クラスの呼び出し(操作キャラに「盗賊ジョブ」を設定
      • player = THIEF.Thief(THIEFファイルのThiefクラス)
    2. 先行後攻を決定
    3. Whileループ
      • break条件はplayer.hp <= 0 か enemy.hp <= 0
    4. プレイヤーのターン
      1. プレイヤーのコマンド選択
        • player.select_command()
      2. プレイヤーが生み出したダメージ数をMANAGEMENTのダメージ変数に代入
      3. ダメージ変数を計算しエネミーのHPを減らす
        • enemy.damaged()
    5. エネミーのターン
      1. エネミーの行動
        • enemy.enemy_action()
      2. エネミーが生み出したダメージ数をMANAGEMENTのダメージ変数に代入
      3. ダメージ変数を計算しプレイヤーのHPを減らす
        • player.damaged()
    6. エネミーのHPが0になったのでbreak
      1. 勝敗の表示

うーん、とっても読む気にならないね。
まー〇〇ターン→ダメージを記録→ダメージを与える→〇〇ターンと言う流れを頭に入れておけば大丈夫
実際のコードを確認しながら流れを追うのが一番いいと思うので早速各処理の解説に移ろう。

コードの解説

Characterクラス(ステータス)について

コードを見るのが一番手っ取り早いのでとりあえず確認しよう。

クリックでコードを展開
class Character(object):
    def __init__(self,
                 name,
                 hp,
                 mp,
                 ap,
                 power,
                 magic_power,
                 spirit_power,
                 defence_power,
                 luck
                 ):
        self.name = name
        self.hp = hp * (1 + int(
            (power * 0.02) * (spirit_power * 0.02)))
        self.mp = mp * (1 + int(
            (magic_power * 0.01) * (spirit_power * 0.01)))
        self.ap = ap
        self.power = power
        self.magic_power = magic_power
        self.spirit_power = spirit_power
        self.defence_power = defence_power
        self.luck = luck
        self.upper_hp = self.hp
        self.upper_mp = self.mp
        self.upper_ap = self.ap
        self.limit_count = 0
        self.gauge_count = 0
        self.gauge = '         '

これは、プレイヤーとエネミーの元となる親玉「Character」というクラス。コードを見ればなんとなく想像がつくだろうがキャラクターのステータスを文字通り形作っている。

name名前
hpHP
hpと力と精神力によって算出される
mpMP
hpと魔力と精神力によって算出される
apAP
アクションポイント。ジョブアクションを行う際に必要
power
各コマンドの威力やHPに影響
magic_power魔力
各コマンドの威力やMPに影響
spirit_power精神力
被魔法ダメージの軽減率やHP、MPに影響
defence_power防御力
被物理ダメージの軽減率に影響
luck
攻撃の回避率に影響
upperがつくやつHP、MP、APの最大値(初期値)を記録しておく
limit_count = 0被ダメージ量を記録しておく。リミットコマンドに影響
gauge_count = 0リミットコマンドに影響
gauge = ‘ ‘リミットゲージの増減に影響

といった具合。このゲームで登場するキャラクター全てにこの値が継承されている。

PlayerクラスとEnemyクラスについて

PlayerクラスとEnemyクラスは上のCharacterクラスを継承したクラス。

クラスの継承とインポート
import random
import numpy as np
from character import CHARACTER
from template import UI
import MANAGEMENT
import STATUS


class Player(CHARACTER.Character):
    def __init__(self,
                 name='未設定',
                 hp=100,
                 mp=100,
                 ap=100,
                 power=100,
                 magic_power=100,
                 spirit_power=100,
                 defence_power=100,
                 luck=10
                 ):
        super().__init__(name, hp, mp, ap, power, magic_power, spirit_power,
                         defence_power, luck)
        self.COMMAND = '  SELECT COMMAND '

    def no_config(self):
        print('未設定')
        self.select_command()

    def show_limit(self):
        if self.gauge_count >= 1:
            self.COMMAND = 'LIMIT IS READY!!'

importしている奴らは小文字のものは組み込みモジュール、大文字のやつは他ファイルの内容を引っ張ってくるヤツ

class PlayerはCharacterクラスを継承している。(CHARACTERファイルのCharacterクラス)

self.COMMANDはUIに’ SELECT COMMAND ‘と表示させるためのヤツ

show_limit関数は、リミットゲージが溜まっている場合self.COMMANDの文字列を変更し’LIMIT IS READY!!’と表示させるヤツ

no_config関数は、内容が未設定のコマンドを選択した場合’未設定’と表示させコマンド選択画面に戻すヤツ

ダメージ

ダメージが発生する仕組み

ダメージの計算はBATTLE.pyとMANAGEMENT.pyという2つのファイルを連携させて行う。

BATTLE.py

    def damaged(self):
        if self.hp > self.upper_hp:
            self.hp = self.upper_hp
        if self.mp > self.upper_mp:
            self.mp = self.upper_mp
        if self.ap > self.upper_ap:
            self.ap = self.upper_ap
        s = 0.01 * int(85 * (1 + (self.luck * 0.001)))
        if s < 1:
            m = 1 - s
        else:
            m = 0
        judge = np.random.choice(['success', 'failure'], p=[s, m])
        if judge == 'success':
            MANAGEMENT.enemy_total_damage = int(
                (MANAGEMENT.enemy_physical_damage
                 * (1 - (self.defence_power / 1000)))
                + MANAGEMENT.enemy_magic_damage
                * ((1 - (self.defence_power / 1000))
                   * (1 - (self.spirit_power / 1000))))
            print(MANAGEMENT.enemy_physical_damage)
            print(MANAGEMENT.enemy_magic_damage)
            print(MANAGEMENT.enemy_total_damage)

            if MANAGEMENT.enemy_total_damage <= 0:
                MANAGEMENT.enemy_total_damage = 0
            self.hp -= MANAGEMENT.enemy_total_damage
            self.limit_count += MANAGEMENT.enemy_total_damage
            print(f'{self.name}に{MANAGEMENT.enemy_total_damage}ダメージ!!')
        else:
            print(f'{self.name}はひらりと躱した!!')
        if MANAGEMENT.enemy_total_damage > 0:
            MANAGEMENT.enemy_total_damage = 0
        MANAGEMENT.enemy_physical_damage = 0
        MANAGEMENT.enemy_magic_damage = 0

MANAGEMENT.py

# ターンダメージ数
physical_damage = 0
magic_damage = 0
total_damage = 0

enemy_physical_damage = 0
enemy_magic_damage = 0
enemy_total_damage = 0
# ターン数
TURN = 1
# プレイヤーのアイテムインベントリ
PLAYER_INVENTORY = ["ポーション", "ポーション", "エーテル", "エーテル",
                    "手榴弾", "手榴弾", "エリクサー"]
ENEMY_INVENTORY = ["ポーション", "ポーション", "エーテル", "エーテル",
                   "手榴弾", "手榴弾", "エリクサー"]
# キャラクター名
chara_name_1 = 'シタン'
chara_name_2 = 'シュタイナー'
chara_name_3 = 'ガーネット'

select_character = 1

ダメージの与え方

ダメージの計算、つまり相手に攻撃をするというアクション。
これが先の私の「ひらめき」の正体、そしてプログラムの心臓部分である。

「ダメージを与える」簡単なことのように見えてやってみると案外難しい。

プレイヤーもエネミーもPythonのクラスを利用して生み出しているわけだが、Playerクラスの関数でEnemyクラスのHP変数を変更する方法がどうしてもわからず、前回のコードではそれぞれのHPをグローバル関数を利用してそれを再現していた。

でもそれだと、後々ジョブを増やしていく際に面倒が臭いし、保守性のかけらもない

そこで

:ダメージを直接相手のクラスに反映させるわけでなく、
:一度ダメージ記録用の変数に格納し、
3:そこからEnemyクラスのDamaged関数でダメージ記録用変数を呼び出し
4:エネミーの数値に反映

という4部構成にした

こうすれば、グローバル関数を使わなくていいのでコードがとっても見やすいし、クラスのインスタンス変数でHPが管理できるのでジョブを増やすのもとってもお手軽に出来る。

そうして〇〇ターン→ダメージを記録→ダメージを与える→〇〇ターンと言う流れが生まれたのである。

このダメージ記録用変数は、実際のコード上ではMANAGEMENTというファイルの中のphysical_damage(物理ダメージ)magic_damage(魔法ダメージ)という名前を持っている。(上のコードではenemy_が付いてると思うが、これは敵用の物理魔法ダメージであるため)

damaged関数の流れ

コードの流れ

仕組みがわかったところで実際のコードの流れを見ていこう。大まかにdamaged関数の流れを書くと以下のようになる

  1. HPが上限値を超えていたらHPを上限値ぴったりに戻す
  2. 攻撃があたったかどうかの計算(回避率)
  3. ステータス値に応じて被ダメージを減らす。
  4. 物理ダメージと魔法ダメージを合計(total_damage)
  5. もしtotal_damageが0以下なら(マイナス値なら)それを0に変更する
  6. HPを減らす&リミットゲージを貯める
  7. total_damageと物理ダメージと魔法ダメージを0に戻す。

1:HPが上限値を超えていたらHPを上限値ぴったりに戻す

        if self.hp > self.upper_hp:
            self.hp = self.upper_hp
        if self.mp > self.upper_mp:
            self.mp = self.upper_mp
        if self.ap > self.upper_ap:
            self.ap = self.upper_ap

この作業をする理由は、HPが回復魔法や回復アイテムなどによって最大値をオーバーしていることがあるかもしれないから。

upper.はクラスが呼び出された際の最大値をただコピーしてあるだけで、厳密な上限というわけじゃない。なのでたまぁにHPやMPが限界突破してることがあるのでそれを治すための措置である。(一応各回復コマンドに同じ設定をしてあるが、万が一に備えて)

2:攻撃があたったかどうかの計算(回避率)

     s = 0.01 * int(85 * (1 + (self.luck * 0.001)))
        if s < 1:
            m = 1 - s
        else:
            m = 0
        judge = np.random.choice(['success', 'failure'], p=[s, m])
        if judge == 'success':
            <略>
        else:
            print(f'{self.name}はひらりと躱した!!')

numpyモジュールのrandom.choiceを用いて攻撃が当たる確率を計算し’success’ならダメージ処理を実行’failure’なら実行しないという処理をしている。

参考

変数sは「基礎成功率」 +「 luck値(運)による上昇率」=成功率
変数mは、失敗率。

3、4

         MANAGEMENT.enemy_total_damage = int(
                (MANAGEMENT.enemy_physical_damage
                 * (1 - (self.defence_power / 1000)))
                + MANAGEMENT.enemy_magic_damage
                * ((1 - (self.defence_power / 1000))
                   * (1 - (self.spirit_power / 1000))))

ここでは、物理・魔法ダメージをキャラクターのステータス値の応じて減少させ合計している。
物理ダメージは防御力に応じて、魔法ダメージは「防御力」と「精神力」に応じて減少する。
※このコード下のprint関数は、正常に動いているかの動作確認に使う。

5,6,7

     if MANAGEMENT.enemy_total_damage <= 0:
                MANAGEMENT.enemy_total_damage = 0
            self.hp -= MANAGEMENT.enemy_total_damage
            self.limit_count += MANAGEMENT.enemy_total_damage
            print(f'{self.name}に{MANAGEMENT.enemy_total_damage}ダメージ!!')
        else:
            print(f'{self.name}はひらりと躱した!!')
        if MANAGEMENT.enemy_total_damage > 0:
            MANAGEMENT.enemy_total_damage = 0
        MANAGEMENT.enemy_physical_damage = 0
        MANAGEMENT.enemy_magic_damage = 0

最後に、合計ダメージが0以下(0と負の値)ならそれを0にし、HPを減少&リミットゲージの増加すべてのダメージ記録用変数を0(初期値)にするという処理を行う。

0と負の値なら0にするというのは、hp-=total_damageという処理をするので、負の値だとhpに加算されてしまうから(0も含まれてるのはミス、「<」だけでいい)

HP減少&リミットゲージの増加は、文字通りの処理。リミットゲージはもらった総ダメージ量に応じてゲージが増える。(まんまFF7)

すべてのダメージ記録用変数を0(初期値)にするのは、次のターンに今回のターンのダメージを持ち越さないようにするため。

基本コマンド

「たたかう」「まほう」「アイテム」コマンドについて

このゲームには現在「たたかう」「まほう」「ジョブ」「アイテム」「リミット」の5つのコマンドが実装してある

  • たたかう
    • 物理ダメージの通常攻撃
  • まほう
    • ルイン:MPを消費して魔法ダメージを与える
    • ケアル:MPを消費してHPを回復する
  • ジョブ
    • 各ジョブ固有のスキル:APを消費
  • アイテム
    • ポーション:HPを回復
    • エーテル:MPを回復
    • エリクサー:HPとMPとAPを全回復
    • 手榴弾:物理ダメージを与える
  • リミット
    • ジョブ固有のひっさつ:リミットゲージを消費

この内「ジョブ」と「アイテム」については「ジョブクラスについて」で取り上げる

「たたかう」コマンド

    def attack(self):
        print(f'>{self.name}のこうげき')
        harf_power = int(self.power * 0.5)
        MANAGEMENT.physical_damage = int(random.uniform(harf_power, self.power))

この「たたかう」コマンドはすべての攻撃コマンドの基礎とも言える処理をしている。逆に言えば、攻撃系コマンドは(今のところは)多少処理が複雑になることはあれどやっていることはほとんど同じ。

harf_powerは力値を半分にした変数(例power100→hard_power50)

で、その(harf_powerとpower)の値を乱数(random.uniform)の範囲(50,100)に設定し計算された乱数をphysical_damageに格納している。

「まほう」コマンド

    def magic_1(self):
        cost_mp = STATUS.MP_3
        if self.mp >= cost_mp:
            print(f'>{self.name}はルインを唱えた')
            self.mp -= cost_mp
            harf_magic_power = int(self.magic_power * 0.5)
            MANAGEMENT.magic_damage = int(
                random.uniform(harf_magic_power, self.magic_power))
        else:
            print('>MPが足りない!!')
            self.select_magic()

「まほう」コマンドは「たたかう」コマンドの魔法ダメージバージョンで、mpを消費処理を少し加えてある。

消費MPはSTATUSファイルのMP_3という変数に格納してある。

まずif文で現在のMPが消費MPを上回っていることを確認、

TrueであればMPを消費MP分だけ減らし「たたかう」の魔法バージョンを実行。
もしFlaseあれば ‘MPが足りない!!’ とprintさせマジック選択画面にもどる。

回復魔法であればHPを回復させる

アイテムコマンド

    def portion(self):
        cure_hp = 100
        if 'ポーション' in MANAGEMENT.PLAYER_INVENTORY:
            print(f'>{self.name}はポーションを使った!!')
            MANAGEMENT.PLAYER_INVENTORY.remove('ポーション')
            if cure_hp + self.hp > self.upper_hp:
                self.hp = self.upper_hp
                print(f'>>{self.name}のHPが全回復した!!')
            else:
                self.hp = cure_hp + self.hp
                print(f'>>{self.name}のHPが{cure_hp}回復した!!')
        else:
            print('>ポーションを持っていない!!')
            self.select_item()

アイテムコマンドはリストの中から特定の要素を取り出す処理とHPを回復させるなりダメージを与えるなりする処理の二部構成。

アイテムリストはMANAGEMENTファイルのPLAYER_INVENTORYの中にある。

defポーションの場合、if文でリストの中から’要素:ポーション’の存在を確認
Trueなら攻撃回復の処理を実行し
Falseならアイテム選択画面にもどる。

UI

コマンド選択画面

今度は実際に「たたかう」「まほう」等のコマンドを呼び出すUIについて見ていこう。

UI.py

import string

COMMAND_UI = r'''
/<<$sec9>>
| <HP> |$sec10 /$sec11|
| <MP> |$sec12 /$sec13|
| <AP> |$sec15 /$sec16|
\ <LIMIT> [$sec14]
「 $sec98」________________________
/ [1]$sec1  <$sec5>    
| [2]$sec2  <$sec6>
| [3]$sec3  <$sec7>
| [4]$sec4  <$sec8>
| [5]もどる   
| [6]リミット 
\__________________________________[TURN $sec99 ]
( info )(Thank you for playing!!)
'''
BATTLE_UI = string.Template(COMMAND_UI)

select_command関数

self.limit_gauge(self.limit_count)
        self.show_limit()
        action_contents = UI.BATTLE_UI.substitute(sec1='たたかう',
                                                  sec2='まほう ',
                                                  sec3='ジョブ ',
                                                  sec4='アイテム',
                                                  sec5=' ',
                                                  sec6=' ',
                                                  sec7=' ',
                                                  sec8=' ',
                                                  sec9=self.name,
                                                  sec10=self.hp,
                                                  sec11=self.upper_hp,
                                                  sec12=self.mp,
                                                  sec13=self.upper_mp,
                                                  sec14=self.gauge,
                                                  sec15=self.ap,
                                                  sec16=self.upper_ap,
                                                  sec98=self.COMMAND,
                                                  sec99=MANAGEMENT.TURN
                                                  )
        print(action_contents)
        try:
            a = int(input('セレクトコマンド>>>'))
            if a == 1:
                self.attack()
            elif a == 2:
                self.select_magic()
            elif a == 3:
                self.select_job_action()
            elif a == 4:
                self.select_item()
            elif a == 5:
                self.select_command()
            elif a == 6:
                self.select_limit()
        except ValueError:
            print('無効な入力です')
            self.select_command()

実行画面

他にもマジックコマンド選択画面(select_magic)やアイテムコマンド選択画面(select_item)等があるが文字を入れ替えてるだけなので割愛

UI.pyというファイルにstringモジュールを使って作成したテンプレートを、substituteを使いselect_commandに持ってきてHPの数値や文字列などを代入
inputでコマンドの呼び出し変数を指定し、try文で指定した以外の文字(int以外)を入力した場合に発生するIndexErrorを回避している。

ジョブクラスについて

ジョブクラスとは、キャラクターに与えられた固有の職業のこと。class jobというクラスがあるわけじゃなく、今回実装してある「盗賊=class Thief」「みならいナイト=class Knight」「召喚士= class Summoner」の3つを包括してそう呼ぶ。

ジョブクラスはPlayerクラスを継承したクラス。PlayerクラスはCharacterクラスを継承しているのでジョブクラスはPlayerとCharacterの間の子というわけ。

ジョブクラスではPlayerクラスの「たたかう」「まほう」「アイテム」に加え「リミット」「ジョブ」コマンドを扱うことが出来る
※正確に言うとPlayerクラスでもリミット・ジョブコマンドは実装されているが中身が入ってない

盗賊はHPや防御力が低めだが運や精神力に優れ、「ぬすむ」で敵のアイテムを懐にいれることが出来る。

みならいナイトはHPや防御力、精神力が高めな反面、魔力や運が低めだが、強力な攻撃コマンドを有している

召喚士は魔力、精神力、運以外のステータスが軒並み低く生存能力が極端に低いが、唯一リミットゲージを自ら上げることが出来、リミット技発動を主体に攻撃していくというユニークなジョブだ。

「リミット」コマンドについて

リミットコマンドも攻撃したり、回復させたりするだけなので処理的には上3つと同じ。

ただ、実行条件が少し複雑だ。

Characterクラスのインスタンス

        self.limit_count = 0
        self.gauge_count = 0
        self.gauge = '         '

リミットゲージを増やす処理

    def limit_gauge(self, limit_count):
        if limit_count >= 1500:
            self.gauge_count = 4
            self.gauge = '////////|'
        elif limit_count >= 1000:
            self.gauge_count = 3
            self.gauge = '//////| '
        elif limit_count >= 600:
            self.gauge_count = 2
            self.gauge = '////|    '
        elif limit_count >= 300:
            self.gauge_count = 1
            self.gauge = '//|      '
スクロールできます

gauge_count=0の時

gauge_count=1の時

gauge_count=2の時

gauge_count=3の時

gauge_count=4の時

リミットの処理

    def limit_break_1(self):
        if self.gauge_count >= 1:
            print(r'>\ \ \ ざ ん て つ け ん / / /')
            x = int(self.power * 99)
            MANAGEMENT.physical_damage = x
            print(x)
            self.limit_count = 0
        else:
            print('>リミットゲージが足りません')
            self.select_limit()

リミットを発動させるには、total_damageをたくさん受ける必要がある。受けたtotal_damageの分だけlimit_countが加算されていき、それに応じてgauge_countが0〜4まで増加していき、各リミット技の発動条件分のgauge_countが溜まった場合そのリミット技が使用できるようになるというわけ。

  1. total_damageを受ける
  2. limit_countがその分だけ増加
  3. gauge_countが規定されたlimit_countになると増加
  4. gauge_count変数の数字に応じてリミット技が使えるようになる
  5. リミット技を使うとlimit_countがリセットされ0にもどる

1と2の処理についてはdamage関数で行われる。

3の処理はlimit_gauge関数で行われる。

4,5については各ジョブクラスのlimit_break_?関数で行われる。

上のリミット技のコードはSUMMONジョブクラスのgauge_count=1の時に使えるリミット「ざんてつけん」の処理が記されたコード。

「ジョブ」コマンドについて

ジョブコマンドはジョブ固有の技を実行することが出来るコマンド。APを消費する

盗賊であれば「ぬすむ」、みならいナイトであれば「とつげき」召喚士であれば「詠唱」といった感じ。

今回は盗賊の「ぬすむ」を例に上げて説明しよう

    def job_action_1(self):
        cost_ap = STATUS.AP_1
        if self.ap >= cost_ap:
            if MANAGEMENT.ENEMY_INVENTORY is not []:
                x = MANAGEMENT.ENEMY_INVENTORY.pop()
                MANAGEMENT.PLAYER_INVENTORY.append(x)
                print(f'>{self.name}は{x}を盗んだ!!')
            else:
                print('>何も持っていない!!')
                self.select_job_action()
        else:
            print('>APが足りない!!')
            self.select_job_action()

「ぬすむ」コマンドは敵のアイテムリスト内の要素を取り出し自分のアイテムリストに加えるというコマンド。

まずif文でAPの確認をして
Trueなら、またif文で「敵のアイテムリストは空ではないか」確認する
 これもTrueならpop()で取り出しappendで自分のリストに加える。
 Falseならジョブコマンド選択画面にもどる
Flaseならジョブコマンド選択画面にもどる

という流れ

BATTLEについて

バトルはひと目でわかる単純明快な構造。

def battle():
    if MANAGEMENT.select_character == 1:                         |
        player = THIEF.Thief(MANAGEMENT.chara_name_1)            |キャラ選択
    if MANAGEMENT.select_character == 2:                         |
        player = KNIGHT.Knight(MANAGEMENT.chara_name_2)          |タイトル画面で
    if MANAGEMENT.select_character == 3:                         |設定してます
        player = SUMMONER.Summoner(MANAGEMENT.chara_name_3)      |
    enemy = ENEMY.Enemy('魔導兵', )                               |

    num_turn = random.random()

    def player_turn():
        show_enemy()
        player.limit_gauge(player.limit_count)
        player.select_command()
        enemy.damaged()

    def enemy_turn():
        enemy.enemy_action()
        player.damaged()

    if num_turn >= 0.5:
        while True:
            player_turn()
            if player.hp <= 0:
                print(AA.gam)
                break
            if enemy.hp <= 0:
                print(AA.vic)
                break

            effect_1()

            enemy_turn()
            if player.hp <= 0:
                print(AA.gam)
                break
            if enemy.hp <= 0:
                print(AA.vic)
                break

            effect_1()

            MANAGEMENT.TURN += 1
    else:
        show_enemy()
        print('不意をつかれた!!')
        while True:
       <略>

一応説明すると、

  1. if文で操作キャラクターを選択(main.pyのファイル内で設定してます。)
  2. Playerターンにやることを指定
    • 敵のアスキーアートを表示
    • リミットゲージを増やす
    • コマンド選択
    • 敵へのダメージ
  3. Enemyターンにやることを指定
    • 敵のコマンド選択
    • プレイヤーへダメージ
  4. 乱数でターン選択=>num_turnが0.5以上
    • Trueならプレイヤーが先攻
    • Falseならプレイヤーは後攻
  5. Whileループで回す
    • break条件は
      player.hp <= 0(ゲームオーバー)
      enemy.hp <= 0(勝利)

まとめ

いかがだっただろうか?

一見簡単そうに見えることでも一から作ってみると案外難しい。(私がただ単に知識不足だけかもしれないけれど、

でもまぁそこに辿り着くまでの過程はどうであれ、作りたいと考えていたものが形になったのだからとりあえず良しとしよう。

まだ実装したいものはたくさんあるけれど、次は何にしようか。tkinterを使ってGUIに挑戦するのもいいし、敵がアホすぎるのでちょっとネジを締めてあげても良いかもしれない。でもやっぱり1人でたたかうのは寂しいからアレを作ってみようかな

目次