作者: 機械伯爵
日時: 2002/4/29(23:15)
「P は Python の P <実戦編>」
 〜やるぞ作るぞRPG!〜


NP-0 ロールプレイングゲームを作ろう!

 ども、機械です。

 というわけで、今回から、ロールプレイングゲーム開発を通してPythonの可
能性を探っていこうと思います。

 とはいっても、どちらかというとコンピュータゲームのRPGではなく、D&D(R)
のような、本来の紙と鉛筆で行うタイプを電脳化したような感じになります。

※D&D(R)…Dungeons & Dragons;初代RPG。なぜ(R)をつけるかというと、すご
  く版権にうるさいのでも有名だったので・・・だった、というのは、これを
  作ってたTSRという会社が数年前に買収されて、現在このシステムのコア(d20
  System)は、一種のフリーウェア扱いになってます(笑)

 かといって、テキストアドベンチャーのような単なるフラグ処理と分岐だけ
のものではなく、グラフィックが無いだけでちゃんと戦闘とかバリバリに行い
ます。

 ・・・つーか、戦闘がメインね(テキストメッセージだけの迷宮探索はZork
で懲りた…)

 というわけで、実際プログラムの知識はほとんど要りません。

 Pythonなら、「Python入門」のチュートリアルか、ネットに上がっている入
門ページを一通りやった方なら多分わかります。

 むしろ、ゲームに対する知識を多少要求されるかもしれませんが、殆どのネ
タがWizardryですので、2・3回軽くWerdna様と遊んできてください。

※Wizardry…Zork、Ultimaと並んでコンピュータRPGの3大祖先の1。ファミコ
  ンで大ヒットした「ドラゴンクエスト」は、UltimaとこのWizardryのシステ
  ムを融合させたような感じ。

 Wizardryにしろ、(初期の)ドラクエにせよ、考えてみれば戦闘の経過は殆ど
文字と数値で表されていたので、戦闘処理方法さえ判っていればできるはずで
す。

 いや・・・ホントはテキストだけでも、Rogueとか作れるのだけど・・・

※Rogue…これはUNIXユーザにはおなじみのゲーム。クローンがやたらと存在す
  る、ダンジョン探索モノゲームです。ただし、テキスト表示とはいえ画面処
  理が必要なので、コンソールベースでは実際にはかなり高度な技術を要しま
  す。

 といわけで、とりあえず始めてみましょう。


NP-1 Endless Battle ver.1

 とはいったものの、最初からちゃんとしたRPGのコードなぞ書き出した日には、
タイプするだけで日が暮れてしまうと思われますので、まずはプロトタイプ
(というかたたき台)のようなゲームを考えて、これをいろいろ改造しながら、
ゲームデザインのプロットを練っていきたいと思います。

 まずは、その小ゲームのゲームデザインです。

※実は、テストしてみて、若干数値を調整しました。

 主人公の勇者(←こんな職業を許すのは最初のうちだけです)は、剣と治癒
魔法が使えます。

 勇者の剣は、命中率80%で、2ダメージを与えます。

 治癒魔法は、MP(Magic Point)を1消費して、HP(Hit Point)を全快させます。

 MPの上限はLV(Level)と同じで、HPの上限は10にLVを足した数になります。

※最初、勇者のHPをLVx10にしたら、強すぎた・・・

 倒すのは・・・やっぱりドラゴンがいいですね。

 ドラゴンは、爪や牙を用いた通常攻撃と、必殺のドラゴンブレスを吐きます。

 ドラゴンの通常攻撃は命中率50%で1ダメージ。

 必殺のドラゴンブレスは、100%命中でドラゴンの現在のHPの半分だけダメー
ジを与えますが、ドラゴンのHPも1減ります。

 ドラゴンのHPは10〜15、50%の確率でドラゴンブレスを吐きます。

※ドラゴン君も、最初HP10固定、ブレス確率20%にしたら、弱い弱い・・・

 勇者は、1匹ドラゴンを倒すと、レベルアップしてHPもMPも上限まで回復し
ます。

 戦いの順序は勇者→ドラゴン→勇者・・・と交互に行います。

 最初はこんなところでしょうか?

 勇者の選択肢は2つ(逃亡は無し(笑))で、攻撃パターンも簡単です。

 戦って戦って、体力が減ったらベホイミ(笑)

※やったはお気づきでしょうが、初代ドラクエの竜王との戦いのパロです。

 それでは、コーディング、まいりましょう。

#---ENDLESS BATTLE V1(ebv1.py)---
# 20面ダイスを作る・・・
from whrandom import randint
def d20():
  return randint(1,20)

# 表示停止プロンプト
def next():
  raw_input("\n<<HIT ENTER KEY>>\n")

