ディグダン

注意:進めるうちコードの追加や変更が非常に困難になったので放棄した。

ディグダンとは

一言でいうと、地底深くの遺跡に潜ってお宝を取る1)ゲーム!

ジャンルでいえばアドベンチャーゲームRPGローグライク。周回がだるくなるのでメインはあっさりめ、サブイベントを大量に用意する。

開発記録

ストップしている理由

ややこしくなりすぎている。 どこをどう触ったらいいのかわからない。 起動してみるとけっこうできてるから、ああいいなと思うんだが、拡張性がないんだ。

本かなんかで学習するフェーズかもしれない。どうしようもないので。別に作れれば本なんていらないんだが、行き詰まったら活用する、という感じでいいだろう。

別モードを開発するには

テキストモードは大体できたので離れて、マップとか戦闘を作りたいが、別にしてどうやって作ればいいのかよくわからない。 まさかmainにくっつけていちいちボタン押して…みたいなことはできない。

オブジェクト指向がよくわかっていない

キーボードイベントからMessageEngineにcursor_xを渡す場面。cursor_xはループで保持する必要がある。

変数がself…で渡せなかったのでクラス変数にして、パッケージから変数を呼び出して渡した。 このやり方でできるが、実際どのようにリスクがあるのか、またなぜ渡せないのかが理解できていない。

インスタンス化せず、importしたパッケージに渡していることになるが、これってオブジェクト指向ではないきがする。 いやそもそもオブジェクト指向とは?それらしい書き方ってどんなものだろう。 作ってきたものが、ダメダメに思えてきた。

機能を遅いながらも順調に作ってきて、いい気になっていたが、2次無能力だ。 落胆することもないのだが、正確に認識しておこう。

ローカルにパッケージをinstallする

普通に

pip3 install .

した場合、パッケージはvenvのディレクトリに保存される。通常のパッケージの動作だ。 しかし、自作の場合それだと書き直すたびにinstallし直さなければならないため、都合が悪い。

パスを通しておいて、それを参照する形にしたい。

pip3 install -e .

でよい。

例外テスト

引数inputの要素数が奇数だと実行できないことをテストする。

input = ['犬を助ける', 'dog1-1', '助けない', 'dog1-2', '余計']
with pytest.raises(IndexError):
    fail_test = MessageEngine.conv_choice(self, input)

0,2,4...を取り出す

range(0, 10, 2)
-> 0, 2, 4, 8, 10

パッケージと元ソースの分離

pyvenvで?パッケージと元ソースが離れた場所に作られるため、いちいちinstallし直さないと反映されない。 おかしくね。

パッケージの方が読み込まれてしまう問題を解決した

pyenvを使用したときに、consts.pyでプロジェクトのホームディレクトリを取得するときに、パッケージの方のディレクトリが読み込まれてしまい、xmlが取得できなくなった。

mainでのconsts呼び出しを登録パッケージではなく、相対パスで指定することにより実行されたmain.pyと同じディレクトリの方のconstsが指定され、fileに正しい値が入力されるようになった。

…しかしそうすると実行ができない。

上階層のimport

ホームディレクトリの““env.py”“にpasswordとかuserIDが保存されているとする。~/my_pythonからインポートして利用するには、

import sys
sys.path.append('..')
import env

あとは、

print(env.password)
-> ...

などと利用する。

クラス直下の変数の使い方

クラス直下で

choice_mode = 0

とすると、

Plot.choice_mode = 1

でplotメソッドからもmainメソッドからもアクセスできるようになった。

モジュールが変数を持つ?インスタンスが関係ない。 クラス関連の理解がかなり曖昧だ。もう一度勉強を…。

文脈によるキーイベントだけの変更がうまくいかない

選択肢を表示しているとき、キーイベントを変化させたい。

普通のwindowtextにおいてEnterキーはページ送りをするが、選択肢の場合は違うメソッドを割り当てたい。横移動も新たに加える必要があるが、選択肢を表示しているときだけ有効にしたい。

