新規ゲームへの対応手順
新規ゲーム対応のコード変更作業は、大きく分けて以下の 2 つの部分を書き換えることになります。
- GUI を司る
playthrough.py
- ゲーム固有の処理を含む
alfort
フォルダ内のコード
playthrough.py
ツールの GUI を提供するコードです。記録モード/再生モードにおける GUI は図のようになっています。
callback_remove
GUI 上の「削除」ボタンが押された時に呼ばれる関数です。 手本フォルダに保存されているセーブデータを削除する部分で、削除したいセーブデータのファイル名を適切に指定してください。
サンプルゲームのセーブデータは、セーブデータ本体とSharedData
というファイルが対となって構成されています。
callback_execute_game
GUI 上の「起動」ボタンが押された時に呼ばれる関数です。
self.game_savedata
(現コードのself.game_shareddata
に相当) にゲームのセーブデータ名を指定してください。 これは手本フォルダからゲームのセーブデータフォルダにセーブデータをコピーする時のコピー先となります。self.game_savedata_dir
にゲーム側のセーブデータが存在するフォルダのパスを設定してください。 UE4 であればWindowsNoEditor/{ゲームのコード名}/Saved/SaveGames/
がデフォルトになります。- セーブデータの仕様に合わせて、起動前にセーブデータをゲーム側のセーブデータフォルダにコピーする処理の部分を変更、もしくは削除してください。 なお、セーブデータのファイル名が定数文字列、かつゲーム起動後のファイル書き換えが反映されない仕様だった場合は、 おそらく対処できないのでゲーム実装を調整する必要があると考えられます。
callback_make_nextdemo
GUI 上の「追加記録」ボタンが押された時に呼ばれる関数です。
次の手本を作成する際、直前の手本の終了地点に対応するセーブデータをロードするための処理です。 セーブデータ名の仕様に合わせてロードすべきファイルを適切に指定してください。
callback_end_make_demo
GUI 上の「記録終了」ボタンが押された時に呼ばれる関数です。
- サンプルゲームにおいては仕様上、ゲームクリア / ゲームオーバー画面ではスタートボタンのメニューを開くことができないため、セーブを行うことができません。 よって、「記録終了」ボタンが押された時点でこれらの画面になっていた場合、セーブデータを作る処理を呼び出さないようにしています。 他のゲームではこの処理が必要でない可能性が高いため、ゲームの仕様を調べた上で変更、もしくは削除してください。
- セーブデータの仕様に合わせて、作成されたセーブデータ (SharedData のようなセーブデータに付随するファイルがあればそれら全ても含む) を適切に 手本フォルダへコピーしてください。
load_checked_demo
GUI 上でチェックを入れて選択した手本を再生リストに追加する関数です。
replay_savelist
に選択された手本に対応するセーブデータのパスを追加してください。 ゲームの仕様を考慮して、それ以外にも追加すべき情報があれば適切に調整してください。- サンプルゲームにおいては仕様上、セーブデータ本体の拡張子なし文字列と SharedData の情報もリストに追加しています。 他のゲームでは必要ない、もしくは存在しない情報である可能性があるので、仕様を確認した上で変更、および削除してください。
load_setting
前回起動時にファイル保存されたユーザー指定のパラメータ (ゲームファイルのパス、起動時のスクリーンサイズなど) をロードし、 その設定を自動で反映するための関数です。
- 記録 / 再生時の設定として、GUI 上から指定する必要のあるゲーム特有のパラメータがあれば、
class GUI
内の各コールバック関数やsave_setting
関数でself.setting
(dict) に追加し、settings.json
に書き込むようにしてください。 settings.json
の読み込みエラー時には各パラメータをデフォルト値に戻す必要があります。 そのために、上記で追加したパラメータのデフォルト値をexcept
部分に指定してください。
alfort/alfort_core.py
全般
playthrough.py
の load_checked_demo
で説明したように、replay_savelist
に格納する内容を変更した場合、
このコード内で同変数を参照している部分も適切に変更してください。
main_loop
毎フレーム呼ばれ、受信したゲーム内情報に応じて適切な処理を行うための関数です。
self.level = xxxx
の部分で、ゲームから送信される情報を参照し、 現在ゲームプレイ画面 or タイトル画面などの非ゲームプレー画面にいるのかを判定しています。 この判定ルールをゲームに応じて適切に設定してください。 基本的にはゲームから現在どのレベルにいるのかという情報が送信されるはずなので、それを元に場合分けすればよいです。 他ツールでも同様の判定が必要とされるため、そちらで既に対応済みなら同じものを流用するだけで問題ありません。
record_demo
記録モードにおいて、毎フレームの記録内容 (人間のパッド操作内容、ゲーム内部データなど) をリストに蓄積しておく関数です。 記録終了時にここで蓄積された内容が csv 出力されます。
- ゲームに合わせて必要な情報を
demo_xxx.csv
に記録してください。基本的に以下の内容は全てのゲームで共通して記録する必要があると考えられます。- プレイヤー座標 (x, y, z)
- プレイヤーの向き (水平方向)
- カメラの角度 (水平方向、垂直方向)
- ゲームで割り振られている 全ての ボタンの入力値
- 現在メッセージ (会話中や看板を読んだ時などのウィンドウ) が表示されているか
- 表示中のメッセージ内容を区別するためのゲーム内部で割り振られた ID
その他、ゲームの自動攻略に必要なゲーム内部情報を追加で記録してください。 なお、事前にゲーム側からそのような情報を Python 側に送信できるようにしておく必要があります。 イベントスキップ時に行われる手本の行飛ばしが厄介になり得るため、 イベントの発生 / 発生中に関わる内部データはできるだけ保存しておくと、後にイベントスキップ時の操作ルールを構築する際に役立つと思われます。
また、UE4 から送信されるデータの形式が bool 値の場合、そのまま保存すると TRUE/FALSE
の文字列 (string) で書き込まれるため、
int に変換してから書き込む必要があることに注意してください。
- サンプルゲームは仕様上場合分けで csv に記録する内容を変えています。 他のゲームでは必要ない処理である可能性があるので、仕様を確認した上で変更、および削除してください。
replay_demo
再生モードにおいて、毎フレームのプレイヤー行動内容の決定や、再生対象となる手本の管理を行う関数です。
- 下記のそれぞれにおいて、ゲームに合わせて適切なセーブデータのパスを指定してください。
- 手本の開始座標が現在地点と離れている手本が再生指定されている場合に行われる、記録時に作成されたセーブデータのロード
- 再生に失敗したと判定された場合に行われる、復帰地点用の一時セーブデータのロード
- 同一の手本で再生失敗判定が続いた場合に行われる、次の復帰地点までスキップするためのセーブデータのロード
copy_tmp_savedata
再生に失敗した時に使われる復帰地点用の一時セーブデータを手本フォルダにコピーする関数です。
- ゲームに合わせて適切なセーブデータのパスを指定してください。
delete_tmp_savedata
再生失敗した時のリトライに用いられるセーブデータを削除する関数です。
- サンプルゲームではセーブデータのファイル名が可変文字列であるために、再生を繰り返すとリトライ用のセーブデータが大量に作られてしまいます。 セーブデータの仕様によっては、そもそもファイルが増殖することはないので、この関数自体を削除してください。
- もしこの関数の機能が必要な場合は、手本フォルダから削除すべきセーブデータを正規表現で検索する必要があります。
tmp_list = glob.glob(self.demo_dir + "_tmp_recoverypoint_hoge")
のhoge
部分をセーブデータのファイル名の形式に合わせて適切に変更してください。
search_latest_savedata
サンプルゲームのセーブデータの仕様に対応するための専用関数です。 詳細な説明はコードを参照してください。他のゲームでは必要ない処理である可能性があるので、仕様を確認した上で変更、および削除してください。
order 関連
タイトル画面/ゲーム内メニュー/デバッグメニュー などのアウトゲーム的な操作 (主に記録/再生開始前後の設定に必要な操作) を行う関数群です。
# alfort_order.py を利用した各種メニュー操作
とコメントが書かれている部分以降がこれに該当します。
後述の alfort_order.py
で定義した細かな単位の操作を組み合わせ、特定のことを行うために必要な一連の操作を定義してください。
例としてサンプルゲームでは以下のものが用意されています。
- セーブデータをロードするために必要な一連のパッド操作
- 特定のデバッグ機能を ON/OFF するために必要な一連のパッド操作 など
goto_load
作成済みのセーブデータをロードしてゲームを開始するための操作を行う関数です。
- サンプルゲームのセーブデータの仕様上、本関数の引数にセーブデータの拡張子なしファイル名を渡し、ロード画面でこれと一致するセーブデータをロードする必要があります。 他のゲームでは必要ない処理である可能性があるので、仕様を確認した上で引数とその値を用いている部分を変更してください。
alfort/alfort_order.py
タイトル画面 / ゲーム内メニュー / デバッグメニュー などで必要な操作を細かな単位に分解したものが定義されています。
ゲームに合わせて適切なものを Order
クラスを継承したクラスとして用意してください(Order
クラス自体を変更する必要はありません)。
ここで必要とされるコードの変更は、ほとんどがゲームによって全く異なると考えられ、決まった手順を書くことはできません。
ゲームの仕様を調べ、それに合うように操作内容の適切なルール化を行ってください。
例としてサンプルゲームでは以下のものが用意されています。
- 特定の項目 (引数と一致する文字列) を選択する
- メニューの開閉を行う
- 指定したボタンを 1 回押す など
基本的な方針は、各関数のボタン操作 (どのボタンが決定、戻る、メニューオープンなどに対応しているのか)、
各処理を行うか否かの判断材料となる game_data
の参照部分 (if の条件に相当)、その他 game_data
を参照している部分を変えればよいはずです。
ゲームによってはこれだけでは不十分である場合も考えられるので、既存の実装を参考に、必要な機能を追加してください。
Order
を継承しているクラスの多くは、Collision Checker や Map Scanner などと共通して使えるため、
他のツールで既に変更対応が済んでいれば流用するだけでよいです。
ただし、他のツールでは必要のない操作が本ツールでは必要となることも考えられるので注意してください。
alfort/alfort_agents.py
再生モードにおいて、手本を参照しながらプレイヤーの行動内容 (=パッド入力内容) を決定するためのコードです。
この実装はゲームによって 非常に大きく左右される ものであり、特に後述の step
関数については作業手順を一般化することが不可能です。
実際にゲームをプレーしたり、ゲームの仕様書を調べたりしながら、ゲームの攻略に必要な全ての行動を上手くルール化してください。
エージェントの行動アルゴリズムを解説した資料や、
コード内のコメントにもルール設計のヒントが書かれています。適宜参照してください。
パラメータの調整
後述の パラメータ設定関連 > alfort.yaml において、 プレイヤーの移動速度に関するパラメータ はゲームの仕様を調べてあらかじめ調整を済ませてください。
load_demo
記録した手本の csv を読み込み、再生時に扱いやすいようにデータを加工する関数です。
- csv から各値を読み込む
tmp_row = list(map(float, row[0:23]))
の部分で、 csv に含まれる値の数 (列数) に応じて index 範囲を適切に変更してください。 なお、csv の情報中に数値以外、つまり文字列が含まれる場合は float への変換ができないので、 列データを別途読み込んでtmp_row
に追加 (append
) してください。 add_index_to_dict
- csv 中に存在する全てのイベント開始行と終了行を対応させるための dict を作成するための関数です。 各イベントに固有の ID が割り振られているならそれを key として、value には開始行と終了行の set にするのがよいです。 これを利用して、再生時には dict の key に含まれる ID のイベントが発生し、現在参照している手本の行が value のイベント開始行 index と等しい場合、value のイベント終了行 index までスキップするという処理が走ります。 ただ、ゲームによってはこれでイベントのスキップが上手く行かない可能性もあるため、 alfort_core.py > record_demo でも説明したように追加で手本に記録した値が存在すれば、それらもセットにして格納してください。
イベントスキップ時のdata_index飛ばしのために (略)
とコメントが書かれている部分は、for ループで手本を一行ずつ読み込んでいく過程で、 現在の行がイベントの開始/終了タイミングなのかどうかを判定している処理です。この判定ルールをゲームに合わせて適切に変更してください。
next_move_checkpoint_data
手本のルートに従った移動以外を行わない状態 (コード上では self.step_state == "move_checkpoint"
) の時に、
ルート通りの移動を実現するために一定間隔で設定される目標座標を更新する関数です。
以下の説明を元に、ゲームに合わせて適切な目標座標設定ルールを記述してください。
- 手本中で移動以外の行動をしていない時は
demo_xxx.csv
のデータをconfig.checkpoint_interval
で指定したフレーム数おき、 つまりこのフレーム数と同じだけの行数おきに読み、その行に書かれている座標を目標座標に設定すればよいです。 - 移動以外の特殊な行動が挟まる (例: ジャンプ、会話、その他の記録時と座標を合わせて行う必要がある特殊行動) 時は、
上記の「
config.checkpoint_interval
で指定したフレーム数おきにデータを読む」ルールを無視し、 それらの行動が開始される瞬間の座標を目標座標に設定する必要があります。
この「ルール無視」はコード中のif (elif) ~ break
の部分に相当し、移動以外の特殊な行動 として定義する内容、 及びdemo_xxx.csv
の内容から いつそれらが開始したか を判定するルールをゲームに合わせてカスタマイズしてください。
step
手本データと毎フレーム送信されるゲーム内情報を元に、実際にゲームに反映するパッド操作内容を決定している関数です。
- ステートマシンで行動を管理しており、
self.step_state
が現在の state を表します。 ゲームに応じて state の種類、ゲームから送信された情報から どのような条件を満たしたときに state を遷移させるか、 各 state における行動内容 をカスタマイズしてください。この関数から呼ばれている他の関数も適切に変更する必要があります。 - イベントスキップを表す state (
self.step_state == "message_skip"
) は 任意の state から飛び道具的に遷移する可能性があるので、ステートマシンを表す if 文の階層の中では最上位に記述しておくとよいです。 - ほとんどのゲームに共通して用いることができると考えられる state においても、
ゲームから送信されているデータを参照している部分 (
game_data["Alfort"]["hoge"]
など) は適切な値に変える必要があります。 また、ゲーム特有の細かな仕様の影響で、if 文の分岐条件を変更したり、例外処理を追加したりする必要があると考えられます。
lib/utils/assert_window_watcher.py
ゲームのウィンドウを常に監視し、ポップアップウィンドウが出現したら自動で閉じるためのコードです。
サンプルゲームでは現在は使用されていませんが、今後エラー状況の記録が必要になる場合を想定しサンプルとして実装してあります。 なお、以下で説明する変更対応を行っても動かない可能性が考えられるため、ゲーム毎に上手くカスタマイズしてください。
__set_foreground_handler
# ウィンドウのタイトル名が XXX から始まる
とコメントにある通り、if name.find('Alfort') >= 0:
の部分で監視対象となるゲームのウィンドウ名の先頭文字列を指定してください。
get_window_handle
handle = win32gui.FindWindow(32770, "ASSERT")
の部分で、第 2 引数を自動で閉じたいウィンドウの名前を指定してください。 また、第 1 引数に閉じたいウィンドウのウィンドウクラスを指定してください。備考ウィンドウ クラスについて - Win32 apps
https://learn.microsoft.com/ja-jp/windows/win32/winmsg/about-window-classes
パラメータ設定関連
data/conf/alfort.yaml
各種パラメータを記述するためのファイルです。
# ===== ゲーム共通のパラメータ =====
とコメントが書かれた部分の項目は、全てのゲームに共通して必要なパラメータだと考えられます。
プレイヤーの行動に関連するパラメータについては ゲーム実装における プレイヤーの移動パラメータも調べつつ、適切な値を設定してください。
# ===== ゲーム固有のパラメータ =====
とコメントが書かれた部分の項目は、ゲームごとの仕様に合わせて必要となるパラメータです。
必要に応じて追加、削除してください。
data/conf/config_template.yaml
alfort.yaml
で設定したパラメータの意味が説明されています。実行には用いられません。
alfort/alfort_config.py
class Config
で alfort.yaml
で使っているパラメータの名前を定義しています。
パラメータの追加や削除を行った場合は、ここの項目も調節してください。
なお、ここで定義したパラメータと alfort.yaml
に記述しているパラメータの間に過不足がある場合、安全のためエラーで止まる仕様になっています。