# 勇者クラスを作る
class Hero:
  def __init__(self):
    self.lv = 0
    self.levelUp()   # HP,MPについては、1レベルにアップさせて初期化する。
  def levelUp(self):
    self.lv += 1
    self.hp = 10 + self.lv
    self.mp = self.lv
  def attack(self):
    print "HERO'S ATTACK!"
    if d20() > 4 :
      print "HIT!"
      return 2
    else :
      print "MISS!"
      return 0
  def heal(self):
    print "USE MAGIC!"
    if self.mp > 0:
      self.hp = 10 + self.lv
      self.mp -= 1
    else:
      print "MP IS CONSUMED!"

# ドラゴンクラスを作る
class Dragon:
  def __init__(self):
    self.hp = randint(10,15)
  def attack(self):
    n = d20()
    if n > 10 : # ドラゴンブレス
      print "DRAGON BREATH!!!"
      d = self.hp / 2
      self.hp -= 1
      return d
    else :    # 通常攻撃
      print "CLAW AND BITE!!"
      if d20() > 10:
        print "HIT!"
        return 1
      else :
        print "MISS!"
        return 0

# ゲーム本体

print 'WELCOME TO THE "ENDLESS BATTLE" FIELD!!'
hero = Hero()

# メインループ

try:
  numberOfSlay = 0 # 退治したドラゴンの数値
  while numberOfSlay < 10 :
    print "Encounter!!"
    dragon = Dragon()
    while 1 :
      print "HERO'S   HP = ",hero.hp
      print "HERO'S   MP = ",hero.mp
      print "DRAGON'S HP = ",dragon.hp
      # 勇者の行動
      while 1:
        command = raw_input("ATTACK(0) OR HEAL(1)? >>")
        if command == "0" :
          dragon.hp -= hero.attack()
          break
        elif command == "1":
          hero.heal()
          break
        else :
          print "BAD COMMAND ... \n PREASE HIT 0 OR 1 AND ENTER"
      # ドラゴンの反撃
      print "\n"
      hero.hp -= dragon.attack()
      next()
      if hero.hp <= 0 : raise "HeroHPZero"
      elif dragon.hp <= 0 : break
    print "YOU WIN!"
    numberOfSlay += 1
    print "LEVEL UP!!"
    hero.levelUp()
    next()
except "HeroHPZero":
  print "--HERO IS DEAD!--"
  print "--THE GAME OVER--"
  next()
else:
  print "--HERO SLAY 10 DRAGONS--"
  print "----CONGRATULATION!!----"
  print "--------GAME END--------"
  next()

#--END---

 簡単なゲームの割には長いコードになりましたね・・・

 今回は、極力直感的に書きましたので、細部のモジュール化はおいおい行っ
ていきます。

 それでは、内容を見ていきましょう。

 どしょっぱなのd20とは、ダイスコードで20面サイコロのことです。

※ダイスコードとは、サイコロの種類と使用個数の書き方で、2d6なら普通の6
  面サイコロ2個を指します。

 d20は各数値の確率が大体5%と使いやすく、実際のゲームで多く使われてい
ますので、まずこれを模したランダム発生ルーチンを作りました。

 次に、画面停止のためにいちいちraw_input()を書くのも面倒なので、next()
という専用手続きを作っておきます。

※ちなみに、大文字のメッセージは、古いゲームの雰囲気を出そうと思っただ
  けで、特に深い意味はありません。

 小道具が揃ったら、役者です。

 まずは主役の勇者クラスを作ります。

 最初なんだから数値で始めようかな、とも思ったのですが、最初からクラス
で処理したほうが断然楽そうです。

 実装したメソッドは、攻撃、治癒魔法、レベルアップの3種類で、現状では
命中率などは内部処理されています。

 んで、仇役のドラゴン君。

 ドラゴンブレス確率が上がったことにより、運が悪いと連発して炎を浴びて、
手も足も出ないままにあっという間に黒焦げになることもあります(笑)が、ス
リルはあります。

 実装されたメソッドは攻撃だけで、HPなどは勇者と同じく、現状では外から
操作されています。

 ゲーム本体の処理としては、ゲームオーバーを例外処理にして多重ループを
抜けてるのがPythonっぽいかな?と思いますが、コマンド入力部分などが若干
見難くなっているので、ここらへんはとっととモジュール化していきたいと思
います。

 また、戦闘ルーチンも現状では非常に単純ですが、複数人数パーティとなる
と、こうはいかないでしょう。

 ゲームを作っていて大事なことは、「調整」作業です。

 プログラムのデバッグ以上に、何度もプレイして「ゲームバランス」を調整
することこそが、面白いゲームを作る必須条件となります。

 このため、調整する数値を固めて見やすい場所に置くことが、ゲームプログ
ラミングを行う上での一つのポイントとなります。

 正確な動作、興味あるシステム、使いやすいインターフェイスなんていうの
は、ゲームでは「必須条件」であり「十分条件」ではありません。

 その点で、非常にシビアなプログラミング分野であると私は思っています。

 ・・・とゆーか・・・最近のゲーム、ちゃんとゲームバランス調整、してる
んだろーか?(汗)

※次回から、少しづつ改造していきます・・・

   機械伯爵