背景やBGMなどは変わらないので、game_stateは変更せず、変数によって分岐させる。

が、うまくいかない。 plot.choice()で、”“self.choice_mode = 1”“をしているが、main側からの変数へのアクセスは常に0になっている。

理由は…

  • インスタンス変数?で、名前は同じだが違うものを指している。
  • 実行タイミング2)

などが考えられる。

self.plot.plot_count += 1している箇所はmainとplotで、共通に扱えているように見える。 plot_countとchoice_modeの違いは何だろうか?

opening()とchoice()は、実行階層がかなり異なる。choice()はかなり下の方にある。

少なくともmain側で宣言せずに使用できるので、self.choice_modeにアクセスできてはいる。値は異なるが。 初期化した値は反映する。 plotのメソッド内での変更が反映できていない?

戦闘やマップモードを抜いたバージョンをまず作る

それらを含むと、目標としてあまりに大きくなりすぎるため。

とりあえずアドベンチャーゲームとしての体をなすには、会話モードができればよい。 目指すものはADV+RPG+ROGUELIKEみたいなものなので、まず一度ADVとして完成させることは悪くないことだと思う。

区分けができていない、モチベーションが上がらない

目隠しをされて、砂漠を歩いているような気分になる。 もっと区分けをするべきだと思う。すぐ忘れるが、一日や週の計画を立てる。

選択肢の全体像

ミニメソッドでplot.choiceが呼ばれる。

plot.choiceが選択肢を描画(msg_engine.draw_choice)し、キーボードイベントモードを切り替える。game_stateはwindowtextのまま。

イベントを送出し、イベントidがplotの配列に追加され、分岐できる。

lenとアクセス

test = ['ああ', 'いい']
print(len(test))
=> 2
for p in test:
  print(p)
  => 0
     1

lenは1始まり。リストへのアクセスは0始まり。

game_state_range_errorになる

定数を使っていて、実際は数字。 ちゃんと範囲に入っているように見える。

if plot_count == 0:
  game_state = FULLTEXT
  self.msg_engine.set(root, 'monologue0')

は、いける。

scenario_sequence = np.array([[FULLTEXT, 'monologue0'],
   [WINDOWTEXT, 'intro0'],
   [WINDOWTEXT, 'dog1-1'],
   ])
game_state = scenario_sequence[self.plot_count][0]
self.msg_engine.set(root, scenario_sequence[self.plot_count][1])

は、ダメ。

最終的にどちらも

return game_state

して、ハンドラで値が判断される。

printで確認すると、どちらも同じ数字である。

テストを書いて確認してみると、数字と文字列の3であることが判明した。 なぜnumpyを使った方は文字列になるのか不明だが、”“int()”“を使うことで解決した。

どうやってねじ込むか

各イベントを連結させるための方法には2つある。

  • 大ループ方式

現在の暫定的方法。Plotクラスのメソッドにイベントの順番を書きカウントして、読み込む。 イベント-イベントを記述でき見通しがいいが、じっさいのイベントはほとんどシステム選択肢に戻るだけなので、あまり意味がない?

  • 芋づる方式

各イベントに次のイベントを記述する。xml内にまとめられる。 が、その分解析する必要があるし、ステータス変化、フラグまで書くことを考えるとちょっとまとまりがなくなる?

やっぱり大ループ方式でいく。 xmlを台本としてシンプルに保ちたいからだ。

現在のイベントの進み方

PlotメソッドでA→B→Cとset(xmlからテキスト取得)する。 各イベントに入るとテキストとスクリプトをページごとに分割し、0番目のページを表示する。

以降enterを押すごとにcur_pageをインクリメント3)し、表示内容を切り替える。 次のページに文字が1文字もなかった場合、cur_pageを0にする。

mainのplot_countをインクリメントして、Plotのメソッドが進み、次のテキストが読み込まれる。

フラグ管理をどうやるか

xml内でやろうとしている。シナリオと一緒にあるのが自然だからだ。 フラグの書き込みは簡単にできると思う。

