メインコンテンツまでスキップ
バージョン: 1.0.0

コード変更

ゲーム操作用クラスの実装

Map Scanner がゲームを起動し、操作可能にするには、最初に以下のクラスを実装する必要があります。

  • ゲーム環境を制御するクラス
  • ゲームからの受信データを処理・格納するクラス
  • プログラム側からゲームを操作するためのパッド入力を定義するクラス

ゲーム環境を制御するクラス

Map Scanner との連携をつかさどるクラスとしてcore/wrapper.pyUE4Wrapperクラスがあります。
個々のゲームに合わせた処理ができるよう、UE4Wrapperを継承したクラスを用意し、以下の関数を実装してください。

  • reset(): リセット処理。ゲームを再度起動し、接続を行う
  • open(): ゲームの起動を行う
  • step(): パッド入力をして、ゲームからの返答を待つ。(ゲーム側の処理を 1 フレーム進める)
  • step_latest(): step() の特殊対応版
  • step_all_queue(): step() の特殊対応版

サンプルゲームでの例

サンプルゲームではalfort/alfort_wrapper.pyにてUE4Wrapperを継承したScanMapThreadWrapperクラスを定義しています。

受信データを処理・格納するクラス

ゲームから受け取るデータを Map Scanner が扱いやすくする事前処理などをcore/container.pyContainerクラスで行っています。
ゲームから送信されるデータは JSON で、Game-Python Bridge ではそれを辞書型に変換して扱うようにしていますが、 Containerクラスを利用することで辞書型を直接扱う機会を減らすことが出来ます。

サンプルゲームでの例

サンプルゲームではalfort/alfort_container.pyにてContainerを継承したAlfortContainerクラスを定義しています。

パッド入力を定義するクラス

プログラム側からゲームを操作するためのパッド入力の定義をするクラスを用意する必要があります。

ゲームパッドを使ったゲームの操作についての定義は core/controller.pyControllerクラスで行っています。
基本的な操作はここに定義しておりますので、Controllerを継承したクラスを用意し、必要であれば追加の操作を定義してください。

サンプルゲームでの例

サンプルゲームではalfort/alfort_conroller.pyにてControllerを継承したAlfortControllerクラスを定義しています。
サンプルゲームではControllerで用意している操作で事足りるため、追加操作は特に定義していません。

スキャンの準備処理の実装

Map Scanner のゲームを操作する処理は基本的にcore/client_thread.pyComThreadクラスに実装されています。
ComThreadクラスの実装を確認することでゲームを操作してマップをスキャンする処理を理解することが可能です。

ComThreadクラスは下記の流れでマップのスキャンを開始します。

  1. ゲームを起動する
  2. menu_debug_setting()を実行して、タイトル画面からゲームプレイ画面に遷移する
  3. game_debug_setting()を実行して、スキャン前に必要な設定を行う
    • ここでデバッグ移動モードへの切り替えも行う
  4. スキャンを開始する

各ゲームでスキャンを行うには、事前にゲームごとに以下の処理の実装をする必要があります。

ゲームプレイ画面までの遷移処理

ComThreadクラスのmenu_debug_setting()abstractmethodとなっており、継承するクラスで中身を実装する必要があります。

サンプルゲームでの例

サンプルゲームではalfort/alfort_client_thread.pyにてComThreadを継承したAlfortComThreadクラスを定義しています。 AlfortComThreadクラスのmenu_debug_setting()で、alfort/alfort_order.pyで定義した命令を組み合わせ、 以下の処理を行うように実装しています。

  1. タイトルメニューから「ロード」を選ぶ
  2. ロード画面から指定されたセーブデータを選ぶ
  3. ローディング画面が終わるのを待つ

スキャン前の設定処理

ComThread クラスの game_debug_setting()abstractmethod となっており、継承するクラスで中身を実装する必要があります。

サンプルゲームでの例

サンプルゲームではalfort/alfort_client_thread.pyにてComThreadを継承したAlfortComThreadクラスを定義しています。
AlfortComThreadクラスのmenu_debug_setting()で、alfort/alfort_order.pyで定義した命令を組み合わせ、以下のような処理を行っています。

  1. 1 秒待つ
  2. デバッグメニューを開く
  3. 「Player 無敵切り替え」を選択し、プレイヤーを無敵状態にする
  4. 「位置情報の表示切り替え」を選択し、画面上に現在の位置情報が表示されるようにする
  5. 「全ての扉を開放する」を選択し、マップ上にある扉を開放する (開放状態のマップをスキャンしたいため)
  6. デバッグメニューを閉じる
  7. 5 秒待つ
  8. デバッグカメラモード (自由移動モード) に切り替える
  9. デバッグカメラモードの移動速度を調整する

