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

コード変更

はじめに

新規ゲームに対応するためには、ゲーム固有の実装である alfort フォルダ内のソースコードを適切に書き換える必要があります。 そのための準備として、以下のことを行ってください。

  1. ダウンロードした Playable! ライブラリの中にある playable-map-scanner フォルダを丸ごとコピーし、 playable-map-scanner-{新規ゲーム名} にリネームします。
  2. playable-map-scanner-{新規ゲーム名}/Python 内の alfort フォルダを {新規ゲーム名} にリネームします。
  3. playable-map-scanner-{新規ゲーム名}/Python 内の各ファイルで from alfort import XXXXfrom alfort.XXXX import YYYY となっている部分を 全て from {新規ゲーム名} import XXXXfrom {新規ゲーム名}.XXXX import YYYY に置換します。

以降で具体的に行うコードの書き換えについて説明します。

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

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

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

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

Map Scanner との連携を司るクラスとして base/base_wrapper.pyUE4Wrapper クラスがあります。
個々のゲームに合わせた処理ができるよう、{新規ゲームの名称}/custom_wrapper.py にある UE4Wrapper の継承クラス MapScanEnvWrapper において、以下の関数を適切に変更してください。

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

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

ゲームから受け取るデータを Map Scanner で扱いやすくするために、base/base_container.pyBaseContainer クラスで前処理を行っています。
ゲームから送信されるデータは JSON で、Game-Python Bridge ではそれを辞書型に変換して扱うようにしていますが、 BaseContainer クラスを利用することで辞書型を直接扱う機会を減らすことができます。
Game-Python Bridge には、様々なゲームで共通して用いられるであろう情報 (パッド操作の入力値など) がデフォルトで送信するように実装されており、BaseContainer クラスにはこうした情報の事前処理が定義されています。ゲーム固有の情報に対する事前処理は {新規ゲームの名称}/custom_container.py にある BaseContainer の継承クラス CustomContainer に定義してください。

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

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

ゲームパッドを使ったゲームの操作についての定義は base/base_controller.pyBaseController クラスで行っています。
基本的な操作はここに定義されていますが、追加の操作が必要であれば {新規ゲームの名称}/custom_controller.py にある BaseController の継承クラス CustomController に定義してください。

備考

サンプルゲームでは BaseController で用意している操作で事足りるため、CustomController に追加の定義は行っていません。

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

Map Scanner がゲームを操作する処理は、基本的に base/base_client_thread.pyBaseComThread クラスに実装されています。BaseComThread クラスの実装を確認することで、ゲームを操作してマップスキャンを行う処理の流れを理解できます。

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

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

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

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

BaseComThread クラスの title_menu_setting()abstractmethod となっており、alfort/custom_client_thread.py 内の継承クラス CustomComThread で中身を実装する必要があります。

サンプルゲームでの例

サンプルゲームにおける title_menu_setting() では、alfort/custom_order.py で定義した命令を組み合わせ、 以下の処理を行うように実装しています。

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

スキャン前の設定処理

BaseComThread クラスの set_debug_function()abstractmethod となっており、alfort/custom_client_thread.py 内の継承クラス CustomComThread で中身を実装する必要があります。

サンプルゲームでの例

サンプルゲームにおける set_debug_function() では、alfort/custom_order.py で定義した命令を組み合わせ、 以下の処理を行うように実装しています。

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

その他の定型処理 (Order)

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

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

備考

Order については Playable! Playthrough Tester でも利用しています。以下の情報も適宜参照してください。

サンプルゲームでの例

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

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

キャラクター制御を行う処理の実装

自由移動モードでスキャンを行いますが、この際のキャラクター制御方法は alfort/client_alfort_agents.pyCustomAgent クラスに定義する必要があります。 このクラスは base/base_client_agents.pyBaseAgent クラスを継承しています。

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

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

スキャン範囲の設定

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

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

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

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

// (略)

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_params:
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_params:
PL_010VIL: // マップ ID
X:
End: 24000 // X範囲終了位置
Start: 1000 // X範囲開始位置
Y:
End: 20000
Start: -10000
Z:
End: 6000
Start: -2000
PL_020RIV:
// (略)

レイキャスト仕様の設定

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

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

注意

スキャン結果を確認して不具合が見つかった場合は、正しいスキャン結果が得られるようにレイキャスト配置を調整してください。

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

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

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

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

// settings.yaml
scan_params: // スキャン設定
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 の探索部分に適切な条件を追加してください。

(アイテム回収などのツールで) 実際に移動が可能かを確かめた上で、問題があればパラメータや規則の調整を繰り返す必要があります。 なお、変更した探索ルールを適用するには、C++ コードの再ビルド をする必要があります。忘れないようにしてください。

map_scanner_searcher.py

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

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

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

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

マップ探索の実行

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