しかし、読み込んで分岐させるのは手間がかかりそうだ。

if(help == '1'):
    go('live')
else:
    go('die')

みたいなことを、ミニスクリプトでやるのはかなりおかしくないか? そもそもxmlに書いてミニスクリプトとして追加しロジックを書いていくのがおかしいのではないか…という疑問がする。

このままやるなら、スクリプト内でpythonを使って、分岐させるとか?

@[AB]スクリプトが読み込まれないバグ

@のエスケープシーケンス関係だと思い、同じ状況をテストしてみたが、普通に読み込まれることがわかる。 なぜだろうといろいろ試して、ようやく辿りつく。

for p in range(len(self.msg_engine.script_d[:, 0]))

とするべきところを、

for p in range(len(self.msg_engine.script_d[0]))

としていた。 行数を取得しないといけないのに、列数を取得していたため、あとの数が取得できていなかったわけである。

気づかなかったのは、@[AB]以降の行がダミーで、行数以下が読み込まれてないためだった。 @[AB]だけが特殊な形式であることに引っ張られてしまった。

よく使うインクリメント

for p in range(len(リスト)):
  print(p)
=>0,1,2,3,...10(リストの要素数)

リストを縦ベクトルに変換し、追加する

arr_args = np.array(arg_list) # listをnpオブジェクトにする
v_arr = arr_args[:, np.newaxis] # 縦ベクトルに変換
goal = np.append(base_np, v_arr, axis=1) # 縦に追加される

縦ベクトルの追加

base = np.array([
                ['chara', "([AB]='.*')"],
                ['bgm', "(bgm='.*')"],
                ])
v = np.array([
             [1],
             [2],
             ])
goal = np.append(base, v, axis=1)
print(goal)
=>[['chara', "([AB]='.*')", '1'],
   ['bgm', "(bgm='.*')", '2']]

縦ベクトルをforで作る方法がわからない。

関数やメソッドを呼ぶのに文字列を使うにはどうしたらいいですか?

https://docs.python.org/ja/3/faq/programming.html

様々なテクニックがあります。

一番いいのは、文字列を関数に対応させる辞書を使うことです。このテクニックの一番の利点は、文字列が関数の名前と同じ必要がないことです。この方法は case 構造をエミュレートするための一番のテクニックでもあります:

  def a():
      pass
  def b():
      pass
  dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs
  dispatch[get_input()]()  # Note trailing parens to call function

多次元配列がよくわからない

足そうとしてエラーが出る。 というか配列とリストとかの違いもよくわかっていない。

進まん。

眠くても続けるのが正解

バイトのあとすぐは眠かったが、しばらく我慢して開発を進め、ちゃんとできた。 昼や夕方は眠くても続けることが大切なのだと思う。どうしてもなときは散歩に行くとか。昼寝は間違いだ。

仮眠はだいたい2時間してしまう。それで夜眠れなくなるし、あまり気分がよくない。頭が働かない。 ウトウトしていても、2時間うとうとすることはない。ほとんどないが30分ウトウトしても寝ないほうが特だ。 そのうち気分がスッキリしてくる。

正規表現をひとつにまとめる

各正規表現を、ひとつの多次元配列に入れることにした。

np.array([['bgm', '全体マッチ用', '引数取得用', '引数取得用'],
          ['bg' , '全体マッチ用', '引数取得用', '引数取得用'],
        ])

スクリプトと引数の関係

たとえばbgm='hello.mp3'の場合、 実行されるのは change_bgm(hello.mp3)である。

実際に実行されるものと、自作命令を紐付けなければならない。 一気にまとめてしまうのもいいかもしれない。

(bgm,bgm='.*',bgm='(.*)')
(bg,bg='.*',bg='(.*)')

などと。

スクリプトの引数取得用リストの変更点

ほかのものは引数にがあるため、それを目印にして()をつけることで取得用正規表現を組み立てられた。 @[AB]にはが存在しない。A、Bが引数だといえる。 なので、@[AB]は例外として表示するようにした。