その他の定型処理(Order)

「メニューを開く」や「セーブデータをロードする」など、1 回のパッド操作で終わらない少し複雑な処理を行うために Order クラスと言う定型処理を実行するための仕組みを用意しています。

menu_debug_setting()game_debug_setting() ではこの Order を順番に実行することで一連の定型操作を自動で行えるようにしています。

備考

Orderについては Playable! Playthrough Tester でも利用しています。詳細は以下を参照してください。

サンプルゲームでの例

サンプルゲームでは alfort/alfort_order.py で以下のような Orderを定義しています。

  • WaitLoading: ローディング画面が終わるのを待つ
  • SetDebugSpeed: デバッグカメラモードの速度を設定する
  • SetDebugMove: デバッグカメラモードの ON/OFF を切り替える
  • OpenDebugMenu: デバッグメニューを開く
  • SetDebugMenu: デバッグメニューの項目を選択する
  • SelectDebugMenu: タイトルメニューの項目を選択する
  • LoadMenu: ロード画面から目的のセーブデータを選択する

キャラクターの動作制御処理の実装

自由移動モードでスキャンを行いますが、 この際のキャラクター制御は core/client_agents.pyBaseAgent クラスを継承したクラスを用意して、そこでゲームごとに実装する必要があります。

実装するのは以下の関数です。

  • debug_move(): デバッグカメラモード (自由移動モード) を使った移動
    • Order.execute() と同様に、現在の状況でどういう操作を行うかを決定する処理を記述してください
  • check_arrival(): 目標座標に到達したかを判定する処理
    • 目的座標に到達している場合 True を返すようにしてください

サンプルゲームでの例

サンプルゲームではalfort/client_alfort_agents.pyにてBaseAgentを継承したAlfortAgentクラスを定義しています。

スキャン範囲の設定

次にスキャンの範囲を設定します。以下の手順に従って settings.yaml の内容を適切に書き換えてください。

  1. テストを行いたい各マップのコード名 (ゲーム内 ID がよいでしょう。以降マップ ID と呼びます) を決めてください。 このコード名はコリジョンテストでも共通して用いるものであることに注意してください。
  2. スキャン開始時に指定したマップへ移動できるように、プレイヤーが各マップにいる状態で作成したセーブデータを用意してください。 サンプルゲームを例にすると、PL_010VIL (ルーン村)のセーブデータは、プレイヤーがPL_010VILに存在し、 かつ 実際のゲームプレーで到達可能な座標に立っている 状態でセーブを行うことで作成できます。
    注意

    プレイヤーの開始地点が通常のゲームプレーで到達できない場所になっていると、後述の経路探索で正しい結果が得られません。

  3. 作成した各マップのセーブデータのファイル名を Scan_set > save_data > {マップ ID} に記述してください。 UE4 であれば、作成したセーブデータはデフォルトでは WindowsNoEditor/{ゲームのコード名}/Saved/SaveGames/ 以下にあります。
  4. 各マップでプレイヤーが行動できる範囲を調べ、Map_data > {マップ ID} > X/Y/Z > End/Start の値に設定してください。 実際にプレイヤーが行動できる範囲よりも少し広めに指定することをおすすめします。
  5. 幅優先探索でプレイヤーが到達可能な範囲を列挙する際の探索開始地点を Search_set > start_pos > {マップ ID} に記述します。 手順 2 で作成したセーブデータの開始地点を指定してください。書式は [[x 座標, y 座標, z 座標]] です。
Scan_set: // スキャン設定

// (略)

save_data: // セーブデータのファイル名 (拡張子なし)
PL_010VIL: 0000_2022-09-05-16-09-04
PL_020RIV: 0000_2022-09-05-16-10-55
PL_030GRP: 0000_2022-09-05-16-09-28
PL_050GRH: 0000_2022-09-05-16-10-00

Search_set:
start_pos: // 探索開始地点
PL_010VIL: [[15933, 6450, -430]] // [[x 座標, y 座標, z 座標]]
PL_020RIV: [[71614, 27177, 1040]]
PL_030GRP: [[-52424, -16670, 3268]]
PL_050GRH: [[-44000, 53795, -52]]

// (略)

Map_data:
PL_010VIL: // マップ ID
X:
End: 24000 // X範囲終了位置
Start: 1000 // X範囲開始位置
Y:
End: 20000
Start: -10000
Z:
End: 6000
Start: -2000
PL_020RIV:
// (略)

レイキャスト仕様の設定

