コード変更
はじめに
新規ゲームに対応するためには、ゲーム固有の実装である alfort
フォルダ内のソースコードを適切に書き換える必要があります。
そのための準備として、以下のことを行ってください。
- ダウンロードした Playable! ライブラリの中にある
playable-map-scanner
フォルダを丸ごとコピーし、playable-map-scanner-{新規ゲーム名}
にリネームします。 playable-map-scanner-{新規ゲーム名}/Python
内のalfort
フォルダを{新規ゲーム名}
にリネームします。playable-map-scanner-{新規ゲーム名}/Python
内の各ファイルでfrom alfort import XXXX
やfrom alfort.XXXX import YYYY
となっている部分を 全てfrom {新規ゲーム名} import XXXX
やfrom {新規ゲーム名}.XXXX import YYYY
に置換します。
以降で具体的に行うコードの書き換えについて説明します。
ゲーム操作用クラスの実装
Map Scanner がゲームを起動し、操作可能にするには、最初に以下のクラスを実装する必要があります。
- ゲーム環境を制御するクラス
- ゲームからの受信データを処理・格納するクラス
- プログラム側からゲームを操作するためのパッド入力を定義するクラス
ゲーム環境を制御するクラス
Map Scanner との連携を司るクラスとして base/base_wrapper.py
の UE4Wrapper
クラスがあります。
個々のゲームに合わせた処理ができるよう、{新規ゲームの名称}/custom_wrapper.py
にある UE4Wrapper
の継承クラス MapScanEnvWrapper
において、以下の関数を適切に変更してください。
reset()
: リセット処理。ゲームを再度起動し、接続を行うopen()
: ゲームの起動を行うstep()
: パッド入力をして、ゲームからの返答を待つ (ゲーム側の処理を 1 フレーム進める)step_latest()
:step()
の特殊対応版step_all_queue()
:step()
の特殊対応版
受信データを処理・格納するクラス
ゲームから受け取るデータを Map Scanner で扱いやすくするために、base/base_container.py
の BaseContainer
クラスで前処理を行っています。
ゲームから送信されるデータは JSON で、Game-Python Bridge ではそれを辞書型に変換して扱うようにしていますが、
BaseContainer
クラスを利用することで辞書型を直接扱う機会を減らすことができます。
Game-Python Bridge には、様々なゲームで共通して用いられるであろう情報 (パッド操作の入力値など) がデフォルトで送信するように実装されており、BaseContainer
クラスにはこうした情報の事前処理が定義されています。ゲーム固有の情報に対する事前処理は {新規ゲームの名称}/custom_container.py
にある BaseContainer
の継承クラス CustomContainer
に定義してください。
パッド入力を定義するクラス
プログラム側からゲームを操作するためのパッド入力を定義するクラスを用意する必要があります。
ゲームパッドを使ったゲームの操作についての定義は base/base_controller.py
の BaseController
クラスで行っています。
基本的な操作はここに定義されていますが、追加の操作が必要であれば {新規ゲームの名称}/custom_controller.py
にある BaseController
の継承クラス CustomController
に定義してください。
サンプルゲームでは BaseController
で用意している操作で事足りるため、CustomController
に追加の定義は行っていません。
スキャンの準備処理の実装
Map Scanner がゲームを操作する処理は、基本的に base/base_client_thread.py
の BaseComThread
クラスに実装されています。BaseComThread
クラスの実装を確認することで、ゲームを操作してマップスキャンを行う処理の流れを理解できます。
BaseComThread
クラスは以下の流れでマップスキャンを開始します。
- ゲームを起動する
title_menu_setting()
を実行して、タイトル画面からゲームプレイ画面に遷移するset_debug_function()
を実行して、スキャン前に必要な設定を行う- ここでデバッグ移動モードへの切り替えも行う
- スキャンを開始する
各ゲームでスキャンを行うには、ゲームごとに以下の処理を実装する必要があります。
ゲームプレイ画面までの遷移処理
BaseComThread
クラスの title_menu_setting()
は abstractmethod となっており、alfort/custom_client_thread.py
内の継承クラス CustomComThread
で中身を実装する必要があります。
サンプルゲームでの例
サンプルゲームにおける title_menu_setting()
では、alfort/custom_order.py
で定義した命令を組み合わせ、
以下の処理を行うように実装しています。
- タイトルメニューから「ロード」を選ぶ
- ロード画面から指定されたセーブデータを選ぶ
- ローディング画面が終わるのを待つ
スキャン前の設定処理
BaseComThread
クラスの set_debug_function()
は abstractmethod となっており、alfort/custom_client_thread.py
内の継承クラス CustomComThread
で中身を実装する必要があります。
サンプルゲームでの例
サンプルゲームにおける set_debug_function()
では、alfort/custom_order.py
で定義した命令を組み合わせ、
以下の処理を行うように実装しています。
- 1 秒待つ
- デバッグメニューを開く
- 「Player 無敵切り替え」を選択し、プレイヤーを無敵状態にする
- 「位置情報の表示切り替え」を選択し、画面上に現在の位置情報が表示されるようにする
- 「全ての扉を開放する」を選択し、マップ上にある扉を開放する (扉が開放された状態のマップをスキャンするため)
- デバッグメニューを閉じる
- 5 秒待つ
- デバッグカメラモード (自由移動モード) に切り替える
- デバッグカメラモードの移動速度を調整する
その他の定型処理 (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.py
の CustomAgent
クラスに定義する必要があります。
このクラスは base/base_client_agents.py
の BaseAgent
クラスを継承しています。
実装するのは以下の関数です。
debug_move()
: デバッグカメラモード (自由移動モード) を使った移動Order.execute()
と同様に、現在の状況でどういう操作を行うかを決定する処理を記述してください。
check_arrival()
: 目標座標に到達したかを判定する処理- 目標座標に到達している場合は
True
を返すようにしてください。
- 目標座標に到達している場合は
スキャン範囲の設定
次にスキャンの範囲を設定します。以下の手順に従って settings.yaml
の内容を適切に書き換えてください。
- テストを行いたい各マップのコード名 (ゲーム内 ID がよいでしょう。以降マップ ID と呼びます) を決めてください。 このコード名はコリジョンテストでも共通して用いるものであることに注意してください。
- スキャン開始時に指定したマップへ移動できるように、プレイヤーが各マップにいる状態で作成したセーブデータを用意してください。
サンプルゲームを例にすると、
PL_010VIL (ルーン村)
のセーブデータは、プレイヤーがPL_010VIL
に存在し、 かつ 実際のゲームプレーで到達可能な座標に立っている 状態でセーブを行うことで作成できます。注意プレイヤーの開始地点が通常のゲームプレーで到達できない場所になっていると、後述の経路探索で正しい結果が得られません。
- 作成した各マップのセーブデータのファイル名を
scan_params > save_data > {マップ ID}
に記述してください。 UE4 であれば、作成したセーブデータはデフォルトではWindowsNoEditor/{ゲームのコード名}/Saved/SaveGames/
以下にあります。 - 各マップでプレイヤーが行動できる範囲を調べ、
map_params > {マップ ID} > X/Y/Z > End/Start
の値に設定してください。 実際にプレイヤーが行動できる範囲よりも少し広めに指定することをおすすめします。 - 幅優先探索でプレイヤーが到達可能な範囲を列挙する際の探索開始地点を
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 に適切な形で追加してください。
マップ探索の実行
上記設定が完了すると、実際にマップのスキャンが可能となります。