TOをどうやるか

とりあえず的にplotの方法と同じでsetしてみるが、エラーが出る。全然わからない。 xml側からシナリオを制御するというのは必要なことだが、忘れててplotだけ、つまりメソッドでシナリオ間の移動する予定になっていた。簡単そうだが、どう手をつけたらいいかわからない。

rootをmainからmsg_engineに持ってくる方法もわからない。循環するのでimportできない→アクセスできないのでもう一回ファイルを読み込み、でいいのか? で、いきなり読み込んでるから多分page_indexとか初期化されてないんじゃないかと思う。

なかなか進まないので、手をつける気になれなくなってきている。後回しにするか。

テストしにくいことが問題をややこしくしている? scriptの関数をテスト化しようか…そもそもアレがややこしいことが原因なんだ、たぶん。常にそうやって切り分けていかないと、新しく機能を追加することは難しい。

\tバグ

  • @Aのあとにだけ\tが混入してしまう。

printしてみると、'\t','\t'という具合になっている。 @A、のあとだけということがヒントだが、全然わからない。ユニットテストの意味なし…。

raw_textの段階で、2つ目のシーンがガタガタにインボートされていることが判明した4)

結局、strip()に

replace('\t','')

を追加していって解決した。strip()全然仕事していないな…。 なぜ1つ目はOKだったかなど、原因ははっきりしない。

音楽の再生

self.cur_musicに音楽のタイトルを格納して、違うときのみ切り替えるようにする。 これによって、ループ内でも再生できる。

選択肢の仕様

全ての選択肢で、シナリオは停止する。 選択肢は次のイベントidを持ち、選ばれたものにスキップする。

選択肢の関数は、

function(
うろつく,plot1-1,
遺跡へ行く,plot1-2,
仲間,plot1-3
)

などとなるだろう。

xmlファイル中では、

@choice
(("うろつく","plot1-1")
 ("遺跡へ行く", "plot1-2")
 ("仲間", "plot1-3"))

などとして表現できる?

選択肢のタイトル、次のイベントidを渡す。シナリオ中で用いる汎用選択肢となる。 システム選択肢については、別に用意する。

用語集

ゲームの流れ

イベント

ループ→会話→ループへ戻る一連のテキスト及びスクリプトのこと。xmlファイルでは<evt id=””>で示される。

システム選択肢

ゲーム進行の主軸となる選択肢のこと。「でかける」「遺跡へ行く」「売買する」「装備を整える」「仲間」「セーブする」などがある。あらゆるイベントのあとは、ここに戻る。 独立したゲームモードを定義しようと思ったが、実際は単なる選択肢の延長のような気もする。

改善案

wikiを充実させるべきだと思う。 もっとメモを増やす。

委託、共同開発

練習としてとりあえずバトルディッガーのクローンとしたが、あまりモチベーションが上がらなくなってきた。 売れるわけがない、と考えているからだ。ゲームは総合芸だ。フルスタックなんかメじゃないくらい。 プログラム+絵+音楽+シナリオ 工数も足りないし、そもそも開発速度が遅いので遅くてやる気が上がらない。

絵と音楽は外注が絶対不可欠。

計画

締め切りと計画を立てる。

  • 11月
    • コマンド選択モードを完成。

シナリオ進行の基本となる。イベント→コマンド→ダンジョン→イベント→コマンド…と続いていく。

  • 12月
    • マップモードを完成。
    • 戦闘モードを完成。
    • 基本的ゲームプレイが可能。

絵や文字をセットして、各ゲームモードをテストプレイできるようにする。

  • 1月

絵、音楽、シナリオを埋め込み完成、バージョン1.0を販売開始。

ゲームシナリオ

1)
生活のために!
2)
イベントハンドラはifだらけで、動作をよくわかっていない
3)
window.next()
4)
1つめのfulltextのほうはできている
article/digger_dungeon.txt · 最終更新: 2020/07/22 21:28 by kijima