スキャン用のレイキャストは水平方向垂直方向の 2 種類に分けられ、 その長さと間隔はスキャン速度とスキャン結果の精度を決定しています。 配置方法は自由に設定することができますが、キャラクターがある方向にデバッグ移動する時にできるだけすべての座標をスキャンできるようにしてください。

ゲームに実装したレイキャストの仕様に合わせて、Scan_set内のレイキャストに関するパラメータを変更してください。

注意

スキャン結果を確認して不具合が見つかった場合は、正しいスキャン結果が得られるまでレイキャスト配置の調整と再スキャンを繰り返し行う必要があります。

設定ファイルは下記のような形式になります。

Scan_set: // スキャン設定
raycast: // レイキャスト情報
X_coverage: 300 // X軸のレイキャスト範囲
Z_coverage: 300 // Z軸のレイキャスト範囲
X_overlaps: 1 // X軸のレイキャスト重複数
Z_overlaps: 4 // Z軸のレイキャスト重複数

スキャン用パラメータの設定

予期せぬ遅延のため、一度のスキャンでは完璧な結果が得られない可能性があります。 このスキャン結果の不備を極力減らすため、同じ箇所を複数回スキャンする仕組みが存在します。 ここではこれらに関わるスキャン用のパラメータを調整します。 一般に、スキャン速度は速くするほど短時間でスキャンが終わりますが、地形を正しくスキャンできない可能性が高まることに注意し、最適なパラメータを設定してください。

// settings.yaml
Scan_set: // スキャン設定
fineness_size: 40 // スキャンデータにおけるボクセル一辺の長さ
scan_Y_speed: 4000 // Y軸に沿ったスキャン速度
X_repetitions: 2 // X軸の繰り返しスキャン回数
Z_repetitions: 2 // Z軸の繰り返しスキャン回数

マップ探索の準備

対象のゲームがプレイヤーの移動操作で直接別のマップに行ける場合(こちらも参照)、 別マップへの遷移判定に使われるコリジョンの情報が記録されたファイルが必要となります。
操作マニュアル - スキャン結果を使って探索の事前計算を行うを参考に、 各マップに対応するファイルを用意してください。

また、スキャンが完了したら、各ファイルを対応するマップのフォルダ下に配置してください。

Source/Astar_model/Astar_model/Astar_model.cpp

C++で書かれた経路探索用コードです。

探索を高速化するため、pybind11というライブラリを使って計算処理の一部を C++で実装しています。 pybind11 の設定とビルドは pybind11 による Python - C++ の連携 を参照してください。

スキャン結果に基づいてプレイヤーが到達可能な範囲を特定し、コリジョンテストやアイテム回収で必要なファイルを作成するための初期探索を行います。
ゲームによってはしゃがみ移動、水面移動、潜水移動、流砂ギミックなどに個別対応する必要があります。
これらは全て移動パラメータが異なると考えられ、場合によっては通常の陸地とは異なる方法で地形をスキャンする必要があるかもしれません。
そのような時にはclass GraphMaker > maker_graphの探索部分に適切な条件を追加してください。

(アイテム回収などのツールで)実際に移動が可能かを確かめた上で、問題があればパラメータや規則の調整を繰り返す必要があります。

map_scanner_searcher.py

以下の部分を適切に変更してください。調整の際はコードのコメントと システム概要 も参照してください。

  • MapDataService
    • _pass_walk_obstacle_floor: キャラクタの身長に合わせて適切に変えます。
    • _search_floor
      • _map_Navigation: 移動可能平面の判定です。実際に移動可能な場所に近い結果が得られるように調整してください。 また、このテンソルはプレイヤーが立つことが可能な傾斜角の判定閾値にも影響します。 各ボクセルを中心とした検索範囲において (z 軸方向のボクセル数)/(x,y 軸方向のボクセル数) ≧ tan(各ゲームで立てる最大傾斜角) を満たすように設定してください。

また、ゲームによっては、移動可能範囲の計算に関する新しいアルゴリズム関数を追加する必要があります。 システム概要 を参考にして適切に実装してください。 これらは (アイテム回収などのツールで) 実際に移動が可能かを確かめた上で、問題があればパラメータや規則の調整を繰り返す必要があります。

ゲーム固有の移動方法 (しゃがみ移動、水面移動、潜水移動、流砂ギミックなど)への対応を行ったことで新たなパラメータが必要となった場合、 Playable! Map Scanner 概要やコードを参考にしながら yaml に適切な形で追加してください。

マップ探索の実行

上記設定が完了すると、実際にマップのスキャンが可能となります。